python-leetcode 61.N皇后

news2025/4/3 17:27:57

题目:

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

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

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

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


每个皇后必须位于不同行和不同列,因此将 N 个皇后放置在 N×N 的棋盘上,一定是每一行有且仅有一个皇后,每一列有且仅有一个皇后,且任何两个皇后都不能在同一条斜线上。基于上述发现,可以通过回溯的方式寻找可能的解。

回溯的具体做法是:使用一个数组记录每行放置的皇后的列下标,依次在每一行放置一个皇后。每次新放置的皇后都不能和已经放置的皇后之间有攻击:即新放置的皇后不能和任何一个已经放置的皇后在同一列以及同一条斜线上,并更新数组中的当前行的皇后列下标。当 N 个皇后都放置完毕,则找到一个可能的解。当找到一个可能的解之后,将数组转换成表示棋盘状态的列表,并将该棋盘状态的列表加入返回列表。

由于每个皇后必须位于不同列,因此已经放置的皇后所在的列不能放置别的皇后。第一个皇后有 N 列可以选择,第二个皇后最多有 N−1 列可以选择,第三个皇后最多有 N−2 列可以选择(如果考虑到不能在同一条斜线上,可能的选择数量更少),因此所有可能的情况不会超过 N! 种,遍历这些情况的时间复杂度是 O(N!)。

为了降低总时间复杂度,每次放置皇后时需要快速判断每个位置是否可以放置皇后,显然,最理想的情况是在 O(1) 的时间内判断该位置所在的列和两条斜线上是否已经有皇后。

方法一:基于集合的回溯

为了判断一个位置所在的列和两条斜线上是否已经有皇后,使用三个集合 columns、diagonals 
1和 diagonals2分别记录每一列以及两个方向的每条斜线上是否有皇后。

列的表示法很直观,一共有 N 列,每一列的下标范围从 0 到 N−1,使用列的下标即可明确表示每一列。

如何表示两个方向的斜线呢?对于每个方向的斜线,需要找到斜线上的每个位置的行下标与列下标之间的关系。

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

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

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

class Solution(object):
    def solveNQueens(self, n):
        """
        :type n: int
        :rtype: List[List[str]]
        """
        def generateBoard():  #生成棋盘
            board=[]  #用于存储棋盘的每一行
            for i in range(n):  #遍历所有行,按queens[i]记录的位置放至Q
                row[queens[i]]="Q"  #row 是 [".", ".", ".", "."](初始化的空白行)
                #queens[i]是皇后在当前行i的索引
                #在queens[i]位置放Q,queens[0] = 1(表示皇后在第 0 行的 第 1 列)

                #row = [".", "Q", ".", "."]

                board.append("".join(row))#将列表转换为字符串,作为棋盘格的一行
                row[queens[i]]="."  #恢复row为初始状态
            return board  #作为当前皇后排列的字符串表示
        def dfs(row):  #当前正在处理的行号(从 0 到 n-1)
            if row==n:  #所有行都放置完毕
                board=generateBoard() # 生成一个合法的棋盘
                solutions.append(board) #保存
            else:
                for i in range(n): #遍历当前行 row 的所有列 i
                    if i in columns or row-i in diagonal1 or row+i in diagonal2:#皇后的列, 记录右下↘对角线 ,记录左下↙对角线
                        continue #若 i 被占用,直接跳过该列
                    queens[row]=i  #录当前行皇后放置的列号
                    columns.add(i) #记录当前列被占用
                    diagonal1.add(row-i)#记录对角线被占用
                    diagonal2.add(row+i) #记录对角线被占用
                    dfs(row+1)#递归尝试下一行的皇后摆放
                    columns.remove(i)#回溯,撤销当前位置的皇后
                    diagonal1.remove(row-i)#回溯,撤销对角线的占用状态
                    diagonal2.remove(row+i)
        solutions=[] #存储所有合法的 N 皇后解法
        queens=[-1]*n #记录每一行皇后的位置,初始化-1表示未放置
        columns=set([])#记录被占用的列
        diagonal1=set([])
        diagonal2=set([])
        row=["."]*n         #用于构造棋盘,初始时所有行都是 "...." 
        dfs(0)
        return solutions

