1 题目
题目:N皇后(N-Queens)
描述:N皇后问题是将n个皇后放置在n*n的棋盘上,皇后彼此之间不能相互攻击(任意两个皇后不能位于同一行,同一列,同一斜线)。给定一个整数n,返回所有不同的N皇后问题的解决方案。每个解决方案包含一个明确的N皇后放置布局,其中“Q”和“.”分别表示一个女王和一个空位置。
lintcode题号——33,难度——medium
样例1:
输入:n = 1
输出:[["Q"]]
解释:只有一种方案。
样例2:
输入:n = 4
输出:
[
// Solution 1
[".Q..",
"...Q",
"Q...",
"..Q."
],
// Solution 2
["..Q.",
"Q...",
"...Q",
".Q.."
]
]
解释:有两种解决方案。
2 解决方案
2.1 思路
该题的难点是将现实问题转变成计算机问题,可以将棋盘上的皇后排列转换成一串数字,首先棋盘上的位置可以解释成行和列的二元组(row,col)
,一个n*n
的棋盘上的皇后排列可以解释成n个(row,col)
二元组,又因为row
这个维度是从0开始不断+1来递增的,如此地有规律,于是考虑使用一个容器来装col
,而容器的下标序号即可代表row
这个维度,这样仅仅使用一串数字就能完整表示棋盘上所有皇后当前的排列。
将棋盘表示成数组之后,问题即转变成对数组中的元素进行全排列,并判断排列是否符合条件的问题了,可以考虑使用深度优先搜索(Depth First Search)解决该问题。
很多现实问题的解决难点都是在于如何将现实问题转换为机器理解的表达方式。
2.2 图解
将现实中的棋盘转换成一元数组表示:(下标从0开始)
2.3 时间复杂度
深度优先搜索的时间复杂度是逻辑图上的节点数(即所有元素的组合数,n个元素,每个元素都有取或不取两种可能,所以是2的n次方)与处理每个节点的耗时(for循环n次)的乘积,该题的算法的时间复杂度为O(2^n * n)。
深度优先搜索的时间复杂度计算没有通用的方式,只能根据具体题目计算,可以理解成看作答案个数与构造每个答案花费的时间的乘积。
2.4 空间复杂度
使用了二维的vector数据结构保存节点,算法的空间复杂度为O(n^2)。
3 源码
细节:
- 将N皇后问题看成一串连续数字序列的全排列,比如4皇后看成0123的全排列,求解为1302,2031。
- 求解方式为穷举全排列,并在过程中判断是否能够互相攻击来进行过滤。
- dfs过程中需要进行
同一分支去重
,沿分支向下传递用于去重的set,set需要被回溯。
C++版本:
/**
* @param n: The number of queens
* @return: All distinct solutions
* we will sort your return value in output
*/
vector<vector<string>> solveNQueens(int n) {
// write your code here
vector<vector<string>> final_results;
if (n == 0)
{
return final_results;
}
vector<vector<int>> results;
vector<int> nums;
for (int i = 0; i < n; i++)
{
nums.push_back(i);
}
vector<int> curPath;
set<int> duplicate; // 同一分支去重,需要递归传入+回溯
dfs(nums, curPath, duplicate, results);
// 数字结果转换成字符串显示
final_results = convert(results);
return final_results;
}
void dfs(vector<int> & nums, vector<int> & curPath, set<int> & duplicate, vector<vector<int>> & results)
{
if (curPath.size() == nums.size())
{
results.push_back(curPath);
return;
}
for (int i = 0; i < nums.size(); i++)
{
if (duplicate.find(nums.at(i)) != duplicate.end())
{
continue;
}
// 判断是否会互相攻击
if (isAttack(curPath, nums.at(i)) == true)
{
continue;
}
duplicate.insert(nums.at(i));
curPath.push_back(nums.at(i));
dfs(nums, curPath, duplicate, results);
curPath.pop_back();
duplicate.erase(nums.at(i));
}
}
bool isAttack(vector<int> path, int pos)
{
int curRow = path.size();
int curCol = pos;
for (int i = 0; i < path.size(); i++)
{
if (i + path.at(i) == curRow + curCol // 副对角线冲突
|| i - path.at(i) == curRow - curCol) // 主对角线冲突
{
return true;
}
}
return false;
}
vector<vector<string>> convert(vector<vector<int>> results)
{
vector<vector<string>> final_results;
if (results.empty())
{
return final_results;
}
int size = results.front().size();
for (auto rowEle : results)
{
vector<string> tempV;
for (auto ele : rowEle)
{
string tmpStr;
for(int i = 0; i < size; i++)
{
if (i != ele)
{
tmpStr += ".";
}
else
{
tmpStr += "Q";
}
}
tempV.push_back(tmpStr);
}
final_results.push_back(tempV);
}
return final_results;
}