之所以放在一起是因为,"四数之和"的解题方法基本与"三数之和"一致
由此我们 可以推出n数之和的解法
本质上,我们只是使用双指针的方法降低此类问题的时间复杂度
当然用哈希法也可以解,那就是另外的故事了
三数之和
力扣题目链接(opens new window)
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
初见分析很像四数相加||啊
那题是用了map去解决的,于是照猫画虎开始做
做到最后会发现,题目要求的是返回由数组中三个相加为0的元素构成元组
这里就™的有问题了,如果按照原来的思路
那么如何保存符合条件的遍历值的下标?并且还没有重复值
思路不整哈希法了,用双指针法解会好理解一些,过程如下图所示:
题目要求是返回三个相加为0的数,即: a+b+c = 0 ,与下标无关
初始化指针还是分开处理,初始化三个指针
a 对应 i(固定值)
b 对应 left
c 对应 right
这里在使用双指针前需要对数组进行排序(用sort就行不用自己写)
遍历数组在遍历过程中会出现以下几种情况:
0、 nums[i] > 0
这种情况的话就 直接return ,因为如果第一个数是大于0的数,那么后面的数肯定都是正数(因为提前经过排序),则不可能再计算得到0
1、 nums[i] + nums[left] + nums[right] > 0
i 对应的数是最小的,不用管,主要看 b 和 c (所以才叫双指针而不是三指针)
因为已经排好序,所以要让整体值变小进而靠近0,需要让最大的值即 right向左移动
2、 nums[i] + nums[left] + nums[right] < 0
同理,要让整体值变大,最小的值 left要向右移动
3、 nums[i] + nums[left] + nums[right] = 0
找到目标三元组,保存
大致结构如下:
class Solution { public: vector<vector<int>> threeSum(vector<int>& nums) { ... for (int i = 0; i < nums.size(); i++) { if (nums[i] > 0) { return result; } //去重 if (i > 0 && nums[i] == nums[i - 1]) { continue; } int left = i + 1; int right = nums.size() - 1; while (right > left) { if (nums[i] + nums[left] + nums[right] > 0) right--; else if (nums[i] + nums[left] + nums[right] < 0) left++; else { //先保存,再去重 } } } } };去除重复值
还是以a+b+c为例
去除a中的重复值
其中,i 为最外层循环,每当 i 更新(向后移动)时,需要对其进行 去重操作
注意,去重时需要使用num[ i ]和num[ i + 1 ]来比较,而不是使用num[ i - 1 ]和num[ i ]
后者会漏掉某些情况,例如
{-1, -1, 2} ↑ ↑ i-1 i
此时,num[ i - 1 ] = num[ i ],理应直接返回,不再继续遍历
但是这样就会把{-1, -1, 2}这种情况漏掉
去除b、c中的重复值
当第一次满足nums[i] + nums[left] + nums[right] = 0条件时,我们先要对本次的数做保存
之后再满足条件就要对值进行去重操作
原理和对 a 的做法是一样的,仍然是拿当前遍历值与它的下一个值做比较,如果相等就继续移动对应的指针
代码class Solution { public: vector<vector<int>> threeSum(vector<int>& nums) { vector<vector<int>> res;//初始化一个vec用来存放结果 //先对数组进行排序 sort(nums.begin(),nums.end()); //遍历数组(最外层,i) for(int i = 0; i < nums.size(); i++){ //判断i是否大于零 //大于零直接返回了 //这种认为规避多余运算的处理称为"剪枝" if(nums[i] > 0){ return res; } //对i进行去重 if(i > 0 && nums[i] == nums[i - 1]){//不要忘了i > 0的条件,因为第一次是不需要去重的 continue;//i往后移 } //初始化双指针(代表b、c) int left = i + 1;//指向i的后一位 int right = nums.size() - 1;//指向数组末尾 while(left < right){ //不能取等于号,那样b和c就是同一个值了,不满足三个数的要求 //判断是否找到满足条件的三个值 //三种情况 if(nums[i] + nums[left] + nums[right] > 0){ //右指针左移,减小整体值 right--; }else if(nums[i] + nums[left] + nums[right] < 0){ //左指针右移,增大整体值 left++; }else{//等于的情况,找到三个目标值 //先保存第一次满足条件的结果 res.push_back(vector<int>{nums[i], nums[left], nums[right]}); //第二次就开始去重 // //b去重(left) // if(left < right && nums[left] == nums[left + 1]){ // left++; // } // //c去重(right) // if(left < right && nums[right - 1] == nums[right]){ // right--; // } //这里不要用if //将所有可能的b、c值都做去重判断,直到不满足left < right,结束本次遍历,更新i进行下次遍历 while(left < right && nums[left] == nums[left + 1]){ left++; } while(left < right && nums[right - 1] == nums[right]){ right--; } //如果没找到重复值,左右指针均向中心移动 left++; right--; } } } return res; } };易错点 1、初始化vector
因为这个vector是用来存放一个三个元素的数组的,所以初始化的时候数据类型应该是vector ,而不是int
解题模板也有提示,注意看
2、去重的时机无论是对a还是b、c进行去重,第一次得到的结果都是不用进行去重的
3、保存结果后的去重操作当第一次满足条件时:
{-1, -1, -1, 3, 2, 2} ↑ ↑ ↑ i left right
我们得到了
这时left、right移动
{-1, -1, -1, 3, 2, 2} ↑ ↑ ↑ i left right
发现nums[left] == nums[left + 1]并且nums[right - 1] == nums[right]
显然是出现了重复,此时需要跳过当前left、right指向的值
直到不出现这种重复情况为止 ,因此这里 必须使用while而不是if
四数之和
力扣题目链接(opens new window)
题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例: 给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 满足要求的四元组集合为: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]
思路和三数之和的思路一样,都是用双指针法去解
无非就是再多套一层循环
解决三/四数之和,或者说n数之和问题, 本质上是利用双指针的方法去降低时间复杂度
四数之和的双指针解法是两层for循环nums[k] + nums[j]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[j] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n 2),四数之和的时间复杂度是O(n 3)
简单来说,这里的固定值有两个: k 和 j
代码class Solution { public: vector<vector<int>> fourSum(vector<int>& nums, int target) { vector<vector<int>> res; //先对数组进行排序 sort(nums.begin(),nums.end()); //剪枝操作 for(int k = 0; k < nums.size(); k++){ //因为target是自定义的,可以dayu0也可以小于0,所以不能单纯的通过nums[k] > target来剪枝 // if (nums[k] > target && nums[k] >= 0) { break; // 这里使用break,统一通过最后的return返回 } //对k进行去重 if(k > 0 && nums[k] == nums[k - 1]){//不要忘了k > 0的条件,因为第一次是不需要去重的 continue;//i往后移 } for(int j = k + 1; j < nums.size(); j++){ // 2级剪枝处理,同理 if (nums[k] + nums[j] > target && nums[k] + nums[j] >= 0) { break; } //对j进行去重 if(j > k + 1 && nums[j] == nums[j - 1]){ continue;//j往后移 } //初始化双指针(代表b、c) int left = j + 1;//指向j的后一位 int right = nums.size() - 1;//指向数组末尾 while(left < right){ if((long)nums[k] + nums[j] + nums[left] + nums[right] > target){ right--; }else if((long)nums[k] + nums[j] + nums[left] + nums[right] < target){ left++; }else{ res.push_back(vector<int>{nums[k], nums[j], nums[left], nums[right]}); //去重 //将所有可能的b、c值都做去重判断,直到不满足left < right,结束本次遍历 while((long) left < right && nums[left] == nums[left + 1]){ left++; } while((long) left < right && nums[right - 1] == nums[right]){ right--; } //如果没找到重复值,左右指针均向中心移动 left++; right--; } } } } return res; } };注意点 1、剪枝操作
不能使用 nums[k] > target 直接进行剪枝操作
例如有:[-4, -1, 0, 0],target = -5
此时nums[k] = -4,nums[k]确实大于target
如果按照上面的条件进行剪枝,该结果集会被直接跳过,显然是不行的
因此这种情况下就不要剪枝了,当target大于0时我们再使用nums[k] > target来判断是否剪枝
综上,本题中的剪枝条件应该改为`nums[k] > target && nums[k] >= 0
查看更多关于【LeetCode】三数之和+四数之和(双指针)的详细内容...