引言

最近在刷leetcode算法题的时候,51题很有意思;

题目是这样的:

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

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

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

 

示例 1:

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入:n = 1
输出:[["Q"]]

 

提示:

    1 <= n <= 9
    皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-queens
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题有意思地方在哪里,是它需要你算错所有的可能性;而不是其中的1种或者2种可能性;

看到这道题想起只有在游戏内做过的消除算法判定方案;《小游戏五子连珠消除解决方案》在这片文章中,我只是去验算内存中能消除的对象,也就是查找所有已经出现的对象进行判定;

这道题是让你从未知的开始,去填充数据,然后找出所有可能性;

题解1

第一遍读完题目的时候,我盲目的自以为然的写了一个算法;

  1. /**
  2. * @author: Troy.Chen(失足程序员, 15388152619)
  3. * @version: 2021-07-08 10:53
  4. **/
  5. class Solution {
  6.  
  7. public List<List<String>> solveNQueens(int n) {
  8. List<List<String>> ret = new ArrayList<>();
  9. List<boolean[][]> ot = new ArrayList<>();
  10. for (int z = 0; z < n; z++) {
  11. for (int x = 0; x < n; x++) {
  12. boolean[][] action = action(ot, n, x, z);
  13. if (action == null) {
  14. continue;
  15. }
  16. List<String> item = new ArrayList<>();
  17. for (boolean[] booleans : action) {
  18. String str = "";
  19. for (boolean a : booleans) {
  20. str += a ? "Q" : ".";
  21. }
  22. item.add(str);
  23. }
  24. ret.add(item);
  25. }
  26. }
  27. return ret;
  28. }
  29.  
  30. public boolean[][] action(List<boolean[][]> ot, int n, int startX, int startZ) {
  31. boolean[][] tmp = new boolean[n][n];
  32. tmp[startZ][startX] = true;
  33. int qN = 1;
  34. for (int z = 0; z < tmp.length; z++) {
  35. for (int x = 0; x < tmp.length; x++) {
  36. if (check(tmp, x, z)) {
  37. tmp[z][x] = true;
  38. qN++;
  39. }
  40. }
  41. }
  42. if (qN >= n) {
  43. if (!ot.isEmpty()) {
  44. for (boolean[][] tItem : ot) {
  45. boolean check = true;
  46. for (int z = 0; z < tmp.length; z++) {
  47. for (int x = 0; x < tmp.length; x++) {
  48. if (tmp[z][x]) {
  49. if (tmp[z][x] != tItem[z][x]) {
  50. check = false;
  51. break;
  52. }
  53. }
  54. }
  55. if (!check) {
  56. break;
  57. }
  58. }
  59. if (check) {
  60. return null;
  61. }
  62. }
  63. }
  64. ot.add(tmp);
  65. return tmp;
  66. } else {
  67. return null;
  68. }
  69. }
  70.  
  71. public boolean check(boolean[][] tmp, int checkx, int checkz) {
  72. /*检查横向*/
  73. for (int x = 0; x < tmp.length; x++) {
  74. if (tmp[checkz][x]) {
  75. return false;
  76. }
  77. }
  78. /*检查纵向*/
  79. for (int z = 0; z < tmp.length; z++) {
  80. if (tmp[z][checkx]) {
  81. return false;
  82. }
  83. }
  84. int tx;
  85. int tz;
  86. {
  87. /*从左到右,从上到下*/
  88. tx = checkx;
  89. tz = checkz;
  90. while (true) {
  91. if (tmp[tz][tx]) {
  92. return false;
  93. }
  94. tx--;
  95. tz--;
  96. if (tx < 0 || tz < 0) {
  97. break;
  98. }
  99. }
  100.  
  101. tx = checkx;
  102. tz = checkz;
  103. while (true) {
  104. if (tmp[tz][tx]) {
  105. return false;
  106. }
  107. tx++;
  108. tz++;
  109. if (tx >= tmp.length || tz >= tmp.length) {
  110. break;
  111. }
  112. }
  113. }
  114. {
  115. /*从右到左,从上到下*/
  116. tx = checkx;
  117. tz = checkz;
  118. while (true) {
  119. if (tmp[tz][tx]) {
  120. return false;
  121. }
  122. tx++;
  123. tz--;
  124. if (tx >= tmp.length || tz < 0) {
  125. break;
  126. }
  127. }
  128. tx = checkx;
  129. tz = checkz;
  130. while (true) {
  131. if (tmp[tz][tx]) {
  132. return false;
  133. }
  134. tx--;
  135. tz++;
  136. if (tx < 0 || tz >= tmp.length) {
  137. break;
  138. }
  139. }
  140. }
  141. return true;
  142. }
  143.  
  144. }

