本期任务:介绍算法中关于回溯思想的几个经典问题
一、问题描述
问题来源:LeetCode 39. 组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
说明:
- 所有数字(包括 target)都是正整数。
- 解集不能包含重复的组合。
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
二、算法思路
- 使用回溯思想,暴力穷举,以示例2为例,每一个位置都可能有2,3,5共3种可能,所有可能 共有 ,穷举过程遵循深度优先搜索规则。
- 剪枝策略:观察剩余target与当前值的大小关系,小于则跳过。
- 结算情形:剩余target恰好为0,且升序排列之后得到的列表未出现过。
示例2的递归树如下:
三、Python代码实现
class Solution:
def combinationSum(self, candidates, target):
self.candidates = candidates
self.ans = []
self.helper(target, [])
return self.ans
def helper(self, target, arr):
if target == 0: # 当目标值为0时进行结算
# 对结果做排序和去重
arr.sort()
if arr not in self.ans:
self.ans.append(arr)
return
for v in self.candidates: # 每次都可以选择所有候选元素
if v <= target:
self.helper(target - v, arr + [v])
def main():
client = Solution()
client.combinationSum(candidates=[2, 3, 5], target=8)
print(client.ans)
if __name__ == '__main__':
main()
输出结果:
[[2, 2, 2, 2], [2, 3, 3], [3, 5]]
四、问题变形
变形1:
问题来源:LeetCode 40. 组合总和 II
- 题目描述
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
- 所有数字(包括目标数)都是正整数。
- 解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]
-
解决方案:
维护一个visited数组,用来记录各个位置的被访问情况,若当前位置已被访问,则剪枝跳过。 -
具体代码:
class Solution:
def combinationSum2(self, candidates, target):
self.candidates = candidates
self.ans = []
self.size = len(self.candidates)
self.visited = [0] * self.size # 记录每个候选元素是否被访问过
self.helper(target, [])
return self.ans
def helper(self, target, arr):
if target == 0: # 当目标值为0时进行结算
# 对结果做排序和去重
arr.sort()
if arr not in self.ans:
self.ans.append(arr)
return
for i, v in enumerate(self.candidates): # 每次都可以选择所有候选元素
if self.visited[i] == 0 and v <= target: # 判断元素是否被访问过,且是否小于target
self.visited[i] = 1
self.helper(target - v, arr + [v])
self.visited[i] = 0
def main():
client = Solution()
client.combinationSum2(candidates=[2, 5, 2, 1, 2], target=5)
print(client.ans)
if __name__ == '__main__':
main()
变形2:
问题来源:[LeetCode 216. 组合总和 III(https://leetcode-cn.com/problems/combination-sum-iii/)
- 题目描述
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
- 解决方案
每个位置暴力穷举所有可能的数字,并维护一个visited数组,用来记录各个数字的被访问情况,若当前位置已被访问,则剪枝跳过。 - 具体代码
class Solution:
def combinationSum3(self, k, n):
self.candidates = list(range(1, 10))
self.existed = [0] * len(self.candidates)
self.ans = []
self.helper([], k, n)
return self.ans
def helper(self, arr, k, n):
if k == 0 and n == 0: # 当k和n同时为0时进行结算
arr.sort()
if arr not in self.ans:
self.ans.append(arr)
return
elif k and n: # 保证k和n同时不为0才有意义
for i, v in enumerate(self.candidates):
if self.existed[i] == 0 and v <= n:
self.existed[i] = 1
self.helper(arr + [v], k - 1, n - v)
self.existed[i] = 0
def main():
client = Solution()
print(client.combinationSum3(k=3, n=7))
if __name__ == '__main__':
main()