码迷,mamicode.com
首页 > 其他好文 > 详细

LeetCode: 回溯法

时间:2021-04-05 11:44:36      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:开始   数字   入行   cti   sel   jin   app   有一个   填充   

回溯法

全排列

LeetCode.46. 全排列

给定一个没有重复数字的序列, 返回其所有可能的全排列. 示例: 
输入: [1,2,3]
输出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]
from typing import List

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        def dfs(path, visited, res):
            if len(path) == len(nums):
                res.append(path[:])
                return None
            for i in range(len(nums)):
                if visited[i]:
                    continue
                path.append(nums[i])
                visited[i] = True
                dfs(path, visited, res)
                path.pop()
                visited[i] = False

        if len(nums) == 0:
            return []
        res = []
        path = []
        visited = [False for _ in range(len(nums))]
        dfs(path, visited, res)
        return res

ans = Solution()
nums = [1, 2, 3]
print(ans.permute(nums))

注意在此 res.append(path[:]) 不能写作 res.append(path), 因为变量 path 是引用传递 (本质上是变量内存地址的值传递), 在深度优先搜索完成之后就回到了根节点, 此时列表 path 为空. 解决的办法是应用列表的切片做一次浅拷贝.

全排列 II

LeetCode.47. 全排列 II

给定一个可包含重复数字的序列 nums, 按任意顺序返回所有不重复的全排列. 示例: 
输入: nums = [1,1,2]
输出: 
[[1,1,2],
 [1,2,1],
 [2,1,1]]
class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        def dfs(path, visited, res):
            if len(path) == len(nums):
                res.append(path[:])
                return None
            for i in range(len(nums)):
                if visited[i]:
                    continue
                if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]:
                    continue
                path.append(nums[i])
                visited[i] = True
                dfs(path, visited, res)
                path.pop()
                visited[i] = False

        if len(nums) == 0:
            return []
        nums.sort()
        res = []
        path = []
        visited = [False for _ in range(len(nums))]
        dfs(path, visited, res)
        return res

组合总数

LeetCode.39. 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target, 找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的数字可以无限制重复被选取.
输入: candidates = [2,3,6,7], target = 7,
所求解集为: 
[
  [7],
  [2,2,3]
]
from typing import List

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        def dfs(candidates, target, begin, res, path):
            if target == 0:
                res.append(path[:])
                return None
            for i in range(begin, len(candidates)):
                if target - candidates[i] < 0:
                    break
                path.append(candidates[i])
                dfs(candidates, target - candidates[i], i, res, path)
                path.pop()
                
        if len(candidates) == 0:
            return []
        res = []
        path = []
        begin = 0
        candidates.sort() # 剪枝, 如果没有排序, 则将 break 换为 continue
        dfs(candidates, target, begin, res, path)
        return res

组合总数 II

LeetCode.40. 组合总和 II

给定一个数组 candidates 和一个目标数 target, 找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在每个组合中只能使用一次. 
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]
from typing import List

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        def dfs(candidates, target, path, res, begin):
            if target == 0:
                res.append(path[:])
                return None
            for i in range(begin, len(candidates)):
                if target - candidates[i] < 0:
                    break
                if i != begin and candidates[i] == candidates[i - 1]:
                    continue
                path.append(candidates[i])
                dfs(candidates, target - candidates[i], path, res, i + 1)
                path.pop()

        if len(candidates) == 0:
            return []
        res = []
        path = []
        begin = 0
        candidates.sort()
        dfs(candidates, target, path, res, begin)
        return res

组合

LeetCode.77. 组合

给定两个整数 n 和 k, 返回 1 ... n 中所有可能的 k 个数的组合. 示例: 
输入: 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]]:
        def dfs(n, k, res, path, begin):
            if len(path) == k:
                res.append(path[:])
                return None # 到达叶子节点
            for i in range(begin, n + 1):
                path.append(i)
                dfs(n, k, res, path, i + 1)
                path.pop()
            
        if k == 0:
            return []
        res = []
        path = []
        dfs(n, k, res, path, 1)
        return res

子集

LeetCode.78. 子集

给你一个整数数组 nums, 数组中的元素互不相同. 返回该数组所有可能的子集 (幂集). 解集不能包含重复的子集. 你可以按任意顺序返回解集. 示例: 
输入: nums = [1,2,3]
输出: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        def dfs(nums, res, path, begin):
            res.append(path[:])
            for i in range(begin, len(nums)):
                path.append(nums[i])
                dfs(nums, res, path, i + 1)
                path.pop()

        if len(nums) == 0:
            return []
        res = []
        path = []
        begin = 0
        dfs(nums, res, path, 0)
        return res

