LeetCode 题集:回溯法和递归(三)矩阵相关问题

本文介绍 LeetCode 题集中,使用回溯法(递归)解决矩阵相关的问题。

LeetCode 其他有关回溯法的问题:
LeetCode 题集:回溯法和递归(一)数组相关问题
LeetCode 题集:回溯法和递归(二)字符串相关问题


79. Word Search(单词搜索)


问题描述

LeetCode 79 问题描述 I
LeetCode 79 问题描述 II
LeetCode 79 问题描述 III

思路与代码


本题的思路,遍历矩阵所有元素,以其为起始点,向各个方向的相邻点搜索,通过回溯法进行求解,只要发现符合的连通域,即返回 True。

代码如下:

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        def backtrack(cor: Tuple[int, int], pos: int, mat_used: List[List[bool]]) -> bool:
            """
            回溯法递归函数

            :param cor:  当前坐标
            :param pos:  单词搜索的当前字母位置
            :param mat_used:  矩阵元素是否被使用

            :return:  单词是否找到
            """

            if board[cor[0]][cor[1]] == word[pos]:
                if pos == len(word) - 1:
                    return True

                else:
                    mat_used[cor[0]][cor[1]] = True

                    if cor[0] > 0 and not mat_used[cor[0] - 1][cor[1]]:  # 向左
                        if backtrack(cor=(cor[0] - 1, cor[1]), pos=pos + 1, mat_used=mat_used):
                            return True
                        else:
                            mat_used[cor[0] - 1][cor[1]] = False
                    if cor[0] < len(board) - 1 and not mat_used[cor[0] + 1][cor[1]]:  # 向右
                        if backtrack(cor=(cor[0] + 1, cor[1]), pos=pos + 1, mat_used=mat_used):
                            return True
                        else:
                            mat_used[cor[0] + 1][cor[1]] = False
                    if cor[1] > 0 and not mat_used[cor[0]][cor[1] - 1]:  # 向上
                        if backtrack(cor=(cor[0], cor[1] - 1), pos=pos + 1, mat_used=mat_used):
                            return True
                        else:
                            mat_used[cor[0]][cor[1] - 1] = False
                    if cor[1] < len(board[0]) - 1 and not mat_used[cor[0]][cor[1] + 1]:  # 向下
                        if backtrack(cor=(cor[0], cor[1] + 1), pos=pos + 1, mat_used=mat_used):
                            return True
                        else:
                            mat_used[cor[0]][cor[1] + 1] = False

            else:
                return False

        # 起始点
        for i in range(len(board)):
            for j in range(len(board[0])):
                mat_used_ = [[False for _ in range(len(board[0]))] for _ in range(len(board))]
                if backtrack(cor=(i, j), pos=0, mat_used=mat_used_):
                    return True

        return False

运行效果:
LeetCode 79 运行效果


54. Spiral Matrix(螺旋矩阵)


问题描述

LeetCode 54 问题描述 I
LeetCode 54 问题描述 II

思路与代码


本题的思路称不上回溯法,但与前一题的搜索方法相似,且更为简单,可通过一次递归完成遍历。

代码如下:

