K-SUM问题

一.2SUM问题

Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

Example:

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

思路:

1.首先对数组按小到大排序,然后设定两个指针head、tail分别指向排序好的数组的首尾:                                

  • 如果两个指针对应的元素和等于target,那么找到了
  • 如果两个指针对应的元素和小于target,那么需要增加和的大小,则把head指针向后移动
  • 如果两个指针对应的元素和大于target,那么需要减少和的大小,则把tail指针向前移动
  • head赶上tail指针时,结束
  • 由于本题中需要返回最后找到的数对的索引,因此,排序是我们不移动原来数组的元素,只是把元素的的索引放到一个新的数组,对这个新的索引数组排序
  • 该算法复杂度为O(Nlog(N))

C++代码: 

class Solution {
private:
    static vector<int> *numbersCopy;
    static bool cmp(int idx1, int idx2)
    {
        return (*numbersCopy)[idx1] < (*numbersCopy)[idx2];
    }
public:
    vector<int> twoSum(vector<int> &numbers, int target) {
        numbersCopy = &numbers;
        int n = numbers.size();
        vector<int> res;
        vector<int> idx(n);
        for(int i = 0; i < n; i++)
            idx[i] = i;
        sort(idx.begin(), idx.end(), Solution::cmp);
        
        int head = 0, tail = n-1;
        while(head < tail)
        {
            int sum=numbers[idx[head]] + numbers[idx[tail]];
            if(sum< target)
                head++;
            else if(sum> target)
                tail--;
            else //found
            {
                res.push_back(min(idx[head], idx[tail]));
                res.push_back(max(idx[head], idx[tail]));
                break;
            }
        }
        
        return res;
    }
};
vector<int> * Solution::numbersCopy = NULL;

2.将数组的数组映射到哈希表,key是元素的值,value是该值在数组中的索引。考虑到数组中元素有重复,我们使用STL中的unordered_map, 它可以允许重复的key存在。映射以后,对于数组中的某个元素num,我们只要在哈希表中查找num2 = target-num。需要注意的是在哈希表中找到了num2,并不一定代表找到了题目要求的两个数,比如对于数组2 7 11 15,target = 4,当num = 2时,num2 = target-num = 2,此时num2可以在哈希表中找到,但是num和num2指向的是同一个元素。因此当num2 = num时,在哈希表找到num2的同时,还需要保证哈希表中num2的个数>=2。

该算法时间复杂度为O(n).

C++代码:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,vector<int>> mark;
        for(int i=0;i<nums.size();i++)
        {
            mark[nums[i]].push_back(i);
        }
        for(int i=0;i<nums.size();i++)
        {
            if(target-nums[i]==nums[i])
            {
                if(mark[nums[i]].size()>1)
                {
                    vector<int> temp{i,mark[nums[i]][1]};
                    return temp;
                }
            }
            else
            {
                if(mark.find(target-nums[i])!=mark.end())
                {
                    vector<int> temp{i,mark[target-nums[i]][0]};
                    return temp;
                }
            }
        }
    }
};

二.3SUM问题

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

Note:

The solution set must not contain duplicate triplets.

Example:

Given array nums = [-1, 0, 1, 2, -1, -4],

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

思路:

1.先对数组排个序,排序之后,我们就可以对数组用两个指针分别从前后两端向中间扫描了,如果是 2Sum,我们找到两个指针之和为target就OK了,那 3Sum 类似,我们可以先固定一个数,然后找另外两个数之和为第一个数的相反数就可以了。3SUM的复杂度为O(N^2 )。(小tips:因为该数组已经从小到大排序,故而当出现nums[i]>0时即可执行break操作,当nums[i]==nums[i-1]时可以执行continue操作)。

S = {-1 0 1 2 -1 -4}
排序后:
S = {-4 -1 -1 0 1 2}
      ↑  ↑        ↑
      i  j        k
         →        ←
i每轮只走一步,j和k根据S[i]+S[j]+S[k]=ans和0的关系进行移动,且j只向后走(即S[j]只增大),k只向前走(即S[k]只减小)
如果ans>0说明S[k]过大,k向前移;如果ans<0说明S[j]过小,j向后移;ans==0即为所求。
至于如何取到所有解,看代码即可理解,不再赘述。

 C++代码: 

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        if(nums.size()<3)
        {
            return res;
        }
        int i,left,right;
        int sum=0;
        sort(nums.begin(),nums.end());
        for(i=0;i<nums.size()-2;i++)
        {
            if(nums[i]>0)
            {
                break;
            }
            if(i>0&&nums[i]==nums[i-1])
            {
                continue;
            }
            left=i+1;
            right=nums.size()-1;
            while(left<right)
            {
                sum=nums[i]+nums[left]+nums[right];
                if(sum==0)
                {
                    res.push_back({nums[i],nums[left],nums[right]});
                    left++;
                    while(left<nums.size()&&nums[left]==nums[left-1])
                    {
                        left++;
                    }
                    right--;
                    while(right>=0&&nums[right]==nums[right+1])
                    {
                        right--;
                    }
                }
                else if(sum>0)
                {
                    right--;
                }
                else if(sum<0)
                {
                    left++;
                }
            }
        }
        return res;
    }
};

