文章目录

  • N 皇后
    • 初始算法 :
    • 修改后的算法
    • 优化后的算法:
  • 总结

N 皇后

题目链接:https://leetcode-cn.com/problems/n-queens/

题目描述:n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

初始算法 :

基于回溯的思想,一个较为粗略的程序如下:solve 函数中,每次选择一个合适的位置(flag 为 false)来设置为 Q,然后更新当前棋盘上已有皇后的活动区域(设置为 true),然后继续下一个位置的查找,直到 Q 的个数 num 等于 n,即表示找到了一种正确的结果,保存并返回。 其中 flag 数组表示棋盘上已有的 Q 的活动区域。

 List<List<String>> ans=new LinkedList<>();public List<List<String>> solveNQueens(int n) {char[][] tmp=new char[n][n];boolean[][] flag=new boolean[n][n];//用来标记棋盘上已有皇后的占领区域。for(int i=0;i<n;i++)for(int j=0;j<n;j++)tmp[i][j]='.';solve(tmp,n,flag,0);return ans;}public void solve(char[][] tmp,int n,boolean[][] flag,int num){if(num==n){ //成功时保存,且程序返回save(ans,tmp,n);return;}for(int i=0;i<n;i++)for(int j=0;j<n;j++){if(flag[i][j]==false){tmp[i][j]='Q';setDomin(flag,n,i,j,true);solve(tmp,n,flag,num+1);setDomin(flag,n,i,j,false);//需要采用别的方法恢复flag到上一个状态。它和 setDomin(true)不是可逆的。tmp[i][j]='.';}}}public void setDomin(boolean[][] flag,int n,int i,int j,boolean status){for(int k=0;k<n;k++){//水平 竖直 方向flag[i][k]=status;flag[k][j]=status;}for(int k=1;k<n;k++){if(i+k<n&&j+k<n)//右下flag[i+k][j+k]=status;if(i-k>=0&&j-k>=0)//左上flag[i-k][j-k]=status;if(i-k>=0&&j+k<n)//左下flag[i-k][j+k]=status;if(i+k<n&&j-k>=0)//右上flag[i+k][j-k]=status;}}public void save(List<List<String>> ans,char[][] tmp,int n){ //将 char[][] 转化为 List<String> 并保存List<String> t=new LinkedList<>();for(int i=0;i<n;i++)t.add(new String(tmp[i]));ans.add(t);}

上面代码在回溯时出现问题,因为它不能恢复 flag 到上一个状态,所以需要修改,拟采用的解决方法为重新绘制除之间的所有 Q 的活动区域,而不包括当前的 Q ,当然,这需要保存之前的 Q 坐标,修改后的算法如下:

修改后的算法

1,增加变量 chosen 记录了已经选取的 Q 位置,方便我们重新设置 flag 数组(在将 flag 数组回退到上一个状态时)。2,修改变量 ans 的类型为 Set ,因为在执行的时候发现,虽然此算法的正确性没有问题,但是需要优化(包括剪枝),在 n 为 4 ,使用 List 存储答案的情况下,得到的最终结果有 48 个, 而每 24 个一摸一样,所以目前先建立了一个 Set 来去掉重复的元素,保证可以得到正确的结果。

 Set<List<String>> ans=new HashSet<>();public List<List<String>> solveNQueens(int n) {char[][] tmp=new char[n][n];boolean[][] flag=new boolean[n][n];//用来标记棋盘上已有皇后的占领区域。for(int i=0;i<n;i++)for(int j=0;j<n;j++)tmp[i][j]='.';LinkedList<int[]> chosen=new LinkedList<>();//用来保存已有的皇后坐标。solve(tmp,n,flag,0,chosen);List<List<String>> re=new LinkedList<>();re.addAll(ans);return re;}public void solve(char[][] tmp,int n,boolean[][] flag,int num,LinkedList<int[]> chosen){if(num==n){ //成功时保存,且程序返回save(ans,tmp,n);return;}for(int i=0;i<n;i++)for(int j=0;j<n;j++){if(flag[i][j]==false){tmp[i][j]='Q';chosen.addLast(new int[]{i,j});setDomin(flag,n,i,j,true);solve(tmp,n,flag,num+1,chosen);chosen.removeLast();//删除当前 Q 的位置cancel(flag,n,chosen);// 重新设置 flag 数组到上一个状态。tmp[i][j]='.';}}}public void cancel(boolean[][] flag,int n,LinkedList<int[]> chosen){for(int i=0;i<n;i++)Arrays.fill(flag[i],false);for(int[] t:chosen)setDomin(flag,n,t[0],t[1],true);}public void setDomin(boolean[][] flag,int n,int i,int j,boolean status){for(int k=0;k<n;k++){//水平 竖直 方向flag[i][k]=status;flag[k][j]=status;}for(int k=1;k<n;k++){if(i+k<n&&j+k<n)//右下flag[i+k][j+k]=status;if(i-k>=0&&j-k>=0)//左上flag[i-k][j-k]=status;if(i-k>=0&&j+k<n)//左下flag[i-k][j+k]=status;if(i+k<n&&j-k>=0)//右上flag[i+k][j-k]=status;}}public void save(Set<List<String>> ans,char[][] tmp,int n){List<String> t=new LinkedList<>();for(int i=0;i<n;i++)t.add(new String(tmp[i]));ans.add(t);}

上面算法属于暴力枚举,其时间复杂度很高。对它进行优化如下:

优化后的算法:

参考对应官方题解,由于上述暴力枚举的时间复杂度非常高,需要利用限制条件加以优化。

需要说明的是,下面算法仍然和之前的算法类似,大体的回溯框架没有变化。唯一变化的是如何判断标记棋盘上已有皇后的占领区域,或者说是下一个皇后可以放置的位置区域。它采用了 3 个集合来降低之前算法进行标记的时间复杂度。

将之前的二维数组表示皇后位置转化为只用一个数组 queens 来表示,比如对于 queens[0]=1 ,表示在第0行第1列放置一个皇后。一旦 queens 数组被填满元素,那么我们就得到了一个放置结果。

当在 queens 数组放置新元素时(即在下一行填充新的皇后位置时):因为肯定是不同行,所以需要关心的是新皇后位置和已有的皇后位置是否在相同列;和已有的皇后位置是否在同一条左上右下斜线上;和已有的皇后位置是否在同一条右上左下斜线上。而 3 个集合的功能就是用来分别判断这三种情况的。

columns 数组保存已有皇后的列下标(判断 columns 中是否已经包含了新位置的列下标)
      diagonals1 数组保存已有皇后的左上右下斜线位置,比如位置(1,2),由于经过(1,2)的此斜线上所有点(x,y)满足" 1-2 == x-y “关系,那么这一条斜线就可以用 -1(1-2) 来表示,即保存 -1 到diagonals1 中(判断 diagonals1 是否已经包含的新位置所在左上右下斜线,即新位置的行下标减去列下标的值)
      diagonals2 数组保存已有皇后的右上左下斜线位置,比如位置(1,2),由于经过(1,2)的此斜线上所有点(x,y)满足” 1+2 == x+y "关系,那么这一条斜线就可以用 3(1+2) 来表示,即保存 3 到diagonals2 中(判断 diagonals2 是否已经包含的新位置所在左上右下斜线,即新位置的行下标加上列下标的值)

如下面算法所示:(每行代码的功能已经注明,可参考理解)

 public List<List<String>> solveNQueens(int n) {List<List<String>> solutions=new ArrayList<>();int[] queens=new int[n]; //保存每一行中成功放置皇后的下标。Arrays.fill(queens,-1);Set<Integer> columns=new HashSet<>();//记录每一列是否可以放置新的皇后Set<Integer> diagonals1=new HashSet<>();//记录左上右下斜线上是否可以放置新的皇后Set<Integer> diagonals2=new HashSet<>();//记录右上左下斜线上是否可以放置新的皇后backtrack(solutions,queens,n,0,columns,diagonals1,diagonals2); // row 表示已经放置了多少行的皇后return solutions;}public void backtrack(List<List<String>> solutions,int[] queens,int n,int row,Set<Integer> columns,Set<Integer> diagonals1,Set<Integer> diagonals2){if(row==n){List<String> board=generateBoard(queens,n);solutions.add(board);}else{for(int i=0;i<n;i++){//上述三个判断语句如果有任意一个为 true,即表示当前位置(row,i)不能被放置新的皇后,所以需要跳过。//并且使用 Set 来存储,可以顺带着去重。比如columns中不同的两行但是列下标相同,就没必要存储了;diagonals1中同一条斜线上的元素也没必要存储了...if(columns.contains(i))continue;int diagonal1=row-i;//左上右下一条斜线上元素的行下标与列下标之差相等。if(diagonals1.contains(diagonal1))continue;int diagonal2=row+i;//右上左下一条斜线上元素的行下标与列下标之和相等。if(diagonals2.contains(diagonal2))continue;queens[row]=i;  //此时在位置(row,i)上找到了一个可以放置新皇后的位置columns.add(i);//将该列下标加入columns。diagonals1.add(diagonal1);//将 (row,i) 表示的对应方向的斜线加入 diagonals1diagonals2.add(diagonal2);//将 (row,i) 表示的对应方向的斜线加入 diagonals2backtrack(solutions,queens,n,row+1,columns,diagonals1,diagonals2); //回溯判断每一种放置的可能性。queens[row]=-1; //恢复到在此位置放置新皇后之前的状态。columns.remove(i);diagonals1.remove(diagonal1);diagonals2.remove(diagonal2);}}}public List<String> generateBoard(int[] queens,int n){ //将已经找到符合条件的 queens 数组转换为标准的返回值(List<String>类型)List<String> board=new ArrayList<>();for(int i=0;i<n;i++){char[] row=new char[n];Arrays.fill(row,'.');row[queens[i]]='Q';board.add(new String(row));}return board;}

总结

目前来看,这道题的整体思路和代码框架是很容易实现的,关键的问题在于如何描述新皇后的可选位置区域这块,并尽可能地降低时间复杂度。

LeetCode算法题9:递归和回溯-N皇后问题相关推荐

  1. leetcode算法题--完全平方数★

    原题链接:https://leetcode-cn.com/problems/perfect-squares/ 相关题目:leetcode算法题–零钱兑换 1.回溯法 就是暴力法,套路就是递归,但是有很 ...

  2. leetcode算法题--不同的二叉搜索树

    原题链接:https://leetcode-cn.com/problems/unique-binary-search-trees/ 相关题目:leetcode算法题--不同的二叉搜索树 II 1.递归 ...

  3. LeetCode算法题-Minimum Depth of Binary Tree(Java实现)

    这是悦乐书的第168次更新,第170篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第27题(顺位题号是111).给定二叉树,找到它的最小深度.最小深度是沿从根节点到最近的 ...

  4. LeetCode算法题-Factorial Trailing Zeroes(Java实现)

    这是悦乐书的第183次更新,第185篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第42题(顺位题号是172).给定一个整数n,返回n!中的尾随零数.例如: 输入:3 输 ...

  5. LeetCode算法题整理(200题左右)

    目录 前言 一.树(17) 1.1.后序遍历 1.2.层次遍历 1.3.中序 1.4.前序 二.回溯(20) 2.1.普通回溯 2.2.线性回溯:组合.排列.子集.分割 2.3.矩阵回溯 三.二分查找 ...

  6. Leetcode算法题:两个有序数组求中位数

    Leetcode算法题:两个有序数组求中位数 要求时间复杂度为O(log(m+n)) 思路: 暴力解决:合并数组并排序,简单且一定能实现,时间复杂度O(m+n) 由于两个数组已经排好序,可一边排序一边 ...

  7. LeetCode算法题-Nth Digit(Java实现)

    这是悦乐书的第215次更新,第228篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第83题(顺位题号是400).找到无限整数序列的第n个数字1,2,3,4,5,6,7,8 ...

  8. LeetCode算法题-Reverse Linked List(Java实现)

    这是悦乐书的第192次更新,第195篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第51题(顺位题号是206).反转单链表.例如: 输入:1-> 2-> 3- ...

  9. LeetCode算法题-Convert a Number to Hexadecimal(Java实现)

    这是悦乐书的第219次更新,第231篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第86题(顺位题号是405).给定一个整数,写一个算法将其转换为十六进制.对于负整数,使 ...

  10. leetcode算法题--零钱兑换

    原题链接:https://leetcode-cn.com/problems/coin-change/ 相关题目:leetcode算法题–完全平方数★ 动态规划 dp[i] i从0到amount,dp[ ...

最新文章

  1. axios队列 vue_(十三 )Vue 封装axios(四种请求)及相关介绍
  2. Relu神经网络输出预测全为0或1,对所有输入样本的预测概率也相同
  3. Flutter TabBar 标签栏背景颜色、点击水波纹颜色配置
  4. 【译】Angular Elements 及其运作原理
  5. 利用HP优盘启动盘格式化工具制作U盘Dos启动盘
  6. java如何编写屏幕保护程序_屏幕保护程序的编写
  7. 整理:状态机的编程思想
  8. Matlab版本svm工具箱,matlab libsvm工具箱
  9. 计算机图形学与OpenGL
  10. 景区大数据可预警客流量
  11. DPDK Rx flexible descriptor在Intel E810网卡中的使用
  12. VS用OLE方式对Excel进行读写操作
  13. Android开发:按一定频率同时获取多个传感器数据
  14. 基于android的即时通讯APP 聊天APP
  15. C语言—整除问题、求余、赋值、逻辑运算符易错点
  16. JMP指令寻址方式总结,JMP BX指令寻址方式是什么
  17. GCC中 -I、-L、-l 选项的作用
  18. 计算几何从入门到入土(一)
  19. 安卓教程:Xposed 框架安装及使用
  20. 应广单片机_呼吸灯理解

热门文章

  1. 正睿 2018 提高组十连测 Day4 T3 碳
  2. nodejs 监控代码变动实现ftp上传
  3. 使用消息队列实现分布式事务-公认较为理想的分布式事务解决方案(转)
  4. 一张表按分类查询:只显示前2行
  5. Markdown语法记录
  6. 复杂的xml转化为java实体
  7. 流程管理产品小故事汇总贴
  8. SpringSide 3 中的 Struts 2
  9. python空间分析_读书笔记——《python地理空间分析指南》
  10. Win64 驱动内核编程-25.X64枚举和隐藏内核模块