18.四数之和

题目

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abc 和 d 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

提示:

  • 1 <= nums.length <= 200
  • -10^9 <= nums[i] <= 10^9
  • -10^9 <= target <= 10^9

示例

示例 1:

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:

输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

解题思路及代码

方法一:暴力搜索

(强烈不推荐!!!)

在不考虑时间代价的前提下,最简单的方式就是采用4个for循环嵌套的方式,但是这样子做的话时间复杂度将会达到可怕的O(n^4),所以还是不建议大家采用这种方式。

这里我也就不给大家展示相关代码了,相信大家应该很容易实现。


方法二:排序+双指针

在做过了leetcode数组系列中的两数之和、三数之和等类似的问题之后,相信大家应该对这种双指针的方式并不陌生了。我这个专栏前面两期的内容中也都给大家介绍过了这种方式,这里我们仍然可以采用排序+双指针的方式解决这个四个数求和的问题。

与三数之和不同的是,我们之前是设置两个指针加一个nums[i]进行遍历,由于这里面涉及到了四个数字,所以我们就需要考虑设置两个指针加上nums[i]、nums[j]来进行遍历操作。

具体的操作方式可以参考我在《15.三数之和》《16.最接近的三数之和》中的介绍。

这里给出几个值得注意的点:

  • 首先开始是一个特例排除,如果数组长度小于4直接返回空。
  • 要注意去重,如果在遍历时遇到连续两个数一样的情况可以直接跳过。

考虑几个剪枝操作:

(注:不进行剪枝操作不影响结果的正确性,只是会减少程序运行的时间)

  • 如果从nums[i]开始连续四个数相加的和已经比target大,我们可以直接结束循环。
  • 如果nums[i]和nums数组结尾的三个数相加的和已经比target小,我们可以直接结束循环。
  • nums[j]与nums[i]类似。

以下是完整代码: 

#include<iostream>
#include<cstdlib>
#include<vector>
#include<algorithm>

using namespace std;

// 排序+双指针
vector<vector<int>> fourSum(vector<int>& nums, int target){
    vector<vector<int>> res = {};
    int n = nums.size();  // nums数组长度

    if(n < 4){  // 数组长度小于4,直接返回空
        return res;
    }
    
    sort(nums.begin(), nums.end());  // nums排序

    for(int i = 0; i < n - 3 && (long)nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] <= target; i++){
        if(i > 0 && nums[i] == nums[i - 1]){  // 去重
            continue;
        }
        if((long)nums[i] + nums[n - 1] + nums[n - 2] + nums[n - 3] < target){  // 剪枝
            continue;
        }
        for(int j = i + 1; j < n - 2 && (long)nums[i] + nums[j] + nums[j + 1] + nums[j + 2] <= target; j++){
            if(j > i + 1 && (long)nums[j] == nums[j - 1]){  // 去重
                continue;
            }
            if((long)nums[i] + nums[j] + nums[n - 1] + nums[n - 2] < target){  // 剪枝
                continue;
            }
            int L = j + 1;
            int R = n - 1;
            while (L < R){
                long sum = (long)nums[i] + nums[j] + nums[L] + nums[R];
                if(sum == target){  // 找到符合条件的四元组
                    res.push_back({nums[i], nums[j], nums[L], nums[R]});
                    while(L < R && nums[L] == nums[L + 1]){  // 去重
                        L++;
                    }
                    while(L < R && nums[R] == nums[R - 1]){  // 去重
                        R--;
                    }
                    L++;
                    R--;
                }else if(sum <target){  // 和小于target,L右移
                    L++;
                }else{  // 和大于target,R左移
                    R--;
                }
            }
        }
    }
    return res;
}

int main(void){
    vector<int> nums = {1,0,-1,0,-2,2};
    int target = 0;
    vector<vector<int>> res = fourSum(nums, target);

    cout << "[";
    for(int i = 0; i < res.size(); i++){
        cout << "[";
        for(int j = 0; j < res[i].size(); j++){
            if(j == res[i].size() - 1){
                cout << res[i][j] << "]";
            }else{
                cout << res[i][j] << ",";
            }
        }
        if(i != res.size() - 1){
            cout << ",";
        } 
    }
    cout << "]" << endl;
    system("pause");
    return 0;
}

方法三:map记录每个数字出现的次数

这个方法是我在《LeetCode Cookbook》中学习到的方法,在我之前的一篇文章《15.三数之和》中也有提到过。

