前几天动态规划,刚做出一点感觉,今天就变成回溯了,惆怅
组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
class Solution:
import bisect
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
current_list = []
candidates.sort()
end = bisect.bisect_right(candidates, target)
def backtrack(numsum, startindex):
if numsum == target:
res.append(current_list[:]) # 复制内容并指向新的内存地址
else:
for i in range(startindex, end):
numsum += candidates[i]
if numsum > target:
numsum -= candidates[i]
break
current_list.append(candidates[i])
backtrack(numsum, i)
numsum -= candidates[i]
current_list.pop()
backtrack(0, 0)
return res
细节需要注意:res.append(current_list[:]),不能使用res.append(current_list)。
区别是:前者复制内容并指向新的内存地址,后者是浅复制,只是指向 current_list 的内存地址,因为后面还有对 current_list 的操作所以只能使用前一种。
组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
class Solution:
import bisect
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
if sum(candidates) < target:
return []
res = []
current_list = []
candidates.sort()
end = bisect.bisect_right(candidates, target)
def backtrack(beginindex, numsum):
if numsum == target and current_list not in res: # 增加了判断条件
res.append(current_list[:])
else:
for i in range(beginindex, end):
numsum += candidates[i]
if numsum>target:
numsum -= candidates[i]
break
current_list.append(candidates[i])
backtrack(i+1, numsum) # 改动的地方
numsum -= candidates[i]
current_list.pop()
backtrack(0, 0)
return res
直接根据 I 改的,改变了两处:
- 由于有重复元素,会有重复组合,所以增加了 current_list not in res 的判断条件;
- 将 backtrack(i, numsum) 改成 backtrack(i+1, numsum)。
组合总和 III
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
class Solution:
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
nums = list(range(1, 10))
res = []
currentlist = []
def backtrack(begin, numsum, count):
if numsum==n and count==k:
res.append(currentlist[:])
else:
for i in range(begin, 9):
numsum += nums[i]
if count>=k or numsum>n:
numsum -= nums[i]
break
currentlist.append(nums[i])
count += 1
backtrack(i+1, numsum, count)
count -= 1
numsum -= nums[i]
currentlist.pop()
backtrack(0, 0, 0)
return res
换汤不换药,只是增加一个 count 用于固定相加的数为 k
组合总和 Ⅳ
给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
还想用回溯,结果一个测试集 [4,2,1] 32 就翻车了哈哈
做了这么多动态规划,为啥换了一题我又想不出状态转移方程了/(ㄒoㄒ)/~~
看完官方解答只能赞叹:牛蛙牛蛙,这都能 dp ?!!!
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
dp = [1] + [0] * target
for i in range(1, target + 1):
for num in nums:
if num <= i:
dp[i] += dp[i - num]
return dp[target]
用 dp[x] 表示选取的元素之和等于 x 的方案数,目标是求 dp[target]。
动态规划的边界是 dp[0]=1。只有当不选取任何元素时,元素之和才为 0,因此只有 1 种方案。
当 1≤i≤target 时,如果存在一种排列,其中的元素之和等于 i,则该排列的最后一个元素一定是数组 nums 中的一个元素。假设该排列的最后一个元素是 num,则一定有 num≤i,对于元素之和等于 i−num 的每一种排列,在最后添加 num 之后即可得到一个元素之和等于 i 的排列,因此在计算 dp[i] 时,应该计算所有的 dp[i−num] 之和。
由此可以得到动态规划的做法:
初始化 dp[0]=1;
遍历 iii 从 111 到 target,对于每个 iii,进行如下操作:
遍历数组 nums 中的每个元素 num,当 num≤i 时,将 dp[i−num] 的值加到 dp[i]。
最终得到 dp[target] 的值即为答案。