回溯专题

回溯算法是一种类似于枚举的深度优先搜索过程,其解空间可以表示成一颗搜索树,叶子节点即为可能满足条件的解,树的一个路径就代表搜索其中一个解的方式。

此类问题一般都需要编写一个backtracking函数,用于递归地进行解空间的搜索和判断。 不满足条件时则撤销此次操作,回溯到上一搜索节点。

通常至少需要维护两个变量:res保存当前已经找到的所有满足条件的解、temp保存当前正在搜索的解。 

temp自始至终只有一个实例,在回溯时(即对temp执行pop时)由于所有temp指向同一内存,会导致res中的结果也都被修改。

有两种方式解决,一种是利用append和pop,以及浅拷贝(在python中用[:]或copy.copy()来进行浅拷贝)例如下面的前几个题;

第二种是用加号运算符来产生临时的变量传递给backtracking函数,此时就不用在调用函数前append、调用函数后pop了,例如下面的后几个题。

例题:(python解法)

leetcode39 组合总和  https://leetcode-cn.com/problems/combination-sum/

从一个集合中有放回的抽取。

class Solution:
    def backtracking(self, res, temp, nums, idx, left):
        # left表示距离给定的target还差多少
        # 考虑往temp中放入nums的第idx个数后凑成和为target
        if left < 0:
            return
        if left==0:
            res.append(temp[:]) 
            return
        for i in range(idx, len(nums)):
            temp.append(nums[i])  #尝试放入该数
            self.backtracking(res, temp, nums, i, left - nums[i]) #递归
            temp.pop()  #弹出该数,回溯到上一个搜索节点
            
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates.sort()  #先排序,提高执行速度
        res = []
        self.backtracking(res, [], candidates, 0, target)
        return res

leetcode40 组合总和II  https://leetcode-cn.com/problems/combination-sum-ii/
从一个集合中无放回的抽取。 由于是无放回的所以要判断重复元素的情况。

class Solution:
    def backtracking(self, res, temp, nums, idx, left):
        # left表示距离给定的target还差多少
        # 考虑往temp中放入nums的第idx个数后凑成和为target
        if left < 0:
            return
        if left==0:
            res.append(temp[:])
            return
        for i in range(idx, len(nums)):
            if i>idx and nums[i]==nums[i-1]:
                continue  # 由于排过序,当碰到重复元素时直接跳过即可
            temp.append(nums[i])
            self.backtracking(res, temp, nums, i+1, left - nums[i])
            temp.pop()
            
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates.sort()  #必须排序
        res = []
        self.backtracking(res, [], candidates, 0, target)
        return res

leetcode46 全排列 https://leetcode-cn.com/problems/permutations/

从一个集合中无放回的抽取。 由于是无放回的所以要判断重复元素的情况。借助一个used数组。

class Solution:
    def backtracking(self, res, temp, nums, used):
        if len(temp)==len(nums):
            res.append(temp[:])
            return
        for i in range(len(nums)):  #遍历集合中所有数字
            if used[i]:  #如果这个数已经用过了就跳过
                continue
            temp.append(nums[i])  #没用过,则加入排列
            used[i]=True  #置该数已被使用
            self.backtracking(res, temp, nums, used) #递归搜索
            temp.pop()  #弹出该数
            used[i]=False  #置该数未被使用
            
    def permute(self, nums: List[int]) -> List[List[int]]:
        res=[]
        used=[False for i in range(len(nums))] #给每个数字一个标志位标明该数是否已经使用
        self.backtracking(res,[],nums,used)
        return res

leetcode17 电话号码的字母组合 https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/

这是一个从多个集合中分别抽取,然后组合起来的题型。

class Solution:
    def __init__(self):
        self.dic = {"2": "abc", "3": "def",
                    "4": "ghi", "5": "jkl", "6": "mno",
                    "7": "pqrs", "8": "tuv", "9": "wxyz"}
        
    def backtracking(self, res, temp, digits, index):
        # 尝试给temp添加digits的第index位
        if len(temp) == len(digits):  #当前解与输入的数字等长时说明搜索结束
            res.append(temp)  #将该解保存下来
            return
        for i in range(index, len(digits)):  #组合之后几位数字所对应的字母
            for char in self.dic[digits[i]]:
                self.backtracking(res, temp + char, digits, i + 1)  #递归搜索
                
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return []
        res = []
        self.backtracking(res, '', digits, 0)
        return res

leetcode22 括号生成 https://leetcode-cn.com/problems/generate-parentheses/

从一个给定集合中无放回的抽取

class Solution:
    def backtracking(self, res, temp, n, n1, n2):
        # n是括号的对数
        # n1/n2分别是已经放置的左/右括号的数量
        if n1>n or n2>n:
            return  # 已经放置的括号数量不可能大于给的括号数量
        if n1==n and n2==n:
            res.append(temp)  # 相等时说明所有括号都放完了,则保存该解
        if n1>=n2:  # 只有当结果中左括号多于右括号时才能继续放置,否则不满足题目要求的“有效”这个条件
            self.backtracking(res, temp+'(', n, n1+1, n2)  #先放左括号
            self.backtracking(res, temp+')', n, n1, n2+1)  #再放右括号
            
        
    def generateParenthesis(self, n: int) -> List[str]:
        res=[]
        self.backtracking(res, '', n, 0, 0)
        return res

猜你喜欢

转载自blog.csdn.net/yuejisuo1948/article/details/88359680