组合总和 II
1.解法
刚开始看起来这道题没有什么特点,就是简单的组合问题求解,不懂组合问题如何求解的可以看我之前的博客,于是我就使用递归三部曲写出了如下代码:
def combinationSum2(candidates, target):
result = []
path = []
candidates = sorted(candidates)
def backtracking(candidates,target,startindex,numsum):
if numsum > target:
return
if numsum == target:
path1 = path.copy()
result.append(path1)
return
for i in range(startindex,len(candidates)):
path.append(candidates[i])
numsum = numsum + candidates[i]
backtracking(candidates,target,i+1,numsum)
numsum = numsum - candidates[i]
path.pop()
backtracking(candidates,target,0,0)
return result
结果报错了,原因可以看第一个实例:
candidates = [10,1,2,7,6,1,5], target = 8
如果使用以上的代码,会得到一下结果
[[1,1,6],[1,2,5],[1,7],[1,2,5],[1,7],[2,6]]
但是预期结果是:
[[1,1,6],[1,2,5],[1,7],[2,6]]
问题在于candidates中有两个1,这就会导致有重复解出现。所以就要想如何去重。本题的关键也在于此。
第一个办法是用set或者使用if path not in result来去重,但是这样会超时。
所以我们没法从结果入手,就只能从过程中入手。
但是要如何去重呢?我们可以先从简单的集合入手,先将这个集合从小到大排序。所以会想到如果该元素和上一个元素相同了,那么就不能取这个值了。但事实真的如此吗?
我们先来画一下树图:
发现如果是在不同树层上我们是可以进行相同元素重复取值的,比如取1再取1这条路(最左边),但是在同一个树层上我们是不能重复取相同值的,比如在取值的第一个树层上,最左边取了1,中间就不能再取1了。但是我们知道,递归处理的每一次操作都是相同的,我们如何来区分这种区别呢。
想到可以使用一个数组来记录哪个元素是否使用过了,在图中就是使用了used数组,通过对used数组进行回溯操作,可以判断当前是在哪一层,这样就能区分开是不同树层的取相同值,还是相同树层的取相同值。
所以我们对代码进行如下修改:
def combinationSum2(candidates, target):
result = []
path = []
used = [0]*len(candidates)
candidates = sorted(candidates) # 注意上述讨论都要建立在集合已经排序的情况下
def backtracking(candidates,target,startindex,numsum,used):
if numsum > target:
return
if numsum == target:
path1 = path.copy()
result.append(path1)
return
for i in range(startindex,len(candidates)):
# i>0是保证索引不会越界;candidates[i]==candidates[i-1]是保证正在取相同值;used[i-1]==0是保证当前是在同一个树层进行操作。
if i>0 and candidates[i]==candidates[i-1] and used[i-1]==0: # 过滤
continue # 这种情况就要跳过不选,这样就实现了去重
path.append(candidates[i])
numsum = numsum + candidates[i]
used[i] = 1
backtracking(candidates,target,i+1,numsum,used)
used[i] = 0 # 注意used数组的回溯是区别是否再同一个树层的关键
numsum = numsum - candidates[i]
path.pop()
backtracking(candidates,target,0,0,used)
return result
2.优化
除了上述使用used数组来区别是否在同一个树层的方法外,还可以直接使用startindex来进行去重。
def combinationSum2(candidates, target):
result = []
path = []
candidates = sorted(candidates)
def backtracking(candidates,target,startindex,numsum):
if numsum > target:
return
if numsum == target:
path1 = path.copy()
result.append(path1)
return
for i in range(startindex,len(candidates)):
if i>startindex and candidates[i]==candidates[i-1]: # 过滤
continue
path.append(candidates[i])
numsum = numsum + candidates[i]
backtracking(candidates,target,i+1,numsum)
numsum = numsum - candidates[i]
path.pop()
backtracking(candidates,target,0,0)
return result
这种方法更易理解,而且更简洁。使用used是为了让读者区别开同一树层和不同树层的区别。
3.总结
python
数组的重复元素初始化:
list = [元素] * 个数
算法
区别不同树层的操作可以用一个数组或者其他变量等元素进行操作和回溯来标记。