leetcode题解-15.三数之和


博客专栏地址:https://blog.youkuaiyun.com/feng964497595/category_9848847.html
github地址:https://github.com/mufeng964497595/leetcode


题目描述

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

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

示例:

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

思路解析

  1. 咋一看很简单,直接三层for循环嵌套就可以搞定,但是时间复杂度就是 O ( n 3 ) O(n^3) O(n3)了,肯定不能这样搞。再细看一下示例,给出的答案是已经从小到大排好序的,这不是赤裸裸的暗示我要排序么。排完序之后,a、b、c这三个数就是呈非递减顺序。我用两个for循环分别遍历a、b这两个数,记下当前在数组的下标为idx_a和idx_b,那么第三个数c肯定要大于等于b,那不就是在idx_b+1开始的有序数组中查找c么?所以把第三组for循环改成二分查找,时间复杂度就从 O ( n 3 ) O(n^3) O(n3)降到了 O ( n 2 l o g n ) O(n^2log_n) O(n2logn)了。抱着忐忑的心情跑了一下代码,居然通过了,耗时600+ms。
  2. 单单这样搞虽然解出了题,但是这解法对不起中等难度这个标签,于是再分析一下。第三层for循环优化了,那接下来就看下第二层怎么优化。在第一层固定的情况下,其实剩下的求解就是在有序数组中求解两数之和了。这种如果搞个map来做,那时间复杂度跟解法1其实是差不多的(hash map的话会优一些),空间复杂度还要大一点。那对于想用 O ( n ) O(n) O(n)的解法,就要考虑下从左右两端逼近的方式了。

[ − 6 , − 3 , − 1 , 0 , 1 , 3 , 5 ] [-6, -3, -1, 0, 1, 3, 5] [6,3,1,0,1,3,5]为例,求两数之和等于0的解。初始时i、j指针分别指向数组两端
原始
此时 n u m s [ i ] + n u m s [ j ] = − 6 + 5 = − 1 < 0 nums[i] + nums[j] = -6 + 5 = -1 < 0 nums[i]+nums[j]=6+5=1<0,意味着是左边的数太大了,所以要移动左指针i,得到新位置。
第一步
此时 n u m s [ i ] + n u m s [ j ] = − 3 + 5 = 2 > 0 nums[i] + nums[j] = -3 + 5 = 2 > 0 nums[i]+nums[j]=3+5=2>0,意味着是右边的数太大了,所以要移动右指针j,得到新位置。
第二步
此时 n u m s [ i ] + n u m s [ j ] = − 3 + 3 = 0 nums[i] + nums[j] = -3 + 3 = 0 nums[i]+nums[j]=3+3=0,即找到答案了,记录下来,然后左右指针同时移动,得到新位置。
接下来就是重复上面的步骤,直至两个指针相遇为止。

从上述算法演示可以看出,只需要遍历一次数组即可,时间复杂度 O ( n ) O(n) O(n)。最后只需要考虑下去除重复的结果集,去重也简单,只要在a、b、c这三个数看到重复值就跳过即可。

示例代码

#include <algorithm>

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        std::sort(nums.begin(), nums.end());    // 排序,控制结果集三个数从小到大排列
        vector<vector<int>> res;
        int len = nums.size();
        for (int i = 0; i < len; ++i) {
            if (i != 0 && nums[i] == nums[i - 1]) {  // 控制第一个数不重复
                continue;
            }
            int j = i + 1, k = len - 1;
            while (j < k) {
                int calc = nums[i] + nums[j] + nums[k];
                if (j > i + 1 && nums[j] == nums[j - 1]) {  // 控制第二个数不重复
                    ++j;
                } else if (k < len - 1 && nums[k] == nums[k + 1]) {  // 控制第三个数不重复
                    --k;
                } else if (calc > 0) {  // 求和大于0,在第一个数不变的情况下,意味着第三个数偏大
                    --k;
                } else if (calc < 0) {  // 求和小于0,在第一个数不变的情况下,意味着第二个数偏小
                    ++j;
                } else {
                    vector<int> tmp = {nums[i], nums[j], nums[k]};
                    res.push_back(tmp);
                    ++j;
                    --k;
                }
            }
        }

        return res;
    }
};

PS:
ipad pencil买了这么久,终于派上用场了,画示意图确实方便很多,不需要visio调得要死要活哈哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值