Day 30 回溯:332. 重新安排行程, 51. N 皇后, 37. 解数独

332. 重新安排行程


  • 思路
    • example
    • 从 JFK(肯尼迪国际机场)出发
    • 按字典排序返回最小的行程组合
    • 假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
      • 有可能某些机场需要访问多次。

有重不可复选?

  • 深搜的时候可能遇到回路(从而使得有些机票未访问到),这个时候就需要回溯了。

  • 两个难点:
    • 在碰到回路(死循环)时,需要回溯。
    • 建立字典 map[起始机场] = list(终点机场 按字典顺序排序) 有向图 (邻接表)
      • 方便使用”字典顺序“
      • 使用pop(0)和append来增删map[]里的终点机场。
    • 注意初始path = ['JFK']
  • 复杂度. 时间:O(?), 空间: O(?)
class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        def backtrack(ticket_dict, start):
            if len(path) == len(tickets)+1:
                return True 
            for _ in ticket_dict[start]:
                end = ticket_dict[start].pop(0)
                path.append(end)
                if backtrack(ticket_dict, end):
                    return True  
                path.pop()
                ticket_dict[start].append(end) # 重新加到末尾
            return False 
        path = ['JFK']
        ticket_dict = collections.defaultdict(list)
        for ticket in tickets:
            ticket_dict[ticket[0]].append(ticket[1])
        for start in ticket_dict:
            ticket_dict[start].sort()
        backtrack(ticket_dict, 'JFK')
        return path 
class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        def backtrack(start):
            if len(path) == n+1:
                return True 
            for _ in table[start]:
                end = table[start].pop(0) 
                path.append(end)
                if backtrack(end):
                    return True 
                path.pop()
                table[start].append(end)
            return False 
        table = collections.defaultdict(list)
        n = len(tickets)
        for i in range(n):
            table[tickets[i][0]].append(tickets[i][1])
        for key in table:
            table[key].sort()
        path = ['JFK']
        backtrack('JFK')
        return path 
class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        def backtrack(start_pos):
            if len(path) == n+1:
                return True  
            for _ in range(len(record[start_pos])): # !!!
                end_pos = record[start_pos].pop(0)
                path.append(end_pos)
                if backtrack(end_pos):
                    return True  
                path.pop() 
                record[start_pos].append(end_pos) 
            return False  
        record = collections.defaultdict(list)  
        n = len(tickets)
        for i in range(n):
            record[tickets[i][0]].append(tickets[i][1]) 
        for key, val in record.items():
            record[key].sort()  
        path = ['JFK'] 
        backtrack('JFK') 
        return path  
class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        def backtrack(start):
            print(start)
            if len(path) == n+1:
                return True 
            #for end in record[start]: # This is wrong! 
            for _ in range(len(record[start])): # !!!
                end = record[start].pop(0) 
                path.append(end)
                if backtrack(end):
                    return True 
                path.pop()
                record[start].append(end)
            return False 
        n = len(tickets)
        record = collections.defaultdict(list)
        for i in range(n):
            record[tickets[i][0]].append(tickets[i][1])
        for key, val in record.items():
            record[key].sort() 
        path = ['JFK'] 
        backtrack('JFK')
        return path 

51. N 皇后

  • 思路
    • example
    • 返回所有不同的 n 皇后问题 的解决方案: 回溯(深搜dfs)
    • 深度:行数 (深搜)
    • 宽度:列数
      - 每一行选择可行列(三个方向:列,左上角,右上角),建立3个数组:
      - col[i]: 第i列是否已使用, col size: n * 1
      - 右上角方向 topright[k]: (i,j)-line with i + j = k, k = 0,1,..., 2n-2; topright size: 2n-1
      - 左上角方向 topleft[k]: (i,j)-line with i - j + n - 1 = k, k = 0, 1, ..., 2n-2, topleft size: 2n-1
    • 为方便,用grid(n * n)来记录棋盘状态。注意字符串的处理。
  • 复杂度. 时间:O(?), 空间: O(?)
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        def backtrack(i):
            if i == n:
                res.append([''.join(row) for row in grid])
                return 
            for j in range(n):
                if column[j] or top_right[i+j] or top_left[i-j+n-1]:
                    continue 
                grid[i][j] = 'Q'
                column[j] = True 
                top_right[i+j] = True 
                top_left[i-j+n-1] = True 
                backtrack(i+1) 
                top_left[i-j+n-1] = False  
                top_right[i+j] = False 
                column[j] = False  
                grid[i][j] = '.'
        res = []
        grid = [['.' for _ in range(n)] for _ in range(n)]
        column = [False for _ in range(n)]
        top_right = [False for _ in range(2*n)]
        top_left = [False for _ in range(2*n)]
        backtrack(0)
        return res 
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        def backtrack(i):
            if i == n:
                res.append([''.join(grid[i]) for i in range(n)])
                return 
            for j in range(n):
                if col[j] or diag1[i-j+n-1] or diag2[i+j]:
                    continue 
                grid[i][j] = 'Q'
                col[j] = True 
                diag1[i-j+n-1] = True 
                diag2[i+j] = True 
                backtrack(i+1)
                diag2[i+j] = False 
                diag1[i-j+n-1] = False 
                col[j] = False 
                grid[i][j] = '.'
        res = []
        col = [False for _ in range(n)]
        diag1 = [False for _ in range(2*n-1)] # i-j+n-1
        diag2 = [False for _ in range(2*n-1)] # i+j
        grid = [['.' for _ in range(n)] for _ in range(n)]
        backtrack(0)
        return res
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        def backtrack(i):
            if i == n:
                res.append([''.join(board[x]) for x in range(n)]) 
                return  
            for j in range(n):
                idx1, idx2 = i+j, j-i+(n-1)
                if col[j] or diag1[idx1] or diag2[idx2]:
                    continue  
                col[j] = True  
                diag1[idx1] = True  
                diag2[idx2] = True  
                board[i][j] = 'Q' 
                backtrack(i+1) 
                board[i][j] = '.' 
                diag2[idx2] = False  
                diag1[idx1] = False  
                col[j] = False  
            return  
        res = []
        board = [['.' for _ in range(n)] for _ in range(n)] 
        col = [False for _ in range(n)] 
        diag1 = [False for _ in range(2*n-1)] 
        diag2 = [False for _ in range(2*n-1)] 
        backtrack(0) 
        return res  

