Leetcode-剪枝

51. N皇后 https://leetcode-cn.com/problems/n-queens/

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

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

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

解:

dfs + 剪枝,枚举每个行。注意在做回溯之前,要把当前放置的皇后拿掉,把其造成影响的标识位都消除。

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        def could_place(row, col):
            # row这一行是没有放置过的行,要检查col这一列、(row,col)所占两条对角线有没有被放置过,如果都没有,(row,col)可以放皇后
            return not (cols[col]+hill_diagonals[row-col]+
                        dale_diagonals[row+col]) 
        
        def place_queen(row, col):
            queens.add((row, col))  # 放皇后,记录位置,标记列和两对角线
            cols[col] = 1
            hill_diagonals[row-col] = 1
            dale_diagonals[row+col] = 1
        
        def remove_queen(row, col):
            queens.remove((row, col))  # 移除皇后,清空列和两对角线的标记
            cols[col] = 0
            hill_diagonals[row-col] = 0
            dale_diagonals[row+col] = 0
        
        def add_solution():
            # 如果找到一个解,按要求记录下来
            solution = []
            for _, col in sorted(queens):
                solution.append('.'*col + 'Q' + '.'*(n-col-1))
            output.append(solution)
        
        def dfs(row):
            # 从第一行row=0开始放置皇后,放到n-1行
            for col in range(n):  # 对于确定的row,遍历所有列col
                if could_place(row, col):
                    place_queen(row, col)  # 如果(row, col)可以放皇后,就放
                    if row == n-1:  # 如果已经放了最后一个,说明找到一个解
                        add_solution()
                    else:  # 没有放到最后一个的话
                        dfs(row+1)  # 去找row行之后所有可能的放置解法
                    remove_queen(row, col)  # 不管是哪种情况都要回溯,移除当前皇后,进入(row, col+1) 的情况
            
        cols = [0] * n
        hill_diagonals = [0] * (2 * n -1)
        dale_diagonals = [0] * (2 * n -1)
        queens = set()
        output = []
        
        dfs(0)
        return output

  

52. N皇后ii https://leetcode-cn.com/problems/n-queens-ii/

给定一个整数 n,返回 n 皇后不同的解决方案的数量。

解:

跟#51基本相同,修改一下最后返回值的处理即可

class Solution:
    def totalNQueens(self, n: int) -> int:
        def could_place(row, col):
            # row这一行是没有放置过的行,要检查col这一列、(row,col)所占两条对角线有没有被放置过,如果都没有,(row,col)可以放皇后
            return not (cols[col]+hill_diagonals[row-col]+
                        dale_diagonals[row+col]) 
        
        def place_queen(row, col):
            queens.add((row, col))  # 放皇后,记录位置,标记列和两对角线
            cols[col] = 1
            hill_diagonals[row-col] = 1
            dale_diagonals[row+col] = 1
        
        def remove_queen(row, col):
            queens.remove((row, col))  # 移除皇后,清空列和两对角线的标记
            cols[col] = 0
            hill_diagonals[row-col] = 0
            dale_diagonals[row+col] = 0
        
        def dfs(row):
            nonlocal res
            for col in range(n):
                if could_place(row, col):
                    place_queen(row, col)
                    if row == n-1:
                        res += 1
                    else:
                        dfs(row+1)
                    remove_queen(row, col)  # 把刚才放置的拿掉才能回溯
                    
        cols = [0] * n
        hill_diagonals = [0] * (2 * n - 1)
        dale_diagonals = [0] * (2 * n - 1)
        queens = set()
        res = 0
        dfs(0)
        return res

  

36. 有效的数独 https://leetcode-cn.com/problems/valid-sudoku/

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

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

 说明:

一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 '.' 。
给定数独永远是 9x9 形式的。

解:

两层嵌套循环遍历即可,分别对行、列、子数独用 n=9 个哈希表(其中key为1~9)来存所有已经遇到过的值,如果某个key的value大于1的话说明数独无效。

