力扣 15 三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
示例 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] 。 注意,输出的顺序和三元组的顺序并不重要。
示例 2:
**输入:**nums = [0,1,1] 输出:[] **解释:**唯一可能的三元组和不为 0 。
示例 3:
**输入:**nums = [0,0,0] 输出:[[0,0,0]] **解释:**唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000-105 <= nums[i] <= 105
双指针法 时间复杂度O(n^2)
如动图所示,双指针的核心思路是 定义左右指针,不断比较三个指针索引处的值的和大小。如果和小于零,说明
left 应该右移;反之如果和大于零,说明 right 应该左移,直至 left 和right 两指针相遇时,改变 i 指针位置,使其右移,再次循环,直至找到所有符合题意的三元组情况。
在这里要注意先排序,因为题给条件不需返回下标,故可以排序,排序后整个思路便可顺利进行。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
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 {
result.push_back(vector<int>{nums[i], nums[left], nums[right]});
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
return result;
}
};
本题核心一是理解双指针,二是理解去重操作
对 a 的去重:
a 实际上就是 nums[i] ,遇到重复元素应该直接跳过,问题在于 判断重复跳过的条件是什么?
有两种写法 nums[i] == nums[i+1] 和 nums[i] == nums[i-1],两种写法的本质区别在于一个是和后一位比较,一个是和前一位比较。
以数组 [-1,-1,2] 为例:
如果是和后一位比较,初始 i = 0,后一位 i = 1,两者索引值都为 -1,按照逻辑会自动跳过,但数组本身为符合条件的三元组。
如果是和前一位比较,i = 0 的前一位为空,可以顺利取到 -1,-1,2,当 i 遍历到 1 时,前一位 i = 0 处索引值为 -1,根据逻辑可以跳过,因此与前一位比较的写法既保证了符合条件数组的保留也保证了同一数组只会出现一次。
因此 nums[i] == nums[i-1] 的写法为正确的去重写法。
对 b 和 c 的去重:
对b 和 c 的去重位置很重要,只有当 a+b+c == 0 找到一组有效答案之后,才执行 b、c 去重。因为在找到一组 [a,b,c] 之后,由于数组有序,和当前 b 一样的后续数字、和当前 c 一样的前面数字,继续配对一定会生成完全重复的三元组,因此要直接跳过。
if (nums[i] + nums[left] + nums[right] == 0) {
res.push_back({nums[i], nums[left], nums[right]});
// b去重:跳过后续所有和当前left相等的值
while(left < right && nums[left] == nums[left + 1]){
left++;
}
// c去重:跳过前面所有和当前right相等的值
while(left < right && nums[right] == nums[right - 1]){
right--;
}
// 最后正常收缩指针
left++;
right--;
}
两个注意点:
-
不能把 b、c 去重写在 while 循环最前面 这样还没找到答案就提前跳过,会直接丢失合法组合,b/c 去重必须找到一组解之后再去重。
-
注意
left < right边界判断 连续相同元素走到尽头,left >= right再访问left+1会数组越界,因此不能带等号。