子集 II

LeetCode.90. 子集 II

给定一个可能包含重复元素的整数数组 nums, 返回该数组所有可能的子集(幂集). 解集不能包含重复的子集. 示例: 
输入: [1,2,2]
输出:
[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]
class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        def dfs(nums, res, path, begin):
            res.append(path[:])
            for i in range(begin, len(nums)):
                if i != begin and nums[i] == nums[i - 1]:
                    continue
                path.append(nums[i])
                dfs(nums, res, path, i + 1)
                path.pop()

        if len(nums) == 0:
            return []
        res = []
        path = []
        begin = 0
        nums.sort()
        dfs(nums, res, path, begin)
        return res

N 皇后

LeetCode.51. N 皇后

n 皇后问题研究的是如何将 n 个皇后放置在 n*n 的棋盘上, 并且使皇后彼此之间不能相互攻击, 皇后彼此不能相互攻击, 也就是说: 任何两个皇后都不能处于同一条横行, 纵行或斜线上. 给你一个整数 n, 返回所有不同的 n 皇后问题的解决方案. 每一种解法包含一个不同的 n 皇后问题的棋子放置方案, 该方案中 ‘Q‘ 和 ‘.‘ 分别代表了皇后和空位. 
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        def dfs(n, vertical, diagonal1, diagonal2, begin):
            if len(path) == n:
                res.append(path[:])
                return None
            for i in range(begin, n):
                for j in range(n):
                    if vertical[j] or diagonal1[i + j] or diagonal2[i - j]:
                        continue
                    vertical[j] = True
                    diagonal1[i + j] = True
                    diagonal2[i - j] = True
                    temp = [‘.‘ for _ in range(n)]
                    temp[j] = ‘Q‘
                    path.append(‘‘.join(temp))
                    dfs(n, vertical, diagonal1, diagonal2, i + 1)
                    path.pop()
                    vertical[j] = False
                    diagonal1[i + j] = False
                    diagonal2[i - j] = False
            return res

        if n == 0:
            return []
        res = []
        path = []
        vertical = [False for _ in range(n)]
        diagonal1 = [False for _ in range(2 * n + 1)]
        diagonal2 = [False for _ in range(2 * n + 1)]
        dfs(n, vertical, diagonal1, diagonal2, 0)
        return res

N皇后 II

LeetCode.52. N皇后 II

给你一个整数 n, 返回 n 皇后问题不同的解决方案的数量. 示例: 
输入: n = 4
输出: 2
class Solution:
    def totalNQueens(self, n: int) -> int:
        def dfs(n, vertical, diagonal1, diagonal2, begin):
            if len(vertical) == n:
                return 1
            count = 0
            for i in range(begin, n):
                for j in range(n):
                    if j in vertical or i + j in diagonal1 or i - j in diagonal2:
                        continue
                    vertical.add(j)
                    diagonal1.add(i + j)
                    diagonal2.add(i - j)
                    count += dfs(n, vertical, diagonal1, diagonal2, i + 1)
                    vertical.remove(j)
                    diagonal1.remove(i + j)
                    diagonal2.remove(i - j)
            return count

        if n == 0:
            return 0
        vertical = set()
        diagonal1 = set()
        diagonal2 = set()
        return dfs(n, vertical, diagonal1, diagonal2, 0)

矩阵中的路径

LeetCode.剑指 Offer 12. 矩阵中的路径

board =
[
  [‘A‘,‘B‘,‘C‘,‘E‘],
  [‘S‘,‘F‘,‘C‘,‘S‘],
  [‘A‘,‘D‘,‘E‘,‘E‘]
]
给定 word = "ABCCED", 返回 true
给定 word = "SEE", 返回 true
给定 word = "ABCB", 返回 false
class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
        
        def dfs(i, j, k):
            if board[i][j] != word[k]:
                return False
            if k == len(word) - 1:
                return True
            board[i][j] = ‘‘
            # visited = set(), visited.add((i, j))
            for di, dj in directions:
                inew, jnew = i + di, j + dj
                if 0 <= inew < len(board) and 0 <= jnew < len(board[0]):
                    if board[inew][jnew] != ‘‘:
                        if dfs(inew, jnew, k + 1):
                            return True
            board[i][j] = word[k]
            # visited.remove((i, j))
            return False
            
        for i in range(len(board)):
            for j in range(len(board[0])):
                if dfs(i, j, 0):
                    return True
        return False

机器人的运动范围

LeetCode.剑指 Offer 13. 机器人的运动范围