时间复杂度:O(N!)其中 N 是皇后数量

空间复杂度:O(N)


方法二:基于位运算的回溯

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

具体做法是,使用三个整数 columns、diagonals 1 和 diagonals 2分别记录每一列以及两个方向的每条斜线上是否有皇后,

每个整数有 N 个二进制位。棋盘的每一列对应每个整数的二进制表示中的一个数位,其中棋盘的最左列对应每个整数的最低二进制位,最右列对应每个整数的最高二进制位。用 0 代表可以放置皇后的位置,1 代表不能放置皇后的位置。

棋盘的边长和皇后的数量 N=8。如果棋盘的前两行分别在第 2 列和第 4 列放置了皇后(下标从 0 开始),则棋盘的前两行如下图所示。

如果要在下一行放置皇后,哪些位置不能放置呢?我们用 0 代表可以放置皇后的位置,1 代表不能放置皇后的位置。新放置的皇后不能和任何一个已经放置的皇后在同一列,因此不能放置在第 2 列和第 4 列,对应 columns=00010100(2)​。

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

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

由此可以得到三个整数的计算方法:

  • 初始时,三个整数的值都等于 0,表示没有放置任何皇后
  • 在当前行放置皇后,如果皇后放置在第 i 列,则将三个整数的第 i 个二进制位(指从低到高的第 i 个二进制位)的值设为 1
  • 进入下一行时,columns 的值保持不变,diagonals1​ 左移一位,diagonals2​ 右移一位,

    由于棋盘的最左列对应每个整数的最低二进制位,即每个整数的最右二进制位,因此对整数的移位操作方向和对棋盘的移位操作方向相反(对棋盘的移位操作方向是 diagonals 1 右移一位,diagonals 2​左移一位)。

 每次放置皇后时,三个整数的按位或运算的结果即为不能放置皇后的位置,其余位置即为可以放置皇后的位置。

class Solution(object):
    def solveNQueens(self, n):
        """
        :type n: int
        :rtype: List[List[str]]
        """

        def generateBoard():  # 生成当前解对应的棋盘布局
            board = []  # 创建一个空列表用于存储最终的棋盘解法
            for i in range(n):
                row[queens[i]] = "Q"
                board.append("".join(row))
                row[queens[i]] = "."
            return board

        def solve(row, columns, diagonals1, diagonals2):  # 当前正在放置皇后的行号, 已被占据的列,两条对角线
            if row == n:  # 递归终止条件,说明所有皇后已放置完毕
                board = generateBoard()  # 生成棋盘布局,并存入 solutions
                solutions.append(board)
            else:
                # (1 << n) - 1 生成 n 位全 1,表示所有列都可用,并计算当前可选的列
                availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2))
                while availablePositions:  # 遍历所有可选的位置
                    position = availablePositions & (-availablePositions)  # 取 availablePositions 的最低位 1,即当前可选的最左侧列
                    availablePositions = availablePositions & (availablePositions - 1)  # 移除当前选择的位置,以便下次循环选择下一个位置
                    column = bin(position - 1).count("1")  # 计算当前皇后应放置的列索引,统计 1 的个数,得到列索引
                    queens[row] = column  # 记录 row 行的皇后放置在 column 列
                    solve(row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1)  # 递归进入下一行,更新列和主副对角线

        solutions = []  # 存储所有可能的 N 皇后解法
        queens = [-1] * n  # 记录每行皇后的列索引,初始化为 -1 表示未放置
        row = ["."] * n  # 构造棋盘行,初始时所有单元格都是 "."
        solve(0, 0, 0, 0)  # 递归从第 0 行开始尝试放置皇后,初始时所有列和对角线都是可用的
        return solutions

时间复杂度:O(N!)

空间复杂度:O(N)

作者:力扣官方题解
 


 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

产教融合|暴雨技术专家执裁江苏省职业院校技能大赛

