N 皇后 II[困难]

一、题目

n皇后问题 研究的是如何将n个皇后放置在n × n的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数n,返回n皇后问题 不同的解决方案的数量。

示例 1:

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

示例 2:
输入:n = 1
输出:1

1 <= n <= 9

二、代码

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

回溯的具体做法是:依次在每一行放置一个皇后,每次新放置的皇后都不能和已经放置的皇后之间有攻击,即新放置的皇后不能和任何一个已经放置的皇后在同一列以及同一条斜线上。当N个皇后都放置完毕,则找到一个可能的解,将可能的解的数量加1

由于每个皇后必须位于不同列,因此已经放置的皇后所在的列不能放置别的皇后。第一个皇后有N列可以选择,第二个皇后最多有N−1列可以选择,第三个皇后最多有N−2列可以选择(如果考虑到不能在同一条斜线上,可能的选择数量更少),因此所有可能的情况不会超过N!种,遍历这些情况的时间复杂度是O(N!)。为了降低总时间复杂度,每次放置皇后时需要快速判断每个位置是否可以放置皇后,显然,最理想的情况是在O(1)的时间内判断该位置所在的列和两条斜线上是否已经有皇后。

以下两种方法分别使用集合和位运算对皇后的放置位置进行判断,都可以在O(1)的时间内判断一个位置是否可以放置皇后,算法的总时间复杂度都是O(N!)

【1】基于集合的回溯: 为了判断一个位置所在的列和两条斜线上是否已经有皇后,使用三个集合columnsdiagonals1diagonals2分别记录每一列以及两个方向的每条斜线上是否有皇后。列的表示法很直观,一共有N列,每一列的下标范围从0N−1,使用列的下标即可明确表示每一列。如何表示两个方向的斜线呢?对于每个方向的斜线,需要找到斜线上的每个位置的行下标与列下标之间的关系。

方向一的斜线为从左上到右下方向,同一条斜线上的每个位置满足行下标与列下标之差相等,例如(0,0)(3,3)在同一条方向一的斜线上。因此使用行下标与列下标之差即可明确表示每一条方向一的斜线。
方向二的斜线为从右上到左下方向,同一条斜线上的每个位置满足行下标与列下标之和相等,例如(3,0)(1,2)在同一条方向二的斜线上。因此使用行下标与列下标之和即可明确表示每一条方向二的斜线。

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

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

    public int backtrack(int n, int row, Set<Integer> columns, Set<Integer> diagonals1, Set<Integer> diagonals2) {
        if (row == n) {
            return 1;
        } else {
            int count = 0;
            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);
                count += backtrack(n, row + 1, columns, diagonals1, diagonals2);
                columns.remove(i);
                diagonals1.remove(diagonal1);
                diagonals2.remove(diagonal2);
            }
            return count;
        }
    }
}

时间复杂度: O(N!),其中N是皇后数量。
空间复杂度: O(N),其中N是皇后数量。空间复杂度主要取决于递归调用层数以及三个集合,递归调用层数不会超过N,每个集合的元素个数都不会超过N

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

具体做法是,使用三个整数columnsdiagonals1diagonals2分别记录每一列以及两个方向的每条斜线上是否有皇后,每个整数有N个二进制位。棋盘的每一列对应每个整数的二进制表示中的一个数位,其中棋盘的最左列对应每个整数的最低二进制位,最右列对应每个整数的最高二进制位。如果要在下一行放置皇后,哪些位置不能放置呢?我们用0代表可以放置皇后的位置,1代表不能放置皇后的位置。新放置的皇后不能和任何一个已经放置的皇后在同一列,因此不能放置在第2列和第4列,对应columns=00010100(2)​。

新放置的皇后不能和任何一个已经放置的皇后在同一条方向一(从左上到右下方向)的斜线上,因此不能放置在第4列和第5列,对应diagonals1=00110000(2)​。其中,第4列为其前两行的第2列的皇后往右下移动两步的位置,第5列为其前一行的第4列的皇后往右下移动一步的位置。新放置的皇后不能和任何一个已经放置的皇后在同一条方向二(从右上到左下方向)的斜线上,因此不能放置在第0列和第3列,对应diagonals2=00001001(2)​。其中,第0列为其前两行的第2列的皇后往左下移动两步的位置,第3列为其前一行的第4列的皇后往左下移动一步的位置。

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

每次放置皇后时,三个整数的按位或运算的结果即为不能放置皇后的位置,其余位置即为可以放置皇后的位置。可以通过(2n−1) & (∼(columns∣diagonals1∣diagonals2))得到可以放置皇后的位置(该结果的值为1的位置表示可以放置皇后的位置),然后遍历这些位置,尝试放置皇后并得到可能的解。

