美文网首页
hard回溯-N皇后+数独 2020-09-16(未允禁转)

hard回溯-N皇后+数独 2020-09-16(未允禁转)

作者: 9_SooHyun | 来源:发表于2020-09-16 23:28 被阅读0次

    所谓leetcode hard级别的回溯问题

    回溯问题其实就是一个模板题,都是通过【带状态恢复的dfs】实现
    我们知道回溯的模板如下

    def backTrack(visited_path, choice_list, global_result, target):
        # visited_path表示当前的问题阶段,choice_list表示当前问题面临的可选项,target出现表示问题无需继续向下搜索,这时result结算可行解,
        # 递归出口
        if visited_path meets target:
            update global_result
            return
    
        # 尝试每一个【当前可行】的选择
        for c in choice_list:
            # 2.更新问题状态: 必须包含3项,visited_path+choice_list+target
            visited_path.add(c)
            new_choice_list = choice_list.remove(c)
            update target
            # 3.递归
            backTrack(visited_path, new_choice_list, global_result, target)
            # 4.状态复原
            visited_path.remove(c)
            reset target
    

    hard题难在什么地方呢?要说难,它就难在【当前可行】的选择需要满足更为复杂的限制条件,也仅此而已了
    只要我们翻译好限制条件,将限制条件用code正确表示出来,其实就还是板子题

    例1 leetcode51 N皇后

    给定一个整数 n,返回所有不同的 n 皇后问题的解决方案,要求皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上
    每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

    示例:
    输入:4
    输出:[
    [".Q..", // 解法 1
    "...Q",
    "Q...",
    "..Q."],
    ["..Q.", // 解法 2
    "Q...",
    "...Q",
    ".Q.."]
    ]
    解释: 4 皇后问题存在两个不同的解法。

    思路:
    假定我们从上到下逐行放置一个皇后,现在我们在第i行要放置一个皇后,那么当前可行的选择有哪些呢?第i行的哪几个位置能放呢?解决了这个问题就好。我们知道,前i-1行已经放置的皇后所在的行、列、斜线都不能放,认为是前皇后势力覆盖区域
    那么,我们维护一个dict来记录整个棋盘的每个方格的势力值,初始化为0。

    • 我们只能在势力值为0的方格放置新的皇后
    • 每放置一个皇后,则她所在的行、列、斜线的各个方格的势力值+1
    • 每次回溯撤回一个皇后,则她所在的行、列、斜线的各个方格的势力值-1

    这样,我们把【已放置皇后所在的行、列、斜线都不能放新皇后】这个限制条件很好地翻译成了代码形式来确定当前可行的选择,得解

    class Solution:
        def solveNQueens(self, n: int) -> List[List[str]]:
            # 设置好规则,【dfs】暴力尝试所有解。如果能够dfs到第n层的,说明整个路径上的皇后位置合法,纳入结果集。
            # 用queen_pos list记录可行的皇后位置;用字典invalid_pos记录当前不可放置的位置,0可放,非0不可放
            queen_pos = list()
            invalid_pos = dict()
            for i in range(n):
                for j in range(n):
                    invalid_pos[(i,j)] = 0
            final_res = []
    
            def helper(queen_pos, invalid_pos, row_index, final_res):
                # 已经到整个棋盘的n行,说明0-n-1行都放好了,结算并返回
                if row_index == n:
                    temp = []
                    for p in queen_pos:
                        s = '.'*p[1] + 'Q' + '.'*(n-1-p[1])
                        temp.append(s)
                    final_res.append(temp)
                    return
    
                for i in range(n):
                    pos = (row_index, i)
                    if invalid_pos[pos] == 0:
                        # 尝试放一个皇后
                        queen_pos.append(pos)
                        ## 更新不可放的位置 ##
                        additional_invalid_pos = []
                        for j in range(n):
                            # 同一行不可再放
                            additional_invalid_pos.append((row_index, j))
                        for j in range(n):
                            # 同一列不可再放
                            additional_invalid_pos.append((j, i))
                        s = row_index + i
                        delta = i - row_index
                        for j in range(n):
                            # 双斜线不可再放
                            if n > s-j >= 0 : additional_invalid_pos.append((j, s-j)) 
                            if 0 <= j + delta < n: additional_invalid_pos.append((j, j+delta)) 
                        for ele in additional_invalid_pos:
                            invalid_pos[ele] += 1
                        ## 更新不可放的位置,完毕 ##
                        # dfs
                        helper(queen_pos, invalid_pos, row_index+1, final_res)
                        # 状态复原
                        queen_pos.pop()
                        for ele in additional_invalid_pos:
                            invalid_pos[ele] -= 1
            
            helper(queen_pos, invalid_pos, 0, final_res)
            return final_res
    

    例2 leetcode 37. 解数独

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

    一个数独的解法需遵循如下规则:
    数字 1-9 在每一行只能出现一次。
    数字 1-9 在每一列只能出现一次。
    数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
    空白格用 '.' 表示。
    假设给定的数独只有唯一解,且数独棋盘为9 x 9规模

    思路:
    好了,又是翻译复杂限制条件来确定当前可行选择的回溯问题
    维护3个set row_d, col_d, block_d,对应3个限制条件,记录每行、每列、每块当前含有的数字

    • 从上到下从左到右先遍历一次数独棋盘,初始化3个set
    • 从上到下从左到右再遍历一次数独棋盘,
      • 如果遇到数字,到下一格去
      • 如果遇到空格,从1到9中选出不在3个set中的数字尝试放置,期间更新set和恢复set即可
    class Solution:
        def solveSudoku(self, board: List[List[str]]) -> None:
            """
            Do not return anything, modify board in-place instead.
            """
    
            # 维护3个set,记录每行、每列、每块当前含有的数字
            row_d, col_d, block_d = dict(), dict(), dict()
            for i in range(9):
                row_d[i] = set()
                col_d[i] = set()
                block_d[i] = set()
            # 初始化这3个set
            for i in range(9):
                for j in range(9):
                    row_d[i].add(board[i][j])
                    col_d[j].add(board[i][j])
                    block_d[(i//3)*3+j//3].add(board[i][j])
    
            def backTracking(board, i, j, row_d, col_d, block_d) -> bool:
                # 递归出口,填到棋盘外的第九行,,,
                if i == 9:
                    return True
    
                # 碰到数字格
                if board[i][j] != '.':
                    if j == 8:
                        res = backTracking(board, i+1, 0, row_d, col_d, block_d)
                        if res:
                            return res
                    else:
                        res = backTracking(board, i, j+1, row_d, col_d, block_d)
                        if res:
                            return res
                # 碰到空格
                else:
                    for ii in range(1, 10):
                        ele = str(ii)
                        if ele not in row_d[i] and ele not in col_d[j] and ele not in block_d[(i//3)*3+j//3]:
                            # 修改状态
                            board[i][j] = ele
                            row_d[i].add(board[i][j])
                            col_d[j].add(board[i][j])
                            block_d[(i//3)*3+j//3].add(board[i][j])
                            # dfs
                            if j == 8:
                                res = backTracking(board, i+1, 0, row_d, col_d, block_d)
                                if res:
                                    return res
                            else:
                                res = backTracking(board, i, j+1, row_d, col_d, block_d)
                                if res:
                                    return res
                            # 恢复状态
                            row_d[i].remove(board[i][j])
                            col_d[j].remove(board[i][j])
                            block_d[(i//3)*3+j//3].remove(board[i][j])
                            board[i][j] = '.'
                    return False
            
            backTracking(board, 0, 0, row_d, col_d, block_d)
    
    
    

    相关文章

      网友评论

          本文标题:hard回溯-N皇后+数独 2020-09-16(未允禁转)

          本文链接:https://www.haomeiwen.com/subject/mprkyktx.html