37. 解数独


  • 思路
    • example
    • 题目数据 保证 输入数独仅有一个解
    • 深搜,dfs, 回溯
    • 先确定搜索顺序,先行后列
      • 可以把board中的空白部分位置(i,j)存入empty数组(一维)进行深搜
      • 对搜索的每一个位置(i,j),模向遍历1-9,利用hash (set,三个限制:行,列,块)存储已使用数字。
        • 注意别忘了对这三个hash初始化(建立empty数组的同时更新)
  • 复杂度. 时间:O(?), 空间: O(?)
class Solution:
    def solveSudoku(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        def backtrack(index):
            if index == len(empty):
                return True 
            i, j = empty[index]
            for num in range(1, 10):
                if num in rows[i] or num in columns[j] or num in blocks[i//3][j//3]:
                    continue
                board[i][j] = str(num)
                rows[i].add(num)
                columns[j].add(num)
                blocks[i//3][j//3].add(num)
                if backtrack(index+1):
                    return True 
                blocks[i//3][j//3].remove(num)
                columns[j].remove(num)
                rows[i].remove(num)
                board[i][j] = '.'
            return False
        rows = [set() for _ in range(9)] # 行已用数字
        columns = [set() for _ in range(9)] # 列已用数字
        blocks = [[set() for _ in range(3)] for _ in range(3)] # 9 blocks
        empty = []
        for i in range(9):
            for j in range(9):
                if board[i][j] == ".":
                    empty.append((i,j))
                else: 
                    rows[i].add(int(board[i][j]))
                    columns[j].add(int(board[i][j]))
                    blocks[i//3][j//3].add(int(board[i][j]))
        backtrack(0)
        # modify in-place, no return
class Solution:
    def solveSudoku(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        def backtrack(idx):
            if idx == len(empty):
                return True 
            i, j = empty[idx]
            for num in range(1, 10):
                if num in row[i] or num in col[j] or num in box[i//3][j//3]:
                    continue 
                board[i][j] = str(num) 
                row[i].add(num)
                col[j].add(num)
                box[i//3][j//3].add(num)
                if backtrack(idx+1):
                    return True 
                box[i//3][j//3].remove(num)
                col[j].remove(num)
                row[i].remove(num)
                board[i][j] = '.'
            return False 
        row = [set() for _ in range(9)]
        col = [set() for _ in range(9)]
        box = [[set() for _ in range(3)] for _ in range(3)]
        empty = []
        for i in range(9):
            for j in range(9):
                if board[i][j] == '.':
                    empty.append((i,j))
                else:
                    row[i].add(int(board[i][j]))
                    col[j].add(int(board[i][j]))
                    box[i//3][j//3].add(int(board[i][j]))
        backtrack(0)
class Solution:
    def solveSudoku(self, board: List[List[str]]) -> None:
        def backtrack(idx):
            if idx == len(filling):
                return  True  # !!!
            i, j = filling[idx][0], filling[idx][1] 
            for num in range(1, 10):
                if str(num) in row[i] or str(num) in col[j] or str(num) in box[i//3][j//3]:
                    continue  
                row[i].add(str(num))  
                col[j].add(str(num)) 
                box[i//3][j//3].add(str(num)) 
                board[i][j] = str(num)
                if backtrack(idx+1):
                    return True  # !!!
                board[i][j] = '.'
                box[i//3][j//3].remove(str(num)) 
                col[j].remove(str(num)) 
                row[i].remove(str(num))
            return False  # !!!
        row = [set() for _ in range(9)] 
        col = [set() for _ in range(9)] 
        box = [[set() for _ in range(3)] for _ in range(3)]
        filling = list() 
        for i in range(9):
            for j in range(9):
                if board[i][j] != '.':
                    row[i].add(board[i][j]) 
                    col[j].add(board[i][j]) 
                    box[i//3][j//3].add(board[i][j])   
                else:
                    filling.append((i, j)) 
        backtrack(0) 
  • 注意下面的区别
rows = [set() for _ in range(9)]
rows2 = [set()] * 9 

你可能感兴趣的:(Day 30 回溯:332. 重新安排行程, 51. N 皇后, 37. 解数独)