三数之和(详细解法:双指针+排序)

2023-04-12 14:31:12 浏览数 (1)

15. 三数之和

难度中等5626

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] nums[j] nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

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

示例 1:

代码语言:javascript复制
输入: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:

代码语言:javascript复制
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

代码语言:javascript复制
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

解题思路:排序 双指针

​ 因为这道题要求答案中不能有重复的三元组,那么我们肯定要进行去重的操作,也是这道题的关键!

​ 如果这道题我们用的是哈希,那么会比较复杂一点,就像这道 454. 四数相加 II 一样,我们可以将两个数组的各位和先加载到 hash 中,然后遍历第三个数组,判断是否和为 0,当然要做去重操作,相对应会比较复杂,难控制,所以这里我们不采用哈希的方法!

​ 这里我们采用排序 双指针来解决,这样子去重的步骤也比较简单!

首先我们为什么要先排序呢 ???因为我们的目的是为了得到和为 0 的三个数,并且使用的是双指针,两个指针在左右两边开始遍历,这样子的话我们其实排序完就会有天然的 “平分” 情况,因为除了为全 0 的三个数,要想得到和为 0,那么肯定会有负数的存在,所以我们相当于左右指针被 0 作为中介隔开,这是为了我们使用双指针的时候,通过中介 0 来判断挪动哪个指针的因素!并且这样子 对我们后面判断是否重复是很关键

​ 接下来就是双指针移动的过程:用循环遍历每个元素,用 i 表示当前下标,lefti 1right 为数组末尾也就是 nums.size() - 1。然后每次移动 left 和 right ,直到它们相遇!

  • 如果 nums[i] nums[left] nums[right] > 0,总和大于0,说明我们的元素选大了,就要调小一点,所以 right 向左移动也就是 right--(这就是为什么要先排序的原因,数组才能向右递增!)
  • 如果 nums[i] nums[left] nums[right] < 0,总和小于0,说明我们的元素选小了,就要调大一点,所以 left 向右移动也就是 left
  • 如果 nums[i] nums[left] nums[right] == 0,总和等于0,说明符合要求,这个时候就 将三个指针的值分别 push_back 到数组中去
    • 注意有个关键的点,就是尾插完因为我们还得继续移动 left 和 right 直到它们两个相遇,那么就有可能会出现重复的元素组合,那么这个时候就要对 left 和 right 处的元素去重,利用 while 循环判断一下!具体看代码!
    • 除此之外就是i 也要去重,我们可以知道,我们每次遍历一个 i 的时候,其实通过后面的双指针 left 和 right 已经将 i 指针的值所有可能已经走过一遍了,那么此时去重 i 指针的值就很简单,只需要判断前一个 num[i - 1] 是否与当前的 nums[i] 相同,是的话说明重复了,则直接 continue,直到遇到不重复的 i 为止!(注意要判断 nums[i-1] 的时候需要先判断 i > 0,不然会越界!)
      • 可能有人就会问为什么不是判断 nums[i] 与 nums[i 1] 是否相同呢,因为我们的 left 固定是一个组合的数,那么可能就会出错,比如说 {-1, -1, 2},此时 i、left、right 分别指向三个元素,如果判断了 nums[i] 等于 nums[i 1] 然后直接 continue的话,那么就错了,因为这三个元素是能构成一个组合的!
  • 此时如果 num[i] 大于 0,那么因为排序的原因,后面的元素肯定不会出现负数和 0,也就是说不会再出现和为 0 的组合了,那么直接 break 即可
代码语言:javascript复制
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end()); // 关键性的一步排序别忘了
        
        vector<vector<int>> vv;
        int n = nums.size();
        for(int i = 0; i < n;   i)
        {
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
            if(nums[i] > 0)
                break;
            
            // 去重i的方法
            if(i > 0 && nums[i] == nums[i - 1])
                continue;
            
            int left = i   1, right = n - 1;
            while(left < right)
            {
                if(nums[i]   nums[left]   nums[right] > 0)
                    right--;
                else if(nums[i]   nums[left]   nums[right] < 0)
                    left  ;
                else
                {
                    vv.push_back(vector<int>{nums[i], nums[left], nums[right]});

                    // 对left和right进行去重
                    while(left < right && nums[right] == nums[right-1])
                        right--;
                    while(left < right && nums[left] == nums[left   1])
                        left  ;
                    
                    // 注意此时还要将left和right更新一次才是不重复元素
                    right--;
                    left  ;
                }
            }
        }
        return vv;
    }
};

0 人点赞