2. 先预处理一遍数组中两两相加的结果,然后再遍历每一个数nums[i],判断target-nums[i]是否在预处理的那个和中,不过这种方法的复杂度也是O(N^2),主要是预处理的复杂度。

三.3SUM Cloest

Given an array nums of n integers and an integer target, find three integers in nums such that the sum is closest to target. Return the sum of the three integers. You may assume that each input would have exactly one solution.

Example:

Given array nums = [-1, 2, 1, -4], and target = 1.

The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).

思路:这道题让我们求最接近给定值的三数之和,在3SUM的基础上又增加了些许难度,那么这道题让我们返回这个最接近于给定值的值,即我们要保证当前三数和跟给定值之间的差的绝对值最小,所以我们需要定义一个变量diff用来记录差的绝对值,然后我们还是要先将数组排个序,然后开始遍历数组,思路跟那道三数之和很相似,都是先确定一个数,然后用两个指针left和right来滑动寻找另外两个数,每确定两个数,我们求出此三数之和,然后算和给定值的差的绝对值存在newDiff中,然后和diff比较并更新diff和结果closest即可。

C++代码:

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        if(nums.size()<3)
        {
            return 0;
        }
        int i,left,right;
        int diff=INT_MAX;
        int closest;
        sort(nums.begin(),nums.end());
        for(i=0;i<nums.size()-2;i++)
        {
            if(i>0&&nums[i]==nums[i-1])
            {
                continue;
            }
            left=i+1;
            right=nums.size()-1;
            while(left<right)
            {
                int sum=nums[i]+nums[left]+nums[right];
                int newdiff=abs(sum-target);
                if(newdiff==0)
                {
                    return sum;
                }
                if(newdiff<diff)
                {
                    diff=newdiff;
                    closest=sum;
                }
                if(sum<target)
                {
                    left++;
                }
                else if(sum>target)
                {
                    right--;
                }
            }
        }
        return closest;   
    }
};

四. 4SUM

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

Note:

The solution set must not contain duplicate quadruplets.

Example:

Given array nums = [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]
]

思路:

1.先遍历第一个数,然后固定第一个数之后,转化为剩下元素的3SUM问题,其算法复杂度是稳定的O(n^3),最外层遍历一遍O(n^2),然后转为3SUM问题之后又是O(n^2)。这种方法相当于4SUM调用3SUM,然后3SUM再调用2SUM,这样函数调用有点多,不方便具体写出来的形式,可以写成最外层两个循环,即固定为两个数之后,再化为2SUM。 

C++代码:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        int n=nums.size();
        if(n<4)
        {
            return res;
        }
        sort(nums.begin(),nums.end());
        for(int i=0;i<n-3;i++)
        {
            if(i>0&&nums[i]==nums[i-1])
            {
                continue;
            }
            if(nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target)
            {
                break;
            }
            if(nums[i]+nums[n-1]+nums[n-2]+nums[n-3]<target)
            {
                continue;
            }
            for(int j=i+1;j<n-2;j++)
            {
                if(j>i+1&&nums[j]==nums[j-1])
                {
                    continue;
                }
                if(nums[i]+nums[j]+nums[j+1]+nums[j+2]>target)
                {
                    break;
                }
                if(nums[i]+nums[j]+nums[n-2]+nums[n-1]<target)
                {
                    continue;
                }
                int left=j+1;
                int right=n-1;
                while(left<right)
                {
                    int sum=nums[i]+nums[j]+nums[left]+nums[right];
                    if(sum==target)
                    {
                        res.push_back(vector<int>{nums[i],nums[j],nums[left],nums[right]});
                        left++;
                        while(left<right&&nums[left]==nums[left-1])
                        {
                            left++;
                        }
                        right--;
                        while(left<right&&nums[right]==nums[right+1])
                        {
                            right--;
                        }
                    }
                    else if(sum<target)
                    {
                        left++;
                    }
                    else if(sum>target)
                    {
                        right--;
                    }
                } 
            }   
        }
        return res;
    }
};

2.先遍历两个数,求和,然后转化为剩下元素的2SUM问题。因为本质上我们是最外层两个循环之后,找是否有target-now的值,我们可以事先做好预处理,即空间换时间,先循环两次,找出两个数所有可能的和,存到map里(这里可以unordered_map)。这两等于是两个O(n^2)的时间复杂度相加和,所以最后时间复杂度为O(n^2); 但是此时需要有一个判重的问题,所以需要map中存如下数 mp[num[i]+num[j]].push_back(make_pair(i,j)); 然后再判重。

C++代码:



 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值