双指针_三数之和_C++
1.题目解析
leedcode题目链接:https://leetcode.cn/problems/3sum/
结合示例1理解题目:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
我们可以像这样选三个位置的数,让它们相加等于0:
其中[-1, 0, 1]
和[0, 1, -1]
是相同的三元组,因为其中的元素是相同的,元素的顺序不重要。
2.算法原理
2.1解法一:暴力解法
最原始的想法:先通过三层for循环,从左向右暴力枚举出所有可能的结果;然后对这些结果一一进行排序,目的是让像[-1, 0, 1]
和[0, 1, -1]
这样的结果都变成[-1, 0, 1]
,统一形式;最后将进过排序的结果放入set
去重。
这样的想法可以进行一些优化:
1.先排序
先把数组进行一次排序,这样就不用对结果一一排序了,仅需一次大的排序即可。
2.再通过三层for循环,暴力枚举出所有可能结果
3.放入set去重
这样的暴力解法时间复杂度是
O(n^3)
2.2解法二:双指针
1.先对数组排序
遇到有序数组,就想到双指针算法和二分法。其中,双指针算法可以在暴力解法的基础上,将时间复杂度直接降低一维。
2.固定一个数a
让i
指向第一个元素-4,固定这个数,然后用双指针在这个数后面的区间内搜索。
3.在这个数后面的区间内,利用双指针算法,快速找到两个数的和等于-a
这里可以参考我之前的博客,和为s的两个数。
4.处理细问题:
1)去重
1. left,right去重
找到一种结果之后,left
和right
指针要跳过重复元素:当left
指向从左往右的第一个0,right
指向从右往左的第一个4时nums[left] + nums[right] == -a
,找到了一个结果。这时,left
向右移动,right
向左移动,还是有nums[left] + nums[right] == -a
,但是这个结果和之前的结果重复,需要去重,所以直接让指针跳过这些重复的元素就可以了。
2. i去重
第一个-4处理完后,i
向后移动一位,指向的元素还是-4,又要重复之前的搜索过程,得到的结果和之前的结果重复,需要去重。直接让i
也跳过重复元素即可。
2)不漏记数
找到一种结果之后,指针不要停,缩小区间,继续寻找。
3)避免越界
这里不细讲,主要是通过
left
和right
之间的关系来避免越界的,放到代码中具体分析。
3.代码演示
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
vector<vector<int>> ret;
// 1.排序
sort(nums.begin(), nums.end());
// 2.利用双指针解决问题
int n = nums.size();
for(int i = 0; i < n; i++)
{
if(nums[i] > 0) break; // 这里有一个小优化,一旦nums[i]>0了,就不用再向下找了
int left = i + 1;
int right = n - 1;
int target = -nums[i]; // 定义目标值
while(left < right)
{
int sum = nums[left] + nums[right];
if(sum < target) left++;
else if(sum > target) right--;
else if(sum == target)
{
ret.push_back({nums[i], nums[left], nums[right]});
left++, right--; // 不漏
// left,right 的去重
while(left < right && nums[left] == nums[left - 1]) // left < right 的限制是防止越界
left++;
while(left < right && nums[right] == nums[right + 1])
right--;
}
}
// 去重i
while(i < n - 1 && nums[i] == nums[i + 1]) // i < n 的限制是防止越界
i++;
// 注意此处是i < n - 1,若写成n,测试用例[0, 0, 0, 0]无法通过
}
return ret;
}
};