LeetCode第216题_组合总和III

LeetCode 第216题:组合总和 III

题目描述

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

  • 只使用数字1到9
  • 每个数字最多使用一次

返回所有可能的有效组合的列表。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

难度

中等

题目链接

点击在LeetCode中查看题目

示例

示例 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,没有有效的组合。

提示

  • 2 <= k <= 9
  • 1 <= n <= 60

解题思路

这是一个组合问题,可以使用回溯算法解决。回溯算法是一种通过探索所有可能的候选解来找出所有解的算法,如果候选解被确认不是一个解(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化来舍弃该解,即回溯并且尝试另一种可能。

针对本题,我们有以下解题思路:

方法一:回溯法(Backtracking)

回溯法是解决这类组合问题的标准方法,我们通过以下步骤解决:

  1. 定义一个回溯函数,参数包括:当前组合、目标和n的剩余值、已使用的数字个数k、起始数字
  2. 在回溯函数中,我们有三种情况:
    • 如果k = 0且n = 0,说明找到了一个有效组合,将其加入结果集
    • 如果k = 0或n < 0,说明当前组合不满足条件,直接返回
    • 否则,从起始数字开始尝试每个可能的数字(范围是1-9)
  3. 对于每个尝试的数字i,我们将其加入当前组合,递归调用回溯函数(参数为:当前组合、n-i、k-1、i+1)
  4. 回溯时,将最后加入的数字从组合中移除,尝试下一个数字

这种方法会遍历所有可能的组合,并筛选出满足条件的组合。

方法二:位运算(Bit Manipulation)

由于本题的数字范围非常有限(仅使用1-9),我们也可以使用位运算来模拟组合的生成:

  1. 使用一个9位的二进制数表示选择状态,第i位为1表示选择了数字i+1
  2. 枚举所有可能的选择状态(最多有2^9种)
  3. 对于每种状态,检查是否恰好选择了k个数字,且它们的和为n
  4. 如果满足条件,将该组合加入结果集

这种方法的时间复杂度为O(2^9),对于本题的规模来说是可接受的。

代码实现

C# 实现

public class Solution {
    // 回溯法实现
    public IList<IList<int>> CombinationSum3(int k, int n) {
        List<IList<int>> result = new List<IList<int>>();
        Backtrack(result, new List<int>(), k, n, 1);
        return result;
    }
    
    private void Backtrack(List<IList<int>> result, List<int> current, int k, int remain, int start) {
        // 如果已经选择了k个数字且剩余和为0,找到一个有效组合
        if (k == 0 && remain == 0) {
            result.Add(new List<int>(current));
            return;
        }
        
        // 提前剪枝:如果k小于0或remain小于0,无法构成有效组合
        if (k <= 0 || remain <= 0) {
            return;
        }
        
        // 从start到9尝试每个数字
        for (int i = start; i <= 9; i++) {
            // 尝试选择当前数字
            current.Add(i);
            // 递归寻找下一个数字
            Backtrack(result, current, k - 1, remain - i, i + 1);
            // 回溯,移除当前数字,尝试其他可能
            current.RemoveAt(current.Count - 1);
        }
    }
    
    // 位运算实现
    public IList<IList<int>> CombinationSum3BitManipulation(int k, int n) {
        List<IList<int>> result = new List<IList<int>>();
        
        // 遍历所有可能的组合(使用位掩码表示)
        for (int mask = 0; mask < (1 << 9); mask++) {
            // 检查是否恰好选择了k个数字
            if (CountBits(mask) == k) {
                // 计算所选数字的和
                int sum = 0;
                List<int> combination = new List<int>();
                
                for (int i = 0; i < 9; i++) {
                    if ((mask & (1 << i)) != 0) {
                        combination.Add(i + 1);
                        sum += (i + 1);
                    }
                }
                
                // 检查和是否等于n
                if (sum == n) {
                    result.Add(combination);
                }
            }
        }
        
        return result;
    }
    
    // 计算二进制数中1的个数
    private int CountBits(int n) {
        int count = 0;
        while (n > 0) {
            count += n & 1;
            n >>= 1;
        }
        return count;
    }
}

Python 实现

class Solution:
    # 回溯法实现
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        result = []
        self.backtrack(result, [], k, n, 1)
        return result
    
    def backtrack(self, result, current, k, remain, start):
        # 如果已经选择了k个数字且剩余和为0,找到一个有效组合
        if k == 0 and remain == 0:
            result.append(current[:])
            return
        
        # 提前剪枝:如果k小于0或remain小于0,无法构成有效组合
        if k <= 0 or remain <= 0:
            return
        
        # 从start到9尝试每个数字
        for i in range(start, 10):
            # 尝试选择当前数字
            current.append(i)
            # 递归寻找下一个数字
            self.backtrack(result, current, k - 1, remain - i, i + 1)
            # 回溯,移除当前数字,尝试其他可能
            current.pop()
    
    # 位运算实现
    def combinationSum3BitManipulation(self, k: int, n: int) -> List[List[int]]:
        result = []
        
        # 遍历所有可能的组合(使用位掩码表示)
        for mask in range(1 << 9):
            # 检查是否恰好选择了k个数字
            if bin(mask).count('1') == k:
                # 计算所选数字的和
                combination = []
                sum_val = 0
                
                for i in range(9):
                    if mask & (1 << i):
                        combination.append(i + 1)
                        sum_val += (i + 1)
                
                # 检查和是否等于n
                if sum_val == n:
                    result.append(combination)
        
        return result

C++ 实现

class Solution {
public:
    // 回溯法实现
    vector<vector<int>> combinationSum3(int k, int n) {
        vector<vector<int>> result;
        vector<int> current;
        backtrack(result, current, k, n, 1);
        return result;
    }
    
private:
    void backtrack(vector<vector<int>>& result, vector<int>& current, int k, int remain, int start) {
        // 如果已经选择了k个数字且剩余和为0,找到一个有效组合
        if (k == 0 && remain == 0) {
            result.push_back(current);
            return;
        }
        
        // 提前剪枝:如果k小于0或remain小于0,无法构成有效组合
        if (k <= 0 || remain <= 0) {
            return;
        }
        
        // 从start到9尝试每个数字
        for (int i = start; i <= 9; i++) {
            // 尝试选择当前数字
            current.push_back(i);
            // 递归寻找下一个数字
            backtrack(result, current, k - 1, remain - i, i + 1);
            // 回溯,移除当前数字,尝试其他可能
            current.pop_back();
        }
    }
    
public:
    // 位运算实现
    vector<vector<int>> combinationSum3BitManipulation(int k, int n) {
        vector<vector<int>> result;
        
        // 遍历所有可能的组合(使用位掩码表示)
        for (int mask = 0; mask < (1 << 9); mask++) {
            // 检查是否恰好选择了k个数字
            if (__builtin_popcount(mask) == k) {
                // 计算所选数字的和
                int sum = 0;
                vector<int> combination;
                
                for (int i = 0; i < 9; i++) {
                    if (mask & (1 << i)) {
                        combination.push_back(i + 1);
                        sum += (i + 1);
                    }
                }
                
                // 检查和是否等于n
                if (sum == n) {
                    result.push_back(combination);
                }
            }
        }
        
        return result;
    }
};

性能分析

各语言实现的性能对比:

实现语言执行用时内存消耗特点
C#96 ms25.8 MB回溯法实现简洁,位运算实现较为高效
Python36 ms13.9 MBPython的列表操作方便,适合回溯算法
C++0 ms6.7 MB性能最优,尤其是位运算实现非常高效

补充说明

代码亮点

  1. 回溯法中使用了剪枝条件(k <= 0 || remain <= 0),提前结束不可能的分支,优化了时间复杂度
  2. 位运算实现利用了二进制表示选择状态,使得代码更简洁
  3. C++实现中使用了内置函数__builtin_popcount计算位数,简化了代码
  4. 对于每种实现,我们都确保了不会产生重复组合,这是因为我们每次选择数字时都从前一个数字的下一个开始

优化方向

  1. 可以增加更多的剪枝条件,例如当剩余的数字不足以组成k个数或剩余和太大无法达成时,可以提前返回
  2. 在回溯过程中,可以先排序可选数字,以便更好地剪枝
  3. 对于位运算实现,可以进一步优化遍历范围,只考虑含有k个1的位掩码

解题难点

  1. 理解回溯算法的核心思想和实现细节
  2. 正确处理组合条件,确保每个数字最多使用一次
  3. 避免生成重复组合
  4. 有效剪枝以提高算法效率

常见错误

  1. 忘记检查选择的数字个数是否为k
  2. 忘记回溯(移除当前尝试的数字)
  3. 不正确的递归终止条件
  4. 在生成组合时没有避免重复

相关题目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值