[C++]LeetCode: 71 4Sum && kSum总结

题目:

Given an array S of n integers, are there elements abc, and d in S such that a + b + c + d = target? Find all unique quadruplets in the array which gives the sum of target.

Note:

  • Elements in a quadruplet (a,b,c,d) must be in non-descending order. (ie, a ≤ b ≤ c ≤ d)
  • The solution set must not contain duplicate quadruplets.

    For example, given array S = {1 0 -1 0 -2 2}, and target = 0.

    A solution set is:
    (-1,  0, 0, 1)
    (-2, -1, 1, 2)
    (-2,  0, 0, 2)

Answer 1: 通用法 退化到2Sum问题

有篇博客详细描述了kSum问题:Summary for LeetCode 2Sum, 3Sum, 4Sum, K Sum

K Sum总结:

K Sum问题描述:给你一组N个数字(比如 vector<int> num), 然后给你一个常数(比如 int target) ,我们的goal是在这一堆数里面找到K个数字,使得这K个数字的和等于target。

注意事项:

1.去除重复项。

注意这一组数字可能有重复项:比如 1 1 2 3 , 求3sum, 然后 target  = 6, 你搜的时候可能会得到 两组1 2 3, 1 2 3,1 来自第一个1或者第二个1, 但是结果其实只有一组,所以最后结果要去重。

2.注意退化过程中,确定前k-2个数字时所取的范围。

比如3Sum问题,第一个数的范围是0~num.size()-2. 因为我们排好序以后, 只需要检测到倒数第三个数字就行了, 因为剩下的只有一种triplet 由最后三个数字组成. 其他k sum问题类似确定范围。

解决方法:先排序后夹逼,将K Sum问题退化为 k-1 Sum问题,最终退化到2Sum问题。然后利用头尾指针找到两个数使得他们的和等于新的target.

给出2 Sum的核心代码:

<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">//2 sum
int i = starting; //头指针
int j = num.size() - 1; //尾指针

while(i < j) {
    int sum = num[i] + num[j];

    if(sum == target) {
        store num[i] and num[j] somewhere;
        if(we need only one such pair of numbers)
            break;
        otherwise
            do ++i, --j;
    }
    else if(sum < target)
        ++i;
    else
        --j;
}<strong>
</strong></span></span></span>
2sum的算法复杂度是O(N log N) 因为排序用了N log N以及头尾指针的搜索是线性的,所以总体是O(N log N),好了现在考虑3sum, 有了2sum其实3sum就不难了,这样想:先取出一个数,那么我只要在剩下的数字里面找到两个数字使得他们的和等于(target – 那个取出的数)就可以了吧。所以3sum就退化成了2sum, 取出一个数字,这样的数字有N个,所以3sum的算法复杂度就是O(N^2 ), 注意这里复杂度是N平方,因为你排序只需要排一次,后面的工作都是取出一个数字,然后找剩下的两个数字,找两个数字是2sum用头尾指针线性扫,这里很容易错误的将复杂度算成O(N^2 log N),这个是不对的。我们继续的话4sum也就可以退化成3sum问题(copyright @sigmainfy),那么以此类推,K-sum一步一步退化,最后也就是解决一个2sum的问题,K sum的复杂度是O(n^(K-1))。 这个界好像是最好的界了,也就是K-sum问题最好也就能做到O(n^(K-1))复杂度,之前有看到过有人说可以严格数学证明,这里就不深入研究了。
AC Code:  (4 Sum)

注意点:注意第一数和第二个数确定的起止范围。

<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">class Solution {
public:
    vector<vector<int> > fourSum(vector<int> &num, int target) {
        vector<vector<int>> ret;
        if(num.size() < 4) return ret;
        int n = num.size();
        vector<int> ivec(num);
        sort(ivec.begin(), ivec.end());
        
        for(int i = 0; i < n - 3;i++)   //(1) trick 1 确定范围到倒数第四个数
        {
            if(i > 0 && ivec[i] == ivec[i-1]) continue;  //防止第一个数字重复  (2)trick 2 避免重复项
            for(int j = i+1; j < n - 2;j++)
            {
                if(j > i + 1 && ivec[j] == ivec[j-1]) continue;  //防止第二个数字重复
                int newtarget = target - ivec[i] - ivec[j];
                int k = j + 1;
                int l = n - 1;
                
                while(k < l)
                {
                    if(ivec[k] + ivec[l] == newtarget)
                    {
                        vector<int> tmp{ivec[i], ivec[j], ivec[k], ivec[l]};
                        ret.push_back(tmp);                    //trick 3 跳过重复项
                        do{
                            k++;
                        }while(k < l && ivec[k] == ivec[k-1]);
                        do{
                            l--;
                        }while(k < l && ivec[l] == ivec[l+1]);
                    }
                    else if(ivec[k] + ivec[l] > newtarget)
                    {
                        l--;
                    }
                    else
                    {
                        k++;
                    }
                }
            }
        }
        
        return ret;
    }
};</span></span></span>

Answer 2 : Hash法

