leetcode题解:第15题3 Sum

https://leetcode-cn.com/problems/3sum/

分析

这道题与Two Sum很像,我们很容易想到利用Two Sum的程序来解答这个问题,对于a+b+c = 0,有-c = a+b,这相当于target设置为-c的Two Sum问题。区别在于,a和b的值不是唯一的,即这个Two Sum的答案不唯一。
除了找到所有正确的a和b外,还要解决三元组重复的问题。

解法一

我们依然考虑在Two Sum的基础上解决这个问题。
首先,Two Sum的核心代码是:

for (int i = 0; i < nums.size(); ++i) {
    if (m.find(target - nums[i]) != m.end()) 
        return {m[target - nums[i]], i};
    m[nums[i]] = i;
}

我们把return那一行做一些修改,改为添加一个三元组到答案中,就可以作为本题的核心代码了。
问题在于,这样做会导致大量重复的三元组,有两种情况的重复:

  1. 调换元素顺序导致的重复:如[-1,0,1]和[0,-1,1]本质上是同一个三元组
  2. 不同元素的值相同导致的重复:如nums=[-4,2,2,2],会导致两个[-4,2,2]的产生;nums=[-1,-1,0,1],会导致两个[-1,0,1]的产生

情况1比较容易解决,我们在添加三元组时,只添加a <= b <= c这样的[a,b,c]到答案中,即指定答案中三元组的顺序。
但这样做是不够的,不仅解决不了情况2,当多个元素值相同时,连情况1也解决不了。如nums=[0,0,0],这时满足a == b == c,我们加的限制条件也就没有用了。

采用排序的方法能够很好地解决这两种情况的重复。
对于排序之后的数组,我们使用双层循环,外层循环用于寻找target值,内层循环的逻辑就是Two Sum的逻辑,借助哈希表来寻找sum为target的两个元素。
我们保证内层循环枚举到的元素不小于外层循环的元素,就可以解决情况1。对于每重循环还要保证相邻两次遍历到的元素值不相同,这是为了解决情况2。伪代码如下:

nums.sort()
for i = 0...n-1
	if i > 0 && nums[i] == nums[i-1] continue;
	for j = i+1...n-1
		if j > i+1 && nums[j] == nums[j-1] continue;
		// a=nums[j], 用哈希表找对应的b

需要注意的是,如果我们像Two Sum中那样一边遍历一边向哈希表中插入元素,可以满足a <= b <= c,即解决情况1。因为在遍历到b时target-b还不在哈希表中,而遍历到c时target-c在哈希表中。
这样做貌似效率很高,但会错过[-4,2,2]这样的三元组。因为当j == 1时nums[j]不在哈希表中,而j=2时又因为nums[j] == nums[j-1],不会参与判断。我们为了去除重复结果矫枉过正了。解决办法是在排序之后先将数组元素插入哈希表中。
我们增加了一个判断条件m[sum - nums[j]] > j来保证a <= b <= c。对于[-4,2,2]这种元素有相同值的情况,也是满足的,因为经过排序后最终覆盖哈希表的肯定是index最大的那个元素。最终,我们要添加的三元组是[nums[i], nums[j], nums[m[target - nums[j]]]]

代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        unordered_map<int, int> m;
        for (int i = 0; i < nums.size(); ++i) m[nums[i]] = i;
        for (int i = 0; i < nums.size(); ++i) {
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            int sum = -nums[i];
            for (int j = i + 1; j < nums.size(); ++j) {
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                if (m.find(sum - nums[j]) != m.end() && m[sum - nums[j]] > j) {
                    result.push_back({nums[i], nums[j],nums[m[sum - nums[j]]]});
                }
            }
        }
        return result;
    }
};
复杂度分析

时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n ) O(n) O(n)。注意用的排序算法时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

解法二

去除重复三元组的思路与解法一相同,但我们不使用哈希表,而是使用双指针来优化暴力解法。
暴力解法是3重循环,同时满足两个条件:

  1. 内层遍历循环元素不小于外层循环遍历的元素
  2. 对于每一重循环,相邻遍历的元素值不相同

即解法一中的核心想法。
3重循环获取到a,b,c,如果a+b+c = 0,它们就是一个满足条件的三元组。自然,a,b对应唯一的c,当b增大时,c减小,因此,我们可以在从左往右枚举b的同时从右往左枚举c

  1. 当a+b+c > 0时,把右指针向左移动,取更小的c
  2. 当a+b+c = 0时,把左指针向右移动,取更大的b,此时肯定有a+b+c > 0
  3. 当a+b+c < 0时,可以确定这个a,b没有对应的c,把左指针向右移动,取更大的b。是否要把右指针向右移动呢?不用。首先要明白为什么会有a+b+c < 0出现,肯定因为1.导致的,如果我们又把右指针往右移,那么又会得到a+b+c > 0的结果,最终还得把右指针移到现在这位置来

这样一来,就可以把第二重和第三重循环压缩成一个循环了。当然,始终要保证b <= c,否则退出循环。

代码
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 (i > 0 && nums[i] == nums[i - 1]) continue;
            int right = nums.size() - 1;
            for (int left = i + 1; left < right; ++left) {
                if (left > i + 1 && nums[left] == nums[left - 1]) continue;
                while (nums[i] + nums[left] + nums[right] > 0 && left < right) right--;
                if (nums[i] + nums[left] + nums[right] == 0 && left < right) {
                    result.push_back({nums[i], nums[left], nums[right]});
                }
            }
        }
        return result;
    }
};
复杂度分析

时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( l o g n ) O(logn) O(logn),排序的空间复杂度为 O ( l o g n ) O(logn) O(logn)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值