【代码随想录】回溯算法(Python版)

回溯解决的问题

组合、切割、排列、子集、棋盘(其中,组合不强调元素顺序,但排列强调元素顺序)

代码模板

(1)参数:一般先写逻辑,需要什么参数再写什么参数;返回值:一般为空

(2)终止条件:回溯法抽象为树形结构,一般到达叶子节点,找到满意的答案,就将答案保存

(3)回溯搜索的遍历过程:回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度递归的深度构成的树的深度

def backtracking(参数) {
    if 终止条件 
        存放结果
        return

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) 
        处理节点
        backtracking(路径,选择列表) // 递归
        回溯,撤销处理结果
    
}

经典错题

画属性图,横向是集合大小,纵向是递归深度!!

1. 组合类型题

(1)电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

解题思路:

回溯三部曲:

①参数及返回值:nums字符串、字符串s收集子路径,res收集所有路径,index遍历第几个数字;返回值一般为空

②终止条件:当index==len(nums),收集结果,返回递归

③回溯搜索的遍历过程:取出index指向的数字的字符集,遍历字符集,加入s,index指向下一个数字,进一步递归,递归结束后,撤销处理结果

代码:

class Solution:
    def __init__(self):
        self.dicts={
            '2':'abc',
            '3':'def',
            '4':'ghi',
            '5':'jkl',
            '6':'mno',
            '7':'pqrs',
            '8':'tuv',
            '9':'wxyz'
        }
    def letterCombinations(self, digits: str) -> List[str]:
        if len(digits)==0: return []
        return self.backtracking(digits,0,'',[])
        
    
    def backtracking(self,digits,ind,s,res):
        if ind==len(digits):
            res.append(s)
            return 
        num=digits[ind]
        letters=self.dicts[num]
        for i in range(len(letters)):
            s+=letters[i]
            self.backtracking(digits,ind+1,s,res)
            s=s[:-1]
        return res
(2)组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 对于给定的输入,保证和为 target 的不同组合数少于 150 个。

解题思路:

回溯三部曲:

①参数及返回值:集合candidites,目标值target,单一结果总和sum,单一结果path,结果集res,for循环起始位置startindex;返回值为空

**如果是一个集合求组和,需要用startindex,如果是多个集合求组合,各个集合之间互不影响,则不用startindex

②终止条件:sum>target和sum=target

③单层搜索逻辑:从startindex开始搜索candidates集合

④剪枝优化:若下一层的sum>target,则无需进入下一层递归

代码:

class Solution:
    def backtracking(self, candidates, target, total, startIndex, path, result):
        if total == target:
            result.append(path[:])
            return

        for i in range(startIndex, len(candidates)):
            if total + candidates[i] > target:
                break
            total += candidates[i]
            path.append(candidates[i])
            self.backtracking(candidates, target, total, i, path, result)
            total -= candidates[i]
            path.pop()

    def combinationSum(self, candidates, target):
        result = []
        candidates.sort()  # 需要排序
        self.backtracking(candidates, target, 0, 0, [], result)
        return result
(3)括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

解题思路:有效括号组合一定是左括号和右括号数目相同,并且以左括号开始的字符串

回溯三部曲:

①参数:当前括号数目i,左括号数目open,单一结果path,结果集res,括号对数n;返回值为空

②终止条件:当前括号数目等于2*n时,将path加入res,然后返回

③单一搜索逻辑:因为要以左括号开始,如果open<n,则将path[i]='(',递归(i+1,open+1,path,res,n)

如果i-open<open,即左右括号个数不匹配,则将path[i]=')',递归(i+1,open,path,res,n)

 代码:

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        path=['']*(2*n)
        res=[]
        return self.backtracking(n,0,0,path,res)
    
    def backtracking(self,n,i,open,path,res): #美剧填左括号还是右括号
    #参数:i表示当前填了几个括号,open表示当前填了几个左括号,i-open表示当前填了几个右括号
        m=2*n
        #终止条件:当i=m时,括号数目达到上限
        if i==m:
            res.append(''.join(path)) #将单一结果加入结果集
            return 
        if open < n: #左括号未达到上限
            path[i]='('
            self.backtracking(n,i+1,open+1,path,res)
        if i-open < open: #可以填右括号
            path[i]=')'
            self.backtracking(n,i+1,open,path,res)
        return res

