数据结构与算法-回溯算法1

回溯算法框架

回溯法和DFS息息相关。
回溯是“撤回一步”的意思

解决一个回溯问题,实际上就是一个决策树的遍历过程。只需要思考3个问题:

  1. 路径:已经做出的选择;
  2. 选择列表:当前可以做的选择;
  3. 结束条件:到达决策树底层,无法再做选择的条件。
res  = []
def  backtrack(路径,选择列表):
	if  满足结束条件:
		result.add(路径)
		return
	for  选择  in 选择列表:
		做选择
		backtrack(路径,选择列表)
		撤销选择

核心:递归调用前做选择,调用后撤销选择

经典问题

排列-Permutation

全排列问题的决策树
遍历到树的底层,路径就是一个全排列。多叉树的遍历框架:

def  traverse(root):
	for(root.child):
		#前序遍历需要的操作
		traverse(child)
		#后序遍历需要的操作

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

定义k:对下标为k的元素做决策

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        if not nums:
            return []
        
        res = []
        self.backtrack(res,0,nums)
        return res

    def backtrack(self,res,k,nums):
        if k==len(nums):
            res.append(nums[:])
            return
        
        for i in range(k,len(nums)):
            nums[i],nums[k] = nums[k],nums[i]#选择nums[i]
            self.backtrack(res,k+1,nums)#k+1,把nums[i]加入路径
            nums[i],nums[k] = nums[k],nums[i]#撤销选择

子集-Subsets

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

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        if not nums:
            return []
        
        curr = []
        res = []
        self.backtrack(curr,res,0,nums)
        return res
    
    def backtrack(self,curr,res,k,nums):
        if k==len(nums):
            res.append(curr[:])
            return
        #不选nums[k]
        self.backtrack(curr,res,k+1,nums)
        #选nums[k]
        curr.append(nums[k])
        self.backtrack(curr,res,k+1,nums)
        curr.pop()

组合-Combination

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

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        curr = []
        res = []
        self.backtrack(curr,res,1,k,n)
        return res
    
    def backtrack(self,curr,res,i,k,n):
        if len(curr)==k:
            res.append(curr[:])
            return 
        
        if i>(n-(k-len(curr)))+1:#剪枝
            return
        
        self.backtrack(curr,res,i+1,k,n)

        curr.append(i)
        self.backtrack(curr,res,i+1,k,n)
        curr.pop()

剪枝:以n=4,k=2为例
当curr=[]时,还需要至少有2个元素可供选择,k-len(curr)
如果i>3则一定不能产生2个数的组合,(n-(k-len(curr)))+1

去重策略

有重复元素的排列问题

47. 全排列 II
给定一个可包含重复数字的序列,返回所有不重复的全排列。

在这里插入图片描述

以[2,2,3]为例,如果有多个2候选,那么只能选第一个2,后面的2此次不能选。
set()辅助判断

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        if not nums:
            return []
        
        res = []
        self.backtrack(nums,0,res)
        return res

    def backtrack(self,nums,k,res):
        if k==len(nums):
            res.append(nums[:])
            return
        seen = set()
        for i in range(k,len(nums)):
            if nums[i] not in seen:
                seen.add(nums[i])
                nums[i],nums[k] = nums[k],nums[i]
                self.backtrack(nums,k+1,res)
                nums[i],nums[k] = nums[k],nums[i]

有重复元素的子集问题

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

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        if not nums:
            return []
        
        **nums.sort()**
        res = []
        curr = []
        self.backtrack(nums,curr,0,res)
        return res
    
    def backtrack(self,nums,curr,k,res):
        if k==len(nums):
            res.append(curr[:])
            return
        #不选nums[k],则与其相等的元素都不选
        i = k+1
        while i<len(nums) and nums[i]==nums[k]:
            i+=1
        self.backtrack(nums,curr,i,res)

        curr.append(nums[k])
        self.backtrack(nums,curr,k+1,res)
        curr.pop()

参考资料:
LeetCode 例题精讲 | 03 从二叉树遍历到回溯算法
labuladong

猜你喜欢

转载自blog.csdn.net/x___xxxx/article/details/107836242
今日推荐