【算法问题】N 皇后问题

目录

  • 1.问题定义
  • 2.思路分析
    • 2.1.基于数组的回溯
    • 2.2.基于集合的回溯
    • 2.3.基于位运算的回溯
  • 3.代码实现 (Java)
    • 3.1.基于数组的回溯
    • 3.2.基于数组的回溯
    • 3.3.基于位运算的回溯
  • 4.扩展

参考:52.N 皇后 II

1.问题定义

(1)在国际象棋的规则中,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。N 皇后问题指的是如何将 N 个皇后放置在 N × N 的棋盘上,并且使皇后彼此之间不能相互攻击。即给你一个整数 N ,返回所有不同的 N 皇后问题的解决方案数量(1 ≤ N ≤ 16)。

(2)例如,当 N = 4 时,有 2 种解决方案,具体如下图所示:

在这里插入图片描述

2.思路分析

2.1.基于数组的回溯

(1)一种比较直观的做法是暴力枚举将 N 个皇后放置在 N×N 的棋盘上的所有可能的情况,并对每一种情况判断是否满足皇后彼此之间不相互攻击。暴力枚举的时间复杂度是非常高的,因此必须利用限制条件加以优化。显然,每个皇后必须位于不同行和不同列,因此将 N 个皇后放置在 N×N 的棋盘上,一定是每一行有且仅有一个皇后,每一列有且仅有一个皇后,且任何两个皇后都不能在同一条斜线上。基于上述发现,可以通过回溯的方式得到可能的解的数量。

(2)回溯的具体做法是:使用一个数组(下面代码中的数组 queens,长度为 N)记录每行放置的皇后的列下标,依次在每一行放置一个皇后。每次新放置的皇后都不能和已经放置的皇后之间有攻击:即新放置的皇后不能和任何一个已经放置的皇后在同一列以及同一条斜线上,并更新数组中的当前行的皇后列下标。当 N 个皇后都放置完毕,则找到一个可能的解。当找到一个可能的解之后,我们将记录解决方案总数的变量 count 加一即可。

(3)在判断新放置的皇后是否与之前任何一个已经放置的皇后在同一列以及同一条斜线上时,我们可以采用二维坐标系中斜率的概念来快速判断,以下图为例,A、B、C、D 这三个皇后的坐标分别为 (3, 1)、(2, 0)、(0, 1)、(1, 3),其中 D 是新放置的皇后。

  • 如果新放置的皇后与之前任何一个已经放置的皇后的纵坐标相等,那么它们必然在在同一列,例如下图中的皇后 A 与皇后 C 在同一列;
  • 如果新放置的皇后与之前任何一个已经放置的皇后的坐标所在的直线的斜率为 1 或者 -1,那么它们必然在同一条斜线上,例如下图中直线 AB 的斜率为 -1,直线 AD 的斜率为 1;

在这里插入图片描述

(4)而且,这样一来之前定义的数组 queens 正好可以用来表示每个皇后在棋盘上的坐标,即 queens[i] = j 表示的对应皇后的坐标为 (i, j),那么对于任意两个皇后 Q1(i, j) 和 Q2(x, y):

  • 如果 j == y,那么说明 Q1 和 Q2 在同一列;
  • 如果 (j - y) / (i - x) == ±1,即 |j - y| == |i - x|,那么说明 Q1 和 Q2 在同一斜线上;

2.2.基于集合的回溯

(1)思路 2.1 是使用一个数组记录每行放置的皇后的列下标,并且根据斜率的相关知识来判断新放置的皇后是否与之前任何一个已经放置的皇后在同一列以及同一条斜线上。但是在实际判断过程中,我们还是需要将新放置的皇后与之前放置的皇后逐一比较。

