Skip to content

Latest commit

 

History

History
535 lines (405 loc) · 14.6 KB

算法-递归-回溯.md

File metadata and controls

535 lines (405 loc) · 14.6 KB

递归

Problems


牛客 0026 括号生成 (中等, 2022-02)

回溯 牛客

问题简述
给出n对括号,请编写一个函数来生成所有的由n对括号组成的合法组合。
例如,给出n=3,解集为:
"((()))", "(()())", "(())()", "()()()", "()(())"

括号生成_牛客题霸_牛客网

思路:递归+回溯
  • 关键是中止条件的判断,详见代码;
Python
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# 
# @param n int整型 
# @return string字符串一维数组
#
class Solution:
    def generateParenthesis(self , n: int) -> List[str]:
        # write code here
        
        ret = []
        tmp = []
        
        def dfs(i, j):
            if i > n or j > n or i < j:
                return
            
            if i == j == n:
                ret.append(''.join(tmp))
                return
            
            tmp.append('(')
            dfs(i + 1, j)
            tmp.pop()
            
            tmp.append(')')
            dfs(i, j + 1)
            tmp.pop()
        
        dfs(0, 0)
        return ret

牛客 0027 集合的所有子集(一) (中等, 2022-02)

回溯 牛客

问题简述
现在有一个没有重复元素的整数集合S,求S的所有子集
注意:
    你给出的子集中的元素必须按升序排列
    给出的解集中不能出现重复的元素

集合的所有子集(一)_牛客题霸_牛客网

思路:递归+回溯(01背包)
Python
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# 
# @param S int整型一维数组 
# @return int整型二维数组
#
class Solution:
    def subsets(self , S: List[int]) -> List[List[int]]:
        # write code here
        
        N = len(S)
        ret = []
        tmp = []
        
        def dfs(i):
            if i == N:
                ret.append(tmp[:])
                return
            
            # 不要当前元素
            dfs(i + 1)
            
            # 要当前元素
            tmp.append(S[i])
            dfs(i + 1)
            tmp.pop()
        
        dfs(0)
        return ret

牛客 0042 有重复项数字的全排列 (中等, 2022-03)

回溯 牛客

问题简述
给出一组可能包含重复项的数字,返回该组数字的所有排列。结果以字典序升序排列。

有重复项数字的全排列_牛客题霸_牛客网

思路:DFS+回溯
Python
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
# 
# @param num int整型一维数组 
# @return int整型二维数组
#
class Solution:
    def permuteUnique(self , num: List[int]) -> List[List[int]]:
        
        ret = []
        tmp = []
        N = len(num)
        
        num.sort()  # 排序
        book = [0] * N
        
        def dfs(deep):
            if deep == N:
                ret.append(tmp[:])
                return 
            
            for i in range(N):
                if book[i]:
                    continue
                
                # 树层剪枝
                if not book[i - 1] and i > 0 and num[i] == num[i - 1]:
                    continue
                # 为什么是 not book[i - 1]?
                #   当遍历完一条路径回到本层的时候,book[i - 1] 会回溯为 0,
                #   此时如果还有 num[i] == num[i - 1],说明当前路径重复,直接跳过
                
                book[i] = 1
                tmp.append(num[i])
                dfs(deep + 1)
                tmp.pop()
                book[i] = 0
        
        dfs(0)
        return ret

牛客 0043 没有重复项数字的全排列 (中等, 2022-03)

回溯 牛客

问题简述
给出一组数字,返回该组数字的所有排列
例如:
[1,2,3]的所有排列如下
[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1].
(以数字在数组中的位置靠前为优先级,按字典序排列输出。)

没有重复项数字的全排列_牛客题霸_牛客网

思路:DFS+回溯
Python
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# 
# @param num int整型一维数组 
# @return int整型二维数组
#
class Solution:
    def permute(self , num: List[int]) -> List[List[int]]:
        
        ret = []
        tmp = []
        N = len(num)
        book = [0] * N
        
        def dfs(deep):
            if deep == N:
                ret.append(tmp[:])
            
            for i in range(N):
                if book[i]:
                    continue
                
                book[i] = 1
                tmp.append(num[i])
                dfs(deep + 1)
                tmp.pop()
                book[i] = 0
        
        dfs(0)
        return ret

牛客 0046 加起来和为目标值的组合(二) (中等, 2022-03)

回溯 牛客

问题简述
给出一组候选数 c 和一个目标数 t ,找出候选数中起来和等于 t 的所有组合。

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

