LeetCode 51.N皇后:
简单解释一下:给你一个 N×N 的棋盘,让你放置 N 个皇后,使得它们不能互相攻击。
PS:皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位。
Kotlin解法如下,先上代码:
class Solution {
/**
* 无重叠子问题,只能用回溯法,暴力穷举
* 指数级时间复杂度
*/
private var result: MutableList<MutableList<String>> = mutableListOf()
fun solveNQueens(n: Int): MutableList<MutableList<String>> {
var board: MutableList<MutableList<String>> = MutableList(n) { MutableList(n) { "." } }
backtrack(board, 0)
return result;
}
/**
* 回溯函数
*/
private fun backtrack(board: MutableList<MutableList<String>>, row: Int) {
//满足条件,每一行都放置了皇后、记录结果
if (row == board.size) {
result.add(helper(board))
return
}
//在当前行的每一列都可以放置皇后
for (column: Int in 0 until board.size) {
//排除可以相互攻击的格子
if (!isValid(board, row, column)) {
continue
}
//做选择
board[row][column] = "Q"
//进入下一行放皇后
backtrack(board, row + 1)
//撤销选择
board[row][column] = "."
}
}
/**
* 判断位置(row , column)是否可以放置皇后
*/
private fun isValid(board: MutableList<MutableList<String>>, row: Int, column: Int): Boolean {
//检查左上方
var i = row - 1
var j = column - 1
while (i >= 0 && j >= 0) {
if (board[i][j] == "Q") {
return false
}
i--
j--
}
//检查正上方
i = row - 1
j = column
while (i >= 0) {
if (board[i][j] == "Q") {
return false
}
i--
}
//检查右上方
i = row - 1
j = column + 1
while (i >= 0 && j < board.size) {
if (board[i][j] == "Q") {
return false
}
i--
j++
}
return true
}
/**
* 数据结构转换函数,
* 将MutableList<MutableList<String>>)转换为 MutableList<String>
*/
private inline fun helper(board: MutableList<MutableList<String>>): MutableList<String> {
var tempList: MutableList<String> = mutableListOf()
var strB = StringBuilder()
for (i: Int in 0 until board.size) {
for (j: Int in 0 until board[i].size) {
strB.append(board[i][j])
}
tempList.add(strB.toString())
strB.clear()
}
return tempList
}
以n=2,即2*2棋盘为例。
决策树的每一层表示棋盘上的每一行;每个节点可以做出的选择是,在该行的任意一列放置一个皇后。
理解算法可能产生的疑问:
问题1:按照 N 皇后问题的描述,为什么不检查左下角,右下角和下方的格子,只检查了左上角,右上角和上方的格子?
因为皇后是一行一行从上往下放的,所以左下方,右下方和正下方不用检查(还没放皇后);因为一行只会放一个皇后,所以每行不用检查。也就是最后只用检查上面,左上,右上三个方向。
回溯算法就是个多叉树的遍历问题,N皇后问题,没有重叠子问题,只能用回溯算法穷举。
动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质,可以用 dp table或者备忘录优化,将递归树大幅剪枝,这就变成了动态规划。
N皇后问题的时间复杂度非常高、指数级,最坏时间复杂度是 O(N^(N+1))。
问题2:时间复杂度太高怎么办?
找所有解法复杂度太高,比如解数独的算法,只要找到一种解法就可以。
/**
* 回溯函数
*/
private fun backtrack(board: MutableList<MutableList<String>>, row: Int): Boolean {
//满足条件,每一行都放置了皇后、记录结果
if (row == board.size) {
result.add(helper(board))
//只要找到一个答案,就返回
return true
}
//在当前行的每一列都可以放置皇后
for (column: Int in 0 until board.size) {
//排除可以相互攻击的格子
if (!isValid(board, row, column)) {
continue
}
//做选择
board[row][column] = "Q"
//进入下一行放皇后
backtrack(board, row + 1)
//撤销选择
board[row][column] = "."
}
return false
}
只要找到一个答案,for 循环的后续递归穷举都会被阻断。