combine sum是面试算法中最常考的一类题型,其主要思想是应用背包问题的延伸。
主要描述为:在一组数字中,寻找子数组,使子数组的元素和为target的所有组合,求罗列所有组合,或求解组合总数,或求解最少使用的组合中元素个数(找零钱问题leetcode322)
这里我们就几道经典leetcode题目来做一下综述和讲解。
本节的三个题目39、40、216都是回溯法解题。关于回溯法的介绍,可以查看以下博文,本人就不做过多介绍:
https://blog.csdn.net/King_Like_Coding/article/details/51866084
leetcode 39
问题描述:给定一个数组candidate = [2, 3, 5],寻找数组中数字加和为target的所有组合,且数组中元素可以重复使用。
分析与算法设计:求所有组合的问题,首先想到的就是回溯法(backtrace),数组元素可重复使用,这里的搜索空间较大,为了方便解空间的搜索,可引入start索引,每次从start开始寻找,遍历至数组尾部,寻找数组区间[start, len(candidate)-1]内满足sum为target的所有组合,并将其加入结果集。在回溯步骤中,每次向目标集合添加元素,若目标集合sum和==target,保留结果,若sum和<target,继续在搜索区间[start, len(candidate)-1]查找目标组合。
算法实现:
class Solution(object): def backtracking(self, candidates, target, start, val): if target == 0: Solution.re.append(val) else: for i in range(start, len(candidates)): # 若第i个元素已经超过target,就一定不是结果,剪枝 if target >= candidates[i]: self.backtracking(candidates, target-candidates[i], i, val+[candidates[i]]) def combinationSum(self, candidates, target): Solution.re = [] candidates.sort(reverse=True) self.backtracking(candidates, target, 0, []) return Solution.re
demo测试:
if __name__ == '__main__': l = [2, 3, 5, 7, 8, 10] s = Solution() r = s.combinationSum(l, 10) for i, elem in enumerate(r): print(i+1, elem)
运行结果如下:
1 [10] 2 [8, 2] 3 [7, 3] 4 [5, 5] 5 [5, 3, 2] 6 [3, 3, 2, 2] 7 [2, 2, 2, 2, 2]
leetcode 40
问题描述:组合数之和,一组存在重复元素的数组,求数组组成的所有子数组中和为target的子集,不包含重复元素
算法分析与设计:求所有组合(回溯),与上一题相似,但每个数字只能用一次,故在搜索时,start位置需要后移,即遍历到第i个元素时,同时需要搜索的区间为[i+1, len(candidates)-1],为了将结果去重,还需要将原数组升序排列。
算法实现:
class Solution(object): def backtracking(self, candidates, target, start, val): if target == 0 and val not in self.re: # 由于原数组存在重复元素,因此满足条件的结果也要去重 self.re.append(val) return for i in range(start, len(candidates)): if target < candidates[i]: # 数组已经升序排列,若当前的元素已经大于target,后面的元素也一定不是最终结果,直接剪枝 break self.backtracking(candidates, target-candidates[i], i+1, val+[candidates[i]]) return def combinationSum2(self, candidates, target): candidates.sort() self.re = [] self.backtracking(candidates=candidates, target=target, start=0, val=[]) return self.re
demo测试:
if __name__ == '__main__': s = Solution() result = s.combinationSum2([10, 1, 2, 7, 6, 1, 5], 8) for i, r in enumerate(result): print(i+1, r)
测试结果:
1 [1, 1, 6] 2 [1, 2, 5] 3 [1, 7] 4 [2, 6]
leetcode 216
问题描述:将数组限制为数字1~9,不重复采样。结果的组合不仅要和为n,还要保证k个数字的加和。(例如n=9且k=3时,结果集是[1, 2, 6],[1, 3, 5],[2, 3, 4])
算法分析及设计:相当于上一题中的数组换成了1-9,而且数组已经排序。再加上个判别条件(是否为k个数字加和)即可。
算法实现:
class Solution(object): def backtracking(self, k, n, start, val): if k==0 and n==0: self.re.append(val) return for i in range(start, 10): if n < i: # 相当于1-9升序排列,若当前数字i已经大于n,直接剪枝 break self.backtracking(k=k-1, n=n-i, start=i+1, val=val+[i]) return def combineSum3(self, k, n): self.re = [] self.backtracking(k, n, 1, []) return self.re
demo测试:
if __name__ == '__main__': s = Solution() result = s.combineSum3(3, 15) for r in result: print(r)
测试结果:
[1, 5, 9] [1, 6, 8] [2, 4, 9] [2, 5, 8] [2, 6, 7] [3, 4, 8] [3, 5, 7] [4, 5, 6]以上这几个题目都是使用的回溯法解决解空间的搜索问题,即求解全部满足条件的解空间。接下来,如果涉及求最优解的问题,就需要用到动态规划的思想了,再下一个主题中,我们将介绍动态规划解决combine sum问题的情况。