研习代码 day26 | 回溯终极版——重新安排行程 && N皇后 && 解数独

一、重新安排行程

        1.1 题目

        给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。

        所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。

  • 例如,行程 ["JFK", "LGA"] 与 ["JFK", "LGB"] 相比就更小,排序更靠前。

假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。

示例 1:

输入:tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
输出:["JFK","MUC","LHR","SFO","SJC"]

示例 2:

输入:tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
输出:["JFK","ATL","JFK","SFO","ATL","SFO"]
解释:另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"] ,但是它字典排序更大更靠后。

提示:

  • 1 <= tickets.length <= 300
  • tickets[i].length == 2
  • fromi.length == 3
  • toi.length == 3
  • fromi 和 toi 由大写英文字母组成
  • fromi != toi

        1.2 题目链接

        332.重新安排行程

        1.3 解题思路和过程想法

        (1)解题思路

       先对机票 Tickets 排序,以保证优先选择字典序最小的票。因需保证出发点是 “JFK",在递归之前传入起始节点。再用 for 遍历“第一个”地点,递归纵向处理后续的节点。递归函数需带返回值,找到当前要找的内容就结束,再重新找其他出发地(因为可能有多种符合要求的情况,但我们只返回最好的那种,故而需要提前返回)。
        # 判断细节:出发起点符合要求,且该票尚未使用
        # 回溯函数的参数:必要参数 tickets,path,res,
                                        辅助变量:标记是否使用的 used 数组,当前要找的出发地
        # 递归出口:路径长度比机票数多1

        (2)过程想法

        有大体的处理思路,但没想到怎么写处理细节

        1.4 代码(代码提醒超时)

class Solution:
    def backTracing(self,tickets,cur,used,path,res):
        # 递归出口:路径长度比机票张数多1
        if len(path) == len(tickets)+1:
            res.append(path[:])
            return True

        for i,ticket in enumerate(tickets):
            # 当前票的起始站为要找的,并且该票还没使用
            if ticket[0] == cur and used[i] == 0:
                path.append(ticket[1])
                used[i] = 1
                state = self.backTracing(tickets,ticket[1],used,path,res)
                path.pop()
                used[i] = 0
                # 找到一个合适的结果就结束
                if state:
                    return True


    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        # 为优先字典排序更小的,先对其排序
        tickets.sort()
        # 标记是否使用过
        used = [0] * len(tickets)
        # 初始化出发点
        path = ["JFK"]
        res = []
        self.backTracing(tickets,"JFK",used,path,res)

        return res[0]

二、N 皇后

        2.1 题目

        按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

        n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

        给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

        每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例 1:

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入:n = 1
输出:[["Q"]]

提示:

  • 1 <= n <= 9

        2.2 题目链接

        51.N皇后

        2.3 解题思路和过程想法

        (1)解题思路

       用 for 遍历“第一行”地点,递归纵向处理其他行的皇后。若当前皇后符合要求,则递归相下判断其他皇后的位置,否则弹出当前皇后的位置,重新尝试其他位置。直到找到所有位置。
        # 判断细节:当前位置与其他皇后不冲突
        # 回溯函数的参数:必要参数 n,row,col,chessboard
        # 递归出口:找到所有位置

        (2)过程想法

        可以直接写出代码,但是对于最后结果形式的转换不熟练

        2.4 代码

class Solution:
    def isValid(self,row,col,chessBoard,n) -> bool:
        # 列
        for i in range(row):
            if chessBoard[i][col] == 'Q':
                return False

        # 右斜线
        i,j = row-1,col+1
        while i >= 0 and j < n:
            if chessBoard[i][j] == 'Q':
                return False
            i -= 1
            j += 1

        # 左斜线
        i,j = row-1,col-1
        while i >= 0 and j >= 0:
            if chessBoard[i][j] == 'Q':
                return False
            i -= 1
            j -= 1
        
        return True

    def backTracing(self,n,row,chessBoard,res):
        if row == n:
            res.append(chessBoard[:])
            return 

        for col in range(n):
            if self.isValid(row,col,chessBoard,n):
                chessBoard[row] = chessBoard[row][:col] + 'Q' + chessBoard[row][col+1:]
                self.backTracing(n,row+1,chessBoard,res)
                chessBoard[row] = chessBoard[row][:col] + '.' + chessBoard[row][col+1:]


    def solveNQueens(self, n: int) -> List[List[str]]:
        chessBoard = ['.' * n for _ in range(n)]
        res = []
        self.backTracing(n,0,chessBoard,res)

        return [[''.join(row) for row in solution] for solution in res]  

三、解数独

        3.1 题目

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:

输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:

提示:

  • board.length == 9
  • board[i].length == 9
  • board[i][j] 是一位数字或者 '.'
  • 题目数据 保证 输入数独仅有一个解

        3.2 题目链接

        37.解数独

        3.3 解题思路和过程想法

        (1)解题思路

         因为棋盘是二维的,考虑使用两个 for 语句来表示棋盘位置。用 for 遍历9个数字,递归纵向处理后续的节点。递归函数需带返回值,找到合适的数字,其他数层就不再搜索了,提前返回。
        解释:
        在 backTracing() 函数中,当为某个位置(i, j)找到一个有效数字时,就会将其赋值给board[i][j]。然后递归调用该函数求解下一个位置。但如果下一个位置有多个有效选项,函数会用不同的数字覆盖当前位置的数字,这就太糟糕了,会导致出错且无限循环。
        在引入返回值后,当为位置 (i, j) 找到一个有效数字时,函数会通过递归调用自身来检查该数字是否导致一个有效的解。如果当前数字指向一个有效解,函数返回 True,表示找到了一个解。如果没有,函数会回溯,将当前位置设置为".",然后继续搜索其他有效数字。

       
        # 判断细节:当前位置与其他数字不冲突
        # 回溯函数的参数:必要参数 board
                                        辅助变量:标记是否使用的 used 数组,当前要找的出发地
        # 递归出口(隐性):处理完所有节点

        (2)过程想法

        有具体的处理思路,但没想到递归函数需要用返回值

        3.4 代码

class Solution:
    def haveNum(self,board,row,col) -> bool:
        if board[row][col] != '.':
            return True
        else:
            return False
    
    def isValid(self,board,row,col,c) -> bool:
        # 行
        for j in range(9):
            if board[row][j] == c:
                return False
        
        # 列
        for i in range(9):
            if board[i][col] == c:
                return False

        # 当前小方格
        startX = (row // 3) * 3
        startY = (col // 3) * 3
        for i in range(startX,startX+3):
            for j in range(startY,startY+3):
                if board[i][j] == c:
                    return False
        
        return True

    def backTracing(self,board) -> bool:
        # for k 语句遍历第一个元素,递归纵向处理其他元素
        for i in range(9):
            for j in range(9):
                if self.haveNum(board,i,j):
                    continue
                for k in range(1,10):
                    if self.isValid(board,i,j,str(k)):
                        board[i][j] = str(k)
                        if self.backTracing(board): return True
                        board[i][j] = '.'
                    # 9个数都试完了还没成功
                return False
        return True

    def solveSudoku(self, board: List[List[str]]) -> None:
        self.backTracing(board)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值