167. 两数之和 II - 输入有序数组
我们先用双指针解答此题,再来引出三数之和:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : vector<int > twoSum (vector<int >& numbers, int target) { int left = 0 ; int right = numbers.size () - 1 ; while (left < right){ int sum = numbers[left] + numbers[right]; if (sum < target){ left++; }else if (sum > target){ right--; }else { return {left + 1 ,right + 1 }; } } return {}; } };
从题目描述来看,可以提取出几个关键信息:
数组有序,因此如果两个数之和比目标值小,left++;两个数之和比目标值大,right--。
返回下标,但特点是下标从 1 开始算,而不是从 0 开始。
有唯一的结果,拿到第一个结果就可以选择返回。
15. 三数之和
题目没有要求返回下标,也允许按任意顺序返回,排序会让信息量增大,对我们有利。
再一个就是不要忘记去重问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Solution {public : vector<vector<int >> threeSum (vector<int >& nums) { vector<vector<int >> result; sort (nums.begin (), nums.end ()); int size = nums.size (); if (nums[0 ] > 0 || nums[size - 1 ] < 0 ) return result; for (int i = 0 ; i < size - 2 ; i++) { if (i > 0 && nums[i] == nums[i - 1 ]) { continue ; } int left = i + 1 ; int right = size - 1 ; while (left < right) { int sum = nums[i] + nums[left] + nums[right]; if (sum > 0 ) { right--; } else if (sum < 0 ) { left++; } else { result.push_back ({nums[i], nums[left], nums[right]}); while (left < right && nums[right] == nums[right - 1 ]) { right--; continue ; } while (left < right && nums[left] == nums[left + 1 ]) { left++; continue ; } right--; left++; } } } return result; } };
数组已经排序,优化策略为如果第一个数大于 0,代表三数之后不可能等于
0,会永远大于 0;或者最后一个数小于
0,代表三数之后不可能等于0,会永远小于 0。
再谈的就是去重问题,容器中存储的结果是这三部分组成
{nums[i], nums[left], nums[right]}
。因此去重就是针对这三个下标的去重。
在此之前,还是应该谈一谈寻找的思路:
image20250118123951411.png
从第一个元素开始遍历,截止到倒数第三个元素(题目保证至少有三个元素),因为后面两个元素不可能有机会往后匹配组成三数之和。left
是遍历元素的后一个元素的下标,right 指向最后一个元素的下标。
left 和 right
就是在[left,right]完成匹配工作。在这个区间内匹配可能成功多次,因此是在一个循环中进行的。
对 i 进行去重:
image20250118125252293.png
我们看图知道一定是第一个 1 第一次匹配三数之和成功,后面再出现的 1
去匹配就是重复的,我们的去重逻辑也就清晰了。
i > 0 && nums[i] == nums[i - 1]
,即当前元素和前一个元素比较,如果相同就代表会重复。
left 和 right 去重:
image20250118131059842.png
我们知道,left 和 right
的去重,是在匹配成功之后进行的,这里面有种确定性地含义。假定现在 left
的右边一个元素 或者 right 的左边的一个元素是重复的,
那么不管是哪种情况,都表明三个数是具体值也都确定(首先 i 已经确定,那么
left 确定情况下 right 就确定,right 确定情况下 left 就确定,left 和
right
都确定那还有什么好说的),不然不可能构成有效的三数之和。但是我们在匹配成功之后,这个时候重复的元素只会导致三数之和是重复的,属于无效组合。因此,有必要对
left 和 right 进行去重。
匹配成功,缩减之后继续匹配:
image20250118130009934.png
因为 left 和 right
构成一个区间,很有可能会多次匹配成功,所以在去重之后继续缩减,继续进行匹配,直到退出循环。