回溯法

摘自Backtracking回溯法(又称DFS,递归)全解,文章写得很不错!!!

回溯是啥

用爬山来比喻回溯,好比从山脚下找一条爬上山顶的路,起初有好几条道可走,当选择一条道走到某处时,又有几条岔道可供选择,只能选择其中一条道往前走,若能这样子顺利爬上山顶则罢了,否则走到一条绝路上时,只好返回到最近的一个路口,重新选择另一条没走过的道往前走。如果该路口的所有路都走不通,只得从该路口继续回返。照此规则走下去,要么找到一条到达山顶的路,要么最终试过所有可能的道,无法到达山顶。
回溯是一种穷举,但与brute force有一些区别,回溯带了两点脑子的,并不多,brute force一点也没带。
第一点脑子是回溯知道回头;相反如果是brute force,发现走不通立刻跳下山摔死,换第二条命从头换一条路走。
第二点脑子是回溯知道剪枝;如果有一条岔路上放了一坨屎,那这条路我们不走,就可以少走很多不必要走的路。

那如何使用回溯法解决问题呢,一般选用递归实现;当然也可以不用,用栈(例如,可以用递归的方式实现二叉树的遍历,也可以用栈的方式实现遍历)。

递归该如何理解?

  • 递归的目的是将大问题化成小问题(根据递归函数的参数来决定规模),一旦小问题解决之后,再由里到外依次解决问题
  • 递归本质上就是一个函数,我们可以将它视为一个黑夹子,要么返回结果,要么对传入的参数进行改变,无需进入递归函数里去思考
  • 每一个递归函数都有一个终止条件

回溯法一般用来解决哪类问题呢?

  • 排列
  • 组合
  • 子集

解决上面三类问题都遵循着特定的套路,请看下面的例子

排列问题

leetcode46:全排列

给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:

输入: [1,2,3]
输出:[ [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1] ]
class Solution:
    def permute(self, nums):
        if len(nums) == 0:
            return []
        self.res = []
        temp = []
        l = len(nums)
        self.helper(l,nums, temp)
        return self.res

    def helper(self, l,nums,temp):
        if len(temp) == l:
            c = temp[:]
            self.res.append(c)
            return
        for i in range(len(nums)):
            temp.append(nums[i])
            self.helper(l,nums[:i]+nums[i+1:],temp)
            temp.pop()

leetcode47:全排列 II

给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:

输入: [1,1,2]
 输出:[ [1,1,2],[1,2,1],[2,1,1]]
class Solution:
    def permute(self, nums):
        if len(nums) == 0:
            return []
        self.res = []
        temp = []
        nums = sorted(nums)
        l = len(nums)
        self.helper(l,nums, temp)
        return self.res

    def helper(self, l,nums,temp):
        if len(temp) == l:
            c = temp[:]
            self.res.append(c)
            return

        n = None 

        for i in range(len(nums)):
            if n == nums[i]: # 剪枝,用来判断是否有重复操作
                continue

            temp.append(nums[i])
            t = nums[:i]+nums[i+1:]
            self.helper(l,nums[:i]+nums[i+1:],temp)
            n = temp.pop()

排列出二维数组的所有可能性

输入[[1,2,4],[3,5],[7]]
输出[[1,3,7],[1,5,7],[2,3,7],[2,5,7],[4,3,7],[4,5,7]]

class Solution(object):
    def listall(self, nums:list) -> list:
        self.res = []
        self.helper(0,nums, [])
        return self.res

    def helper(self, start, nums, temp):

        if len(temp) == len(nums):
            self.res.append(temp[:])
            return

        for i in range(start,len(nums)):
            for j in range(len(nums[i])):
                temp.append(nums[i][j])
                self.helper(i+1,nums,temp)
                temp.pop()


if __name__ == '__main__':
    print(Solution().listall([[1,2,4],[3,5],[7]]))

组合问题

leetcode77:组合

给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例:

输入: n = 4, k = 2
输出:[ [2,4],[3,4],[2,3],[1,2],[1,3],[1,4],]
class Solution(object):
    def combine(self, n, k):
        if n < k:return []
        self.res = []
        self.helper(n,k,1,[])
        return self.res


    def helper(self,n,k,start,temp):
        if len(temp) == k:
            c = temp[:]
            self.res.append(c)
            return

        for i in range(start,n+1):

            temp.append(i)
            self.helper(n,k,i+1,res,temp)
            temp.pop()

leetcode39:组合总和

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

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。
    示例
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]
class Solution(object):
    def combinationSum(self,candidates, target):
        self.res = []
        self.helper(0,candidates,target,[])
        return self.res

    def helper(self,start,candidates,target,temp):
        if sum(temp) == target:
            c = temp[:]
            self.res.append(c)
            return
        if sum(temp) > target:
            return

        for i in range(start,len(candidates)):

            temp.append(candidates[i])
            self.helper(i,candidates,target,temp)
            temp.pop()

leetcode40:组合总和 II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
说明:

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

示例 1:

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
  [1,2,2],
  [5]
]
class Solution(object):
    def combinationSum2(self,candidates, target):
        self.res = []
        candidates = sorted(candidates)
        self.helper(0,candidates,target,[])
        return self.res

    def helper(self,start,candidates,target,temp):
        if sum(temp) == target:
            c = temp[:]
            self.res.append(c)
            return
        if sum(temp) > target:
            return

        n = None
        for i in range(start,len(candidates)):
            if n == candidates[i]:
                continue
            temp.append(candidates[i])
            self.helper(i+1,candidates,target,temp)
            n = temp.pop()

leetcode216:组合总和 III

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

  • 所有数字都是正整数。
  • 解集不能包含重复的组合。
    示例:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
class Solution:
    def combinationSum3(self, k, n):
        """
        :type k: int
        :type n: int
        :rtype: List[List[int]]
        """
        self.res = []
        if n < 1 or k < 1: return self.res
        self.helper(k,n,1,[])
        return self.res

    def helper(self,k,n,start,temp):
        if len(temp) > k :
            return

        if len(temp) == k and sum(temp) == n:
            c= temp[:]
            self.res.append(c)
            return

        for i in range(start,10):
            temp.append(i)
            self.helper(k,n,i+1,temp)
            temp.pop()

子集问题

leetcode78:子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:

输入: nums = [1,2,3]
输出:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]
class Solution:
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        self.res = []
        if len(nums)<1: return self.res
        self.helper(nums,0,[])
        self.res.append([])
        return self.res


    def helper(self,nums,start,temp):

        for i in range(start,len(nums)):
            temp.append(nums[i])
            c = temp[:]
            self.res.append(c)
            self.helper(nums,i+1,temp)
            temp.pop()

补充

剑指offer25:二叉树中和为某一值的所有路径,其中路径定义是从根结点到叶子结点所形成的路径

class Solution:
    # 返回二维列表,内部每个列表表示找到的路径
    def FindPath(self, root, expectNumber):
        # write code here
        if root is None: return []
        self.res = []
        self.helper(root, expectNumber, [])
        return self.res


    def helper(self, root, expectNumber, temp):
        temp.append(root)
        xsum = sum(map(lambda x: x.val, temp))
        if  xsum == expectNumber and root.left is None and root.right is None:
            self.res.append(list(map(lambda x: x.val, temp[:])))
            return
        if xsum > expectNumber:
            return
        if root.left is not None:
            self.helper(root.left, expectNumber, temp)
            temp.pop()
        if root.right is not None:
            self.helper(root.right, expectNumber, temp)
            temp.pop()

你可能感兴趣的:(回溯法)