注意:
1. 题目中所有的数字(包括目标数 t )都是正整数
2. 组合中的数字要按非递减排序
3. 结果中不能包含重复的组合
4. 组合之间的排序按照索引从小到大依次比较,小的排在前面,如果索引相同的情况下数值相同,则比较下一个索引。
思路:DFS回溯
  • 定义 dfs(start, rest) 表示从 start 开始遍历剩下的元素,剩余目标和 rest
  • 剪枝要点(详见代码);
    • 先对 arr 排序;
    • 当前层跳过重复值,即 arr[i] == arr[i-1]continue
    • 遍历当前层元素时,若 rest < arr[i] 直接 break
Python
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
# 
# @param num int整型一维数组 
# @param target int整型 
# @return int整型二维数组
#
class Solution:
    def combinationSum2(self , arr: List[int], target: int) -> List[List[int]]:
        
        N = len(arr)
        arr.sort()
        ret = []
        tmp = []
        
        def dfs(start, rest):
            if rest == 0:
                ret.append(tmp[:])
                return
            
            for i in range(start, N):
                if i > start and arr[i] == arr[i - 1]:
                    continue
                # 因为排过序了,所以当前值不够的话,后面的肯定都不够了,直接全部剪掉
                if rest < arr[i]:
                    break
                    
                tmp.append(arr[i])
                # 注意这里不是 start + 1,而是 i + 1,表示下一层应该从 i + 1 开始尝试
                # dfs(start + 1, rest - arr[i])  # err
                dfs(i + 1, rest - arr[i])
                tmp.pop()
                    
        dfs(0, target)
        return ret

牛客 0047 数独 (较难, 2022-03)

回溯 牛客

问题简述
请编写一个程序,给数独中的剩余的空格填写上数字
空格用字符'.'表示
假设给定的数独只有唯一的解法

数独_牛客题霸_牛客网

思路
  • 使用三个矩阵记录每行、每列、每块出现过的数字;开始时,需要对已经出现过的数字初始化;
  • 难点1:
    • board[i][j] 很容易确定他所在的行和列,但是确定所在块需要对坐标做一个转换;
    • 具体来说,假设块的标号从左往右,从上往下依次为 0~8,则对 (i,j),其所在块的 id 为 k = 3*(i/3) + j/3
    • 简单验证几个位置,(0,0) 在第 0 块,(8,8) 在第 8 块;(4,5) 在第 4 块;
  • 难点2:
    • 确定下一个遍历位置,最直观的顺序是从左往右,从上往下;
    • 假设当前位置为 (i, j) 表示第 i 行,第 j 列;则下一个位置 (nxt_i, nxt_j) 为:
      • nxt_i = i if j != 8 else i + 1
      • nxt_j = j + 1 if j != 8 else 0
  • 详见代码;
Python
class Solution:
    def solveSudoku(self , board):
        if not board: return []
        
        n = len(board)  # 9
        row = [[0] * (n + 1) for _ in range(n)]
        col = [[0] * (n + 1) for _ in range(n)]
        blk = [[0] * (n + 1) for _ in range(n)]
        
        def init():
            """初始化 row, clo, blk 记录出现过的数字"""
            for i in range(n):
                for j in range(n):
                    # 确定块 id
                    k = 3 * (i // 3) + j // 3
                    if (x := board[i][j]) != '.':
                        x = int(x)
                        row[i][x] = col[j][x] = blk[k][x] = 1
        
        def dfs(i, j):
            # 顺利达到最后一行,说明每个位置都满足要求
            if i == n: return True
            
            # 确定下一个遍历位置
            nxt_i = i if j != (n - 1) else i + 1
            nxt_j = j + 1 if j != (n - 1) else 0
            
            # 如果当前位置不需要填,直接到下一个位置
            if board[i][j] != '.':
                return dfs(nxt_i, nxt_j)
            
            # 对当前位置遍历每个可用数字
            k = 3 * (i // 3) + j // 3  # 块 id
            for x in range(1, 10):
                # 不可用,跳过
                if row[i][x] or col[j][x] or blk[k][x]:
                    continue
                # 可用,DFS回溯
                row[i][x] = col[j][x] = blk[k][x] = 1
                board[i][j] = str(x)
                if dfs(nxt_i, nxt_j):
                    return True
                board[i][j] = '.'
                row[i][x] = col[j][x] = blk[k][x] = 0
            
            return False
        
        init()
        dfs(0, 0)
        return board