这个写法,是我脑袋里的第一想法,代码怎么理解呢;

就是说我从第一个位置开始去摆放皇后,然后依次去递推其他格子皇后摆放位置,然后查询皇后数量是否符合提议;自己测试了即便就感觉对了,在leetcode提交代码

提交代码过后啪啪打脸了,失败了

查看执行输出,当输入n=6的时候;

leetcode的输出结果是有4种可能性,但是我输出可能性只有1种;

对比之前五子棋消除方案来讲;check代码无疑是没有问题的,起码我在对于纵向横向,斜45度检查方案是没问题的;能保住输出是符合要求的,

那么问题就在于查找可能性代码;我们能找出符合条件的可能性,只是没有办法去找出所有的可能性

仔细分析了一下,不难看出完整的循环一次,得出的结果其实是固定;

怎么理解呢,因为每一次for循环数值都是从0开始推导;那么找到第一个符合条件的格子就落子了,所以得到的结果总是一致的;

既然我们需要算出所有组合;

那么我们能有什么样的方式去完成这个组合求解呢?

题解2

思考了良久;既然正常的循环其实无法查找每一种可能性,只能放弃正常循环;

思考了一下能不能按照当前行,每一个格子,依次往下逐行去扫描每一种可能性;

如果我们要这样去逐行扫描,我们就得在初始化棋盘格子到时候摆放上,上一行的情况才能去推算出当前行能在那些地方摆放;

当前行的递推的时候也是按照第一个格子到最后一个格子去验算能摆放的格子,然后查找到能摆放的格子,就把现在的快照数据提交给下一行进行推算;

这样的做法就是,当第一行开始判断是,第一个格子肯定能摆放棋子对吧,然后把第一个格子摆放上棋子过后,把棋盘直接推给第二行,

第二行拿到棋盘过后,去查找能摆放的格子,每查找到能摆放的格子就把棋盘拷贝一次副本,然后设置当前查找的格子落子,然后把副本棋盘传递给下一行;

这样依次类推,如果能达到最后一行,并且找到适合的格子,那么就是一种可能性,

我们在递推的同时,每一次都会记录自己的进度,再次往下递推;

这么说可能有些抽象;来看一下图

我们的递推过程就是,从a1开始,每一次找到合适的格子就把棋盘拷贝一次,传递给下一行(b),然后b开始从第一个格子开始递推,找到符合规则的格子就再一次拷贝棋盘传递给C;

这样的话,就是完完整的逐行扫描了,所有的肯能行;