class Solution:
    def isValidSudoku(self, board: List[List[str]]) -> bool:
        # 9行,9列,9个子数独
        rows = [{} for _ in range(9)]  # 每个哈希表存,列表下标对应行的,已填数字情况 
        cols = [{} for _ in range(9)]
        boxes = [{} for _ in range(9)]  # 子数独编号为boxes的下标,从上到下从左到右索引
        
        for i in range(9):
            for j in range(9):
                num = board[i][j]
                if num != '.':     # 如果某个位置已经填入数
                    num = int(num)
                    box_idx = (i//3)*3 + j//3   # 当前位置所处的子数独索引
                    
                    rows[i][num] = rows[i].get(num, 0) + 1  # 先保留当前的数,在哈希表中保存
                    cols[j][num] = cols[j].get(num, 0) + 1
                    boxes[box_idx][num] = boxes[box_idx].get(num, 0) + 1
                    
                    # 检查如果保留当前数的话,是否合法
                    if rows[i][num] > 1 or cols[j][num] > 1 or boxes[box_idx][num] > 1:
                        return False
        return True

37. 解数独 https://leetcode-cn.com/problems/sudoku-solver/

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

解:

dfs, 枚举每个空格。

from collections import defaultdict
class Solution:
    def solveSudoku(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        def could_place(d, row, col):
            """
            Check if one could place a number d in (row, col) cell
            """
            return not (d in rows[row] or d in columns[col] or 
                    d in boxes[box_index(row, col)])
        
        def place_number(d, row, col):
            """
            Place a number d in (row, col) cell
            """
            rows[row][d] += 1
            columns[col][d] += 1
            boxes[box_index(row, col)][d] += 1
            board[row][col] = str(d)
            
        def remove_number(d, row, col):
            """
            Remove a number which didn't lead 
            to a solution
            """
            del rows[row][d]
            del columns[col][d]
            del boxes[box_index(row, col)][d]
            board[row][col] = '.'    
            
        def place_next_numbers(row, col):
            """
            Call backtrack function in recursion
            to continue to place numbers
            till the moment we have a solution
            """
            # if we're in the last cell
            # that means we have the solution
            if col == N - 1 and row == N - 1:
                nonlocal sudoku_solved
                sudoku_solved = True
            #if not yet    
            else:
                # if we're in the end of the row
                # go to the next row
                if col == N - 1:
                    backtrack(row + 1, 0)
                # go to the next column
                else:
                    backtrack(row, col + 1)
                
                
        def backtrack(row = 0, col = 0):
            """
            Backtracking
            """
            # if the cell is empty
            if board[row][col] == '.':
                # iterate over all numbers from 1 to 9
                for d in range(1, 10):
                    if could_place(d, row, col):
                        place_number(d, row, col)
                        place_next_numbers(row, col)
                        # if sudoku is solved, there is no need to backtrack
                        # since the single unique solution is promised
                        if not sudoku_solved:
                            remove_number(d, row, col)
            else:
                place_next_numbers(row, col)
                    
        # box size
        n = 3
        # row size
        N = n * n
        # lambda function to compute box index
        box_index = lambda row, col: (row // n ) * n + col // n
        
        # init rows, columns and boxes
        rows = [defaultdict(int) for i in range(N)]
        columns = [defaultdict(int) for i in range(N)]
        boxes = [defaultdict(int) for i in range(N)]
        for i in range(N):
            for j in range(N):
                if board[i][j] != '.': 
                    d = int(board[i][j])
                    place_number(d, i, j)
        
        sudoku_solved = False
        backtrack()

  

除了普通的dfs,还可以加一些剪枝的条件来加速。先枚举可选项少的空格:预处理,先遍历一边格子,找出每个空格的可选数,并排序,dfs搜索时就从可选数最少的空格开始。

原文地址:https://www.cnblogs.com/chaojunwang-ml/p/11363167.html