遍历可以放置皇后的位置时,可以利用以下两个按位与运算的性质:
1、x & (−x)可以获得x的二进制表示中的最低位的1的位置;
2、x & (x−1)可以将x的二进制表示中的最低位的1置成0

具体做法是,每次获得可以放置皇后的位置中的最低位,并将该位的值置成0,尝试在该位置放置皇后。这样即可遍历每个可以放置皇后的位置。

class Solution {
    public int totalNQueens(int n) {
        return solve(n, 0, 0, 0, 0);
    }

    public int solve(int n, int row, int columns, int diagonals1, int diagonals2) {
        if (row == n) {
            return 1;
        } else {
            int count = 0;
            int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));
            while (availablePositions != 0) {
                int position = availablePositions & (-availablePositions);
                availablePositions = availablePositions & (availablePositions - 1);
                count += solve(n, row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);
            }
            return count;
        }
    }
}

时间复杂度: O(N!),其中N是皇后数量。
空间复杂度: O(N),其中N是皇后数量。由于使用位运算表示,因此存储皇后信息的空间复杂度是O(1),空间复杂度主要取决于递归调用层数,递归调用层数不会超过N

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

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

相关文章

Rust学习笔记005:结构体 struct

在 Rust 中&#xff0c;struct 是一种用于创建自定义数据类型的关键字&#xff0c;它允许你定义和组织数据的结构。struct 可以包含多个不同类型的字段&#xff08;fields&#xff09;&#xff0c;每个字段都有一个名称和一个类型。 定义结构体 下面是一个简单的例子&#xff…

基于JAVA的酒店管理系统设计

摘 要 我们对酒店进行调研&#xff0c;发现部分酒店依托第三方平台&#xff0c;但第三方平台没有办法更好帮助酒店管理&#xff0c;故我们决定帮助酒店开发一套基于 Java 的酒店管理系统。使用基于Java的酒店管理系统可以帮助酒店完成顾客入住信息的管理&#xff0c;酒店物资的…

构建高效数据流转的 ETL 系统:数据库 + Serverless 函数计算的最佳实践

作者&#xff1a;柳下 概述 随着企业规模和数据量的增长&#xff0c;数据的价值越来越受到重视。数据的变化和更新变得更加频繁和复杂&#xff0c;因此及时捕获和处理这些变化变得至关重要。为了满足这一需求&#xff0c;数据库 CDC&#xff08;Change Data Capture&#xff…

2022年全球运维大会(GOPS上海站)-核心PPT资料下载

一、峰会简介 GOPS 主要面向运维行业的中高端技术人员&#xff0c;包括运维、开发、测试、架构师等群体。目的在于帮助IT技术从业者系统学习了解相关知识体系&#xff0c;让创新技术推动社会进步。您将会看到国内外知名企业的相关技术案例&#xff0c;也能与国内顶尖的技术专家…

【大数据面试知识点】Spark中的累加器

Spark累加器 累加器用来把Executor端变量信息聚合到Driver端&#xff0c;在driver程序中定义的变量&#xff0c;在Executor端的每个task都会得到这个变量的一份新的副本&#xff0c;每个task更新这些副本的值后&#xff0c;传回driver端进行merge。 累加器一般是放在行动算子…

解决相机库CameraView多滤镜拍照错乱的BUG (一) : 复现BUG

1. 前言 这段时间&#xff0c;在使用 natario1/CameraView 来实现带滤镜的预览、拍照、录像功能。 由于CameraView封装的比较到位&#xff0c;在项目前期&#xff0c;的确为我们节省了不少时间。 但随着项目持续深入&#xff0c;对于CameraView的使用进入深水区&#xff0c;逐…

lambda表达式和包装器

正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 我们在使用库里的排序算法时如果排序的是自定义类型或者库里默认的排序不能满足我们则需求&…

【单片机项目实战】温度控制系统

本项目的主要作用是实现温度调控&#xff0c;通过设定一个预定的温度值&#xff0c;实现实时检测外界温度&#xff0c;当外界温度小于预定值时&#xff0c;电机正转&#xff0c;实现降温效果&#xff1b;当外界温度大于预定值时&#xff0c;电机反转&#xff0c;实现升温效果&a…

网络安全—PKI公钥基础设施

文章目录 前提知识散列函数非对称加密数字签名 PKI受信任的人RA注册CA颁发IKE数字签名认证&#xff08;交换证书&#xff09;密钥管理 前提知识 散列函数 散列也可以叫哈希函数&#xff0c;MD5、SHA-1、SHA-2、、&#xff08;不管叫啥&#xff0c;都记得是同一个东西就行&…

