2020年4月1日 星期三

Leetcode題解 Python: N-Queens II

求出在 N * N的棋盤放上 N 個彼此無法互吃的解法數量。

最直觀的就是暴力解法,若 N = 4,就會有 16 格,於是第一步的可能下法就會有16種。

取出前面的結果,找出第二步下法的地方,得到第二步符合規則的所有下法,依序往下傳。

傳到第 N 步下完後,剩下多少種不相同的可行下法就是解答。(這樣會找到重複解。

這種暴力解法多計算了不需要的「流程」,每一步先後順序都沒有差。

換一種想法,有沒有更快速的下法?

每排上面一定有一個皇后,於是可以逐排安排皇后。

在逐排之下,要是該欄可以放皇后,就往下排從頭搜尋,到底則次數+1,不行則取走上排的皇后,退回上排再往下一欄搜尋。


回去偷看一下範例,才想到這樣的解法。(誰叫我當初選擇跳過不看呢,呵呵。
class Solution:
    def totalNQueens(self, n: int) -> int:
        def is_not_under_attack(Queens, y, x):
            for queen in Queens:
                qy, qx = queen
                if qy == y: return False
                if qx == x: return False
                if abs(qx-x) == abs(qy-y): return False
            return True

        Queens = [] 
        row, col = 0, 0 
        count = 0
        while col <= n:
            #print(row,col)
            if col == n:
                # remove queen
                if Queens:
                    row, col = Queens.pop()
                    col = col+1             
                else: break                    
            elif row == n: 
                count += 1
                row, col = Queens.pop()
                col = col+1      
            elif is_not_under_attack(Queens, row, col):
                # place queen
                Queens.append((row,col))
                row, col = row + 1, 0              
            else:
                col += 1

        return count
當走到最後一排,順利放完就會觸發超出排數,這代表順利找到一個解法,成功將 N 個皇后放在棋盤上。取走上一個的皇后,然後繼續搜尋。

當走到最後一欄,超出欄數,就代表該排無法放置皇后,取走上一個皇后。如果在第 0 排,沒有皇后可取,就跳出迴圈,回傳計數。

有了這種思路,解答速度就會有所保證,接下來就是優化,讓速度更快速。
class Solution:
    def totalNQueens(self, n: int) -> int:
        result = []
        queens, xy_sum, xy_dif = {}, {}, {}
        def searchNext(row):
            if row == n:
                result.append("")
            else:
                for col in range(n):     
                    xyS = row + col
                    xyD = row - col
                    if not col in queens and not xyS in xy_sum and not xyD in xy_dif:
                        queens[col] = xy_sum[xyS] = xy_dif[xyD] = None
                        searchNext(row+1)
                        del queens[col], xy_sum[xyS], xy_dif[xyD]                                               
        searchNext(0)     
        return len(result)
用了一個串列 result 接收結果,要解法可以添加 queens 當下所有鍵,只要解法數目最後用 len() 就能得到所有解的數目,要添加甚麼到result就隨便喜好。

這不得不說是參考前段班大神的解法,用xy相加與相減就能快速找到是否在斜線上,由於是一排排的下,所以也不會有同排(同y)的狀況要排除。

考慮到「查值是否存在」的速度差異,使用字典比起串列還快不少,所以都換成字典,畢竟只會使用 in 去查鍵是否存在,該鍵的值是甚麼就不重要。

在串列使用 append() 添加比 + 還快,用 + 會回傳一個新串列,如果沒有需要新生成或是有別的辦法替代,先用 append() 再傳會比較快。

這優化解法一開始就會直接到深處開始,不像前面的解法是一排排往下找,相快速度跟代碼長度就會快很多跟少很多。找完就刪掉最後一項(最後下的Queen)然後往右下一欄,走完所有欄,就會回上層繼續。