您的位置:首页 > 其它

LeetCode (1) Two Sum (15)Three sum (16)Three sum closest (17)Four sum

2013-11-22 01:26 465 查看
这是个系列问题,都从最基本的2sum问题展开而来。如下:

Given an array of integers, find two numbers such that they add up to a specific target number.

The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.

You may assume that each input would have exactly one solution.

Input: numbers={2, 7, 11, 15}, target=9

Output: index1=1, index2=2

2sum问题简单地说,是这样的。给定一个数组array[ ]和一个数target作为输入,请从数组中找到两个数,使得它们的和为target. 最简单暴力地解决2sum问题的方法是用2层循环来遍历,我之前就是这么做的,这个办法很笨重。

2sum问题可以纵向扩展,得到3sum问题和4sum问题。3sum问题可以横向扩展,得到3sum closest问题。

3sum问题:给定一个数组array[ ]和一个数target,请从数组中找到三个数,使其的和为target 。update: 后来leetcode的时间要求变得严格了,必须严格检查重复元素才能避免超时。更新后的代码在本文后半部分。

4um问题:给定一个数组array[ ]和一个数target,请从数组中找到四个数,使其的和为target 。

3sum Closest问题:给定一个数组array[ ]和一个数target,请从数组中找到三个数,使其和最接近target 。

如果沿用暴力办法解决3sum问题,4sum问题, 3sum Closest问题,时间复杂度将会很高,不能在OJ上通过,所以应该考虑如何降低时间复杂度。

通过在网上的搜索,找到一个解决办法:排序。

1 优化解决2sum问题

以2sum问题为例。

如果把输入数组排序,那么要找到2个数a , b满足 a + b = target。只需要分别指向数组的开头i和结尾j,观察开头和结尾的和,如果两数之和比target小,则说明需要将开头向中间移动一步,如果两数之和比target大,则说明需要将结尾向中间移动一步,如果两数之和和target一样,则找到了满足条件的两个数,将它存起来,然后继续下一步比较(因为可能有不止一个答案),继续下一步比较的办法是把开头i和结尾j同时向中间移动一步。持续这个过程直到开头i和结尾j相遇。

排序的时间复杂度为O(N lgN) ,在排序后的数组上进行上述搜寻的时间复杂度是线性的,是O(N),所以最终的时间复杂度为 O(N lgN)。

2sum问题的代码如下

class Solution {
public:
    vector<int> twoSum(vector<int> &numbers, int target) {
            vector<int> out_res;
            vector<int> in_vec;
            for(int i=0;i<numbers.size();i++)
                in_vec.push_back(numbers[i]);
            std::sort(in_vec.begin(),in_vec.end());
            int i=0,j=(int)in_vec.size()-1;
            while(i<j){
                int sum = in_vec[i]+in_vec[j];
                if(sum>target){
                    j--;
                } else if(sum<target) {
                    i++;
                }else {
                    out_res.clear();
                    out_res.push_back(in_vec[i]);
                    out_res.push_back(in_vec[j]);
                    int index1=0,index2=0;
                    for(int i=0;i<numbers.size();i++)
                    {
                        if(numbers[i]==out_res[0])
                        {
                            index1=i+1;
                            break;
                        }
                    }
                    for(int i=0;i<numbers.size();i++)
                    {
                        if((numbers[i]==out_res[1])&&(i!=index1-1)){
                            index2=i+1;
                            break;
                        }
                    }
                    out_res.clear();
                    if(index1<index2){
                        out_res.push_back(index1);
                        out_res.push_back(index2);
                    }else {
                        out_res.push_back(index2);
                        out_res.push_back(index1);
                    }
                  //  std::cout<<out_res[0]<<","<<out_res[1]<<std::endl;
                    return out_res;
                }
                
            }  
        }
};


update: 2014-12-08 上面的时间复杂度是O(N²),因为每次搜到i和j后,还要回到原数组去找一遍i和j的真正的index。所以考虑把原数组的index保存起来之后再排序。

这样时间复杂度就真正降为O(N*lgN)了

struct NumberSet{
    int numbers;
    int index;
    NumberSet(int n, int i):numbers(n), index(i){}  
}; //struct 后面要写分号

bool my_order(struct NumberSet a, struct NumberSet b){
    return (a.numbers <= b.numbers);
};

