力扣18:四数之和

力扣18:四数之和

题目

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

· 0 <= a, b, c, d < n

· a、b、c 和 d 互不相同

· nums[a] + nums[b] + nums[c] + nums[d] == target

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

思路

首先题目提到了所有满足条件的四元组,看见所有满足这几个字我们很容易能想到用回溯来做,那么这道题我们就先用回溯的思想看能不能做出来。

想要使用回溯我们首先得得出回溯的结束条件是什么?我们使用临时数组temp来存储一个四元组,用整数total来存储这个四元组的和。那么回溯的结束条件就是total等于target并且temp的长度为4。回溯的结束条件找到了,那么回溯的主体是什么呢?我们遍历数组nums来找到符合条件的值,然后先插入到temp中再进行下一轮的回溯再把值从temp中删除。

所以回溯的结束条件和主体都很简单,那么这道题的关键是在哪呢?即剪枝和去重。去重是肯定要的因为题目就是这样要求的,想要完成去重也很简单只需要判断当前位置的值和前一个位置的值是否相同即可,那么为什么要进行剪枝呢?我们可以想一想如果我们不进行剪枝的话整个回溯的时间复杂度是多少,我们需要的是一个四元组所以最起码我们有四层循环,第一层确定第一个数第二层确定第二个数第三层第三个第四次第四个,在第四层再次调用回溯函数的时候我们才会进行判断当前的temp是否符合条件,可以想象的到没有剪枝的回溯的时间复杂度是非常高的。所以想要ac的话我们就需要狠狠的剪枝。

  1. 第一个剪枝:我们可以判断我们还需要的数字数量是否大于剩下可以挑选的数字如果大于了我们就直接return了说明这轮回溯不行,还需要的数字就是4减去temp的长度而剩下可以挑选的数字也就是nums的长度减去当前位置。
  2. 第二个剪枝:大家想一想如果我们知道当前位置之后的所有位置里的最小值了那么我们是不是可以用total加上当前位置的值再加上最小值和还需要数字的数量的乘积与target目标值进行对比,如果这几个数的和都大于target了不就说明这轮回溯根本没有进行下去的必要了吗,剩下数字都是最小值都大于target了那不就必然大于target了。想要完成这个剪枝我们首先要对数组nums进行升序排序这样我们才好得到当前位置之后的最小值在哪里也就是当前位置加1的地方,其次这样才满足和大于target不需要进行循环的条件因为数组是升序的那么下一个位置的值一定是大于当前位置的值的,当前和加上当前值再加上最小乘积都大于target了那么当前值再变大就更不可能等于target了,所以我们就可以直接return退出此轮回溯。
  3. 第三个剪枝:第二个剪枝是拿当前和加上当前值加上最小乘积与target进行判断,那么我们是否可以把最小乘积变成最大乘积再拿去与target进行判断呢。当然是可以的,如果当前和加上当前值加上最大乘积都小于target了那么我们就可以判断当前值是肯定不符合插入条件的了,所以我们需要continue直接进行下一个循环。这里为什么不是return而是continue呢?因为数组是升序的当前值不满足条件不代表下一个位置的值不满足条件。最大乘积也很好找就是数组的最后一位的值与还需要数字的数量的乘积。

代码

cpp 复制代码
class Solution {
public:
    vector<vector<int>> res;
    vector<int> temp;
    void dfs(int start, int total, int target, vector<int>& nums,
             vector<int>& temp) {
        // 回溯结束条件:相加的值等于target并且数组的长度为4
        if (total == target && temp.size() == 4) {
            res.emplace_back(temp);
            return;
        }

        // 循环选择数字
        for (int i = start; i < nums.size(); i++) {
            // 剪枝:如果剩下可以选择的数字比数组还需要的数字的数量还小就直接return
            if (nums.size() - i < long(4 - temp.size())) {
                // cout << 1 << endl;
                return;
            }
            // 去重:如果当前数字和前一个数字相同就continue
            if (i > start && nums[i] == nums[i - 1]) {
                // cout << 2 << endl;
                continue;
            }
            // 剪枝:如果当前位置的值加上后面一个位置的值的三倍都大于target的话就return
            // 因为nums是升序的,后一个位置的值的三倍已经是最小的了这都大于target就不可能再有答案了
            if (i < nums.size() - 1 &&
                total + long(nums[i]) + long(3 - temp.size()) * nums[i + 1] >
                    target) {
                // cout << 3 << endl;
                return;
            }
            // 剪枝:如果当前位置的值加上最后一个位置的值的三倍都小于target的话就continue
            // 因为最后一个位置的三倍已经是最大的值了,加上最大值还小于target那么我们就需要让自己更大
            if (i < nums.size() - 1 &&
                total + long(nums[i]) +
                        long(3 - temp.size()) * nums[nums.size() - 1] <
                    target) {
                // cout << 4 << endl;
                continue;
            }
            // 来到这就说明这个数可以插入
            // cout << nums[i] << endl;
            temp.emplace_back(nums[i]);
            dfs(i + 1, total + nums[i], target, nums, temp);
            temp.pop_back();
        }
        return;
    }
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        // 排序
        sort(nums.begin(), nums.end());
        int n = nums.size();
        if (n < 4) {
            return res;
        }
        // 从哪里开始 temp数组的和 目标值 nums 临时数组
        dfs(0, 0, target, nums, temp);
        return res;
    }
};
相关推荐
元亓亓亓1 小时前
LeetCode热题100--98. 验证二叉搜索树--中等
算法·leetcode·职场和发展
程序员小富1 小时前
令牌桶VS漏桶:谁才是流量控制的“最优解”?
java·后端·算法
fsnine1 小时前
机器学习回顾(二)——KNN算法
人工智能·算法·机器学习
aiwery1 小时前
算法题——找到字符串中所有字母异位词
javascript·算法
拒绝摆烂1 小时前
LeetCode Hot 100 第7天
算法·leetcode·哈希算法
纵有疾風起2 小时前
数据结构——队列
c语言·数据结构·算法·链表
代码不停2 小时前
双指针算法题目练习
java·算法
youzjuer3 小时前
算法之排序
算法
zhangfeng11333 小时前
Shi-Tomasi 算法和 Harris 角点检测算法都是经典的角点检测方法,但它们在理论基础和实现细节上有一些区别。下面我将详细对比这两种算法。
opencv·算法·计算机视觉
拼好饭和她皆失3 小时前
算法加训 动态规划熟悉30题 ---下
算法·动态规划