回溯&递归


preface

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 “回溯” 返回,尝试别的路径。

回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。

代码框架:

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return

    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

参考博客:

leetcode题目:https://leetcode-cn.com/tag/backtracking/

itertools库文档

77. 组合

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

1. itertools 库
class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
    return list(itertools.combinations(range(1,n+1),k))
2. 回溯
class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        ans=[]

        def bk(n,k,tmp,start):
            if len(tmp)==k: 
                ans.append(tmp[:])
                return 
            for i in range(start,n+1):
                tmp.append(i)
                bk(n,k,tmp,i+1)
                tmp.pop()

        bk(n,k,[],1)
        return ans

46. 全排列

1. itertools 库
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        return list(itertools.permutations(nums))
2. 回溯
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        ans = []
        def bk(tmp, use):
            if not use:
                ans.append(tmp[:])
                return
            for i,idx in enumerate(use):
                tmp.append(nums[idx])
                bk(tmp,use[:i]+use[i+1:])
                tmp.pop()
        bk([],list(range(len(nums))))
        return ans

(1)22. 括号生成

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

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        ans=[]
        def backtrack(s,l,r): #s表cur_str_list,l 表示左括号数
            if len(s)==2*n:
                ans.append(''.join(s))
                return 
            if l<n:
                s.append('(')
                backtrack(s,l+1,r)
                s.pop()
            if r<l:
                s.append(')')
                backtrack(s,l,r+1)
                s.pop()
        backtrack([],0,0)
        return ans

(2)无重复字符串的排列组合

无重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合,字符串每个字符均不相同。

# 方法一:用itertools库中的permutations
return [''.join(i) for i in list(permutations(S))]

#方法二:回溯
class Solution:
    def permutation(self, S: str) -> List[str]:
        if not S: return []
        ans=[]
        def backtrack(s,path,ans):
            if not s:
                ans.append(path)
                return 
            for i in range(len(s)):
                backtrack(s[:i]+s[i+1:],path+s[i],ans)
        backtrack(S,'',ans)
        return ans

(3)幂集

编写一种方法,返回某集合的所有子集。集合中不包含重复的元素。
输入: nums = [1,2,3]
输出:[[3],[1],[2],[1,2,3],[1,3],[2,3],[1,2],[]]

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans=[]
        #l表示可取的左点,r表示可取的右点
        def backtrack(ans,subset,l,r):
            ans.append(subset[:])
            for i in range(l,r):
                subset.append(nums[i])
                backtrack(ans,subset,i+1,r)
                subset.pop()

        backtrack(ans,[],0,len(nums))
        return ans

#回溯法厉害啊,2020-9-13的第三道回溯
#这代码应该可以叫模板了

(4)八皇后问题

https://leetcode-cn.com/problems/eight-queens-lcci/

设计一种算法,打印 N 皇后在 N × N 棋盘上的各种摆法,其中每个皇后都不同行、不同列,也不在对角线上。这里的“对角线”指的是所有的对角线,不只是平分整个棋盘的那两条对角线。

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        ans=[]
        def queen(A, cur=0):
            if cur == len(A):
                ans.append(A[:])
                return 
            for i in range(len(A)):
                A[cur] = i
                flag = True
                #检验与前面的皇后是否冲突
                for j in range(cur):
                    if A[j]==i or abs(i - A[j]) == cur - j:
                        flag = False
                        break
                if flag: queen(A, cur+1)
        queen([None]*n)

        #接口对接部分
        temp=[[['.' for _ in range(n)] for _ in range(n)] for _ in range(len(ans))]

        for i,res in enumerate(temp): #第i个答案
            for j,row in enumerate(res):  #第j个行
                row[ans[i][j]]="Q"
                temp[i][j]="".join(temp[i][j])
                
        return temp
# 这是个代码片段
# A为答案,cur为第几行下标
def main(n):
    ans=[]
    
    def queen(A, cur=0):
        if cur == len(A):
            ans.append(A[:]) #没写[:]不行,什么机制还不知道
            return 
        for i in range(len(A)):
            A[cur] = i
            flag = True
            #检验与前面的皇后是否冲突
            for j in range(cur):
                if A[j]==i or abs(i - A[j]) == cur - j:
                    flag = False
                    break
            if flag: queen(A, cur+1)
    queen([None]*n)
    
    return ans
main(8)

(5)解数独