class Solution {
public:
    vector<int> twoSum(vector<int> &numbers, int target) {
        vector<NumberSet> v;
        vector<int> result;
        for (int i = 0; i < numbers.size(); ++i) 
            v.push_back(NumberSet(numbers[i], i)); 
            //因为line 4写了构造函数 NumberSet(int n, int i):numbers(n), index(i){}  
            //所以这里可以这么简化。
            //否则就要写
            //struct NumberSet each_number;
            //each_number.numbers = numbers[i];
            //each_number.index = i;
            //v.push_back(each_number);
            //这么长一堆了。
        std::sort(v.begin(), v.end(), my_order);
        int i = 0, j = numbers.size() - 1;
        while(i <= j) {
            if (v[i].numbers +v[j].numbers == target) {
                result.push_back(v[i].index + 1);
                result.push_back(v[j].index + 1);
                std::sort(result.begin(), result.end());
                return result;
            }
            if (v[i].numbers +v[j].numbers > target) 
                j--;
            else if (v[i].numbers +v[j].numbers < target)
                i++;
        }
    }
};




2 优化解决3sum问题

如果用暴力搜索,3sum问题的时间复杂度为O(N³)借助解决2sum问题的思路,可以优化解决3sum问题。只需要在2sum的搜索过程外层增加一层循环即可,时间复杂度为O(N²)

#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
class Solution {
public:
    vector<vector<int> > threeSum(vector<int> &num) {
        vector< vector<int> > out_vec;
        if(num.size()<3)
            return out_vec;
        vector<int> in_vec;
        for(int i=0;i<num.size();i++)
            in_vec.push_back(num[i]);
        sort(in_vec.begin(),in_vec.end());
        set< vector<int> > res_set;
        set< vector<int> >::const_iterator res_set_it;
        vector<int> result_vec;
        for(int i=0;i<in_vec.size();i++) {
            int two_sum=0-in_vec[i];
            int l=i+1;
            int r=(int)in_vec.size()-1;
            while(l<r){
                int tmp_sum = in_vec[l]+in_vec[r];
                if(tmp_sum<two_sum){
                    l++;
                }else if((tmp_sum>two_sum)) {
                    r--;
                }else {
                    result_vec.clear();
                    result_vec.push_back(-two_sum);
                    result_vec.push_back(in_vec[l]);
                    result_vec.push_back(in_vec[r]);
                    sort(result_vec.begin(),result_vec.end());
                    res_set.insert(result_vec);
                    l++;
                    r--;
                }
            }
        }
        for(res_set_it=res_set.begin();res_set_it!=res_set.end();res_set_it++)
            out_vec.push_back(*res_set_it);
        return out_vec;
    }
};


但是,发现leetcode的要求变得严格了,时间需要严格控制才能避免超时。

update: 2014-12-17

three sum

class Solution {
public:
    vector<vector<int> > threeSum(vector<int> &num) {
        std::sort(num.begin(), num.end());
        int target1 = 0;
        vector<int> every_vec;
        vector<vector<int> > final_vec;
        set<vector<int> > filter_set;
        set<vector<int> >::iterator  filter_set_it;
        if (num.size() <= 2) return final_vec; //corner case:
        for (int i = 0; i < num.size() - 2; ++i) {
            if(i > 0 && num[i] == num[i - 1]) 
                continue;
            target1 = 0 - num[i];
            int first = i + 1;
            int last = num.size() - 1;
            while (first < last) {
                if (num[first] + num[last] < target1) {
                    while (first < num.size() - 1 && num[first] == num[first + 1]) first++;
                    first++;
                }else if (num[first] + num[last] > target1) {
                    while (last > 0 && num[last] == num[last - 1]) last--;
                    last--;
                }else {
                    every_vec.clear();
                    every_vec.push_back(num[i]);
                    every_vec.push_back(num[first]);
                    every_vec.push_back(num[last]);
//                    std::sort(every_vec.begin(), every_vec.end()); //没有必要sort
                    if (filter_set.find(every_vec) == filter_set.end()) {
                        final_vec.push_back(every_vec);
                        filter_set.insert(every_vec);
                    }
                    //break; //这里不可以break,因为还有别的可能的解答。
                     while (first < num.size() - 1 && num[first] == num[first+1]) first++;
                     while (last > 0 && num[last] == num[last - 1]) last--;
                     first++;
                     last--;
                }
            }
        }
        return final_vec;
    }
};


