回溯算法

原问题:构造长为n的字符串->枚举一个字母->子问题:构造长为n-1的字符串 (递归)

回溯有一个增量构造答案的过程通常用递归实现

dfs(i)表示大于等于i的还要枚举

17. 电话号码的字母组合

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

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

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

代码:

MAPPING = "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        n = len(digits)
        if n == 0:
            return []
        ans = []
        path = [''] * n  # 本题 path 长度固定为 n
        def dfs(i: int) -> None:
            if i == n:
                ans.append(''.join(path))
                return
            for c in MAPPING[int(digits[i])]:
                path[i] = c  # 直接覆盖
                dfs(i + 1)
        dfs(0)
        return ans

递归过程  :

当digit="23"时,递归过程如下:

初始调用dfs(0)
i = 0,digits[i] = “2”
遍历 MAPPING[2] = “abc”
path[0] = ‘a’
调用dfs(1)
i = 1,digits[i] = “3”
遍历 MAPPING[3] = “def”
path[1] = ‘d’
调用dfs(2)

i = 2,达到n=2,此时path = [‘a’, ‘d’]
将 ‘ad’ 添加到ans中
返回上一层
回到i = 1
遍历 MAPPING[3] = “def”
path[1] = ‘e’
调用dfs(2)
i = 2,达到n=2,此时path = [‘a’, ‘e’]
将 ‘ae’ 添加到ans中
返回上一层
回到i = 1
遍历 MAPPING[3] = “def”
path[1] = ‘f’
调用dfs(2)
i = 2,达到n=2,此时path = [‘a’, ‘f’]
将 ‘af’ 添加到ans中
返回上一层
回到i = 0
遍历 MAPPING[3] = “def”
path[0] = ‘b’
调用dfs(1)

i = 1,digits[i] = “3”

遍历 MAPPING[3] = “def”

path[1] = ‘d’
调用dfs(2)

重复上述步骤,直到遍历完所有可能的组合

最终,返回的ans为[‘ad’, ‘ae’, ‘af’, ‘bd’, ‘be’, ‘bf’, ‘cd’, ‘ce’, ‘cf’]。

1.子集型回溯

每个元素都可以选/不选

78. 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的

子集

(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

代码部分:

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans=[]
        path=[]
        n=len(nums)
        def dfs(i):
            if i==n:
                ans.append(path.copy())
                return

            dfs(i+1)
            path.append(nums[i])
            dfs(i+1)
            path.pop()

        dfs(0)
        return ans

      

思路

ans接受最终答案,path接收递归过程中的答案,dfs这个函数,首先在i==n的时候,也就是第三次,返回递归得到的值,重点理解

            dfs(i+1)
            path.append(nums[i])
            dfs(i+1)
            path.pop()

其实这里第一行应该和第二行第三行是两个单独的模块,第一行表示不选,第二行第三行表示选,并且加进path中,最后pop是恢复现场

什么是恢复现场?为什么要恢复现场

在每一次递归调用结束后,需要将最后选择的元素从路径中移除,以确保路径的正确性。

在递归到某一“叶子节点”(非最后一个叶子)时,答案需要向上返回,而此时还有其他的子树(与前述节点不在同一子树)未被递归到,又由于path为全局变量。若直接返回,会将本不属于该子树的答案带上去,故需要恢复现场。
恢复现场的方式为:在递归完成后 dfs(i+1); 后,进行与“当前操作”相反的操作,“反当前操作”。

什么时候才会执行pop呢,当dfs(n+1)后i==n了,此时直接return,就到了上一个dfs(n+1)那里了,这时候ans已经从path拷贝了一个正确结果,如果这时候path不清空上一个元素,就会导致出错

如果 path 的长度不固定,或者每次递归到 i,path【i】 不一定会被修改,就需要恢复现场。

方法二

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans=[]
        path=[]
        n=len(nums)
        def dfs(i):
            ans.append(path.copy())
            if i == n:
                return
            for j in range(i,n):
                path.append(nums[j])
                dfs(j+1)
                path.pop()

        dfs(0)
        return ans

第一次dfs(3)时,path此时等于【1,2,3】,在经过三次path后清空,因为dfs(3),dfs(2),dfs(1)后面都有一次pop

131. 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 

回文串

 。返回 s 所有可能的分割方案。

示例 1:

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

示例 2:

输入:s = "a"
输出:[["a"]]

思路:假设每两个相邻字符之间有一个逗号,考虑选还是不选,转化成子集问题

代码

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        ans=[]
        path=[]
        n=len(s)

        def dfs(i):

            
            if i == n:
                ans.append(path.copy())
                return
            for j in range(i,n):
                t=s[i:j+1]
                if t=t[::-1]:
                    path.append(nums[j])
                    dfs(j+1)
                    path.pop()

        dfs(0)
        return ans

组合型回溯

77. 组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]
class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        ans=[]
        path=[]

        def dfs(i):
            d=k-len(path)
            if i<d:
                return
            if len(path)==k:
                ans.append(path.copy())
                return
            for j in range(i,0,-1):
                path.append(j)
                dfs(j-1)
                path.pop()
        dfs(n)
        return ans
        

