leetcode
leetcode 201 ~ 250
组合总和 III

组合总和 III

难度:

标签:

题目描述

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

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

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

 

示例 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

代码结果

运行时间: 32 ms, 内存: 14.8 MB


/*
题目思路:
1. 使用递归回溯的方法找到所有可能的组合。
2. 从1到9选择数字,确保每个数字最多使用一次,并且总和等于n。
3. 使用Java Streams的组合生成和过滤功能来实现。
4. 使用一个辅助函数来进行递归,传递当前组合、剩余的数字、当前的和以及结果列表。
5. 如果当前组合长度等于k并且和等于n,则将该组合加入结果列表。
6. 如果当前和已经大于n或组合长度大于k,则停止搜索。
*/
 
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
 
public class CombinationSumIII_Stream {
    public List<List<Integer>> combinationSum3(int k, int n) {
        return IntStream.rangeClosed(1, 9)
                .boxed()
                .flatMap(i -> IntStream.rangeClosed(i + 1, 9)
                        .boxed()
                        .flatMap(j -> IntStream.rangeClosed(j + 1, 9)
                                .boxed()
                                .map(l -> List.of(i, j, l))))
                .filter(list -> list.size() == k && list.stream().mapToInt(Integer::intValue).sum() == n)
                .collect(Collectors.toList());
    }
}
 

解释

方法:

这个题解使用了回溯算法。它首先定义了候选数字集合candidates为1到9。然后定义了一个辅助函数backtrack,用于生成所有可能的组合。在backtrack函数中,如果当前路径path的长度等于k且路径上数字的和等于n,则找到一个有效组合,将其加入结果列表result中。如果path长度已经大于等于k或者数字和已经大于等于n,则剪枝返回,因为继续搜索也不会找到有效组合。否则,从candidates中当前位置start开始,依次将每个数字加入path,递归调用backtrack,然后再将该数字从path中移除,实现回溯。最后,在主函数中调用backtrack,并返回结果列表result。

时间复杂度:

O(9^k)

空间复杂度:

O(k) + O(C(9,k))

代码细节讲解

🦆
你是如何确定使用回溯算法是解决这个问题的最佳方法?有没有考虑过其他算法?
回溯算法是解决组合、排列和子集问题的常用方法,特别适用于需要探索所有可能解的情况。考虑到组合总和 III 需要找出所有可能的组合使其数字之和等于 n,且组合中数字个数为 k,这种问题的本质是寻找满足特定条件的所有子集,这正是回溯算法擅长的。虽然可以考虑使用动态规划,特别是在处理类似“组合总和”的问题时,但在这种需要明确组合中数字个数的情况下,动态规划的实现会相对复杂,不如回溯直观和简单。因此,考虑到问题的特性和实现的简洁性,使用回溯算法是最合适的选择。
🦆
在backtrack函数中,path列表的长度和数字总和都用于控制递归的终止。请问这种方式是否可能导致一些潜在的有效组合被过早地剪枝掉?
在这个问题中,使用 path 列表的长度和数字总和控制递归终止是合理的。因为一旦 path 的长度超过了 k 或者数字总和超过了 n,继续添加更多的数字不会得到有效的解。所以这种剪枝方式并不会导致有效组合被过早地剪枝掉,反而是一种避免无效计算和提高算法效率的必要措施。
🦆
为什么选择候选数字集合为1到9,使用这个范围有什么特别的考虑吗?
选择1到9作为候选数字集合是根据题目的要求。题目指定是找出所有可能的组合使其数字之和等于 n,且每个组合恰好包含 k 个不重复的数字,这些数字取自1到9。这个范围确保了每个数字都是独一无二的,且符合常见的数字组合逻辑,不需要处理较大数字或是负数的复杂情况。
🦆
题解中提到,每个节点最多有n个子节点,具体是如何实现的?能否详细解释这个回溯树的构建过程?
在题解中,'每个节点最多有 n 个子节点'实际上是指,每个节点在递归过程中最多考虑从当前数字开始到9的数字作为可能的子节点。在实际的回溯树中,每次递归调用backtrack函数时,都会从 'start' 参数指定的位置开始尝试将每个数字加入到当前的 path 中,并递归地继续探索加入下一个数字的可能性。这里的 'n' 更多是代指候选数字的个数上限,而不是严格的子节点数。每次递归都会将 'start' 参数加一,这样就可以确保不会重复使用数字,符合题目“组合中的数字是不重复”的要求。

相关问题

组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

 

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

 

提示:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • candidates 的所有元素 互不相同
  • 1 <= target <= 40