上面有许多需要注意的地方:
1 关于超时的避免方法。如果排好序的num中,存在重复元素,则需要跳过第二次和以后次出现元素,因为它们已经被探测过了。所以下面三句就是在跳过重复项元素。

if(i > 0 && num[i] == num[i - 1])  //跳过sum(i, first, last)中位置i的重复元素。
    continue;


while (first < num.size() - 1 && num[first] == num[first + 1]) first++; //跳过sum(i, first, last)中位置first的重复元素。


while (last > 0 && num[last] == num[last - 1]) last--; //跳过sum(i, first, last)中位置last的重复元素。


while (first < num.size() - 1 && num[first] == num[first+1]) first++; //同时跳过(i, first, last)中位置first, last的重复元素。
while (last > 0 && num[last] == num[last - 1]) last--;   //<span style="font-family: Arial, Helvetica, sans-serif;">同时跳过(i, first, last)中位置first, last的重复元素。</span>


2 虽然省不了多少时间,但是精确地说,for循环是这样的

for (int i = 0; i < num.size() - 2; ++i) {
结束点是 num.size() - 2不是num.size()

3 对corner case要进行检查

if (num.size() <= 2) return final_vec; //corner case:
4 没有必要的sort可以不做。因为实在排序数组num上进行搜索,(i, first, last)确保了它们保持递增顺序,所以没有必要对每次的三元组结果内部再进行排序。

//  std::sort(every_vec.begin(), every_vec.end());
5 找到一组解之后,可能在相同i的情况下,还有别的first ,last的解,此时不能跳出内层循环。一个例子来说明跳出内存循环后的错误。

// Input:	[-2,0,1,1,2]
// Output:	[[-2,0,2]]
// Expected:	[[-2,0,2],[-2,1,1]]
6 上面的代码是
if (filter_set.find(every_vec) == filter_set.end()) {...}


我一开始写错了,==写成了!= ,然后在!=的情况下直接 continue,没有考虑到在出现重复的情况下,也需要进行接下来的搜寻,也需要进行first ++ , last- -的逻辑。由于和上面的5相似的原因。

update: 2015-01-20

其实set滤重没有必要,因为原来在排序数组上进行找寻的过程已经严格地控制了num[i] < num[first] < num[second] 所以每次找到的结果不会有重复。

//68ms
class Solution {
public:
    vector<vector<int> > threeSum(vector<int> &num) {
        std::sort(num.begin(), num.end());
        int target1 = 0;
        vector<int> every_vec;
        vector<vector<int> > final_vec;
        if (num.size() <= 2) return final_vec; //corner case:
        for (int i = 0; i < num.size() - 2; ++i) {
            if(i > 0 && num[i] == num[i - 1]) 
                continue;
            target1 = 0 - num[i];
            int first = i + 1;
            int last = num.size() - 1;
            while (first < last) {
                if (num[first] + num[last] < target1) {
                    while (first < num.size() - 1 && num[first] == num[first + 1]) first++;
                    first++;
                }else if (num[first] + num[last] > target1) {
                    while (last > 0 && num[last] == num[last - 1]) last--;
                    last--;
                }else {
                    every_vec.clear();
                    every_vec.push_back(num[i]);
                    every_vec.push_back(num[first]);
                    every_vec.push_back(num[last]);
                    final_vec.push_back(every_vec);
                    while (first < num.size() - 1 && num[first] == num[first+1]) first++;
                    while (last > 0 && num[last] == num[last - 1]) last--;
                    first++;
                    last--;
                }
            }
        }
        return final_vec;
    }
};


3 优化解决4sum问题

类比解决3sum问题过程,也可以优化解决4sum问题。只需要在2sum过程外层增加2层循环,时间复杂度为O(N³)。

class Solution {
public:
        vector<vector<int> > fourSum(vector<int> &num, int target) {
            vector< vector<int> > out_vec;
            int num_size=(int)num.size();
            if(num_size<4)
                return out_vec;
            sort(num.begin(),num.end());
            set< vector<int> > res_set;
            set< vector<int> >::const_iterator res_set_it;
            for(int j=0;j<num_size;j++) {
                for(int i=j+1;i<num_size;i++) {
                    int l=i+1;
                    int r=num_size-1;
                    int target_sum = target-num[j]-num[i];
                    while(l<r){
                        int tmp_sum=num[l]+num[r];
                        if(tmp_sum<target_sum){
                            l++;
                        }else if(tmp_sum>target_sum) {
                            r--;
                        }else {
                            vector<int> result_vec;
                            result_vec.push_back(num[j]);
                            result_vec.push_back(num[i]);
                            result_vec.push_back(num[l]);
                            result_vec.push_back(num[r]);
                            res_set.insert(result_vec);
                            l++;
                            r--;
                        }
                    }
                }
            }
            for(res_set_it=res_set.begin();res_set_it!=res_set.end();res_set_it++)
                out_vec.push_back(*res_set_it);
            return out_vec;
        }
};


update: 2014 - 12 - 16

目前leetcode也严格要求了4sum的时间,和3sum类似,必须写上下标的滤重部分的优化,才能AC。

class Solution {
public:
    vector<vector<int> > fourSum(vector<int> &num, int target) {
        std::sort(num.begin(), num.end());
        int target1 = 0;
        vector<int> every_vec;
        vector<vector<int> > final_vec;
        set<vector<int> > filter_set;
        set<vector<int> >::iterator  filter_set_it;
        if (num.size() <= 3) return final_vec;
        for (int j = 0; j < num.size() -3; ++j) { // j的起始是0
            if(j > 0 && num[j] == num[j - 1]) 
                continue;
            for (int i = j+1; i < num.size() - 2; ++i) { //i的起始是j + 1,不是0, 因为是(i, j, first, last)的四元组。
                if(i > j+1 && num[i] == num[i - 1]) 
                    continue;
                target1 = target - num[j] - num[i];
                int first = i + 1;
                int last = num.size() - 1;
                while (first < last) {
                    if (num[first] + num[last] < target1) {
                        while (first < num.size() - 1 && num[first] == num[first + 1]) first++;
                        first++;
                    }else if (num[first] + num[last] > target1) {
                        while (last > 0 && num[last] == num[last - 1]) last--;
                        last--;
                    }else {
                        every_vec.clear();
                        every_vec.push_back(num[j]);
                        every_vec.push_back(num[i]);
                        every_vec.push_back(num[first]);
                        every_vec.push_back(num[last]);
                        filter_set.insert(every_vec);
                        while (first < num.size() - 1 && num[first] == num[first+1]) first++;
                        while (last > 0 && num[last] == num[last - 1]) last--;
                        first++;
                        last--;
                    }
                }
            }
        }
        for (filter_set_it = filter_set.begin(); filter_set_it != filter_set.end(); filter_set_it++) 
            final_vec.push_back(*filter_set_it);
        return final_vec;
      
    }
};


update: 2015-01-21

set滤重部分可以不用写。因为在排序数组上进行找寻的过程已经严格地控制了num[i] < num[j] < num[first] < num[second] 所以每次找到的结果不会有重复。

//136ms
class Solution {
public:
    vector<vector<int> > fourSum(vector<int> &num, int target) {
        std::sort(num.begin(), num.end());
        int target1 = 0;
        vector<int> every_vec;
        vector<vector<int> > final_vec;

        if (num.size() <= 3) return final_vec;
        for (int j = 0; j < num.size() -3; ++j) {
            if(j > 0 && num[j] == num[j - 1]) 
                continue;
            for (int i = j+1; i < num.size() - 2; ++i) {
                if(i > j+1 && num[i] == num[i - 1]) 
                    continue;
                target1 = target - num[j] - num[i];
                int first = i + 1;
                int last = num.size() - 1;
                while (first < last) {
                    if (num[first] + num[last] < target1) {
                        while (first < num.size() - 1 && num[first] == num[first + 1]) first++;
                        first++;
                    }else if (num[first] + num[last] > target1) {
                        while (last > 0 && num[last] == num[last - 1]) last--;
                        last--;
                    }else {
                        every_vec.clear();
                        every_vec.push_back(num[j]);
                        every_vec.push_back(num[i]);
                        every_vec.push_back(num[first]);
                        every_vec.push_back(num[last]);
                        final_vec.push_back(every_vec);
                        while (first < num.size() - 1 && num[first] == num[first+1]) first++;
                        while (last > 0 && num[last] == num[last - 1]) last--;
                        first++;
                        last--;
                    }
                }
            }
        }
        return final_vec;
    }
};


4 优化解决3sum Closest

和3sum问题思路基本一致,区别在于每次要记录当前sum和target之间的差距,直到找到和target差距最小的三元组。

class Solution {
public:
    int threeSumClosest(vector<int> &num, int target) {
        vector< vector<int> > out_vec;
        if(num.size()<3)
            return 0;
        vector<int> in_vec;
        for(int i=0;i<num.size();i++)
            in_vec.push_back(num[i]);
        sort(in_vec.begin(),in_vec.end());
        int min_value=INT_MAX;
        int record=0;
        for(int i=0;i<in_vec.size();i++) {
            int two_sum=target-in_vec[i];
            int l=i+1;
            int r=(int)in_vec.size()-1;
            while(l<r){
                int tmp_sum = in_vec[l]+in_vec[r];
                if(tmp_sum<two_sum){
                    if(two_sum-tmp_sum<min_value) {
                        min_value=two_sum-tmp_sum;
                        record=target-min_value;
                    }
                    l++;
                }else if((tmp_sum>two_sum)) {
                    if(tmp_sum-two_sum<min_value) {
                        min_value=tmp_sum-two_sum;
                        record=target+min_value;
                    }
                    r--;
                }else {
                    min_value=0;
                    record=target;
                    break;
                }
            }
        }
        return record;   
    }
};


update: 2014 - 12 - 16

class Solution {
public:
    int threeSumClosest(vector<int> &num, int target) {
        std::sort(num.begin(), num.end());
        if (num.size() == 0) return 0; 
        if (num.size() == 1) return num[0];
        if (num.size() == 2) return num[0] + num[1];
        int difference = 0;
        int closest_difference = INT_MAX;
        bool is_positive = true;
        int target1 = 0;
        int closest_target = 0;
        vector<int> every_vec;
        for (int i = 0; i < num.size() - 2; ++i) {
            if(i > 0 && num[i] == num[i - 1]) 
                continue;
            target1 = target - num[i];
            int first = i + 1;
            int last = num.size() - 1;
            while (first < last) {
                if (num[first] + num[last] < target1) {
                    difference = target1 - num[first] - num[last];
                    if (difference < closest_difference){
                        closest_difference = difference;
                        closest_target = target - closest_difference;
                    }
                    while (first < num.size() - 1 && num[first] == num[first + 1]) first++;
                        first++;
                }else if (num[first] + num[last] > target1) {
                    difference = num[first] + num[last] - target1;
                    if (difference < closest_difference) {
                        closest_difference = difference;  
                        closest_target = target + closest_difference;
                    }
                    while (last > 0 && num[last] == num[last - 1]) last--;
                        last--;
                }else {
                    closest_difference = difference = 0;
                    return closest_difference + target;
                }
            }
        }
        return  closest_target;
    }
};


5 小结

(1)一开始干了一件很挫的事情。在3sum问题中,是这样写的

for(int i=0;i<in_vec.size();i++) {
            int two_sum=target-in_vec[i];
            vector<int> tmp_vec(in_vec);
            tmp_vec.erase(tmp_vec.begin()+i);
            int l=0;
            int r=(int)tmp_vec.size()-1;
            while(l<r){
....
}
写这个tmp_vec是为了避免重复选取元素。其实不需要这么麻烦,只需要改下标就可以了。

for(int i=0;i<in_vec.size();i++) {
            int two_sum=target-in_vec[i];
            int l=i+1;
            int r=(int)in_vec.size()-1;
            while(l<r){
......
}


(2)每次移动1步还是移动2步

以3 sum 为例子,这句

l++;
                            r--;
为什么要这么写呢?每次两个指针各移动1步,一共就移动了2步,不怕漏掉了吗?其实不会。假设只让开头指针l向中心移动,也就是只l++,而保持r指针不变。那么如果此时的三元组是满足条件的三元组,那它必然和上一个满足条件的三元组一模一样,这将会在滤重中被去掉。同理,假设只移动结尾r指针而保持开头指针l保持不变,也是如此。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: