双指针
相向双指针
两数之和
题意是找到不同两个数使得它们相加和为target,数组有序
利用数组有序的性质,判断指针前后的区间的性质
例如:2 3 4 6 8, target 9
2 + 8 = 10 > 9, 因为非递减序列,2之后的每个数都会大等于2,所以 2之后的每个数和8相加都会 > 9,eg 3+8,4+8,6+8…。
在这种情况下,已经不可能有解。造成无解的原因是因为 8 这个数 太大, 因此将r指针左移去除8。
2 + 6 = 8 < 9, 因为非递减序列,所以 6之前的每个数相加都会 < 9,eg 2+4,2+3,…
在这种情况下,已经不可能有解。造成无解的原因是因为2 这个数 太小, 因此将l指针右移去除2
class Solution {
public:vector<int> twoSum(vector<int>& numbers, int target) {int l = 0, r = numbers.size() - 1;// 2 3 4 6 8, target 9// 2 + 8 = 10 > 9, 因为非递减序列,所以 2之后的每个数相加都会>9,eg 3+8,4+8,6+8..// 所以我们直接删除 8,r--// 2 + 6 = 8 < 9, 因为非递减序列,所以 6之前的每个数相加都会<9,eg 2+4,2+3,..// 所以我们直接删除 2,l++while(l < r){if(numbers[l] + numbers[r] > target) r --;else if(numbers[l] + numbers[r] < target) ++ l;else break;}return {l + 1, r + 1};}
};
三数之和
同两数之和, 增加一个遍历第一个数即可
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {int n = nums.size();ranges::sort(nums);vector <vector<int>> res;for(int i = 0;i < n - 2;i ++){int x = nums[i];if(i > 0 && x == nums[i - 1]) continue;// 当前三个小的数相加已经大于0, 后续的数只会比当前的数更大,因此一定大于0,可以直接结束if(x + nums[i + 1] + nums[i + 2] > 0) break; // 当前的数x 加上最大的两个数仍然比0小,因此在和(i+1, n-1)内的数相加也一定小于0,可以跳过。 后续遍历到更大的x的时候仍有机会有解if(x + nums[n - 1] + nums[n - 2] < 0) continue;int j = i + 1, k = n - 1;while(j < k){int s = x + nums[j] + nums[k];if(s > 0) -- k;else if(s < 0) ++ j;else{res.push_back({x, nums[j], nums[k]});j ++;while(j < k && nums[j - 1] == nums[j]) ++ j;-- k;while(j < k && nums[k + 1] == nums[k]) -- k;}}}return res;}
};
一些变形题目:
lc2824. 统计和小于目标的下标对数目 https://leetcode.cn/problems/count-pairs-whose-sum-is-less-than-target/
lc16. 最接近的三数之和 https://leetcode.cn/problems/3sum-closest/
lc18. 四数之和 https://leetcode.cn/problems/4sum/
lc611. 有效三角形的个数 https://leetcode.cn/problems/valid-triangle-number/
其中, 有效的三角形个数是对最长边(最大数开始循环),而不是之前的最小数。
因为如果遍历最小数,会出现如下情况:
a+b太小了,a是固定的,所以j要往大的方向走,即j++
c太大了,k往小的方向走,即k-- 这一个分支里不可能同时包含这两种处理方式,因为j++和k–是相向而行的,这必定会少得到一些情况。其次,就算只用其中一种处理办法,也会漏掉。
根本原因是a + b == c在这种情况下有歧义,因为我们固定的是最小边,而最小边在这个式子里是a,当出现==情况时,既有可能是b,也有可能是c造成的,这就好像一个等式两边都有自变量3x=4y,谁对这个等式都有影响。 这样会导致我们对指针的移动情况判断不是唯一的,到底是增大b还是减小c ?因此,这类问题,也就是通过判定值来对双指针指向的值构成的条件进行指针遍历的问题,并且要保证根据判定值的大小关系使得指针的变化必须是唯一的。
固定最大边,就能保证这个等式的含义一定是:a+b的值太小了,得往值更大的方向走。这样就能在遍历c的时,完整地找出所有符合条件的a和b。 所以在使用相向双指针,思考要固定哪边时,就想想大于小于或等于这三种表达式有没有歧义。
class Solution {
public:int triangleNumber(vector<int>& nums) {// a + b > cranges::sort(nums);int n = nums.size();int res = 0;for(int k = n - 1; k > 1;k --){int c = nums[k];int l = 0, r = k - 1;while(l < r){if(nums[l] + nums[r] > c){ // 当前下标l加上 l之后的值都满足条件,计算答案res += r - l;-- r;}else{++ l; // a值太小或者说(a + b)值太小,增加}}}return res;}
};
接雨水 (双指针 以及 前后缀数组
前后缀数组:对于数组的某个位置 I,通过预处理的方式,利用数组存储信息,可以 O(1) 得到 I 之前/之后的数最大值/最小值/区间和/单调性等信息。最大/小值是取max/min;区间和就是前缀和;单调性是指O(1)判断某个区间是否为单调区间,计算某个区间的递增次数和区间长度进行对比,若两者相等则这当前区间为单调区间,否则不是。
滑动窗口 (同向双指针)
要求一个连续的子数组 (若答案和顺序无关也可以排序后考虑), 其次是单调性 ,保证操作对于结果是单调的,递增或递减
滑动窗口的核心要点:
1.维护一个有条件的滑动窗口;
2.右端点右移,导致窗口扩大,是不满足条件的原因;
3.左端点右移目的是为了缩小窗口,重新满足条件;
这个条件一般就是题目变化的地方,例如数字或者字符出现次数、乘积、区间和。
可以用许多种数据结构 or 变量来维护这个条件,用O(1)的时间判断窗口内是否符合条件。
对于条件可以进行些处理,如果正向操作太难,考虑反着求解,正难则反。
答案可以是最短/最长/方案数等等,对于方案数,最终符合条件的不一定只是窗口内的,也要考虑窗口外的。