地上有一个 m 行 n 列的方格, 从坐标 [0, 0] 到坐标 [m-1, n-1] . 一个机器人从坐标 [0, 0] 的格子开始移动, 它每次可以向左、右、上、下移动一格(不能移动到方格外), 也不能进入行坐标和列坐标的数位之和大于k的格子. 例如, 当k为18时, 机器人能够进入方格 [35, 37] , 因为3+5+3+7=18. 但它不能进入方格 [35, 38], 因为3+5+3+8=19. 请问该机器人能够到达多少个格子?

版本1 深度优先搜索

class Solution:
    def movingCount(self, m: int, n: int, k: int) -> int:
        def digitsum(x):
            s = 0
            while x != 0:
                s += x % 10
                x = x // 10
            return s

        def dfs(i, j):
            nonlocal count # 代替 self.count = 0
            count += 1
            visited.add((i, j))
            for di, dj in directions:
                inew, jnew = i + di, j + dj
                if 0 <= inew < m and 0 <= jnew < n:
                    if digitsum(inew) + digitsum(jnew) <= k:
                        if (inew, jnew) not in visited:
                            dfs(inew, jnew)
        
        directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
        visited = set()
        count = 0
        dfs(0, 0)
        return count

版本2 可以直接用 len(visited) 代替 count, 并且机器人只可能向右和下走.

class Solution:
    def movingCount(self, m: int, n: int, k: int) -> int:
        def digitsum(x):
            s = 0
            while x != 0:
                s += x % 10
                x = x // 10
            return s
        
        def dfs(i, j, visited):
            visited.add((i, j))
            if i < m - 1 and (i + 1, j) not in visited:
                if digitsum(i + 1) + digitsum(j) <= k:
                	dfs(i + 1, j, visited)
            if j < n - 1 and (i, j + 1) not in visited:
                if digitsum(i) + digitsum(j + 1) <= k:
                	dfs(i, j + 1, visited)
        
        visited = set()
        dfs(0, 0, visited)
        return len(visited)

版本3 递推

class Solution:
    def movingCount(self, m: int, n: int, k: int) -> int:
        def digitsum(x):
            s = 0
            while x > 0:
                s += x % 10
                x = x // 10
            return s
        
        count = 0
        visited = set()
        visited.add((0, 0))
        for i in range(m):
            for j in range(n):
                if (i == 0 and j == 0) or digitsum(i) + digitsum(j) > k:
                    continue
                if i - 1 >= 0 and (i - 1, j) in visited:
                    visited.add((i, j))
                if j - 1 >= 0 and (i, j - 1) in visited:
                    visited.add((i, j))
        return len(visited)

版本4 广度优先搜索 (使用队列)

class Solution:
    def movingCount(self, m: int, n: int, k: int) -> int:
        def digitsum(x):
            s = 0
            while x > 0:
                s += x % 10
                x = x // 10
            return s
        
        queue = deque()
        queue.append((0, 0))
        visited = set()
        while queue:
            i, j = queue.pop()
            if i >= m or j >= n or digitsum(i) + digitsum(j) > k or (i, j) in visited:
                continue
            visited.add((i, j))
            queue.append((i + 1, j))
            queue.append((i, j + 1))
        return len(visited)

被围绕的区域

LeetCode.130. 被围绕的区域

给你一个 m x n 的矩阵 board, 由若干字符 ‘X‘ 和 ‘O‘ 组成, 找到所有被 ‘X‘ 围绕的区域, 并将这些区域里所有的 ‘O‘ 用 ‘X‘ 填充. 被围绕的区间不会存在于边界上, 换句话说, 任何边界上的 ‘O‘ 都不会被填充为 ‘X‘. 任何不在边界上, 或不与边界上的 ‘O‘ 相连的 ‘O‘ 最终都会被填充为 ‘X‘. 如果两个元素在水平或垂直方向相邻, 则称它们是“相连”的. 示例: 
输入: board = [["X","X","X","X"],
             ["X","O","O","X"],
             ["X","X","O","X"],
             ["X","O","X","X"]]
输出: [["X","X","X","X"],
     ["X","X","X","X"],
     ["X","X","X","X"],
     ["X","O","X","X"]]