class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        if not len(matrix):
            return []

        def rec(cor: Tuple[int, int], direction: str, num_rem: int,
                mat_visit: List[List[bool]], list_visit: List[int]) -> List[int]:
            """
            递归函数

            :param cor:  当前位置
            :param direction:  当前搜索方向
            :param num_rem:  剩余点数
            :param mat_visit:  矩阵元素是否已被访问
            :param list_visit:  已访问的点列表

            :return: list_visit:  已访问的点列表(更新)
            """

            if not num_rem:
                return list_visit

            # 调整搜索方向
            if direction == 'right':
                if not (cor[1] < len(matrix[0]) - 1 and not mat_visit[cor[0]][cor[1] + 1]):  # 右转下
                    direction = 'down'
            if direction == 'down':
                if not (cor[0] < len(matrix) - 1 and not mat_visit[cor[0] + 1][cor[1]]):  # 下转左
                    direction = 'left'
            if direction == 'left':
                if not (cor[1] > 0 and not mat_visit[cor[0]][cor[1] - 1]):  # 左转上
                    direction = 'up'
            if direction == 'up':
                if not (cor[0] > 0 and not mat_visit[cor[0] - 1][cor[1]]):  # 上转右
                    direction = 'right'

            # 继续遍历
            if direction == 'right':
                list_visit.append(matrix[cor[0]][cor[1] + 1])
                mat_visit[cor[0]][cor[1] + 1] = True
                list_visit = rec(cor=(cor[0], cor[1] + 1), direction=direction, num_rem=num_rem - 1,
                                 mat_visit=mat_visit, list_visit=list_visit)
            elif direction == 'down':
                list_visit.append(matrix[cor[0] + 1][cor[1]])
                mat_visit[cor[0] + 1][cor[1]] = True
                list_visit = rec(cor=(cor[0] + 1, cor[1]), direction=direction, num_rem=num_rem - 1,
                                 mat_visit=mat_visit, list_visit=list_visit)
            elif direction == 'left':
                list_visit.append(matrix[cor[0]][cor[1] - 1])
                mat_visit[cor[0]][cor[1] - 1] = True
                list_visit = rec(cor=(cor[0], cor[1] - 1), direction=direction, num_rem=num_rem - 1,
                                 mat_visit=mat_visit, list_visit=list_visit)
            else:
                list_visit.append(matrix[cor[0] - 1][cor[1]])
                mat_visit[cor[0] - 1][cor[1]] = True
                list_visit = rec(cor=(cor[0] - 1, cor[1]), direction=direction, num_rem=num_rem - 1,
                                 mat_visit=mat_visit, list_visit=list_visit)

            return list_visit

        mat_visit_ = [[False for _ in range(len(matrix[0]))] for _ in range(len(matrix))]
        mat_visit_[0][0] = True
        list_visit_ = [matrix[0][0]]
        list_visit_ = rec(cor=(0, 0), direction='right', num_rem=len(matrix) * len(matrix[0]) - 1,
                          mat_visit=mat_visit_, list_visit=list_visit_)

        return list_visit_

运行效果:
LeetCode 54 运行效果


59. Spiral Matrix II(螺旋矩阵 II)


问题描述

LeetCode 59 问题描述

思路与代码


本题实际上并不需要用到回溯法或递归函数,但由于是前一题的变体,因此顺便列出。

代码如下:

class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        if not n:
            return [[]]

        matrix = [[0 for _ in range(n)] for _ in range(n)]

        num_rem = n ** 2  # 剩余数量
        i, j = 0, 0
        matrix[i][j] = n ** 2 - num_rem + 1
        num_rem -= 1
        direction = 'right'
        while num_rem:
            # 更新行进方向
            if direction == 'right':
                if not (j < n - 1 and not matrix[i][j + 1]):
                    direction = 'down'
            if direction == 'down':
                if not (i < n - 1 and not matrix[i + 1][j]):
                    direction = 'left'
            if direction == 'left':
                if not (j and not matrix[i][j - 1]):
                    direction = 'up'
            if direction == 'up':
                if not (i and not matrix[i - 1][j]):
                    direction = 'right'

            # 遍历填数
            if direction == 'right':
                j += 1
                matrix[i][j] = n ** 2 - num_rem + 1
            elif direction == 'down':
                i += 1
                matrix[i][j] = n ** 2 - num_rem + 1
            elif direction == 'left':
                j -= 1
                matrix[i][j] = n ** 2 - num_rem + 1
            else:
                i -= 1
                matrix[i][j] = n ** 2 - num_rem + 1

            num_rem -= 1

        return matrix

运行效果:
LeetCode 59 运行效果


51. N-Queens(N 皇后)


问题描述

LeetCode 51 问题描述

思路与代码


本题思路为经典的回溯法,笔者在之前的博客中曾有所介绍:

N皇后问题的两种求解方法:回溯法、约束规划

具体代码如下:

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        list_result = []
        list_state = [-1 for _ in range(n)]

        def is_valid(row: int, column: int) -> bool:
            """
            judge valid for a cell

            :param row:  row index of current cell
            :param column:  column index of current cell

            :return:  if current cell is valid
            """

            for i in range(row):
                # next row: avoid covered columns and diagonals
                if (list_state[i] == column) or (abs(row - i) == abs(column - list_state[i])):
                    return False

            return True

        def backtrack(row: int):
            """
            backtracking, recursion
            :param row:  current row index
            :return: nothing
            """

            # end condition
            if row == n:
                list_solution = []

                for i in range(n):
                    str_row = ''
                    for j in range(n):
                        str_row += 'Q' if j == list_state[i] else '.'
                    list_solution.append(str_row)

                list_result.append(list_solution.copy())
                return

            # recursion body
            for column in range(n):
                if is_valid(row=row, column=column):
                    list_state[row] = column
                    backtrack(row=row + 1)
                    list_state[row] = -1

        backtrack(row=0)

        return list_result

