73、【数组】leetcode——15. 三数之和(C++/Python版本)

题目描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
原题链接:15. 三数之和

解题思路

本题的难点在于去重,针对两种不同的方式:双指针和Hash,采用不同的去重判定条件。

1、去重的目标

要明确,去重的是重复三元组,而不是三元组里重复的数。

2、去重初步思路

为了方便去重,第一步是先排序,可保证 nums[i]≤nums[j]≤nums[k] 。当按此顺序从前往后依次遍历时,不会出现因异位而出现的重复三元组,例如[1,1,2][1,2,1],将其排序后都为[1,1,2],这样子答案就只会记录一种情况,不会出现重复情况。
但是,可能会出现因数组中本身存在的相同元素导致出现记录重复三元组,例如[-1,0,0,0,1][-1,-1,0,0,1],此时由于多个重复的0,导致会对[-1,0,1]进行重复记录。因此,需要对其进行去重。

由上述分析可知,现在的问题是,可能会因出现重复元素而导致在遍历时可能会被重复记录,从而出现重复三元组
因此,我们要解决的问题便是:对于含有重复元素的数,每次仅对比记录一次,若后续再次出现重复元素,则将其跳过。

一、排序+双指针法

1、双指针思路

双指针的思路是从前往后,确定第一个元素nums[i]后,再在该元素的后续集合中的首和尾分别设置指针lr。利用数组元素中的单调性,找到nums[i] + nums[l] + nums[r] == 0时的三个数。

(1)若nums[i] + nums[l] + nums[r] < 0,说明三者相加的数小了,l++

(2)若nums[i] + nums[l] + nums[r] > 0,说明三者相加的数大了,r--

(3)若nums[i] + nums[l] + nums[r] == 0,则找到目标数,记录到结果集res中。

总结来说,首选选择一个元素,定做基准。然后,从遍历后面区间内的元素,找到可以满足相加为0的情况。同时,要对会出现重复的情况进行去重。

2、去重思路

主要是对[a, b, c]三个进行去重。

对于a, b去重:

  1. 对于第一个元素nums[i],为了保证第一次出现的元素被记录并且还不会记录后续重复情况,去重条件应设为 nums[i] == nums[i - 1],当满足该条件时,认为有重复元素,第一次的情况已被记录,再次重复出现情况不应被记录,应跳过本次遍历,执行continue
    注:如果设置为nums[i]==nums[i+1]作为去重条件,那么可能会就让某种情况李含有重复元素的结果被删除,而不是让出现重复情况的结果被去重。

对于b, c去重:
2. 对于第二个元素nums[l],由于,我们会在之前就将第一次出现并满足三者相加等于0的元素加入到了结果集res中,所以,去重条件为nums[l] == nums[l + 1],确保后续重复元素不再被记录。
3. 对于第三个元素nums[k],理由同上,去重条件为nums[r] == nums[r - 1],确保后续重复元素不再被记录。

C++版本

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());     // 先排序
        for(int i = 0; i < n - 2; i++) {    // 对i从0~n-2,留出两个位置用作l和r
            if(nums[i] > 0)     break;      // 当遍历的第一个数大于0,则后续相加的两个数一定不会为0
            if(i > 0 && nums[i] == nums[i - 1])     continue;   // 去重。与i-1相比,若之前i已有相等数加入其中,则次数若再加入,则会出现重复三元组。不和i+1进行比较的原因是避免出现漏记元素
            int l = i + 1, r = n - 1;       // 双指针分别从i后的首和尾开始遍历
            while(l < r) {
                while(l < r && nums[i] + nums[l] + nums[r] < 0)     l++;    // 比0小,说明需要增大,l++
                while(l < r && nums[i] + nums[l] + nums[r] > 0)     r--;    // 比0大,说明需要减小,r--
                if(l < r && nums[i] + nums[l] + nums[r] == 0) {             // 找到相加等于0时
                    res.push_back(vector<int>{ nums[i], nums[l], nums[r]}); // 先将目标结果加入其中
                    while(l < r && nums[l] == nums[l + 1])      l++;        // 去重。因已有nums[l]加入结果中,如果下一个数与之相等,那么会出现重复三元组
                    while(l < r && nums[r] == nums[r - 1])      r--;        // 理由同上
                    l++, r--;                                               // 上述while只是保证了后续不出现重复元素,还需要更新指针的值,指向后续元素
                }
            }
        }
        return res;
    }
};

时间复杂度 O ( n 2 ) O(n^2) O(n2)
空间复杂度 O ( l o g n ) O(log n) O(logn) (忽略存储答案的空间,额外的排序的空间复杂度为 O ( l o g n ) O(log n) O(logn)

Python版本

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        nums.sort()
        res = []
        if n < 3 or nums[0] > 0 :
            return []
        
        for i in range(n - 2) :        	
            if nums[i] > 0:										// 剪枝
                break
            if i > 0 and nums[i] == nums[i - 1] :				// 去重
                continue            
            l, r = i + 1, n - 1									// 找满足三数之和为0
            while l < r :
                s = nums[i] + nums[l] + nums[r]
                if s < 0 :										// 小于0,则加更大的数
                    l += 1
                elif s > 0 :									// 大于0,则加更小的数
                    r -= 1
                elif s == 0 :									// 等于0,则找到结果并对后两个数去重
                    res.append([nums[i], nums[l], nums[r]])
                    while l < r and nums[l] == nums[l + 1] :
                        l += 1
                    while l < r and nums[r] == nums[r - 1] :
                        r -= 1
                    l += 1
                    r -= 1                    
        
        return res

二、排序+hash表法(不方便)

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());     // 先对元素排序
        for(int i = 0; i < n; i++) {
            if(nums[i] > 0)     break;      // 如果起始遍历的元素大于0,那么后续的元素再相加一定不会为0
            if(i > 0 && nums[i] == nums[i - 1])     continue;   // 去重nums[i]
            unordered_set<int> record;
            for(int j = i + 1; j < n; j++) {
                // 去重nums[j]
                if(j > i + 2 && nums[j] == nums[j - 1] && nums[j - 1] == nums[j - 2])
                    continue;
                int gap = 0 - (nums[i] + nums[j]);      // 找到相加为0的nums[k]
                if(record.find(gap) != record.end()) {
                    res.push_back({ nums[i], nums[j], gap});
                    record.erase(gap);      // 去重nums[k]
                } else {
                    record.insert(nums[j]);
                }
            }
        }
        return res;
    }
};

参考文章: 第15题. 三数之和三数之和

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辰阳星宇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值