class Solution:
    def solveSudoku(self, board: List[List[str]]) -> None:
        def backtrack():
            for i in range(9):
                for j in range(9):
                    if board[i][j]!= '.': continue
                    for num in "123456789":
                        if check(i,j,num):
                            board[i][j]=num
                            if backtrack(): return True
                            board[i][j]='.'
                    return False
            return True

        def check(x,y,num):
            for i in range(9):
                if board[x][i]==num: return False
                if board[i][y]==num: return False
            for i in [0,1,2]:
                for j in [0,1,2]:
                    if board[x//3*3+i][y//3*3+j]==num: return False
            return True

        backtrack()

#回溯,,6
#这种暴力回溯,时间复杂度太高了
#尝试了一下引入参数,回溯不回来了,好菜

(7)131. 分割回文串

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

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

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

        def bk(tmp,l,r):
            if l==r: 
                ans.append(tmp[:])
                return
            for i in range(l+1,r+1):
                cur=s[l:i]
                if cur==cur[::-1]:
                    tmp.append(cur)
                    bk(tmp,i,r)
                    tmp.pop()

        bk([],0,len(s))
        return ans

# 后期试错出来的,
# 回溯神奇啊

(8)组合总和 II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

  • 所有数字(包括目标数)都是正整数。
  • 解集不能包含重复的组合。 
class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        ans=[]
        candidates.sort()
        def bk(tmp,l,r):
            if sum(tmp)==target:
                ans.append(tmp[:])
                return
            if sum(tmp)>target: return 

            for i in range(l,r):
                if i>l and candidates[i]==candidates[i-1]:
                    continue   #这种回溯中去重。。。
                tmp.append(candidates[i])
                bk(tmp,i+1,r)
                tmp.pop()
        bk([],0,len(candidates))
        return ans

# 记录报错:unhashble type: 'list',列表中列表不能集合去重
# 回溯之前,sort优化,并方便去重

(9)组合总和 III

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

class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        ans=[]
        def bk(path,l,k,n):
            if n==k==0:  
                ans.append(path[:])
                return 
            if n<0 or k==0: return 
            for i in range(l,10):
                path.append(i)
                bk(path,i+1,k-1,n-i)
                path.pop()

        bk([],1,k,n)
        return ans

# if剪枝 if结束

(10)复原IP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

有效的 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成),整数之间用 . 分隔。

class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        ans=[]
        def f(s,tmp):
            if len(s)==0 and len(tmp)==4:
                ans.append(".".join(tmp))
                return 
            if len(tmp)<4:
                for i in range(min(3,len(s))):
                    head,tail=s[:i+1],s[i+1:]
                    if head and 0<=int(head)<=255 and str(int(head))==head:
                        f(tail,tmp+[head])        
        f(s,[])
        return ans

# 一棵递归树
# str(int(i))==i排除前缀0

212. 单词搜索 II

class Trie:
    def __init__(self):
        self.children = defaultdict(Trie)
        self.word = ""              # 用于唯一标识
    def insert(self, word):
        cur = self
        for c in word:
            cur = cur.children[c]
        cur.is_word = True
        cur.word = word

class Solution:
    def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
        rows, cols = len(board), len(board[0])
        res = set()

        trie = Trie()
        for word in words:
            trie.insert(word)

        def dfs(p, i, j):
            if board[i][j] not in p.children:
                return 
            
            c = board[i][j]
            p = p.children[c]

            if p.word:
                res.add(p.word)
                p.word = ""

            if p.children:
                board[i][j] = '#'
                for x,y in [(i-1,j),(i+1,j),(i,j-1),(i,j+1)]:
                    if 0<=x<rows and 0<=y<cols:
                        dfs(p, x, y)
                board[i][j] = c                    #回溯

        for i in range(rows):
            for j in range(cols):
                dfs(trie, i, j)
        
        return list(res)

980. 不同路径 III

在二维网格 grid 上,有 4 种类型的方格:

  • 1 表示起始方格。且只有一个起始方格。
  • 2 表示结束方格,且只有一个结束方格。
  • 0 表示我们可以走过的空方格。
  • -1 表示我们无法跨越的障碍。
  • 返回在四个方向(上、下、左、右)上行走时,从起始方格到结束方格的不同路径的数目。

每一个无障碍方格都要通过一次,但是一条路径中不能重复通过同一个方格。

class Solution:
    def uniquePathsIII(self, grid: List[List[int]]) -> int:
        rows, cols = len(grid), len(grid[0])

        cnt = 0
        for i in range(rows):
            for j in range(cols):
                if grid[i][j] == 0:
                    cnt += 1
                elif grid[i][j] == 1:
                    start = (i,j)
        
        def bk(cur, steps):
            x, y = cur
            if grid[x][y] == 2:
                return 1 if steps==0 else 0

            ans = 0
            for i, j in [(x-1,y),(x+1,y),(x,y-1),(x,y+1)]:
                if 0<=i<rows and 0<=j<cols and grid[i][j] != -1:
                    grid[x][y] = -1
                    ans += bk((i,j), steps-1)
                    grid[x][y] = 0                  #回溯
            
            return ans

        return bk(start, cnt + 1)

文章作者: ╯晓~
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ╯晓~ !
评论
  目录