题目: leetcode 51

隐式图、状态/关系、DFS和回溯图的DFS搜索过程形成搜索树,七月君的视频和pdf中有对图的DFS/BFS详细过程的描述。 注意这个说明是针对图(邻接表的实现)的结构进行的; DFS/BFS的时间复杂度均为o(nm ); (n节点数、m边缘数); 但是,现实问题中的图的节点和边并不是从一开始就明确给出的,有必要将现实问题转换为“隐式图”。

隐式图需要根据问题划分“状态”和“关系”。 状态是指从初期到目标,问题经验的中间过程; 状态一般是隐式图的节点的关系是指在状态之间进行一步操作并转移到下一个状态,一般是隐式图的边缘。 以n皇后问题为例,状态可以是指棋盘上有一个子排列的状态,另一个子排列,使棋盘从状态1变为状态2; 因此,实际上,n皇后问题是遍历隐式图,以找到满足约束条件(皇后不能相互攻击-同一列、同一行或对角线)的节点)的集合。 然后,在出现了符合条件的状态节点的情况下(n个皇后排列且相互不攻击),记录该节点。

因此,不像n皇后问题那样,图的节点集合从一开始就很容易得到,也不知道沿着哪个边集合的路径扫描才能得到预期的结果,不需要像给定顶点集合和边集合的图的例子那样实例化邻接表和邻接矩阵; 7月给了你DFS的码帧:

voidDFS(intI,int v[] () if (visited ) I ) )返回; 可视[ I ]=true; 因为for(j是相邻边的vertex )//n皇后问题没有现成结构的相邻表结构DFS(j,v ) },所以需要回溯法(搜索和回溯法)的框架。 从某个状态(节点)开始,执行某个动作(将棋子放在某个坐标上),以DFS方式搜索下一个状态,进入某个步骤,返回不符合限制条件或不最佳解的前一步骤的部分解结构的数据结构和副作用

在n皇后问题中,通过放置选择-棋子发生状态转移,状态参数为row、col,大致的回溯帧为:

语音后台(introw ) for ) col0-n ) if ) notunderattack (row,col ) ) place queen (row,col ); //找到解(状态节点集合符合约束) if ) row1==n ) addSolution ); 电子后台(row 1; //追溯,取消当前一步row,col相关数据和不良反应; 移除队列(row,col ),尝试下一个呼叫; )回溯法框架和dfs框架的执行过程相似,其递归过程均实质上构成多叉树,可以利用备忘录缓存和剪枝消除不必要的搜索路径; 回溯法需要明确的回溯动作,DFS的抽象意义与回溯无关。

解决问题时,我们应该考虑的是状态的参数是什么? e.g .子集问题,状态参数只有数组索引,n皇后问题的状态参数为row、col; 状态参数还与“选择”行为相关。 在开始选择之前/之后,下一个可用状态集合通常是状态参数。

//N皇后问题算法//–row0到backtrack(0); 如果满足- loop col 0 – n约束notunderattack(row,col ),则放置皇后; *如果找到一个解(放置皇后之后,row 1==n,深度递归至此,row表示一个col可以放置n个皇后),如果没有输出*,则返回下一个row、backtrack(row1) *追溯当前row、col对应的数据结构(去除row、col女王,去除其他数据结构),尝试下一个col; 时间复杂度DFS的时间为o(nm ),n为隐式图的节点数,m为隐式图的边数; n皇后问题的状态节点有多少个? N*N的棋盘,n个棋子,n行n列,每行有n种摆法,n行,状态(vertex节点)个数为n ) n; n=N^N; 所以,听说n皇后问题的时间复杂度非常大,o(n )目前能计算出的最大n是13;

约束notUnderAttack约束的设计和操作确实是个难点,如果不是看到leetcode问题,我很难设计

int queue[n]; 标记放置队列的位置e.g. queue[row]=col; 队列[0]=2; 0,2 int rows [ n ]; //标记现在放在哪个列; e.g. rows[2]=1; 请勿放入2nd col; hills[n-1]; //标记’/’主对角线main_axis,共n – 1个位置; //主对角线row col=常数,e.g.0=0,10=01=1,dales[n-1]; //标记’\’次对角线,row – col=常数,e.g。

0 – 3 = -3, 0 – 2 = -2, 1 – 3 = -2;

调用placeQueen(row, col), 更新相应以上结构;

注意,