2. 排列类型题

(1)全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

解题思路:

回溯三部曲:

①参数及返回值:排列是有序的,每个元素都要使用,因此需要一个used数组记录该元素是否使用过,还需要nums数组;返回值一般为

②终止条件:当path的长度等于nums的长度时,则找到一个全排列

③回溯搜索的遍历过程:遍历used,如果使用过->跳过;否则将当前节点标记为使用过,然后加入到path,递归搜索,递归返回后撤销操作

代码:

class Solution:
    def permute(self, nums):
        result = []
        self.backtracking(nums, [], [False] * len(nums), result)
        return result

    def backtracking(self, nums, path, used, result):
        if len(path) == len(nums):
            result.append(path[:])
            return
        for i in range(len(nums)):
            if used[i]:
                continue
            used[i] = True
            path.append(nums[i])
            self.backtracking(nums, path, used, result)
            path.pop()
            used[i] = False

3. 子集问题

(1)子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

解题思路:子集也是一种组合问题,无序的,因此元素不能重复使用

回溯三部曲:

①参数及返回值:nums数组,path收集子路径,res收集所有路径,startind遍历开始索引;返回值一般为空

②终止条件:当startind大于nums数组长度,剩余集合为空,直接返回

③回溯搜索的遍历过程:处理节点-递归-回溯

(**收集子集要放在终止条件上面,否则会漏掉自己)

代码:

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        return self.backtracking(nums,0,[],[])
        

    def backtracking(self,nums,start,path,res):
        res.append(path[:])
        if start>len(nums)-1:
            return 
        for i in range(start,len(nums)):
            path.append(nums[i])
            self.backtracking(nums,i+1,path,res)
            path.pop()
        return res

4. 切割问题

(1)分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串。返回 s 所有可能的分割方案。

解题思路:(与组合问题类似)

 回溯三部曲:

①参数:单一结果path,结果集res,切割线ind,字符串s

②终止条件:当切割线切到了字符串最后一个字符,找到一种切割方法,将结果加入结果集

③单层搜索逻辑:截取子串s[ind:i],判断是否为回文串,如果是加入path

判断回文子串的方法:双指针法,如果前后指针所指向的元素是相等的,就是回文字符串了。

代码:

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        res=[]
        self.backtracking(s,0,[],res)
        return res

    def backtracking(self,s,ind,path,res):
        if ind==len(s):
            res.append(path[:])
            return 
        
        for i in range(ind,len(s)):
            #判断是否为回文串
            if self.substr(ind,i,s):
                sub=s[ind:i+1]
                path.append(sub)
                self.backtracking(s,i+1,path,res)
                path.pop()
 
    def substr(self,start,end,strs): #双指针法
        while start<end:
            if strs[start]!=strs[end]:
                return False
            start+=1
            end-=1
        return True

5. 棋盘问题

(1)N皇后

 按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

解题思路:

 回溯三部曲:

①递归参数:棋盘大小n,结果集res,遍历到第几行row

②终止条件:递归到棋盘最底层,收集结果返回

③单层搜索逻辑:row控制递归深度,for循环的遍历控制棋盘的列,每行从0开始搜索

验证棋盘是否合法:

不能同列

不能同斜线(45°或135°)

代码:

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        path=['.'*n for _ in range(n)]
        res=[]
        self.backtracking(n,path,res,0)
        return [[''.join(row) for row in p] for p in res]

    
    def backtracking(self,n,path,res,row):
        #终止条件:到达棋盘底部
        if row==n:
            res.append(path[:])
            return 
        
        for i in range(n):
            if self.check_valid(row,i,path):
                path[row]=path[row][:i]+'Q'+path[row][i+1:]
                self.backtracking(n,path,res,row+1)
                path[row]=path[row][:i]+'.'+path[row][i+1:]
    
    def check_valid(self,row,col,path):
        #不能同列
        for i in range(row):
            if path[i][col]=='Q':
                return False
        
        #不能同斜线  45°
        i,j=row-1,col-1
        while i>=0 and j>=0:
            if path[i][j]=='Q':
                return False
            i-=1
            j-=1
        #135°
        i,j=row-1,col+1
        while i>=0 and j<len(path):
            if path[i][j]=='Q' :
                return False
            i-=1
            j+=1
        return True

  • 20
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值