3Sum

一. 3Sum

Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Note: The solution set must not contain duplicate triplets.

For example, given array S = [-1, 0, 1, 2, -1, -4],

A solution set is:
[
[-1, 0, 1],
[-1, -1, 2]
]

Difficulty:Medium

TIME:TIMEOUT

解法

这道题其实和之前做过的Two Sum很类似,都是在数组中找几个数,这几个数的和为目标数值。不过这道从两个数变为了三个数。而且还要输出所有的满足条件的集合。

其实这道题的解法很容易能够想到,先遍历一遍数组将所有的数与目标数的差值存入集合中,之后遍历两遍数组,如果两个数的和在集合中,则这三个数就满足要求。因此复杂度大约为 O(n2)

想法是很简单,但实现起来却很麻烦,最重要的问题就是去重的问题,由于实在没有想到理想的去重方法,我就直接采用了集合来去重(之前有Increasing Subsequences将数组转成字符串存入map中来去重,但那那方法想想就有点奇怪,还是不要用了)。

但是代码写好之后,提交各种超时,都是卡在最后的3000样例数据上,3000的平方为900万,按理说是不会超时的,但是由于代码的时间复杂度虽然为 O(n2) ,但是常系数是很高的,特别是map的find方法(或者是下标引用,二维向量的下标引用竟然有8以上的常系数)。

而且一直没怎么想到处理很多元素相同的比较好的办法,一开始我是用二维向量,来保存处理过序列,但是二维向量的查找复杂度太高了,一直超时,就改成了预处理去重的方法。因为相同的元素超过了两次就没有意义,而且0超过了三次也没有意义(之前一直卡在全0的数据)。预处理之后,终于能够通过这道题了。

vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> v;
    if (nums.size() < 3)
        return v;
    set<vector<int>> s;  //这个集合用来去除重复的数组序列
    unordered_map<int, int> loc;  //这个map用来存目标数以及需要该目标数的下标
    unordered_map<int, int> dup; //这个是用来去除数组中重复的元素
    int need = 0;
    vector<int> tmp;
    for (int i = 0; i < nums.size(); i++) {
        if (dup[nums[i]] < 2 || (nums[i] == 0 && dup[nums[i]] < 3)) { //预处理去除重复元素
            dup[nums[i]]++;
            loc[-nums[i]] = tmp.size();
            tmp.push_back(nums[i]);
        }
    }
    for (int i = 0; i < tmp.size(); i++) {
        for (int j = i + 1; j < tmp.size(); j++) {
            need = tmp[i] + tmp[j];
            if (loc.find(need) != loc.end()) {
                if (loc[need] == i || loc[need] == j) //避免这两个数是需要目标数的数
                    continue;
                vector<int> t{ tmp[i],tmp[j],-need };
                sort(t.begin(), t.end());
                s.insert(t);  //插入的时候去除重复的序列
            }
        }
    }
    vector<vector<int>> result(s.begin(), s.end());
    return result;
}

代码的时间复杂度为 O(n2) ,实际运行时间约为500ms。

优化

既然map会有那么高的常系数,而且处理重复序列也需要耗费那么多操作,那么有没有一种算法可以不依靠map和去重就能解决这道题呢,比如类似于Increasing Subsequences的第二种解法,利用问题的特性在解决问题的过程中就排除了重复的情况。

在处理这个问题我最大的失误就是没有考虑排序的情况(其实如果复杂度一定不会优于 O(nlogn) ,就可以优先考虑排序会不会让处理的问题更加简单)。

这道题在排序过后就能提供给我们很多额外的信息,而且还可以采取不同的解法。排序之后左边总是比右边的数小,这样如果一个数在数组左端一个数在数组右端,两个的数相加之后,如果我们想让这个数变小一点,就可以让右端的下标向左移,如果我们想让这个数变大一点,就可以让左端的下标向右移,注意,这种方式不会漏掉任意一个我们需要的和。

因此,优化后的算法就是基于这样的思想,能够去除所有的重复情况并且不依赖于map来求解。

vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> v;
    if (nums.size() < 3)
        return v;
    sort(nums.begin(), nums.end());
    int left, right;
    for (int i = 0; i < nums.size() - 1; i++) {
        left = i + 1;
        right = nums.size() - 1;
        while (left < right) {
            if (nums[left] + nums[right] + nums[i] < 0) //需要的数比目标数小,移动左边的下标
                left++;
            else if (nums[left] + nums[right] + nums[i] > 0) //需要的数比目标数大,移动右边的下标
                right--;
            else if (nums[left] + nums[right] + nums[i] == 0) { //刚好和目标数相等,则记录结果
                vector<int> tmp{ nums[i],nums[left],nums[right] };
                v.push_back(tmp);
                //排除掉所有的重复数字(固定了三个数中两个,因此同样的数字已经没用了)
                while (left + 1 < right && nums[left + 1] == nums[left])
                    left++;
                while (left + 1 < right && nums[right - 1] == nums[right])
                    right--;
                left++;
                right--;
            }
        }
        //继续排除掉重复的数字,最先出现的相同数字具有的情况比晚出现的多(和求递增子序列的思想一样)
        while (i < nums.size() && nums[i + 1] == nums[i])
            i++;
    }
    return v;
}

代码的时间复杂度为 O(n2) ,实际运行时间约为100ms。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值