class Solution:
    def solve(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        def dfs(board, i, j):
            if i < 0 or i >= len(board) or j < 0 or j >= len(board[0]) or board[i][j] != ‘O‘:
                return None
            board[i][j] = ‘Y‘
            directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
            for di, dj in directions:
                inew, jnew = i + di, j + dj
                dfs(board, inew, jnew)

        for i in range(len(board)):
            dfs(board, i, 0)
            dfs(board, i, len(board[0]) - 1)
        for j in range(1, len(board[0]) - 1):
            dfs(board, 0, j)
            dfs(board, len(board) - 1, j)
        for i in range(len(board)):
            for j in range(len(board[0])):
                if board[i][j] == ‘O‘:
                    board[i][j] = ‘X‘
                if board[i][j] == ‘Y‘:
                    board[i][j] = ‘O‘
        return None

岛屿数量

LeetCode.200. 岛屿数量

给你一个由 ‘1‘ (陆地) 和 ‘0‘ (水) 组成的的二维网格, 请你计算网格中岛屿的数量. 岛屿总是被水包围, 并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成. 此外, 你可以假设该网格的四条边均被水包围. 示例: 
输入: grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出: 3
class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        def dfs(grid, i, j):
            if i < 0 or i >= len(grid) or j < 0 or j >= len(grid[0]) or grid[i][j] != ‘1‘:
                return None
            grid[i][j] = ‘2‘
            directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
            for di, dj in directions:
                inew, jnew = i + di, j + dj
                dfs(grid, inew, jnew)

        count = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == ‘1‘: # 只要存在一个就可以确定整个岛屿
                    count += 1
                dfs(grid, i, j)
        return count

迷宫

LeetCode.490.迷宫

由空地和墙组成的迷宫中有一个. 球可以向上下左右四个方向滚动, 但在遇到墙壁前不会停止滚动. 当球停下时, 可以选择下一个方向. 给定球的起始位置, 目的地和迷宫, 判断球能否在目的地停下. 迷宫由一个 0 和 1 的二维数组表示. 1 表示墙壁, 0 表示空地. 你可以假定迷宫的边缘都是墙壁. 起始位置和目的地的坐标通过行号和列号给出. 示例: 
输入 1: 迷宫由以下二维数组表示
0 0 1 0 0
0 0 0 0 0
0 0 0 1 0
1 1 0 1 1
0 0 0 0 0
输入 2: 起始位置坐标 (rowStart, colStart) = (0, 4)
输入 3: 目的地坐标 (rowDest, colDest) = (4, 4)
输出: true

版本1 深度优先搜索

class Solution:
    def hasPath(self, maze: List[List[int]], start: List[int], destination: List[int]) -> bool:
    	def dfs(maze, start, destination):
            if start == destination:
                return True
            if (start[0], start[1]) in visited:
                return False
            visited.add((start[0], start[1]))
            directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
            for di, dj in directions:
                i, j = start[0], start[1]
                while 0 <= i + di < len(maze) and 0 <= j + dj < len(maze[0])                         and maze[i + di][j + dj] == 0:
                    i += di
                    j += dj
                if dfs(maze, [i, j], destination):
                    return True
            # 只需要找到一条可行解, 所以不需要 visited.remove((start[0], start[1]))
            return False

        visited = set()
        return dfs(maze, start, destination)

ans = Solution()
maze = [[0, 0, 1, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 1, 0],
        [1, 1, 0, 1, 1],
        [0, 0, 0, 0, 0]]
start = [0, 4]
destination = [3, 2]
print(ans.hasPath(maze, start, destination))

版本2 visited 集合改为直接在矩阵内修改值为 2, 此时 0 和 2 都代表 0.

class Solution:
    def hasPath(self, maze: List[List[int]], start: List[int], destination: List[int]) -> bool:
        if start == destination:
            return True
        #if (start[0], start[1]) in visited:
        if maze[start[0]][start[1]] == 2:
            return False
        # visited.add((start[0], start[1]))
        maze[start[0]][start[1]] = 2
        directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
        for di, dj in directions:
            i, j = start[0], start[1]
            while 0 <= i + di < len(maze) and 0 <= j + dj < len(maze[0])                     and maze[i + di][j + dj] != 1:
                i += di
                j += dj
            if self.hasPath(maze, [i, j], destination):
                return True
        return False

版本3 广度优先搜索, 队列

from typing import List
from collections import deque


class Solution:
    def hasPath(self, maze: List[List[int]], start: List[int], destination: List[int]) -> bool:
        queue = deque([[start[0], start[1]]])
        visited = set()
        # visited = {(start[0], start[1])}
        directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
        while queue:
            i, j = queue.popleft()
            if [i, j] == destination:
                return True
            visited.add((i, j))
            for di, dj in directions:
                inew, jnew = i, j
                while 0 <= inew + di < len(maze) and 0 <= jnew + dj < len(maze[0])                         and maze[inew + di][jnew + dj] == 0:
                    inew += di
                    jnew += dj
                if (inew, jnew) in visited:
                    continue
                queue.append([inew, jnew])
        return False

LeetCode: 回溯法

标签:开始   数字   入行   cti   sel   jin   app   有一个   填充   

原文地址:https://www.cnblogs.com/funmore233/p/14610566.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!