(2)那么为了加快判断的过程,我们可以使用“空间替换时间”的策略。具体来说,为了判断一个位置所在的列和两条斜线上是否已经有皇后,使用三个集合 columnsdiagonals1diagonals2 分别记录每一列以及两个方向的每条斜线上是否有皇后。

  • 列的表示法很直观,一共有 N 列,每一列的下标范围从 0 到 N−1,使用列的下标即可明确表示每一列。
  • 我们假设方向一的斜线为从左上到右下方向,同一条斜线上的每个位置满足行下标与列下标之差相等,例如 (0,0) 和 (3,3) 在同一条方向一的斜线上。因此使用行下标与列下标之差即可明确表示每一条方向一的斜线。
    在这里插入图片描述
  • 我们假设方向二的斜线为从右上到左下方向,同一条斜线上的每个位置满足行下标与列下标之和相等,例如 (3,0) 和 (1,2) 在同一条方向二的斜线上。因此使用行下标与列下标之和即可明确表示每一条方向二的斜线。
    在这里插入图片描述

上面的两张图来自 52.N 皇后 II

(3)每次放置皇后时,对于每个位置判断其是否在三个集合中,如果三个集合都不包含当前位置,则当前位置是可以放置皇后的位置。

2.3.基于位运算的回溯

有关位运算的相关技巧可与参考【算法技巧】位运算这篇文章。

(1)思路 2.2 使用三个集合记录分别记录每一列以及两个方向的每条斜线上是否有皇后,每个集合最多包含 N 个元素,因此集合的空间复杂度是 O(N)。如果利用位运算记录皇后的信息,就可以将记录皇后信息的空间复杂度从 O(N) 降到 O(1)。

(2)具体做法是,使用三个整数 columnsdiagonals1diagonals2 分别记录每一列以及两个方向的每条斜线上是否有皇后,每个整数有 N 个二进制位。棋盘的每一列对应每个整数的二进制表示中的一个数位,其中棋盘的最左列对应每个整数的最低二进制位,最右列对应每个整数的最高二进制位

(3)得到这三个整数的计算方法如下:

  • 初始时,三个整数的值都等于 0,表示没有放置任何皇后;
  • 在当前行放置皇后,如果皇后放置在第 i 列,则将三个整数的第 i 个二进制位(指从低到高的第 i 个二进制位)的值设为 1;
  • 进入下一行时,columns 的值保持不变,diagonals1 左移一位,diagonals2 右移一位,由于棋盘的最左列对应每个整数的最低二进制位,即每个整数的最右二进制位,因此对整数的移位操作方向和对棋盘的移位操作方向相反(对棋盘的移位操作方向是 diagonals1 右移一位,diagonals2 左移一位)。

3.代码实现 (Java)

3.1.基于数组的回溯

class Solution {
    //记录解决方案总数
    int count = 0;

    public int totalNQueens(int n) {
        // queens[t] 表示第 t 个皇后放在棋盘的第 t 行的第 queens[t] 列 (0 ≤ t < n)
        int[] queens = new int[n];
        backtrack(queens, 0);
        return count;
    }

    public void backtrack(int[] queens, int t) {
        int n = queens.length;
        if (t == n) {
        	//找到一种解决方案
            count++;
            return;
        }
        //遍历当前第 t 个皇后在第 t 行的第 0 ~ n - 1 列
        for (int i = 0; i < n; i++) {
            //将第 t 个皇后放在棋盘的第 t 行的第 i 列
            queens[t] = i;
            //判断第 t 个皇后能不能放在当前位置
            if (check(queens, t)) {
                //能够放在当前位置,开始判断下一个皇后
                backtrack(queens, t + 1);
            }
        }
    }

    //在一行只放一个皇后的前提下,判断第 t 个皇后能否放在当前位置
    public boolean check(int[] queens, int t) {
        //判断当前第 t 个皇后是否与前 0 ~ t - 1 行的皇后能相互攻击
        for (int i = 0; i < t; i++) {
            /*
                t - i == Math.abs(queens[i] - queens[t]): 当前第 t 个皇后与之前的第 i 个皇后在同一斜线上
                queens[i] == queens[t]: 当前第 t 个皇后与之前的第 i 个皇后在同一纵行上
            */
            if ((t - i) == Math.abs(queens[i] - queens[t]) || queens[i] == queens[t]) {
                return false;
            }
        }
        return true;
    }
}

3.2.基于数组的回溯

class Solution {

    //记录解决方案总数
    int count = 0;

