对角线性质分析
在二维数组中:
主对角线(黑色)满足条件:y - x = n,其中 n 是一个固定值,对于同一条主对角线,n 值相同,不同的主对角线 n 值不同。
副对角线(红色)满足条件:y + x = n,其中 n 也是一个固定值,对于同一条副对角线,n 值相同,不同的副对角线 n 值不同。
利用这个特性,可以高效处理某些涉及二维数组对角线的算法问题,而不需要暴力枚举每个元素。
样例分析
力扣-N皇后
对于N皇后问题,我们就可以通过对角线问题巧妙解决。
N 皇后
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位
解法
首先n×n的棋盘,需要摆放n个皇后,因为皇后可以左右,所以一定是每行一个,可以上下攻击,肯定也是每列一个。因此这2个情况很容易分析,我们只需要每行进行遍历放一个皇后,即可解决左右攻击问题,用个数组来记录哪列放了皇后,即可解决上下攻击问题。关键问题就是斜线,如果暴力解决,花费的时间成本就比较高,我们通过二维数组的横纵坐标的关系,进行解决。
总结一下流程
解法思路
行列攻击限制:
每行只能放置一个皇后,因此可以逐行遍历,每次在当前行放置一个皇后。
用一个数组 col 记录哪些列已经放置过皇后,避免重复。
主副对角线攻击限制:
主对角线:满足 y - x = n,需要额外对负值偏移进行调整,映射到非负下标。
副对角线:满足 y + x = n,直接映射即可。
使用两个数组 dig1 和 dig2 记录主、副对角线是否被占用。
递归搜索:
从第 0 行开始,逐行尝试每个列的位置,验证是否满足列、主对角线、副对角线的限制。
如果满足,将当前皇后放置,递归进入下一行。
如果某次尝试失败(所有列均不满足),回溯上一行进行其他尝试。
参考代码
class Solution {
public:
vector<bool> col,dig1,dig2;//记录列,主对角线,副对角线。
vector<vector<string>> ans;//记录结果
vector<string> path;//记录棋盘
int _n = 0;
vector<vector<string>> solveNQueens(int n) {
_n = n;
path.resize(n,string(n,'.'));
col.resize(n,false);
dig1.resize(n*2,false);//这里就是需要稍微调整的地方,由于数组下标没有负数,所以我们对y-x=b都加上一个n,这样就保证必然是大于0,能够进行映射。
dig2.resize(n*2,false);//因为x+y可能是n-1+n-1,所以我们开的是2倍n
dfs(0);
return ans;
}
void dfs(int row)//这个参数表示我们现在处理的是多少行。
{
if(row == _n)//这里表示进行到了最后,相当于放成功了
{
ans.push_back(path);
return ;
}
for(int i=0;i<_n;i++)//这里就是找可以放皇后的列
{
if(!col[i]&&!dig1[row-i+_n]&&!dig2[row+i]) //行可以放,主对角线可以放(意思就是上几行放的皇后的主对角线没有经过这里),副对角线可以放。
{
path[row][i] = 'Q';//这里尝试放皇后
col[i] = dig1[row-i+_n]=dig2[row+i] = true;
dfs(row+1);//进行下一行的尝试
path[row][i] = '.';
col[i] = dig1[row-i+_n]=dig2[row+i] = false;
}
}
}
};
总结
对角线解题的优势
1、减少搜索空间
通过主、副对角线的特性,可以快速判断某位置是否被斜线占用,无需逐一遍历棋盘,极大提高了算法效率。
2、简单的映射关系
使用简单的数学公式(y - x 和 y + x)将二维棋盘的斜线问题转化为一维数组的访问问题,代码实现直观简洁。