来看一下带实现

  1. /**
  2. * @author: Troy.Chen(失足程序员, 15388152619)
  3. * @version: 2021-07-08 10:53
  4. **/
  5. class Solution {
  6.  
  7. public List<List<String>> solveNQueens(int n) {
  8. List<List<String>> ret = new ArrayList<>();
  9. List<boolean[][]> ot = new ArrayList<>();
  10.  
  11. boolean[][] tmp = new boolean[n][n];
  12. check(ot, tmp, 0, 0);
  13.  
  14. for (boolean[][] a : ot) {
  15. ret.add(convert(a));
  16. }
  17. return ret;
  18. }
  19.  
  20. /*按照规定转化字符串*/
  21. public List<String> convert(boolean[][] tmp) {
  22. List<String> item = new ArrayList<>();
  23. for (boolean[] booleans : tmp) {
  24. String str = "";
  25. for (boolean a : booleans) {
  26. str += a ? "Q" : ".";
  27. }
  28. item.add(str);
  29. }
  30. return item;
  31. }
  32.  
  33. public void check(List<boolean[][]> ot, boolean[][] tmp, int checkx, int checkz) {
  34. for (int x = checkx; x < tmp.length; x++) {
  35. if (check0(tmp, x, checkz)) {
  36. /*相当于逐行进行扫描所以要拷贝代码*/
  37. int tmpz = checkz;
  38. boolean[][] clone = clone(tmp);
  39.  
  40. clone[tmpz][x] = true;
  41. tmpz++;
  42. if (tmpz < tmp.length) {
  43. check(ot, clone, 0, tmpz);
  44. } else {
  45. ot.add(clone);
  46. }
  47. }
  48. }
  49. }
  50.  
  51. /*拷贝数组*/
  52. public boolean[][] clone(boolean[][] tmp) {
  53. boolean[][] clone = tmp.clone();
  54. for (int i = 0; i < tmp.length; i++) {
  55. clone[i] = tmp[i].clone();
  56. }
  57. return clone;
  58. }
  59.  
  60. public boolean check0(boolean[][] tmp, int checkx, int checkz) {
  61. /*检查横向*/
  62. for (int x = 0; x < tmp.length; x++) {
  63. if (tmp[checkz][x]) {
  64. return false;
  65. }
  66. }
  67. /*检查纵向*/
  68. for (int z = 0; z < tmp.length; z++) {
  69. if (tmp[z][checkx]) {
  70. return false;
  71. }
  72. }
  73. int tx;
  74. int tz;
  75. {
  76. /*从左到右,从上到下*/
  77. tx = checkx;
  78. tz = checkz;
  79. while (true) {
  80. if (tmp[tz][tx]) {
  81. return false;
  82. }
  83. tx--;
  84. tz--;
  85. if (tx < 0 || tz < 0) {
  86. break;
  87. }
  88. }
  89.  
  90. tx = checkx;
  91. tz = checkz;
  92. while (true) {
  93. if (tmp[tz][tx]) {
  94. return false;
  95. }
  96. tx++;
  97. tz++;
  98. if (tx >= tmp.length || tz >= tmp.length) {
  99. break;
  100. }
  101. }
  102. }
  103. {
  104. /*从右到左,从上到下*/
  105. tx = checkx;
  106. tz = checkz;
  107. while (true) {
  108. if (tmp[tz][tx]) {
  109. return false;
  110. }
  111. tx++;
  112. tz--;
  113. if (tx >= tmp.length || tz < 0) {
  114. break;
  115. }
  116. }
  117. tx = checkx;
  118. tz = checkz;
  119. while (true) {
  120. if (tmp[tz][tx]) {
  121. return false;
  122. }
  123. tx--;
  124. tz++;
  125. if (tx < 0 || tz >= tmp.length) {
  126. break;
  127. }
  128. }
  129. }
  130. return true;
  131. }
  132.  
  133. }

再次提交代码;

代码执行通过了,得到的结论也是一样的了;

但是不幸的是我的代码只击败了8%的用户,希望得到各位园友性能更高效的算法;

总结

这样的递推方式,其实可以实现像五子棋AI算法;通过落子情况去递推在哪里下子可以得到五个棋子在一条线上;

只是递推开上时棋盘已经定下了落子情况;从当前的落子情况去递推他的可能性;