    public int totalNQueens(int n) {
        Set<Integer> columns = new HashSet<Integer>();
        Set<Integer> diagonals1 = new HashSet<Integer>();
        Set<Integer> diagonals2 = new HashSet<Integer>();
        backtrack(n, 0, columns, diagonals1, diagonals2);
        return count;
    }

     /*
        n: 棋盘的大小,也就是皇后的数量
        row: 当前皇后正在放置的行数,初始值为 0
        columns: 存储之前放置的皇后所在的列
        diagonals1: 方向一的斜线,存储该斜线上皇后坐标的行下标与列下标之差
        diagonals2: 方向二的斜线,存储该斜线上皇后坐标的行下标与列下标之和
     */
    public void backtrack(int n, int row, Set<Integer> columns, Set<Integer> diagonals1, Set<Integer> diagonals2) {
        if (row == n) {
            count++;
            return;
        }
        //遍历当前第 row 个皇后在第 t 行的第 0 ~ n - 1 列
        for (int i = 0; i < n; i++) {
            if (columns.contains(i)) {
                //当前皇后所在的列已有其它皇后占据
                continue;
            }
            int diagonal1 = row - i;
            if (diagonals1.contains(diagonal1)) {
                //当前皇后所在的斜线(从左上往右下方向)有其它皇后占据
                continue;
            }
            int diagonal2 = row + i;
            if (diagonals2.contains(diagonal2)) {
                //当前皇后所在的斜线(从左下往右上方向)有其它皇后占据
                continue;
            }
            columns.add(i);
            diagonals1.add(diagonal1);
            diagonals2.add(diagonal2);
            //能够放在当前位置,开始判断下一个皇后
            backtrack(n, row + 1, columns, diagonals1, diagonals2);
            columns.remove(i);
            diagonals1.remove(diagonal1);
            diagonals2.remove(diagonal2);
        }
    }
}

3.3.基于位运算的回溯

class Solution {

    //记录解决方案总数
    int count = 0;

    public int totalNQueens(int n) {
        //调用回溯函数,从第 0 行开始放置皇后
        backtrack(n, 0, 0, 0, 0);
        return count;
    }

    public void backtrack(int n, int row, int columns, int diagonals1, int diagonals2) {
        if (row == n) {
            //找到了一个解决方案
            count++;
            return;
        }
        /*
        	计算可用的位置,假设 n = 8,columns、diagonals1 、diagonals2 的二进制表示分别如下:
        	0001 0100
        	0011 0000
        	0000 1001
			columns | diagonals1 | diagonals2: 其二进制表示中,所有值为 1 的位置均不可放置皇后,例如 0000 ... 0011 1101
			~(columns | diagonals1 | diagonals2): 其二进制表示中,所有值为 0 的位置均不可放置皇后,例如 1111 ... 1100 0010
			((1 << n) - 1) & (~(columns | diagonals1 | diagonals2)): 保证从低 0 ~ n - 1 位范围内所有值为 1 的位置才能放置皇后
		*/
        int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));
        //不断尝试可用的位置
        while (availablePositions != 0) {
            //取出最右边的可用位置放置皇后
            int position = availablePositions & (-availablePositions);
            //将该位置从可用位置中移除
            availablePositions = availablePositions & (availablePositions - 1);
            backtrack(n, row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);
        }
    }
}

4.扩展

如果需要求出具体每种解决方案,那么只需在找到每种解决方案时,记录一下即可,具体代码如下:

class Solution {

    List<List<String>> res = new ArrayList<>();

    public List<List<String>> solveNQueens(int n) {
        // queens[t] 表示第 t 个皇后放在棋盘的第 t 行的第 queens[t] 列 (0 ≤ t < n)
        int[] queens = new int[n];
        backtrack(queens, 0);
        return res;
    }