3月28-30日&#xff0c;由江苏省教育厅、省发改委、省工信厅等15家单位主办的2025年江苏省职业院校技能大赛网络系统管理赛项如期举办。此次赛事吸引了全省52支参赛队伍、156名选手踊跃参与&#xff0c;参赛人数再创新高。 暴雨信息技术专家李明宇作为此赛项的往届省赛冠军&am…

BUUCTF-web刷题篇(6)

15.PHP 知识点&#xff1a; ①__wakeup()//将在反序列化之后立即调用&#xff08;当反序列化时变量个数与实际不符是会绕过&#xff09;我们可以通过一个cve来绕过:CVE-2016-7124。将Object中表示数量的字段改成比实际字段大的值即可绕过wakeup函数。条件&#xff1a;PHP5<…

周总结aa

上周学习了Java中有关字符串的内容&#xff0c;与其有关的类和方法 学习了static表示静态的相关方法和类的使用。 学习了继承(extends) 多态&#xff08;有继承关系&#xff0c;有父类引用指向子类对象&#xff09; 有关包的知识&#xff0c;final关键字的使用&#xff0c;及有…

31天Python入门——第17天:初识面向对象

你好&#xff0c;我是安然无虞。 文章目录 面向对象编程1. 什么是面向对象2. 类(class)3. 类的实例关于self 4. 对象的初始化5. __str__6. 类之间的关系继承关系组合关系 7. 补充练习 面向对象编程 1. 什么是面向对象 面向对象编程是一种编程思想,它将现实世界的概念和关系映…

计算机视觉准备八股中

一边记录一边看&#xff0c;这段实习跑路之前运行完3DGAN&#xff0c;弄完润了&#xff0c;现在开始记忆八股 1.CLIP模型的主要创新点&#xff1a; 图像和文本两种不同模态数据之间的深度融合、对比学习、自监督学习 2.等效步长是每一步操作步长的乘积 3.卷积层计算输入输出…

【C语言】文件操作(2)

一、文件的随机读写 在前面我们学习了文件的顺序读写的函数&#xff0c;那么当我们要读取某个指定位置的内容的时候&#xff0c;是否只能顺序的读取到这个内容&#xff1f;还有在对文件进行输入的时候&#xff0c;需要对指定的位置进行写入&#xff0c;那么此时应该怎么办呢&a…

CCCC天梯赛L1-094 剪切粘贴

题目链接&#xff1a; 字符串函数&#xff1a; 1、截取字符串&#xff1a; //起始位置为3,结束位置为5string s "aabcdefg";//下标从0开始 [从开始位置,结束位置]string sub s.substr(3,3);//输出cde, 有返回值string//并且原字符串不改变&#xff0c; s"aab…

某地81栋危房自动化监测试点项目

1. 项目简介 房屋进入老龄化阶段后&#xff0c;结构安全风险越来越大。近10年来&#xff0c;每年都会产生房屋倒塌人员伤亡的重大安全事故。调研分析显示&#xff0c;老旧房屋结构安全风险管理的有效路径为&#xff0c;通过“人防技防”的组合模式&#xff0c;对房屋安全风险进…

远程装个Jupyter-AI协作笔记本,Jupyter容器镜像版本怎么选?安装部署教程

通过Docker下载Jupyter镜像部署&#xff0c;输入jupyter会发现 有几个版本&#xff0c;不知道怎么选&#xff1f;这几个版本有什么差别&#xff1f; 常见版本有&#xff1a; jupyter/base-notebookjupyter/minimal-notebookjupyter/scipy-notebookjupyter/datascience-notebo…

山东大学软件学院项目创新实训开发日志(4)之中医知识问答数据存储、功能结构、用户界面初步设计

目录 数据库设计&#xff1a; 功能设计&#xff1a; 用户界面: 数据库设计&#xff1a; --对话表 (1个对话包含多条消息) CREATE TABLE conversations ( conv_id VARCHAR(36) PRIMARY KEY, -- 对话ID user_id VARCHAR(36) NOT NULL, -- 所属用户 title VARCHAR(100), -- 对话…

20.思科交换机二层链路聚合的详细配置命令解析

思科交换机二层链路聚合的详细配置命令解析 一、PAgP协议的配置SW1的配置SW2的配置二、LACP标准协议三、配置聚合组的带宽和速率四、确保所有接口的双工模式和速率一致五、故障排除和监控在Cisco设备上配置链路聚合(也称为端口通道或EtherChannel)可以增强网络连接的带宽和可…

【多线程】单例模式和阻塞队列

目录 一.单例模式 1. 饿汉模式 2. 懒汉模式 二.阻塞队列 1. 阻塞队列的概念 2. BlockingQueue接口 3.生产者-消费者模型 4.模拟生产者-消费者模型 一.单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是一种常用的软件设计模式&#xff0c;其核心思想是确保…

Qt5.14.2+Cmake使用mingw64位编译opencv4.5成功图文教程

​ 一、下载安装相关编译环境软件 1.1 Python3.8&#xff1a;安装路径:C:\Users\Administrator\AppData\Local\Programs\Python\Python38-32 安装包&#xff1a;python3.8.exe 1.2 QT5.14.2&#xff1a;安装路径:C:\Qt\Qt5.14.2 1.3 opencv4.5&#xff1a;解压路径D:\o…

Mamba4D阅读

CVPR 2025 创新 基于transformer的4D主干由于其二次复杂度而通常存在较大的计算成本&#xff0c;特别是对于长视频序列。 开发了帧内空间Mamba模块&#xff0c;建立时空相关性。 GPU占用和速度很有优势。 代码还没发。 Pipeline 输入点云序列&#xff0c;根据超参数构建点管…

手工排查后门木马的常用姿势

声明&#xff01;本文章所有的工具分享仅仅只是供大家学习交流为主&#xff0c;切勿用于非法用途&#xff0c;如有任何触犯法律的行为&#xff0c;均与本人及团队无关&#xff01;&#xff01;&#xff01; 1. 检查异常文件 &#xff08;1&#xff09;查找最近修改的文件 # 查…

算法导论(动态规划)——简单多状态

算法思路&#xff08;17.16&#xff09; 状态表示&#xff1a; 在处理线性动态规划问题时&#xff0c;我们可以通过“经验 题目要求”来定义状态表示。通常有两种选择&#xff1a; 以某个位置为结尾的情况&#xff1b;以某个位置为起点的情况。 本题中&#xff0c;我们选择更常…

LeetCode 438. 找到字符串中所有字母的异位词

438. 找到字符串中所有字母的异位词 题目描述 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 输入输出示例及数据范围 思路 这道题的思路其实很简单&#xff0c;就是一个滑动窗口的裸题&a…

java详细笔记总结持续完善

一.Java开发环境的搭建 1. 单位换算 1TB 1024GB 1GB 1024MB 1MB 1024KB 1KB 1024Byte (字节) 1Byte 8 bit(位) 注意&#xff1a;一个字节占8位 2. DOS命令 DOS : Disk Operation System 磁盘操作系统 即用于操作本地磁盘的系统 命令操作符号盘符切换命令盘符名:查看当前文…

wsl2的centos7安装jdk17、maven

JDK安装 查询系统中的jdk rpm -qa | grep java按照查询的结果&#xff0c;删除对应版本 yum -y remove java-1.7.0-openjdk*检查是否删除 java -version 下载JDK17 JDK17&#xff0c;下载之后存到wsl目录下&#xff08;看你自己&#xff09;然后一键安装 sudo rpm -ivh jd…

乐鑫ESP-Mesh-Lite方案,启明云端乐鑫代理商,创新组网拓展智能应用边界

在当今智能化浪潮的背景下&#xff0c;智能家居、智能农业、能源管理等领域对设备组网的需求日益增长。然而&#xff0c;传统的Wi-Fi组网方式常常受限于设备数量、路由器位置以及网络覆盖范围等因素&#xff0c;难以满足复杂场景下的多样化需求。 一方面&#xff0c;需要支持更…