递推算法,AI衍生的更多相关文章

  1. 数据结构与算法之递推算法 C++与PHP实现

    数据结构是算法实现的基础,算法总是要依赖于某种数据结构来实现的.往往是在发展一种算法的时候,构建了适合于这样的算法的数据结构.一种数据结构假设脱离了算法,也就没有存在的价值了. 算法的作用----解决 ...

  2. 穷举算法和递推算法(Java)

    穷举算法 概念: 最简单算法,依赖计算机的强大计算能力穷尽每一种可能的情况.穷举算法效率不高,但是适合一些没有明显规律可循的场合. 思想: 在使用穷举算法时,需要明确问题答案的范围,这样才可能在指定范 ...

  3. 求逆元的两种方法+求逆元的O(n)递推算法

    到国庆假期都是复习阶段..所以把一些东西整理重温一下. gcd(a,p)=1,ax≡1(%p),则x为a的逆元.注意前提:gcd(a,p)=1; 方法一:拓展欧几里得 gcd(a,p)=1,ax≡1( ...

  4. c语言-递推算法1

    递推算法之一:倒推法 1.一般分析思路: if 求解初始条件F1 then begin { 倒推 } 由题意(或递推关系)确定最终结果Fn; 求出倒推关系式Fi-1 =G(Fi ); i=n; { 从 ...

  5. Re.常系数齐次递推

    前言 嗯   我之前的不知道多少天看这个的时候到底在干什么呢 为什么那么..  可能大佬们太强的缘故 最后仔细想想思路那么的emmm 不说了  要落泪了 唔唔唔 前置 多项式求逆 多项式除法/取模 常 ...

  6. 算法笔记_091:蓝桥杯练习 递推求值(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 问题描述 已知递推公式: F(n, 1)=F(n-1, 2) + 2F(n-3, 1) + 5, F(n, 2)=F(n-1, 1) + 3F(n- ...

  7. 从三数之和看如何优化算法,递推-->递推加二分查找-->递推加滑尺

    人类发明了轮子,提高了力的使用效率. 人类发明了自动化机械,将自己从重复的工作中解脱出来. 提高效率的方法好像总是离不开两点:拒绝无效劳动,拒绝重复劳动.人类如此,计算机亦如是. 前面我们说过了四数之 ...

  8. LG5487 【模板】线性递推+BM算法

    [模板]线性递推+BM算法 给出一个数列 \(P\) 从 \(0\) 开始的前 \(n\) 项,求序列 \(P\) 在\(\bmod~998244353\) 下的最短线性递推式,并在 \(\bmod~ ...

  9. Berlekamp Massey算法求线性递推式

    BM算法求求线性递推式   P5487 线性递推+BM算法   待AC.   Poor God Water   // 题目来源:ACM-ICPC 2018 焦作赛区网络预赛 题意   God Wate ...

随机推荐

  1. unity ab包打包和加载的简单学习示例

    闲着没事结合项目看了下unity AssetBundle打包和使用,写了一些测试例子,需要的可以拿去,导入一个空项目即可 链接:https://pan.baidu.com/s/1H85dnMNkRoW ...

  2. 达梦数据库产品支持技术学习分享_Week1

    本周主要从以下几个方面进行本人对达梦数据库学习的分享,学习进度和学习情况因人而异,仅供参考. 一.达梦数据库的体系架构 二.达梦数据库的安装 三.达梦数据库的数据类型 四.达梦数据库的DDL.DML. ...

  3. NGINX缓存使用官方指南

    我们都知道,应用程序和网站一样,其性能关乎生存.但如何使你的应用程序或者网站性能更好,并没有一个明确的答案.代码质量和架构是其中的一个原因,但是在很多例子中我们看到,你可以通过关注一些十分基础的应用内 ...

  4. K8S集群etcd备份与恢复

    参考链接: K8S集群多master:Etcd v3备份与恢复 K8S集群单master:Kubernetes Etcd 数据备份与恢复 ETCD系列之一:简介:https://developer.a ...

  5. .NET Worker Service 作为 Windows 服务运行及优雅退出改进

    上一篇文章我们了解了如何为 Worker Service 添加 Serilog 日志记录,今天我接着介绍一下如何将 Worker Service 作为 Windows 服务运行. 我曾经在前面一篇文章 ...

  6. 【dog与lxy】8.25题解-necklace

    necklace 题目描述 可怜的dog最终还是难逃厄运,被迫于lxy签下城下之约.这时候lxy开始刁难dog. Lxy首先向dog炫耀起了自己的财富,他拿出了一段很长的项链.这个项链由n个珠子按顺序 ...

  7. 前台使用Vue

    前台搭建遇到问题 ----前台访问量大 未采用vue 单页面SAP 的方式构建 使用多HTML构建页面 项目构建 vue 2.6 https://cn.vuejs.org/ elementUI htt ...

  8. 有模有样解决Flutter里Webview无法访问HTTP页面的问题

    探索过程 Android9(好像是吧)开始谷歌就默认不让开发者访问不安全HTTP内容了,如果非要用HTTP,那必须在networkSecurityConfig里配置cleartextTrafficPe ...

  9. Java知识复习(四)

    最近准备跳槽,又要好好复习基本知识了.过了个年,前面刚接触的springboot也只能先放放了.就先把自己复习了哪些罗列出来吧. Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用== ...

  10. Android开发问题之Installation failed due to invalid URI!

    真机调试遇到以下问题: [2017-07-20 13:43:53 - VCL02PANEL] Installation failed due to invalid URI![2017-07-20 13 ...