Leetcode 51:N皇后(最詳細的解法!!!)
n 皇后問題研究的是如何將 n 個皇后放置在 n×n 的棋盤上,並且使皇后彼此之間不能相互攻擊。
上圖為 8 皇后問題的一種解法。
給定一個整數 n,返回所有不同的 n 皇后問題的解決方案。
每一種解法包含一個明確的 n 皇后問題的棋子放置方案,該方案中 'Q'
和 '.'
分別代表了皇后和空位。
示例:
輸入: 4
輸出: [
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
解釋: 4 皇后問題存在兩個不同的解法。
解題思路
我們首先想到的解法是暴力解法,通過回溯法去解決這個問題,我們先看這樣一個簡單的例子。對於一個4x4的棋盤。如果我們的第一步放在
1 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
接著我們要往第二行上放,這就不能放在這兩個位置。
1 0 0 0
&
0 1 0 0
這實際上是一種剪枝行為。最後,我們將第二個皇后放在
1 0 0 0
0 0 1 0
0 0 0 0
0 0 0 0
接著,我們考慮第三個皇后的位置,我們發現此時,第三個皇后沒有位置可以放了。所以我們將第二個皇后,移動到下一個可行位置
1 0 0 0
0 0 0 1
0 0 0 0
0 0 0 0
現在我們放入第三個皇后
1 0 0 0
0 0 0 1
0 1 0 0
0 0 0 0
接著我們放入第四個皇后,我們發現第四個皇后沒有位置可以放了,而第三個和第二個皇后也沒有可行位置了,我們就調整第一個皇后的位置,最後我們可以得到這樣兩個可行解。
0 1 0 0 0 0 1 0
0 0 0 1 1 0 0 0
1 0 0 0 0 0 0 1
0 0 1 0 0 1 0 0
我們現在就要思考怎麼通過程式設計去解決放的位置是否合法
這個問題。也就是我們要解決,橫向豎向的座標表示,以及兩個斜線方向的座標表示。橫向豎向很簡單
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
我們通過簡單的座標表示就可以確定兩個點是不是在同一個橫向和豎向上。那麼斜線方向呢?我們將x+y
0 1 2 3 1 2 3 4 2 3 4 5 3 4 5 6
相信你也看出規律來了y=x
這個斜線方向,可以通過x+y
表示。我們再將x-y
0 -1 -2 -3
1 0 -1 -2
2 1 0 -1
3 2 1 0
為了避免出現負數,所以我們將這個矩陣加上一個常數n-1
,就變成了
3 2 1 0
4 3 2 1
5 4 3 2
6 5 4 3
我們可以將y=-x
上的斜線,通過x-y+n-1
表示出來。這樣我們就解決了最核心的問題,依照前面的解題思想,我們很容易寫出這樣的程式碼
class Solution:
def solveNQueens(self, n):
"""
:type n: int
:rtype: List[List[str]]
"""
result, R, col, dia1, dia2 = list(), list(), list(), list(), list()
col = [0 for i in range(n)]
dia1 = [0 for i in range(2*n - 1)]
dia2 = [0 for i in range(2*n - 1)]
def generateBoard(m, row):
board = [str() for i in range(n)]
for i in range(m):
board[i] = row[i]*'.' + 'Q' + '.'*(m - row[i] - 1)
return board
def putQueen(m, index, row):
if index == m:
result.append(generateBoard(m, row))
return
for i in range(m):
if not (col[i] or dia1[index + i] or dia2[index - i + m - 1]):
row.append(i)
col[i], dia1[index + i], dia2[index - i + m - 1] = 1, 1, 1
putQueen(m, index + 1, row)
col[i], dia1[index + i], dia2[index - i + m - 1] = 0, 0, 0
row.pop()
putQueen(n, 0, R)
return result
我們能不能通過迭代去解決這個問題呢?也是可以的。我們不想在迭代中,再通過記錄不同的變數的形式去判斷我們放入的皇后是否滿足條件,有沒有什麼更好的策略呢?其實我們觀察皇后擺放的位置就可以發現這樣的規律
- 考慮第
n
行皇后的擺放位置,對於之前的所有行i
要滿足:row[i] != row[n] and abs(row[i] - row[n] )!=abs(i - n)
class Solution:
def solveNQueens(self, n):
"""
:type n: int
:rtype: List[List[str]]
"""
result = list()
row = [0 for i in range(n)]
def generateBoard(m, row):
board = [str() for i in range(n)]
for i in range(m):
board[i] = row[i]*'.' + 'Q' + '.'*(m - row[i] - 1)
return board
def isVaild(k, row):
for i in range(k):
if row[i] == row[k] or abs(row[i] - row[k]) == abs(i - k):
return 0
return 1
k = 0
while k >= 0:
while row[k] < n and not isVaild(k, row):
row[k] += 1
if row[k] < n:
if k == n - 1:
result.append(generateBoard(n, row))
row[k] += 1
else:
k += 1
else:
row[k] = 0
k -= 1
row[k] += 1
return result
實際上這個問題和之前的Leetcode 46:全排列(最詳細的解法!!!) 很類似。這個問題有一個非常簡潔的寫法。你觀察這個問題的解,你會發現這些解實際上是有規律的,有什麼規律?所有解都是range(n)
全排列的子集。也就是我們可以通過遍歷這個全排列集合去尋找到它們。那麼什麼樣的子集才滿足解的條件呢?只要滿足這兩個條件
- 解
k
中的第i
個元素減去i
組成的集合大小為n
- 解
k
中的第i
個元素加上i
組成的集合大小為n
注意,我這裡指的解是row
,對於4x4問題,也就是[1,3,0,2]
和[2,0,3,1]
這兩個解。
import itertools
class Solution:
def solveNQueens(self, n):
"""
:type n: int
:rtype: List[List[str]]
"""
result = list()
for permute in itertools.permutations(range(n)):
if len(set(i + v for i, v in enumerate(permute))) == n and \
len(set(i - v for i, v in enumerate(permute))) == n:
result.append(['.'*v + 'Q' + '.'*(n - v - 1) for v in permute])
return result
但是實際上上面的解法將所有結果窮舉了出來,顯然速度很慢。
如有問題,希望大家指出!!!