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 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容