大致的思路就是设置一个map提前存储每个数字出现的次数,然后就可以将原本可能带有重复数字的nums数组转化为一个不含有重复数字的uniqNums数组和一个记录数字出现次数的map。

这个时候就可以根据nums[a]、nums[b]、nums[c]、nums[d]的关系来将问题分成四种情况来讨论:

  • 四个数都相同:只需要保证这个数在map中出现四次及以上即可。
  • 两个不同的数:分为“3个相同的数+另一个数”和“两个相同的数+另外两个相同的数”两种情况。还是需要保证数字在map中出现的次数即可。
  • 三个不同的数:两个相同的数+另两个不同的数,保证相同的那个数在map中出现两次及以上即可。
  • 四个不同的数:这里面我们设置一个值diff=target-我们确定的那三个数,如果这个差值diff可以在我们的map中找到,就说明存在四个数的和等于target,这样就可以减少循环的数量,避免四重循环产生。

以下是完整代码:

#include<iostream>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<map>

using namespace std;

vector<vector<int>> fourSum(vector<int>& nums, int target){
    vector<vector<int>> res = {};

    map<int, int> counter;  // 记录每个数字出现的次数
    for(int num : nums){
        counter[num]++;
    }

    vector<int> uniqNums;  // 存储不重复的数字
    for(auto& entry : counter){
        uniqNums.push_back(entry.first);
    }
    sort(uniqNums.begin(), uniqNums.end());  // 排序

    for(int i = 0; i < uniqNums.size(); i++){
        if((long)uniqNums[i] * 4 == target && counter[uniqNums[i]] >= 4){  // 四个数都相同
            res.push_back({uniqNums[i], uniqNums[i], uniqNums[i], uniqNums[i]});
        }
        for(int j = i + 1; j < uniqNums.size(); j++){  // 两个不同的数
            if((long)uniqNums[i] * 3 + uniqNums[j] == target && counter[uniqNums[i]] >= 3){
                res.push_back({uniqNums[i], uniqNums[i], uniqNums[i], uniqNums[j]});
            }
            if((long)uniqNums[j] * 3 + uniqNums[i] == target && counter[uniqNums[j]] >= 3){
                res.push_back({uniqNums[i], uniqNums[j], uniqNums[j], uniqNums[j]});
            }
            if((long)uniqNums[i] * 2 + uniqNums[j] * 2 == target && counter[uniqNums[i]] >= 2 && counter[uniqNums[j]] >= 2){
                res.push_back({uniqNums[i], uniqNums[i], uniqNums[j], uniqNums[j]});
            }
            for(int k = j + 1; k < uniqNums.size(); k++){  // 三个不同的数
                if((long)uniqNums[i] * 2 + uniqNums[j] + uniqNums[k] == target && counter[uniqNums[i]] >= 2){
                    res.push_back({uniqNums[i], uniqNums[i], uniqNums[j], uniqNums[k]});
                }
                if((long)uniqNums[j] * 2 + uniqNums[i] + uniqNums[k] == target && counter[uniqNums[j]] >= 2){
                    res.push_back({uniqNums[i], uniqNums[j], uniqNums[j], uniqNums[k]});
                }
                if((long)uniqNums[k] * 2 + uniqNums[i] + uniqNums[j] == target && counter[uniqNums[k]] >= 2){
                    res.push_back({uniqNums[i], uniqNums[j], uniqNums[k], uniqNums[k]});
                }
                int diff = target - uniqNums[i] - uniqNums[j] - uniqNums[k];
                if(diff > uniqNums[k] && counter[diff] > 0){  // 四个不同的数
                    res.push_back({uniqNums[i], uniqNums[j], uniqNums[k], diff});
                }
            }
        }
    }
    return res;
}

int main(void){
    vector<int> nums = {1,0,-1,0,-2,2};
    int target = 0;
    vector<vector<int>> res = fourSum(nums, target);

    cout << "[";
    for(int i = 0; i < res.size(); i++){
        cout << "[";
        for(int j = 0; j < res[i].size(); j++){
            if(j == res[i].size() - 1){
                cout << res[i][j] << "]";
            }else{
                cout << res[i][j] << ",";
            }
        }
        if(i != res.size() - 1){
            cout << ",";
        } 
    }
    cout << "]" << endl;
    system("pause");
    return 0;
}

参考资料

15.三数之和-CSDN博客

16.最接近的三数之和-CSDN博客

0018.4 Sum | LeetCode Cookbook (halfrost.com)

18. 四数之和 - 力扣(LeetCode)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嗨小哲同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值