1.找出正整数数组nums中所有子集和等于target的可能组合,每个元素可重复选择,数组中无重复元素
第一次尝试:
from typing import List
def subset_sum(nums: List[int], target: int) -> List[List[int]]:
"""
给定一个整数数组 nums 和一个整数 target,请找出并返回所有和为 target 的子集。
你可以按任意顺序返回答案。
"""
# 描述问题状态
state = []
# 存储所有选择
choices = nums
# 存储符合条件的解析
res =[]
# 存储子集和
sub_sum = 0
# 回溯函数
backtrack(state, choices, target, sub_sum, res)
return res
def backtrack(state, choices, target, sub_sum, res):
# 判断是否为解
if sub_sum == target:
# 若为解,记录解
res.append(list(state))
# 记录后,结束本轮选择
return
# 若不为解,遍历所有选择
for i in range(len(choices)):
# 对选择剪枝,此时子集和不能超过目标值
if sub_sum + choices[i] < target:
# 尝试,更新状态
state.append(choices[i])
# 回溯,判断选择是否为解,是否进行下一轮选择
backtrack(state, choices, target, sub_sum + choices[i], res)
# 回退,撤销选择,恢复状态
state.pop()
if __name__ == '__main__':
nums = [3, 4, 5]
target = 9
print(subset_sum(nums, target))
错误:
1.剪枝条件错误,当进行本次选择,子集和恰好等于target时,不满足条件被剪掉
第二次尝试:第一种剪枝思路
# 存储符合条件的解析
res =[]
# 存储子集和
sub_sum = 0
# 回溯函数
backtrack(state, choices, target, sub_sum, res)
return res
def backtrack(state, choices, target, sub_sum, res):
# 判断是否为解
if sub_sum == target:
# 若为解,记录解
res.append(list(state))
# 记录后,结束本轮选择
return
# 若不为解,遍历所有选择
for i in range(len(choices)):
# 对选择剪枝,此时子集和不能超过目标值
if sub_sum < target:
# 尝试,更新状态
state.append(choices[i])
# 回溯,判断选择是否为解,是否进行下一轮选择
backtrack(state, choices, target, sub_sum + choices[i], res)
# 回退,撤销选择,恢复状态
state.pop()
if __name__ == '__main__':
nums = [3, 4, 5]
target = 9
print(subset_sum(nums, target))
缺点:
1.所得结果存在重复子集
第二次尝试重构:第二种剪枝思路(相较于第一种剪枝思路,第二种剪枝思路运行步骤更少)
from typing import List
def subset_sum(nums: List[int], target: int) -> List[List[int]]:
"""
给定一个整数数组 nums 和一个整数 target,请找出并返回所有和为 target 的子集。
你可以按任意顺序返回答案。
"""
# 描述问题状态
state = []
# 存储所有选择
choices = nums
# 存储符合条件的解析
res =[]
# 存储子集和
total = 0
# 回溯函数
backtrack(state, choices, target, total, res)
return res
def backtrack(state, choices, target, total, res):
# 判断是否为解
if total == target:
# 若为解,记录解
res.append(list(state))
# 记录后,结束本轮选择
return
# 若不为解,遍历所有选择
for i in range(len(choices)):
# 对选择剪枝,此时子集和不能超过目标值
if total + choices[i] > target:
continue
# 尝试,更新状态
state.append(choices[i])
# 回溯,判断选择是否为解,是否进行下一轮选择
backtrack(state, choices, target, total + choices[i], res)
# 回退,撤销选择,恢复状态
state.pop()
if __name__ == '__main__':
nums = [3, 4, 5]
target = 9
print(subset_sum(nums, target))
缺点:
1.存在重复子集
1.1去除重复子集
第一次尝试:尝试借助集合
from typing import List
def subset_sum(nums: List[int], target: int) -> List[List[int]]:
"""
给定一个整数数组 nums 和一个整数 target,请找出并返回所有和为 target 的子集。
你可以按任意顺序返回答案。
"""
# 描述问题状态
state = []
# 存储所有选择
choices = nums
# 存储符合条件的解析
res =[]
# 存储子集和
total = 0
# 回溯函数
backtrack(state, choices, target, total, res)
return res
def backtrack(state, choices, target, total, res):
# 判断是否为解
if total == target:
# 若为解,记录解
res.append(list(state))
# 记录后,结束本轮选择
return
duplicated = set()
# 若不为解,遍历所有选择
for i in range(len(choices)):
# 对选择剪枝,此时子集和不能超过目标值
if total < target:
if choices[i] not in duplicated:
# 尝试,更新状态
state.append(choices[i])
duplicated.add(choices[i])
# 回溯,判断选择是否为解,是否进行下一轮选择
backtrack(state, choices, target, total + choices[i], res)
# 回退,撤销选择,恢复状态
state.pop()
if __name__ == '__main__':
nums = [3, 4, 5]
target = 9
print(subset_sum(nums, target))
失败:
1.借助集合剪枝,是防止相同元素在同一轮选择中重复选择
第二次尝试:
from typing import List
def subset_sum(nums: List[int], target: int) -> List[List[int]]:
"""
给定一个整数数组 nums 和一个整数 target,请找出并返回所有和为 target 的子集。
你可以按任意顺序返回答案。
"""
# 描述问题状态
state = []
# 存储所有选择
choices = nums
# 存储符合条件的解析
res = []
# 存储子集和
total = 0
# 创建变量start,剪去造成重复子集组合的分支
start = 0
backtrack(state, choices, target, total, start, res)
return res
def backtrack(state, choices, target, total, start, res):
# 判断是否为解
if total == target:
# 若为解,记录解
res.append(list(state))
# 记录后,结束本轮选择
return
# 若不为解,遍历所有选择
for i in range(start, len(choices)):
# 对选择剪枝,此时子集和不能超过目标值
if total + choices[i] > target:
continue
# 尝试,更新状态
state.append(choices[i])
# 回溯,判断选择是否为解,是否进行下一轮选择
backtrack(state, choices, target, total + choices[i], i, res)
# 回退,撤销选择,恢复状态
state.pop()
if __name__ == '__main__':
nums = [3, 4, 5]
target = 9
print(subset_sum(nums, target))
2.在正整数数组nums中找到所有子集和等于target的组合。数组中包含重复元素,且每个元素只能被选择一次
def subset_sum(nums, target):
# 描述问题状态
state = []
# 存储所有选择
choices = nums
# 存储符合条件的结果
res = []
# 创建total变量储存子集和
total = 0
# 创建start进行重复选择剪枝和相同元素剪枝,起始位置限定
start = 0
backtrack(state, choices, target, total, start, res)
return res
def backtrack(state, choices, target, total, start, res):
# 判断是否为解
if total == target:
# 若为解,记录解
res.append(list(state))
# 记录后,终止本次选择
return
# 若不为解,遍历所有解
for i in range(start, len(choices)):
# 对选择进行剪枝
if total + choices[i] > target:
continue
# 1.每个元素只能选择一次
# 2.相同元素剪枝
if i > start and choices[i] is not choices[i - 1]:
continue
# 尝试,更新状态
state.append(choices[i])
# 回溯,判断尝试是否为解,及是否进行下一轮选择
backtrack(state, choices, target, total + choices[i], i + 1, res)
# 回退,撤回选择,恢复状态
state.pop()
if __name__ == '__main__':
nums = [4, 4, 5]
target = 9
print(subset_sum(nums, target))