【回溯算法】应用 I

回溯算法

力扣上典型的回溯算法相关题目如下:

序号 题目
1 332. 重新安排行程
2 51. N 皇后
3 37. 解数独

应用

应用1:Leetcode.332

题目

332. 重新安排行程

分析

假设有 \(n\) 张机票,那么,就可以经过 \(n + 1\) 个机场,因此,回溯过程的终止条件,即路径上的点的个数比机票数多 \(1\)

我们定义个递归函数:

def dfs(self, ticket_num: int, trips: dict[dict[int]], path: List[str]) -> bool:

利用后序遍历的思路,去判断从当前起点开始,能否经过所有的机场。

算法步骤:

  • 先对所有的机票按照字典序排序;

  • \(hash\)\(trips\) 记录每一对起点到终点的数量;

  • 从起点开始,按照字典序回溯每一个目的机场最终能否经过所有的机场;

    • 如果能到达所有的机场,则该路径满足条件;

    • 如果不能到达所有的机场,则继续回溯。

代码实现

class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        trips = defaultdict(dict)

        # 先对tickets按照字典序排序,因为python中的dict是有序字典
        tickets.sort(key = lambda x: (x[0], x[1]))
        for _from, _to in tickets: 
            if _from not in trips:
                trips[_from] = defaultdict(int)
            trips[_from][_to] += 1

        path = ["JFK"]
        self.dfs(len(tickets), trips, path)
        return path

    def dfs(self, ticket_num: int, trips: dict[dict[int]], path: List[str]) -> bool:
        if len(path) == ticket_num + 1:
            return True

        start = path[-1]
        # 优先遍历字典序靠前的目的机场,如果满足条件则加入路径中
        for destination, count in trips[start].items():
            if trips[start][destination] < 1:
                continue

            path.append(destination)
            trips[start][destination] -= 1
            if self.dfs(ticket_num, trips, path):
                return True
            path.pop()
            trips[start][destination] += 1

        return False

应用2:Leetcode.51

题目

51. N 皇后

分析

枚举每一个位置,回溯所有满足条件的位置,找到满足条件的路径,就记录下来。

算法步骤:

  • 从起点开始,从左上向右下,遍历所有的列;

  • 对于当前位置,检查是否可以放置皇后,同时满足如下规则:

    • 当前位置的正上方的列没有放置皇后;

    • 当前位置的左上方向没有放置皇后;

    • 当前位置的右上方向没有放置皇后。

  • 对于任意起点,递归的结束条件:从当前起点位置开始,所有的位置都已经处理完成,即遍历完棋盘上所有的行(\(0,\cdots,n-1\)),\(row = n\)

  • 回溯完所有的位置后,记录满足条件的路径即可。

注:因为我们是从左上往右下的方向遍历的,所以,只需要判断已经经过的位置即可,不需要判断下方的位置。

代码实现

from typing import List


class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        results = list()
        path = [["."] * n for _ in range(n)]
        visited = [["."] * n for _ in range(n)]
        self.dfs(results=results, path=path, row=0, n=n, visited=visited)
        return results

    def dfs(self, results: List[List[str]], path: List[List[str]], row: int, n: int, visited: List[List[str]]):
        if row == n:
            results.append(list(["".join(candidate) for candidate in path]))
            return

        # 遍历所有的列,colum = 0 ... n-1
        for column in range(n):
            # 判断当前位置是否可以合法
            if not self.is_valid(path, row, column):
                continue
            # 如果当前位置满足条件,继续回溯下一列
            path[row][column] = "Q"
            self.dfs(results, path, row + 1, n, visited)
            path[row][column] = "."
        return

    def turn_right_top(self, start, end):
        """ 朝右上方检查 """
        i, j = start[0], start[1]
        while i >= end[0] and j < end[1]:
            yield i, j
            i -= 1
            j += 1

    def turn_left_top(self, start, end):
        """ 朝左上方检查 """
        i, j = start[0], start[1]
        while i >= end[0] and j >= end[1]:
            yield i, j
            i -= 1
            j -= 1

    def is_valid(self, path, row, column):
        """ 是否可以在位置(row, column)放置皇后 """
        # 检查正上方是否有皇后冲突
        for x in range(len(path)):
            if path[x][column] == "Q":
                return False

        # 检查右上方是否有皇后冲突
        for x, y in self.turn_right_top((row - 1, column + 1), (0, len(path))):
            if path[x][y] == "Q":
                return False

        # 检查左上方是否有皇后冲突
        for x, y in self.turn_left_top((row - 1, column - 1), (0, 0)):
            if path[x][y] == "Q":
                return False
        return True

应用3:Leetcode.37

题目

37. 解数独

分析

代码实现

class Solution:
    def solveSudoku(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        self.dfs(board)
        return board

    def dfs(self, board: List[List[str]]) -> bool:
        # 遍历行
        for i in range(len(board)):
            # 遍历列
            for j in range(len(board[0])):
                # 只处理空格
                if board[i][j] != ".":
                    continue
                # 遍历1-9所有的数字
                for num in range(1, 10):
                    # 如果当前数字不能填在当前位置,则跳过
                    if not self.is_valid(board, i, j, str(num)):
                        continue

                    board[i][j] = str(num)
                    if self.dfs(board):
                        return True

                    board[i][j] = "."
                # 9个数都不满足,直接返回false
                return False
        return True

    def is_valid(self, board, row, column, num) -> bool:
        # 判断行
        for i in range(9):
            if board[i][column] == num:
                return False

        # 判断列
        for j in range(9):
            if board[row][j] == num:
                return False

        # 九宫格的起点
        start_row = (row // 3) * 3
        start_column = (column // 3) * 3
        # 判断当前九宫格
        for i in range(start_row, start_row + 3):
            for j in range(start_column, start_column + 3):
                if board[i][j] == num:
                    return False
        return True

参考:

posted @ 2023-05-09 18:58  LARRY1024  阅读(95)  评论(0编辑  收藏  举报