15. 三数之和(C++)

题目

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],
 
满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

分析与题解

核心思路是采取双指针遍历法来求解。首先因为所求容器为数值的vector集合,对于下标没有硬性要求,因此先对数组进行升序排列

sort(nums.begin(), nums.end());

对于求取a+b+c=0,我们优先固定a初始位置下标为i,简化为双指针问题,再设置左指针为i+1,右指针位置为nums.size()-1。此时左右指针指向元素分别为考虑范围内的最大和最小值。

while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) {
                    right--;
                }
                else if (sum < 0) {
                    left++;
                }
                else {
                    res.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    //确认结果后,进行内层的去重
                    while(left < right && nums[left] == nums[left + 1]) left++;
                    while(left < right && nums[right] == nums[right - 1]) right--;
                    //去重后此时再收缩,两边界都不再是重复元素
                    left++;
                    right--;
                }
            }

三指针移动策略为:

  • a+b+c>0,需要将右指针向左滑动,减少和大小
  • a+b+c<0,需要将左指针向右滑动,增大和大小
  • a+b+c=0,可以将当前一组数值push_back进结果列表中

另外根据我们对于题目的理解,可以额外对一些情况进行排除。由于数组在遍历前已经进行升序排列,当最小值a>0,三数和不可能为0,可以直接排除这种情况。

for (int i = 0; i < size - 2; i++) {
            // 升序数组的最小元素如果>0
            // 三数之和不可能为0
            if (nums[i] > 0) break;
 
            //...
 
            }

去重

尽管数组进行重新排序,但还有可能出现重复元素,而如果逐位进行结果的筛选,最终的vector列表中可能就会出现重复的组合,因此需要对结果进行筛选。

①内层循环去重(左右指针)

while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) {
                    right--;
                }
                else if (sum < 0) {
                    left++;
                }
                else {
                    res.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    //确认结果后,进行内层的去重
                    while(left < right && nums[left] == nums[left + 1]) left++;
                    while(left < right && nums[right] == nums[right - 1]) right--;
                    //去重后此时再收缩,两边界都不再是重复元素
                    left++;
                    right--;
                }
            }

去重的位置设置在找到满足a+b+c=0的组合后,因为排序数组的重复元素肯定时相邻的,如果不加去重,相同数值的结果便会添加进列表。

②外层循环(首指针)

外层指针一般在找到满足条件的组合时相对来说位置都是固定的。只要保证每次提供给内层循环的数值不同即可实现外层去重。需要注意的是,以下这种去重方法是不严谨的

for (int i = 0; i < size - 2; i++) {
    if (nums[i] == nums[i + 1])
        continue;
}

因为这样会导致在进入内层循环前,首指针已经将选定数值的重复元素全部筛除掉。这样会漏掉首指针跟左指针数值相同的vector组合,例如[-1,-1,2]

因此正确的方法是遇到重复元素时,直接进行内层循环,退出后再将外层循环的重复元素进行排除。

if (i > 0 && nums[i] == nums[i - 1])
                continue;

最后完整代码如下:

class Solution {
public:
    vector<vector<int>> res;
    vector<vector<int>> threeSum(vector<int>& nums) {
        // 首先对数组进行排序
        sort(nums.begin(), nums.end());
        int size = nums.size();
        //至少留有left、right两个位置,最大边界进行收缩
        for (int i = 0; i < size - 2; i++) {
            // 升序数组的最小元素如果>0
            // 三数之和不可能为0
            if (nums[i] > 0) break;
            // 错误去重方式
            // 会错过 [-1, -1, 2]的情况
            // if (nums[i] == nums[i + 1])
            //     continue;
 
            //对最外层进行去重
            if (i > 0 && nums[i] == nums[i - 1])
                continue;
 
            int left = i + 1, right = size - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) {
                    right--;
                }
                else if (sum < 0) {
                    left++;
                }
                else {
                    res.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    //确认结果后,进行内层的去重
                    while(left < right && nums[left] == nums[left + 1]) left++;
                    while(left < right && nums[right] == nums[right - 1]) right--;
                    //去重后此时再收缩,两边界都不再是重复元素
                    left++;
                    right--;
                }
            }
        }
        return res;
    }
};
posted @ 2020-12-04 09:03  脱线森林`  阅读(652)  评论(0编辑  收藏  举报