分库分表之Mycat应用学习五

5 Mycat 离线扩缩容 当我们规划了数据分片&#xff0c;而数据已经超过了单个节点的存储上线&#xff0c;或者需要下线节 点的时候&#xff0c;就需要对数据重新分片。 5.1 Mycat 自带的工具 5.1.1 准备工作 1、mycat 所在环境安装 mysql 客户端程序。 2、mycat 的 lib 目录…

mac上使用Navicat Premium 在本地和生产环境中保持数据库同步

Navicat Premium 是一款功能强大的数据库管理和开发工具&#xff0c;支持多种数据库系统&#xff0c;如 MySQL、Oracle、SQL Server 等。作为程序员&#xff0c;我深知在开发过程中需要一款方便、高效的数据库管理工具来提升工作效率。而 Navicat Premium 正是这样一款不可多得…

Spring5注解驱动(六)

5. 自动装配 5.1. Autowired&Qualifier&Primary 在原来&#xff0c;我们就是使用Autowired的这个注解来进行自动装配&#xff1b; 现在有一个BookController 类 package com.hutu.controller;import com.hutu.service.BookService; import org.springframework.bea…

2023最新租号平台系统源码支持单独租用或合租使用

这是一款租号平台源码&#xff0c;采用常见的租号模式。目前网络上还很少见到此类类型的源码。 平台的主要功能如下&#xff1a; 支持单独租用或采用合租模式&#xff1b; 采用易支付通用接口进行支付&#xff1b; 添加邀请返利功能&#xff0c;以便站长更好地推广&#xf…

基于蚁狮算法优化的Elman神经网络数据预测 - 附代码

基于蚁狮算法优化的Elman神经网络数据预测 - 附代码 文章目录 基于蚁狮算法优化的Elman神经网络数据预测 - 附代码1.Elman 神经网络结构2.Elman 神经用络学习过程3.电力负荷预测概述3.1 模型建立 4.基于蚁狮优化的Elman网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针…

逻辑卷学习后续----------缩容

一、缩容&#xff1a;缩减大小 ext4可以 &#xff0c; xfs无法缩减&#xff0c;缩减会影响业务 1.解挂载 2.检查文件系统完整性 3.缩减文件系统 4.缩减逻辑卷上下一致 5.再挂载回去 添加磁盘 文件系统只能装ext4 缩减文件系统 resize2fs 挂载失败需要重新安装文件系统…

磁盘阵列(RAID)

1.独立硬盘冗余阵列&#xff08;RAID, Redundant Array of Independent Disks&#xff09; 旧称廉价磁盘冗余阵列&#xff08;Redundant Array of Inexpensive Disks&#xff09;&#xff0c;简称磁盘阵列 用虚拟化存储技术把多个硬盘组合起来&#xff0c;成为一个或多个硬盘阵…

[C#]使用ONNXRuntime部署一种用于边缘检测的轻量级密集卷积神经网络LDC

源码地址&#xff1a; github.com/xavysp/LDC LDC: Lightweight Dense CNN for Edge Detection算法介绍&#xff1a; 由于深度学习方法的快速发展&#xff0c;近年来&#xff0c;用于执行图像边缘检测的卷积神经网络&#xff08;CNN&#xff09;模型爆炸性地传播。但边缘检测…

Selenium教程04:鼠标+键盘网页的模拟操作

在webdriver 中&#xff0c;鼠标操作都封装在ActionChains类中&#xff0c;使用的时候需要导入这个包。 from selenium.webdriver import ActionChainsActionChains方法列表如下&#xff1a; click(on_elementNone) ——单击鼠标左键click_and_hold(on_elementNone) ——点击…

开始使用MEVN技术栈开发02 MongoDB介绍

开始使用MEVN技术栈开发02 MongoDB介绍 MongoDB介绍 As indicated by the ‘ M ’ in MEVN, we will use MongoDB as the backend database for our app. MongoDB is a NoSQL database. Before we talk about what is a NoSQL database, let ’ s first talk about relationa…

[每周一更]-(第48期):一名成熟Go开发需储备的知识点(问题篇)- 1

问题篇 1、Go语言基础知识 什么是Go语言&#xff1f;它有哪些特点&#xff1f;Go语言的数据类型有哪些&#xff1f;Goroutine是什么&#xff1f;它与线程的区别是什么&#xff1f;介绍一下Go语言的垃圾回收机制。 2、并发和并行 什么是并发和并行&#xff1f;它们之间的区别…