双指针
- 快慢指针
- 对撞指针
- 滑动窗口
双指针指的是用两个不同指向的浮标,在一个for循环中完成两个for循环的操作
快慢指针
由于数组的地址是连续的,所以无法像链表一样删除指定的某一项,因此在数组中,删除操作都是由覆盖来实现。
若给你⼀个数组 n u m s nums nums 和⼀个值 v a l val val,你需要原地移除所有数值等于 v a l val val 的元素,并且不要改变其他元素的相对位置,不要使⽤额外的数组空间,你必须原地修改输⼊的数组。
例如: n u m s = [ 1 , 2 , 3 , 4 , 3 , 5 , 6 ] , v a l = 3 nums = [1, 2, 3, 4, 3, 5, 6], val = 3 nums=[1,2,3,4,3,5,6],val=3,删除后数组的长度变为 5 5 5 则应该将原数组的前 5 5 5 项变为 n u m s = [ 1 , 2 , 4 , 5 , 6 ] nums = [1, 2, 4, 5, 6] nums=[1,2,4,5,6] 即可,第五个元素之后的东西是什么我们并不关心,即:如果你的 n u m s nums nums 数组为: [ 1 , 2 , 4 , 5 , 6 , X , X , X , X . . . . ] [1,2,4,5,6,X,X,X,X....] [1,2,4,5,6,X,X,X,X....],我们不关心第五项后的 X X X是什么。
如果是暴力解题,即遇到指定的 v a l val val 则将后续元素依次迁移一位,则运行复杂度为 O ( n 2 ) O(n^2) O(n2)
void DeleteVal(vector<int>&vec, int val){int len = vec.size();for(int i = 0; i < len; i++) {if (vec[i] != val)continue;for (int j = i + 1; j < len; j++) {vec[j - 1] = vec[j];}i--; //覆盖了第i项,所以要重新对第i项进行检查len--; //有效长度减一}
}
现在,我们假设有一个处理之后的数组是一个新数组,试想有两个浮标 L L L 和 R R R, R R R 指向当前要判断的原数组的元素的位置, L L L 指向该元素要放在新数组的哪个位置。如果 n u m s [ R ] nums[R] nums[R]不是指定元素 v a l val val,则将 n u m s [ R ] 放在 n u m s [ L ] 处 nums[R] 放在 nums[L]处 nums[R]放在nums[L]处,便得到了以下代码
void DeleteVal(vector<int>&nums, int val){int len = nums.size();int L = 0, R = 0;for(R = 0; R < len; R++){if(nums[R] != val){nums[L] = nums[R];L++;}}
}
对撞指针
与上个情况相同,如果不考虑保持相对位置不变的话,我们可以试着将 R R R 指向数组尾部, L L L 指向数组头部,我们从左往右找到第一个 v a l val val 的位置和最后一个不是 v a l val val 的位置,然后交换这两个位置的值。一直重复这个操作,直到 L L L 和 R R R 相遇。
void DeleteVal(vector<int>& nums, int val) {int L = 0;int R = nums.size() - 1;while (L <= R) {while (L <= R && nums[L] != val){L++; //寻找第一个等于val的值}while (L <= R && nums[R] == val) {R--; //寻找最后一个不是val的值}if (L < R) {// L < R 时才能交换nums[L] = nums[R];L--; R++;}}
}
若给你一个升序数组 n u m s nums nums,要你返回一个包含 n u m s nums nums 数组中所有元素平方的非递减有序数组,例如: n u m s = [ − 4 , − 2 , − 1 , 1 , 2 , 3 , 4 ] nums = [-4,-2,-1,1,2,3,4] nums=[−4,−2,−1,1,2,3,4],你需要返回 a n s = [ 1 , 1 , 4 , 4 , 9 , 16 , 16 ] ans = [1,1,4,4,9,16,16] ans=[1,1,4,4,9,16,16]。
因为有负数的存在,所以我们会发现平方之后的数有可能中间小两边大,因此可以设置两个指针 L L L和 R R R, L L L 指向 n u m s nums nums 的开头 R R R 指向结尾,每次比较 n u m s [ L ] 2 nums[L]^2 nums[L]2 和 n u m s [ R ] 2 nums[R]^2 nums[R]2 的大小,然后从后往前放入新数组里,直到 L L L 和 R R R 相遇时结束这个过程。
int* DeleteVal(vector<int> nums) {int L = 0, R = nums.size() - 1;int *ans = new int[R + 1], nowIndex = R;while (L <= R) {if(abs(nums[R]) >= abs(nums[L])){ans[nowIndex--] = nums[R] * nums[R];R--;}else{ans[nowIndex--] = nums[L] * nums[L];L++;}}return ans;
}
滑动窗口
滑动窗⼝,就是不断的调节⼦序列的起始位置和终⽌位置,从⽽得出我们要想的结果。
在暴⼒解法中,是⼀个 f o r for for循环滑动窗⼝的起始位置,⼀个 f o r for for循环为滑动窗⼝的终⽌位置,⽤两个 f o r for for循环完成了⼀个不断搜索区间的过程。
那么如何⽤⼀个 f o r for for循环来完成这个操作呢?
⾸先要思考 如果⽤⼀个 f o r for for循环,那么应该表示滑动窗⼝的起始位置,还是终⽌位置呢?
如果只⽤⼀个 f o r for for循环来表示滑动窗⼝的起始位置,那么如何遍历剩下的终⽌位置?
所以 只⽤⼀个 f o r for for循环,那么这个循环的索引,⼀定是表示滑动窗⼝的终⽌位置。
那么滑动窗⼝的起始位置如何移动呢?
例如:给定⼀个含有 n n n 个正整数的数组 n u m s nums nums 和⼀个正整数 s s s ,找出该数组中满⾜其和 ≥ s ≥ s ≥s 的⻓度最⼩的连续⼦数组,并返回其⻓度。如果不存在符合条件的⼦数组,返回 0。
若 s = 7 s=7 s=7, n u m s = [ 2 , 3 , 1 , 2 , 4 , 3 ] nums = [2,3,1,2,4,3] nums=[2,3,1,2,4,3],我们会发现,最短的符合的子数组长度为 2 , 子数组为 [ 4 , 3 ] 2,子数组为[4,3] 2,子数组为[4,3]
在实现滑动窗⼝时主要确定如下三点:
- 窗⼝内是什么?
- 如何移动窗⼝的起始位置?
- 如何移动窗⼝的结束位置?
窗⼝内是满⾜其和 ≥ s ≥ s ≥s 的⻓度最⼩的连续⼦数组。
窗⼝的起始位置如何移动:如果当前窗⼝的值⼤于 s s s了,窗⼝就要向前移动了(也就是该缩⼩了)。
窗⼝的结束位置如何移动:窗⼝的结束位置就是遍历数组的指针,也就是 f o r for for循环⾥的索引
int DeleteVal(vector<int> nums, int s) {int L = 0;int sum = 0, ans = INT_MAX;int subLength;for(int R = 0; R < nums.size(); R++) {sum += nums[R];/*** 核心段 ***/while (sum >= s) {subLength = (R - L + 1);ans = min(ans, subLength);sum -= nums[L++];}/**********************/}return ans == INT_MAX ? 0 : ans;
}