原题解的hills[4 * n]的范围过大了,无需给出4 * n – 1的范围;另外, removeQueen, placeQueen中的hill数组中的索引是 row – col + n – 1, 这样被映射到 0 – n-1; 不正确的话leetcode会有sanitizer测出访问的地址越界报出AddressSanitizer: heap-buffer-overflow错误; 代码 递归实现 #include <iostream>#include <vector> using namespace std;class Solution {public: vector<vector<string>> solveNQueens(int n) { this->n = n; rows = new int[n]; queen = new int[n]; hills = new int[2 * n – 1]; // e.g. n = 4, “/” hills有7个位置; dales = new int[2 * n – 1]; for (int i = 0; i < n; ++i) { rows[i] = 0; queen[i] = 0; } for (int j = 0; j < 2 * n – 1; ++j) { hills[j] = dales[j] = 0; } backtrack(0); return output; } void backtrack(int row) { for (int col = 0; col < n; col++) { if (notUnderAttack(row, col)) { placeQueue(row, col); if (row + 1 == n) { addSolution(); } else backtrack(row + 1); removeQueue(row, col); } } } void placeQueue(int row, int col) { queen[row] = col; rows[col] = 1; hills[row – col + n – 1] = 1; dales[row + col] = 1; } void removeQueue(int row, int col) { queen[row] = 0; rows[col] = 0; hills[row – col + n – 1] = 0; dales[row + col] = 0; } bool notUnderAttack(int row, int col) { int result = rows[col] + hills[row – col + n – 1] + dales[row + col]; return result == 0 ? true : false; } void addSolution() { vector<string> row; for (int i = 0; i < n; ++i) { string s = “”; for (int j = 0; j < n; ++j) { if (queen[i] == j) { s += “Q”; } else s += “.”; } row.push_back(s); } output.push_back(row); }private: int* rows; int* queen; int* hills; int* dales; int n; vector<vector<string>> output;}; 非递归实现 栈的结构struct Position,需要有3个字段, row, col, need_remove_queen;类Solution中辅助的数据结构需要增加一个cols数组; 即rows[row] = 1标记本行被占用, cols[col] = 1标记本列被占用;增加重载placeQueen()函数

下面只贴出最重要的与递归实现不同的代码部分

struct Position { int row; int col; bool need_remove_queen; Position() { Position(-1, -1); } Position(int r, int c, bool b_remove = false): row(r), col(c), need_remove_queen(b_remove) {}};void Solution::placeQueen(struct Position& x, int row, int col) { x.need_remove_queen = true; placeQueen(row, col); }void Solution::robot(int n) { vector<Position> stack; stack.push_back({-1, -1}); stack.push_back({0, 0}); while (stack.size()) { Position x; while ((x = stack.back(), x.col < n && x.col >= 0)) { if (notUnderAttack(x.row, x.col)) { Position &z = stack.back(); placeQueen(z, z.row, z.col); if (x.row + 1 == n) { addSolution(); } else { stack.push_back({ ++x.row, 0 }); continue; } } else { stack.pop_back(); if (x.need_remove_queen) removeQueen(x.row, x.col); stack.push_back({x.row, ++x.col}); } } stack.pop_back(); if (stack.size()) { auto y = stack.back(); stack.pop_back(); if (y.need_remove_queen) removeQueen(y.row, y.col); if (y.col < n – 1 && y.col >= 0) { stack.push_back({y.row, ++y.col}); } } }}vector<vector<string>> Solution::solveNQueens(int n) { this->n = n; rows = new int[n]; cols = new int[n]; queen = new int[n]; hills = new int[2 * n – 1]; // e.g. n = 4, “/” hillsÓÐ7¸öλÖÃ; dales = new int[2 * n – 1]; for (int i = 0; i < n; ++i) { rows[i] = cols[i] = queen[i] = 0; } for (int j = 0; j < 2 * n – 1; ++j) { hills[j] = dales[j] = 0; } // backtrack(0); robot(n); return output; }

递归版本和非递归版本的代码都在leetcode通过

参考 lleetcode官方题解快三大小单双位技巧准确率99(s); } output.push_back(row); }private: int* rows; int* queen; int* hills; int* dales; int n; vector<vector<string>> output;}; 非递归实现 栈的结构struct Position,需要有3个字段, row, col, need_remove_queen;类Solution中辅助的数据结构需要增加一个cols数组; 即rows[row] = 1标记本行被占用, cols[col] = 1标记本列被占用;增加重载placeQueen()函数

下面只贴出最重要的与递归实现不同的代码部分

struct Position { int row; int col; bool need_remove_queen; Position() { Position(-1, -1); } Position(int r, int c, bool b_remove = false): row(r), col(c), need_remove_queen(b_remove) {}};void Solution::placeQueen(struct Position& x, int row, int col) { x.need_remove_queen = true; placeQueen(row, col); }void Solution::robot(int n) { vector<Position> stack; stack.push_back({-1, -1}); stack.push_back({0, 0}); while (stack.size()) { Position x; while ((x = stack.back(), x.col < n && x.col >= 0)) { if (notUnderAttack(x.row, x.col)) { Position &z = stack.back(); placeQueen(z, z.row, z.col); if (x.row + 1 == n) { addSolution(); } else { stack.push_back({ ++x.row, 0 }); continue; } } else { stack.pop_back(); if (x.need_remove_queen) removeQueen(x.row, x.col); stack.push_back({x.row, ++x.col}); } } stack.pop_back(); if (stack.size()) { auto y = stack.back(); stack.pop_back(); if (y.need_remove_queen) removeQueen(y.row, y.col); if (y.col < n – 1 && y.col >= 0) { stack.push_back({y.row, ++y.col}); } } }}vector<vector<string>> Solution::solveNQueens(int n) { this->n = n; rows = new int[n]; cols = new int[n]; queen = new int[n]; hills = new int[2 * n – 1]; // e.g. n = 4, “/” hillsÓÐ7¸öλÖÃ; dales = new int[2 * n – 1]; for (int i = 0; i < n; ++i) { rows[i] = cols[i] = queen[i] = 0; } for (int j = 0; j < 2 * n – 1; ++j) { hills[j] = dales[j] = 0; } // backtrack(0); robot(n); return output; }

递归版本和非递归版本的代码都在leetcode通过

参考 lleetcode官方题解