思路:O(n^2)的算法,和前面相当,都是先对数组排序。我们先枚举出所有二个数的和存放在哈希map中,其中map的key对应的是二个数的和,因为多对元素求和可能是相同的值,故哈希map的value是一个链表(下面的代码中用数组代替),链表每个节点存的是这两个数在数组的下标;这个预处理的时间复杂度是O(n^2)。接着和算法1类似,枚举第一个和第二个元素,假设分别为v1,v2, 然后在哈希map中查找和为target-v1-v2的所有二元对(在对应的链表中),查找的时间为O(1),为了保证不重复计算,我们只保留两个数下标都大于V2的二元对(其实我们在前面3sum问题中所求得的三个数在排序后的数组中下标都是递增的),即时是这样也有可能重复:比如排好序后数组为-9 -4 -2 0 2 4 4,target = 0,当第一个和第二个元素分别是-4,-2时,我们要得到和为0-(-2)-(-4) = 6的二元对,这样的二元对有两个,都是(2,4),且他们在数组中的下标都大于-4和-2,如果都加入结果,则(-4,-2,2,4)会出现两次,因此在加入二元对时,要判断是否和已经加入的二元对重复(由于过早二元对之前数组已经排过序,所以两个元素都相同的二元对可以保证在链表中是相邻的,链表不会出现(2,4)->(1,5)->(2,4)的情况,因此只要判断新加入的二元对和上一个加入的二元对是否重复即可),因为同一个链表中的二元对两个元素的和都是相同的,因此只要二元对的一个元素不同,则这个二元对就不同。我们可以认为哈希map中key对应的链表长度为常数,那么算法总的复杂度为O(n^2)

Attention:

1. 想要将有相同和的对,存到链表中,可以构造value为数组的map.

<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;"> unordered_map<int, vector<pair<int,int> > > pairs;</span></span></span>
2. 初始化map, 存储两两的和。注意j从i+1开始,否则会把自身求和算进去。

<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">for(int i = 0; i < n; i++)
       for(int j = i+1 ; j < n; j++)
             pairs[num[i]+num[j]].push_back(make_pair(i,j));</span></span></span>
3. 去除重复的手段和3Sum类似。

4. 针对pair的问题,我们还需要找到符合下标递增要求,并且不能有重复的数字添加(判断第三位是否一致)。

<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">bool isFstpush = true;
//将符合要求的不重复且下标递增的pair存进答案
 for(int k = 0; k < sum2.size(); k++)
{
    if(sum2[k].first <= j) continue;
    //第一次存储的重复数字,或者不是重复数字,判断第三位是否相等
    if(isFstpush || (ret.back())[2] != ivec[sum2[k].first]){...}</span></span></span>

5. 注意pair的操作和数组调用原数组数字的使用。

<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;"> vector<int> tmp{ivec[i], ivec[j], ivec[sum2[k].first], ivec[sum2[k].second]}; </span></span></span>

6.reserve函数:

给vector预分配存储区大小,即capacity的值 ,但是没有给这段内存进行初始化。reserve 的参数n是推荐预分配内存的大小,实际分配的可能等于或大于这个值,即n大于实际分配空间的值,就会reallocate内存 (实际分配空间的值)capacity的值会大于或者等于n 。这样,当ector调用push_back函数使得size 超过原来的默认分配的capacity值时 避免了内存重分配开销。

需要注意的是:reserve 函数分配出来的内存空间,只是表示vector可以利用这部分内存,但vector不能有效地访问这些内存空间(在创建对象之前,不能引用容器内的元素),访问的时候就会出现越界现象,导致程序崩溃。

<span style="font-size:14px;"><span style="font-size:14px;"> pairs.reserve(n*n);</span></span>
AC Code:

<span style="font-size:14px;"><span style="font-size:14px;">class Solution {
public:
    vector<vector<int> > fourSum(vector<int> &num, int target) {
        int n = num.size();
        vector<vector<int> > ret;
        if(n < 4) return ret;
        vector<int> ivec(num);
        sort(ivec.begin(), ivec.end());
        unordered_map<int, vector<pair<int,int> > > pairs;
        pairs.reserve(n*n);
        
        //将n个数字的两两组合求和存进map 相同和的对在map中按照链表存储,即存在vector中 j从i+1开始
        for(int i = 0; i < n; i++)
        {
            for(int j = i+1; j < n; j++)
            {
                pairs[ivec[i] + ivec[j]].push_back(make_pair(i,j));
            }
        }
        
        for(int i = 0; i < n - 3; i++)
        {
            if(i > 0 && ivec[i] == ivec[i-1]) continue;
            for(int j = i+1; j < n - 2; j++)
            {
                if(j > i+1 && ivec[j] == ivec[j-1]) continue;
                if(pairs.find(target - ivec[i] - ivec[j]) != pairs.end())
                {
                    vector<pair<int, int> > sum2 = pairs[target - ivec[i] - ivec[j]];
                    bool isFstpush = true;
                    //将符合要求的不重复且下标递增的pair存进答案
                    for(int k = 0; k < sum2.size(); k++)
                    {
                        if(sum2[k].first <= j) continue;
                        //第一次存储的重复数字,或者不是重复数字,判断第三位是否相等
                        if(isFstpush || (ret.back())[2] != ivec[sum2[k].first])
                        {
                           vector<int> tmp{ivec[i], ivec[j], ivec[sum2[k].first], ivec[sum2[k].second]}; 
                           ret.push_back(tmp);
                           isFstpush = false;
                        }
                    }
                }
            }
        }
        
        return ret;
    }
    
};</span></span>



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值