运行效果:
LeetCode 51 运行效果 1

实际上,回溯法还可以通过非递归的方式(while 循环)实现,具体代码如下:

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        list_result = []
        list_state = [-1 for _ in range(n)]

        def is_valid(x, y):
            for i in range(x):
                if list_state[i] == y or abs(x - i) == abs(y - list_state[i]):
                    return False
            return True

        row = 0
        while True:
            column = list_state[row] + 1
            while column < n:
                if is_valid(x=row, y=column):
                    list_state[row] = column

                    # a new solution found
                    if row == n - 1:
                        solution = [''.join(['Q' if j == list_state[i] else '.' for j in range(n)]) for i in range(n)]
                        list_result.append(solution)

                    # a valid state, go on for the next queen
                    else:
                        row += 1
                        break

                column += 1

            if column == n:
                if not row:  # all solutions found, search end
                    break
                list_state[row] = -1
                row -= 1  # back to last row to get other solutions

        return list_result

运行效果:
LeetCode 51 运行效果 2

上述方法为标准的回溯法,查看官方题解,发现一种基于位运算的回溯法更加巧妙,具体思路如下:

LeetCode 51 官方题解 基于位运算的回溯法

LeetCode 51 官方题解 I
LeetCode 51 官方题解 II
LeetCode 51 官方题解 III
LeetCode 51 官方题解 IV
LeetCode 51 官方题解 V

代码如下:

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        def generate_board():
            board = list()
            for i in range(n):
                row[queens[i]] = 'Q'
                board.append(''.join(row))
                row[queens[i]] = '.'
            return board

        def solve(row: int, columns: int, diagonals_1: int, diagonals_2: int):
            if row == n:
                board = generate_board()
                solutions.append(board)

            else:
                available_positions = ((1 << n) - 1) & (~(columns | diagonals_1 | diagonals_2))
                while available_positions:
                    position = available_positions & (-available_positions)  # 获得二进制表示中的最低位的 1 的位置
                    available_positions = available_positions & (available_positions - 1)  # 将二进制表示中的最低位的 1 置成 0

                    # 存储当前可行列
                    column = bin(position - 1).count('1')
                    queens[row] = column

                    # 下一行
                    solve(row=row + 1, columns=columns | position, diagonals_1=(diagonals_1 | position) << 1, diagonals_2=(diagonals_2 | position) >> 1)

        solutions = list()
        queens = [-1] * n
        row = ["."] * n
        solve(row=0, columns=0, diagonals_1=0, diagonals_2=0)

        return solutions

运行效果:
LeetCode 51 运行效果 3

此外,从运筹学的角度讲,还有一种约束规划的方法,使用 OR-Tools CP-SAT 求解器获取所有可行解,具体操作方法可见笔者之前的博客(前文有链接)。


52. N-Queens II(N皇后 II)


问题描述

LeetCode 52 问题描述

思路与代码


本题为前一题的变体,由于只需返回可行解的数量,代码中可省去很多结果的存储,只需计数即可。

笔者在此选用基于位运算的回溯法,代码如下:

class Solution:
    def totalNQueens(self, n: int) -> int:
        self.num_solution = 0

        def solve(row: int, columns: int, diagonals_1: int, diagonals_2: int):
            if row == n:
                self.num_solution += 1
                return

            else:
                available_positions = ((1 << n) - 1) & (~(columns | diagonals_1 | diagonals_2))
                while available_positions:
                    position = available_positions & (-available_positions)  # 获得二进制表示中的最低位的 1 的位置
                    available_positions = available_positions & (available_positions - 1)  # 将二进制表示中的最低位的 1 置成 0

                    solve(row=row + 1, columns=columns | position,
                          diagonals_1=(diagonals_1 | position) << 1, diagonals_2=(diagonals_2 | position) >> 1)  # 下一行

        solve(row=0, columns=0, diagonals_1=0, diagonals_2=0)

        return self.num_solution

运行效果:
LeetCode 52 运行效果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值