216. 组合总和 III

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次 

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。
class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        ans=[]
        path=[]
    

        def dfs(i,t):
            d=k-len(path)
            if t<0 or t>(i*2-d+1)*d//2:
                return
            if i<d:
                return
            if len(path)==k :
                ans.append(path.copy())
                return
            for j in range(i,0,-1):
                path.append(j)
               
                dfs(j-1,t-j)
                path.pop()
                
        dfs(9,n)
        return ans

22. 括号生成

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

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1
输出:["()"]

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        m=2*n
        ans=[]
        path=['']*m
        def dfs(i,open):
            if i==m:
                ans.append(''.join(path))
                return
            if open<n:
                path[i]='('
                dfs(i+1,open+1)
            if i-open<open:
                path[i]=')'
                dfs(i+1,open)
        dfs(0,0)
        return ans

排列型回溯

46. 全排列

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

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

用path记录路径上的数字,s记录剩余未选择的数字

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        n=len(nums)
        ans=[]
        path=[0]*n
        def dfs(i,s):
            if i==n:
                ans.append(path.copy())
                return
            for x in s:
                path[i]=x
                dfs(i+1,s-{x})
        dfs(0,set(nums))
        return ans



        

也可以用布尔数组onpath记录在path中的数,num【i】如果在,onpath【i】就为真

51. N 皇后

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

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

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

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

示例 1:

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入:n = 1
输出:[["Q"]]

提示:

  • 1 <= n <= 9
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        ans=[]
        col=[0]*n
        def vaild(r,c):
            for R in range(r):
                C=col[R]
                if r+c==R+C or r-c==R-C:
                    return False
            return True

        def dfs(r,s):
            if r==n:
                ans.append(['.'*c + 'Q' + '.'*(n-1-c) for c in col])
                return
            for c in s:
                if vaild(r,c):
#也可以把vaild直接加到if中,if all( r+c!=R+col[R] or r-c!=R-col[R] for c in col)
                    col[r]=c
                    dfs(r+1,s-{c})
        dfs(0,set(range(n)))
        return ans


            
            


思路col表示n个皇后所在列的位置,col[1]=2表示第二个皇后在第三列, 

方法二

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        m = n * 2 - 1
        ans = []
        col = [0] * n
        on_path, diag1, diag2 = [False] * n, [False] * m, [False] * m
        def dfs(r: int) -> None:
            if r == n:
                ans.append(['.' * c + 'Q' + '.' * (n - 1 - c) for c in col])
                return
            for c, on in enumerate(on_path):
                if not on and not diag1[r + c] and not diag2[r - c]:
                    col[r] = c
                    on_path[c] = diag1[r + c] = diag2[r - c] = True
                    dfs(r + 1)
                    on_path[c] = diag1[r + c] = diag2[r - c] = False  # 恢复现场
        dfs(0)
        return ans

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值