【算法】【回溯篇】第5节:组合问题

本期任务:介绍算法中关于回溯思想的几个经典问题

【算法】【回溯篇】第1节:八皇后问题

【算法】【回溯篇】第2节:解数独问题

【算法】【回溯篇】第3节:正则表达式问题

【算法】【回溯篇】第4节:全排列问题

【算法】【回溯篇】第5节:组合问题

【算法】【回溯篇】第6节:子集问题

【算法】【回溯篇】第7节:0-1背包问题


一、问题描述

问题来源:LeetCode 39. 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]
示例 2:

输入: candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

二、算法思路

  • 使用回溯思想,暴力穷举,以示例2为例,每一个位置都可能有2,3,5共3种可能,所有可能 共有 3 3 3^3 ,穷举过程遵循深度优先搜索规则。
  • 剪枝策略:观察剩余target与当前值的大小关系,小于则跳过。
  • 结算情形:剩余target恰好为0,且升序排列之后得到的列表未出现过。

示例2的递归树如下:
在这里插入图片描述


三、Python代码实现

class Solution:
    def combinationSum(self, candidates, target):
        self.candidates = candidates
        self.ans = []

        self.helper(target, [])

        return self.ans

    def helper(self, target, arr):
        if target == 0:  # 当目标值为0时进行结算
            # 对结果做排序和去重
            arr.sort()
            if arr not in self.ans:
                self.ans.append(arr)
            return

        for v in self.candidates:  # 每次都可以选择所有候选元素
            if v <= target:
                self.helper(target - v, arr + [v])


def main():
    client = Solution()
    client.combinationSum(candidates=[2, 3, 5], target=8)
    print(client.ans)

    
if __name__ == '__main__':
    main()

输出结果:

[[2, 2, 2, 2], [2, 3, 3], [3, 5]]

四、问题变形

变形1:

问题来源:LeetCode 40. 组合总和 II

  1. 题目描述
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

- 所有数字(包括目标数)都是正整数。
- 解集不能包含重复的组合。 

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
  [1,2,2],
  [5]
]

  1. 解决方案:
    维护一个visited数组,用来记录各个位置的被访问情况,若当前位置已被访问,则剪枝跳过。

  2. 具体代码:

class Solution:
    def combinationSum2(self, candidates, target):
        self.candidates = candidates
        self.ans = []

        self.size = len(self.candidates)
        self.visited = [0] * self.size  # 记录每个候选元素是否被访问过

        self.helper(target, [])

        return self.ans

    def helper(self, target, arr):
        if target == 0:  # 当目标值为0时进行结算
            # 对结果做排序和去重
            arr.sort()
            if arr not in self.ans:
                self.ans.append(arr)
            return

        for i, v in enumerate(self.candidates):  # 每次都可以选择所有候选元素
            if self.visited[i] == 0 and v <= target:  # 判断元素是否被访问过,且是否小于target
                self.visited[i] = 1
                self.helper(target - v, arr + [v])
                self.visited[i] = 0


def main():
    client = Solution()
    client.combinationSum2(candidates=[2, 5, 2, 1, 2], target=5)
    print(client.ans)


if __name__ == '__main__':
    main()


变形2:

问题来源:[LeetCode 216. 组合总和 III(https://leetcode-cn.com/problems/combination-sum-iii/)

  1. 题目描述
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。
解集不能包含重复的组合。 
示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
  1. 解决方案
    每个位置暴力穷举所有可能的数字,并维护一个visited数组,用来记录各个数字的被访问情况,若当前位置已被访问,则剪枝跳过。
  2. 具体代码
class Solution:
    def combinationSum3(self, k, n):
        self.candidates = list(range(1, 10))
        self.existed = [0] * len(self.candidates)
        self.ans = []

        self.helper([], k, n)
        return self.ans

    def helper(self, arr, k, n):
        if k == 0 and n == 0:  # 当k和n同时为0时进行结算
            arr.sort()
            if arr not in self.ans:
                self.ans.append(arr)
            return

        elif k and n:  # 保证k和n同时不为0才有意义
            for i, v in enumerate(self.candidates):
                if self.existed[i] == 0 and v <= n:
                    self.existed[i] = 1
                    self.helper(arr + [v], k - 1, n - v)
                    self.existed[i] = 0


def main():
    client = Solution()
    print(client.combinationSum3(k=3, n=7))


if __name__ == '__main__':
    main()
原创文章 36 获赞 32 访问量 2742

猜你喜欢

转载自blog.csdn.net/weixin_43868754/article/details/105680802