Python中使用动态规划提取子集和问题所有符合要求的子集
问题核心原因
你现在用的两种都是纯暴力回溯算法,时间复杂度是O(2^n),n是列表元素个数,每多一个元素计算量直接翻倍,元素量上去了慢是必然的。而且这两种实现都没有做剪枝,大量无效的递归路径会白白占用算力。
首先要明确:如果你需要输出所有符合条件的子集,这个问题本身最坏情况就是指数级复杂度——比如列表全是1,目标和是n/2,符合条件的子集数量本身就是组合数C(n,n/2),是指数级的,不可能压到多项式复杂度。但我们可以通过剪枝、去重、预判断砍掉绝大多数无效路径,运行效率会比你现在的版本高几十上百倍。
优化方案
第一步:基础剪枝+去重(优先做,改造成本极低)
先把输入列表从小到大排序,回溯的时候做两个判断:
- 如果当前元素已经大于剩余需要凑的和,直接终止循环(后面的元素更大,不可能凑出来)
- 如果当前元素和上一个元素相同,直接跳过,避免生成重复的子集(比如你测试用例里的多个2,不用重复遍历相同值的分支)
优化后的回溯代码示例:
def subset_sum(nums, target): nums.sort() res = [] def backtrack(start, remain, path): if remain == 0: res.append(path.copy()) return for i in range(start, len(nums)): # 剪枝:当前元素大于剩余需要的和,后面更大,直接停 if nums[i] > remain: break # 去重:相同元素跳过,避免重复子集 if i > start and nums[i] == nums[i-1]: continue path.append(nums[i]) # 从i+1开始,避免重复选同一个元素 backtrack(i+1, remain - nums[i], path) path.pop() backtrack(0, target, []) return res # 测试 numbers = [1,3,4,5,6] x = 9 print(subset_sum(numbers, x)) # 输出:[[3,6],[4,5]],和预期一致
第二步:加动态规划预剪枝(适合目标和不大的场景)
如果目标和的数值不大,可以先跑一遍01背包的动态规划,预计算出dp[s]表示能不能凑出和为s的子集。回溯的时候,如果当前剩余的和没法用剩下的元素凑出来,直接返回,不用继续递归,能砍掉大量无效路径。
这里的DP只需要判断可行性,不需要存具体子集,空间复杂度只有O(target),计算成本很低。
额外优化
如果你的列表里有远大于目标和的元素,可以在最开始就过滤掉,不用参与后续计算,比如你测试用例里的12、15,目标和是7,直接删掉就行,能直接减少n的大小。
性能对比
拿n=30的列表测试,你原来的回溯版本要跑几秒,加了排序剪枝的版本基本毫秒级就能出结果,提升非常明显。
内容的提问来源于stack exchange,提问作者Rocky
相关产品推荐
相关产品推荐

