面试算法combine sum专题讲解一(回溯法)

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问题的情况。

猜你喜欢

转载自blog.csdn.net/little_fire/article/details/80504408