力扣 216题 组合之和Ⅲ 记录

题目描述

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

只使用数字19
每个数字 最多使用一次 
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7

示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。

示例 3:
输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。

思路

基本思路:使用递归构建数字的组合路径,当满足条件(组合大小为 k 且和为 n)时,记录当前路径并回溯以尝试其他可能的路径。

  • 递归条件:

    • 如果当前路径的大小等于 k,检查路径和是否等于 n。
    • 如果当前和已经超过 n,直接返回,不再继续。
    • 使用一个循环从当前起点开始尝试加入下一个数字。
  • 回溯步骤:

    • 选择一个数字加入路径。
    • 递归调用继续尝试下一个数字。
    • 回溯时移除最近加入的数字以尝试其他路径。
  1. 剪枝优化
    为了优化算法,在递归过程中添加了几个剪枝条件:
  • 剪枝条件 1:当前和超过目标和 n

    • 解释:当当前路径的和已经超过 n 时,不可能再通过加入更多数字使和等于 n,因此立即返回避免不必要的递归。

    • 实现方式:在每次递归调用时,计算当前和并检查,如果超过 n 则直接返回。

  • 剪枝条件 2:剩余数字不足以组成 k 个数

    • 解释:如果当前路径长度加上剩余可用数字的数量不足以达到 k,即使使用所有剩余数字也无法满足组合大小要求,因此可以提前终止搜索。

    • 实现方式:在循环中,检查剩余数字的数量是否足够。如果不够则直接跳出循环,结束当前路径的探索。

  • 剪枝条件 3:使用累加和避免重复计算

    • 解释:通过使用一个变量来跟踪当前路径的累加和,避免每次递归调用时重新计算路径和,从而减少计算时间。

    • 实现方式:在递归调用中传递一个累加和参数,每次选择数字后更新该和。

  1. 执行过程详解
    以下是代码的执行过程,逐步分析如何利用递归和剪枝策略解决问题。
  • 初始化:

    • 创建一个结果集合 result 来存储所有符合条件的组合。
    • 创建一个路径集合 path 用于记录当前的选择路径。
    • 从数字 1 开始进行回溯,并初始化当前和为 0。
    • 回溯探索与剪枝
  • 递归过程:

    • 从起始数字开始(如从 1 开始),将当前数字加入路径。
    • 累加当前路径的和并判断:
      • 如果当前路径长度达到 k:
        • 检查当前和是否等于 n。
        • 如果和等于 n,将当前路径记录到结果中。
        • 无论是否等于 n,都需返回以尝试其他路径。
      • 如果当前和超过 n:
        • 直接返回并剪枝,避免多余的计算。
  • 循环与剪枝:

    • 在递归循环中尝试加入下一个数字。
    • 剪枝条件:
      • 剩余数字不足以组成 k 个数时跳出循环。
      • 当前和超过 n 时返回。
  • 回溯:

    • 在递归调用完成后,移除路径中的最后一个数字以尝试其他路径。
  • 结束条件:

    • 当遍历完所有可能的起始点后,返回结果集合 result,这包含了所有符合条件的组合。
  • 示例过程:
    假设 k = 3, n = 7:

    路径 [1]:

    当前和:1
    继续尝试加入下一个数字。
    路径 [1, 2]:

    当前和:3
    继续尝试加入下一个数字。
    路径 [1, 2, 3]:

    当前和:6
    未满足条件 6 == 7,回溯。
    路径 [1, 2, 4]:

    当前和:7
    满足条件,将 [1, 2, 4] 加入结果。
    路径 [1, 2, 5]:

    当前和:8
    超过 7,剪枝并回溯。
    继续探索 [1, 3], [1, 4], …

通过递归调用继续探索其他路径。

  • 代码执行分析
    递归调用与剪枝:

    • 递归调用从小到大依次选择数字,避免重复组合。
    • 剪枝条件有效减少了无效路径的搜索,使程序更高效。
  • 时间复杂度分析:

    • 因为最多遍历 9 个数字,因此最坏情况的时间复杂度为 O ( 2 n ) O(2^n) O(2n),但剪枝优化使其通常远低于此。

完整代码

#include<iostream>
#include<vector>

using namespace std;

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;

    int pathSum(vector<int> vec){
        int sum = 0;
        for(int value : vec)
            sum += value;
        return sum;
    }

    void backtracking(int k, int n, int startIndex){
        if(path.size() == k && pathSum(path) == n){
            result.push_back(path);
            return;
        }
        for(int i = startIndex; i <= 9; i++){
            path.push_back(i);
            backtracking(k, n, i + 1);
            path.pop_back();
        }
    }

public:
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(k, n, 1);
        return result;
    }
};

int main()
{
    int k, n;
    cin >> k >> n; //和为 n, k 个数
    Solution s;
    vector<vector<int>> result = s.combinationSum3(k, n);
    for(const auto& combinationSum : result){
        cout << "[";
        for(int i = 0; i < combinationSum.size(); i++){
            cout << combinationSum[i];
            if(i != combinationSum.size() - 1)
                cout << ",";
        }
        cout << "]" << endl;
    }
    return 0;
}

剪枝优化后的代码

取消了求和函数,在递归过程中使用一个变量来维护当前路径的和,避免在每次递归时重新计算和。

代码随想录中的剪枝条件是:当 currentSum > n 的时候,就可以停止了,因为此时已经大于目标值,则退出循环;
下面代码的剪枝条件是: 当剩余可选数字不足以组成 k 个数,则退出循环。

#include <iostream>
#include <vector>

using namespace std;

class Solution {
private:
    vector<vector<int>> result;  // 存储所有符合条件的组合
    vector<int> path;            // 存储当前的组合路径

    // 回溯函数,负责生成所有可能的组合
    void backtracking(int k, int n, int startIndex, int currentSum) {
        // 剪枝条件:当前和已经超过目标和 n
        if (currentSum > n) {
            return;  // 剪枝
        }

        // 如果当前组合的大小达到 k
        if (path.size() == k) {
            // 检查组合的和是否为 n
            if (currentSum == n) {
                result.push_back(path);  // 如果和为 n,则将组合加入结果集
            }
            return;  // 结束当前递归
        }

        // 从 startIndex 开始循环,尝试加入每个可能的数字
        for (int i = startIndex; i <= 9; i++) {
            // 剪枝条件:如果剩余可选数字不足以组成 k 个数,则退出循环
            if (path.size() + (9 - i + 1) < k) {
                break;  // 剪枝
            }

            path.push_back(i);                  // 将当前数字加入组合路径中
            backtracking(k, n, i + 1, currentSum + i); // 递归调用,选择下一个数字
            path.pop_back();                    // 回溯,移除最近加入的数字
        }
    }

public:
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(k, n, 1, 0);  // 从数字 1 开始进行回溯,初始和为 0
        return result;   // 返回所有符合条件的组合
    }
};

int main() {
    int k, n;
    cin >> k >> n;  // 和为 n, k 个数
    Solution s;
    vector<vector<int>> result = s.combinationSum3(k, n);
    for (const auto& combinationSum : result) {
        cout << "[";
        for (int i = 0; i < combinationSum.size(); i++) {
            cout << combinationSum[i];
            if (i != combinationSum.size() - 1)
                cout << ",";
        }
        cout << "]" << endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值