【题目】
给你一个 无重复元素 的整数数组 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 <= 302 <= candidates[i] <= 40candidates的所有元素 互不相同1 <= target <= 40
【题目解析】
思路
要解决这个问题,我们采用回溯算法,这是一种通过探索所有可能的候选解来寻找所有解的算法。在回溯过程中,我们构造候选解,并一步步地尝试解决问题。如果当前的候选解最终不满足条件,我们会回溯到上一个步骤,然后尝试另一种可能的候选解。
具体步骤如下:
- 选择路径:从候选数组的开始选择一个起点。
- 构建解决方案:逐步添加候选数字至当前组合,如果当前组合的和等于目标数,则将其加入结果列表。
- 回溯:如果当前组合的和大于目标数,或者我们已经到达候选数组的末尾,我们回溯到上一个决策点。
- 重复尝试:从最后一个决策点尝试不同的候选数字,直到所有可能的组合都被探索完毕。
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
def backtrack(remain, combo, start):
if remain == 0:
result.append(list(combo))
return
elif remain < 0:
return
for i in range(start, len(candidates)):
combo.append(candidates[i])
backtrack(remain - candidates[i], combo, i)
combo.pop()
result = []
backtrack(target, [], 0)
return result
执行
【总结】
组合总和问题是一个经典的搜索问题,它要求我们找出所有可能的组合,这些组合的元素和等于一个给定的目标数。这个问题涉及到一个关键的概念:如何从一组候选数中选择若干个数,使得它们的和等于目标数。
适合的问题类型
这类方法适合用于以下类型的问题:
- 组合搜索问题:当需要从一组数中找出满足特定条件的所有组合时。
- 决策树问题:在每一步都有多个选择,需要探索所有可能性以找到解决方案。
- 约束满足问题:需要满足一系列约束条件,找到所有可行解。
- 无限重复选择问题:与组合总和问题类似,候选数可以被无限次选择。
使用的算法
解决组合总和问题的算法是回溯算法。回溯算法是一种通过探索所有可能路径来找到所有解的算法。它类似于一种有系统的试错法,通过从一系列的决策点开始,逐步前进来构建解决方案,如果当前路径不再满足条件,就回溯到上一步或几步,尝试其他可能的选项。
解题策略
具体到组合总和问题,解题策略涉及以下步骤:
- 追踪当前组合:使用一个列表来保存当前的组合状态。
- 递归和回溯:递归地选择下一个可能的候选数,如果不满足条件就回溯。
- 处理候选数重复选择:由于同一个数可以重复选择,我们在递归时保持候选数的索引不变。
- 剪枝:如果当前组合的和已经大于目标数,提前终止这条路径的探索。
总结
回溯算法在解决这类问题时非常有效,因为它能够确保所有可能的解都被探索到,而不遗漏任何一个可能的组合。同时,算法的剪枝操作还可以减少不必要的计算,提高解题效率。
在实际应用中,回溯算法是解决组合问题的强大工具。不仅在算法竞赛中,在实际的软件工程中,如数据库查询优化、AI游戏设计、解决约束满足问题等领域,回溯算法都有着广泛的应用。
对于希望提升算法能力的开发者而言,掌握回溯算法以及相关的剪枝技巧是非常有价值的。这不仅能帮助解决类似的算法题,更能在面对实际问题时,设计出高效且可靠的算法解决方案。