    public void backtrack(int[] queens, int t) {
        int n = queens.length;
        if (t == n) {
            List<String> tmp = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                StringBuilder builder = new StringBuilder();
                for (int j = 0; j < n; j++) {
                    if (j == queens[i]) {
                        builder.append('Q');
                    } else {
                        builder.append('.');
                    }
                }
                tmp.add(builder.toString());
            }
            res.add(tmp);
            return;
        }
        for (int i = 0; i < n; i++) {
            queens[t] = i;
            if (check(queens, t)) {
                backtrack(queens, t + 1);
            }
        }
    }

    public boolean check(int[] queens, int t) {
        for (int i = 0; i < t; i++) {
            if ((t - i) == Math.abs(queens[i] - queens[t]) || queens[i] == queens[t]) {
                return false;
            }
        }
        return true;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/223240.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

操作教程|JumpServer搭载RD Client App,让你的移动办公更轻松

随着信息技术的普及和发展&#xff0c;移动办公逐渐成为新的时代趋势。移动办公又被称为3A办公&#xff0c;即办公人员可在任何时间&#xff08;Anytime&#xff09;、任何地点 &#xff08;Anywhere&#xff09;处理与业务相关的任何事情&#xff08;Anything&#xff09;。 …

2024年软件测试面试八股文

前言 &#xff08;第一个就刷掉一大批人&#xff09; 有很多“会自动化”的同学来咨询技术问题&#xff0c;他总会问到我一些元素定位的问题。元素定位其实都不算自动化面试的问题。 一般我都会问&#xff1a;你是定位不到吗&#xff1f;通常结果都是说确实定位不到。 做自…

本地启动tomcat,打印的日志中中文乱码

修改配置文件 /conf/logging.properties 修改配置项 java.util.logging.ConsoleHandler.encoding 从UTF-8改成GBK

solidity案例详解(六)服务评价合约

有服务提供商和用户两类实体&#xff0c;其中服务提供商部署合约&#xff0c;默认诚信为true&#xff0c;用户负责使用智能合约接受服务及评价&#xff0c;服务提供商的评价信息存储在一个映射中&#xff0c;可以根据服务提 供商的地址来查找评价信息。用户评价信息&#xff0c…

今天刷basic

一 在kali里边链接这个服务器 ssh -p 25199 rootnode4.buuoj.cn 然后回车 yes 输入密码123456 ls查看发现什么都没有&#xff0c;cd ..返回上一级目录 ls 发现有flag.txt 查看文件得到flag flag{477f20d3-acd3-46e1-b50a-633e58b769c7}

【Vue3从入门到项目实现】RuoYi-Vue3若依框架前端学习——登录页面

若依官方的前后端分离版中&#xff0c;前端用的Vue2&#xff0c;这个有人改了Vue3的前端出来。刚好用来学习&#xff1a; https://gitee.com/weifengze/RuoYi-Vue3 运行前后端项目 首先运行项目 启动前端&#xff0c;npm install、npm run dev 启动后端&#xff0c;按教程配置…

35亿元!开源类ChatGPT平台Mistral AI,再获巨额融资

12月6日&#xff0c;彭博消息&#xff0c;开源类ChatGPT平台Mistral AI获得4.5亿欧元&#xff08;近35亿元&#xff09;融资&#xff0c;估值近20亿美元&#xff08;142亿元&#xff09;。本次由英伟达、 Salesforce等投资。 Mistral AI的开源大语言模型Mistral 7B主打参数小、…

Apache Doris 详细教程(一)

1、Doris简介 1.1、doris概述 Apache Doris 由百度大数据部研发&#xff08;之前叫百度 Palo&#xff0c;2018 年贡献到 Apache 社区后&#xff0c; 更名为 Doris &#xff09;&#xff0c;在百度内部&#xff0c;有超过 200 个产品线在使用&#xff0c;部署机器超过 1000 台…

PIKA,一个神奇的AI工具

随着人工智能技术的不断发展&#xff0c;越来越多的创新性工具开始涌现&#xff0c;为各行各业带来了巨大的变革。其中&#xff0c;视频生成AI工具PIKA&#xff0c;以其独特的功能和广泛的应用领域&#xff0c;吸引了众多用户的关注。本文将详细介绍PIKA的功能、特点以及应用前…

学习设计模式的网站

Refactoring and Design Patternshttps://refactoring.guru/

前端CSS(层叠样式表)总结

CSS2总结 一、CSS基础 1. CSS简介 CSS 的全称为&#xff1a;层叠样式表 ( Cascading Style Sheets ) 。CSS 也是一种标记语言&#xff0c;用于给 HTML 结构设置样式&#xff0c;例如&#xff1a;文字大小、颜色、元素宽高等等。 简单理解&#xff1a; CSS 可以美化…

运维之远程桌面连接失败问题排查

背景&#xff1a;同一局域网&#xff0c;可以ping通但是远程连接不上&#xff0c;排查一下问题。 1、被远程计算机是否允许远程连接 2、被远程计算机防火墙是否允许 3、被远程计算机远程桌面服务是否正常 4、查看用户权限

openGauss学习笔记-145 openGauss 数据库运维-备份与恢复-备份与恢复概述

文章目录 openGauss学习笔记-145 openGauss 数据库运维-备份与恢复-备份与恢复概述145.1 逻辑备份与恢复145.2 物理备份与恢复145.3 闪回恢复145.4 三种备份恢复类型对比145.5 备份方案与策略 openGauss学习笔记-145 openGauss 数据库运维-备份与恢复-备份与恢复概述 数据备份…

Webgis学习总结

前言&#xff1a; 作者跟随视频学习了webgis内容进行如下学习复习总结 参考&#xff1a;新中地学习笔记 WebGIS第一课&#xff1a;测试高德API并通过&#xff1a; 注册申请高德API成为开发者&#xff0c;创建自己的项目和key进行项目初始化&#xff0c;可以使用JS API官方文…

DateTimePicker之禁用当前日期时间之前的数据以及校验函数

1、禁用当前日期时间功能效果 2、需要用到的属性 disabledDate: 禁用日期。disabledTime: 禁用时间。 3、相关代码 fieldProps{{disabledDate(date) {return date && date < moment().startOf(day);},disabledTime: (date: any) > disabledTime(date),}}//相关…

【微服务】spring循环依赖深度解析

目录 一、循环依赖概述 1.2 spring中的循环依赖 二、循环依赖问题模拟 2.1 循环依赖代码演示 2.2 问题分析与解决 2.2.1 使用反射中间容器 三、spring循环依赖问题解析 3.1 spring中的依赖注入 3.1.1 field属性注入 3.1.2 setter方法注入 3.1.3 构造器注入 3.2 spri…

Allure生成测试报告这样生成,阿里p10都直呼牛逼

Allure是一个开源的测试报告生成框架&#xff0c;提供了测试报告定制化功能&#xff0c;相较于我们之前使用过pytest-html插件生成的html格式的测试报告&#xff0c;通过Allure生成的报告更加规范、清晰、美观。 pytest框架支持使用Allure生成测试报告&#xff0c;接下来让介绍…

Vue3 Element-Plus 一站式生成动态表单:简化前端开发流程

文章目录 1. 引言2. Vue3 和 Element-Plus 简介2.1 Vue32.2 Element-Plus 3. 动态表单的需求与挑战4. Vue3 和 Element-Plus 动态表单的优势4.1 Vue3的组合式API4.2 Element-Plus的表单组件 5. 一站式生成动态表单的实现5.1 准备工作5.2 创建动态表单组件5.3 使用动态表单组件 …

ELK实现日志收集

1.介绍 ELK是三个开源软件的缩写&#xff0c;分别表示&#xff1a;Elasticsearch , Logstash, Kibana , 它们都是开源软件。 Elasticsearch是个开源分布式搜索引擎&#xff0c;提供搜集、分析、存储数据三大功能。它的特点有&#xff1a;分布式&#xff0c;零配置&#xff0c…

Sprite Editor图片编辑器的使用_unity基础开发教程

Sprite Editor图片编辑器的使用 什么是Sprite Editor安装插件&#xff08;3D项目&#xff09;切片方式Automatic&#xff1a;自动切片Grid By Cell Size&#xff1a;按照像素大小进行切片Grid By Cell Count&#xff1a;按照个数进行切片Isometric Grid&#xff1a;等距网格切片…