4月18日N皇后+解数独

news2024/12/23 14:09:24

51.N皇后

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

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

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

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

思路

直观的做法是暴力枚举,把皇后放在每一个位置的情况都考虑一遍,并去掉那些有皇后在同一行或同一列或同一斜线上的情况。但是暴力枚举时间复杂度过高,所以需要进行优化。

因为每个皇后必须位于不同行不同列,因此每一行有且仅有一个皇后,每一列有且仅有一个皇后,并且每两个皇后都不能处在同一斜线上。

基于上述发现,我们可以使用回溯法来求解该问题。

使用一个数组记录每行皇后放置的位置的列下标,依次在每一行放置一个皇后,并且该皇后不能与已有的皇后有相互攻击关系:即新皇后不能和任何一个已有的皇后处在同一列以及同一斜线上。当N个皇后都放置完毕,则找到一个可能的解,并将数组转换成答案要求的列表格式。

方法一 使用集合判断一个位置所在列和两条斜线上是否已经有皇后

使用三个集合columns、d1和d2分别记录每一列以及两个方向上的每条写线上是否有皇后。

列的表示用当前列的下标即可。

难点在于斜线如何表示,通过观察可以发现,从左上到右下的斜线中,行下表-列下标始终不变;而左下到右上的斜线中,行下标+列下标始终不变,如果在一个位置放了一个皇后,那么我们就把列下标、行下标-列下标、行下标+列下标的值分别存入columns、d1、d2三个集合中,在下一行的递归过程里,若遇到集合中已存在的位置则直接跳过。

class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> solutions = new ArrayList<List<String>>();
        int[] queens = new int[n];
        Arrays.fill(queens, -1);
        Set<Integer> columns = new HashSet<Integer>();
        Set<Integer> diagonals1 = new HashSet<Integer>();
        Set<Integer> diagonals2 = new HashSet<Integer>();
        backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);
        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++) {
                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;
                columns.add(i);
                diagonals1.add(diagonal1);
                diagonals2.add(diagonal2);
                backtrack(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) {
        List<String> board = new ArrayList<String>();
        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;
    }
}

方法二 使用位运算来节省空间开销

这个方法很妙啊,既运用了位运算又结合了棋盘结构,非常的灵活。

具体做法是,使用三个整数columns、d1、d2来记录每一列以及两个方向上的每条斜线上是否有皇后,每个整数有N个二进制位,棋盘左边对应整数最低二进制位。我当时就想,用这样的d1和d2怎样表示斜线上的皇后呢?原来只要在当前行落子之后,将落子位置记录在d1和d2上,然后d1左移一位,d2右移一位,因为期盼左边对应最低位,所以实际操作中相当于d1相对棋盘右移一位,d2左移一位,而d1代表左上到右下斜线,d2代表左下到右上斜线,所以这样就把当前行的落子在下一行的攻击位占好了。

此外,我们可以使用(2n-1)&(~(columns|d1|d2)得到每一行可以放置皇后的位置(1<<n-1创建一个全为可放置棋子的行,column、diagonals1、diagonals2表示所有已占用位置,1表示占用,所以取反,与1<<n-1与运算得到当前行可放置棋子位置,1为可放置)。然后遍历这些位置,找到可能的解。

遍历这些位置时,可以利用以下两个按位与运算的性质:

x&(-x)可以获得x的二进制表示中的最低位的1的位置(原理是补码)

x&(x-1)可以将x最低位的1置0.

具体代码如下

class Solution {
        public List<List<String>> solveNQueens(int n) {
            List<List<String>> solutions=new ArrayList<List<String>>();
            int[] queens=new int[n];
            Arrays.fill(queens,-1);
            dfs(solutions,queens,n,0,0,0,0);
            return solutions;
        }
        public void dfs(List<List<String>> solutions,int[] queens,int n,int row,int columns,int diagonals1,int diagonals2){
            if(row==n){
                List<String> board=generateBoard(queens,n);
                solutions.add(board);
            }else{
                int available=((1<<n)-1)&(~(columns|diagonals1|diagonals2));//1<<n-1创建一个全为可放置棋子的行,column、diagonals1、diagonals2表示所有已占用位置,1表示占用,所以取反
                //与1<<n-1与得到当前行可放置棋子位置,1为可放置。
                //若available为0,则当前行已经没有可以放置的位置
                while(available!=0){
                    int position=available&(-available);//原理是负数使用二进制补码表示
                    available=available&(available-1);//将最低位的1置0表示已经考虑过
                    int column=Integer.bitCount(position-1);//计算前面有几个1
                    queens[row]=column;
                    dfs(solutions,queens,n,row+1,columns|position,(diagonals1|position)<<1,(diagonals2|position)>>1);
                    queens[row]=-1;
                }
            }
        }
        public List<String> generateBoard(int[] queens,int n){
            List<String> board=new ArrayList<String>();
            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;
        }
    }

37.解数独

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:

输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:

思路 

通过递归加回溯的方法按行遍历空白格,将能够填入的数字依次填入,当递归到最后一个空白格后,如果依然没有冲突,说明找到了答案,如果填不了任何一个数字,则进行回溯。

由于每个数字在同一行、同一列、同一九宫格中只会出现一次,因此我们可以使用line[i],column[j],block[x][y]来表示第i行,第y列,第(x,y)个九宫中填写数字的情况。九宫格的范围为0<=x<=2以及0<=y<=2,具体地,第i行第y列的格子位于第([i/3],[y/3])个九宫格中,其中除法向下取整。

方法一 回溯

使用一个数组记录每个数字是否出现,比如用line[2][3]=true表示数字4在第二行已经出现过。

我们首先先对整个数组进行遍历,当遍历到i行j列时:

如果为空白格,那么我们将其加入一个存储空白格位置的列表中,方便后续递归操作

如果是一个数字x那么将line[i][x-1],column[j][x-1],block[i/3][j/3][x-1]均置为true。

结束遍历之后开始递归枚举,当枚举到spaces最后一个元素后,返回答案。

当枚举到i行j列时,枚举填入的数字x,根据要求,此时line[i][x-1],column[j][x-1],block[i/3][j/3][x-1]必须均为false。

填入数字后,将它们置为true,并递归下一个空白格位置,并再将它们置为false。

    class Solution {
        private boolean[][] line=new boolean[9][9];
        private boolean[][] column=new boolean[9][9];
        private boolean[][][] block=new boolean[3][3][9];
        private boolean valid=false;
        private List<int[]> spaces=new ArrayList<int[]>();
        public void solveSudoku(char[][] board) {
            for(int i=0;i<9;i++){
                for(int j=0;j<9;j++){
                    if(board[i][j]=='.'){
                        spaces.add(new int[]{i,j});
                    }else{
                        int digit=board[i][j]-'0'-1;
                        line[i][digit]=column[j][digit]=block[i/3][j/3][digit]=true;
                    }
                }
            }
            dfs(board,0);
        }
        public void dfs(char[][] board,int pos){
            if(pos==spaces.size()){
                valid=true;
                return;
            }
            int[] space=spaces.get(pos);
            int i=space[0],j=space[1];
            for(int digit=0;digit<9&&!valid;digit++){
                if(!line[i][digit]&&!column[j][digit]&&!block[i/3][j/3][digit]){
                    line[i][digit]=column[j][digit]=block[i/3][j/3][digit]=true;
                    board[i][j]=(char)(digit+'0'+1);
                    dfs(board,pos+1);
                    line[i][digit]=column[j][digit]=block[i/3][j/3][digit]=false;
                }
            }
        }
    }

方法二 位运算优化

与n皇后优化思路相同,仅使用一个整数来表示某行、某列或者某九宫格是否有某个数字出现过。

具体地,b的二进制表示的第i位为1,当且仅当数字i+1已经出现过。

定义一个函数flip,用于将数digit填入i行j列,该方法用以将line[i]的第digit位置为1,表示填入。

public void flip(int i,int j,int digit){
            line[i]^=(1<<digit);
            column[j]^=(1<<digit);
            block[i/3][j/3]^=(1<<digit);
        }

其他思路与n皇后相似

    class Solution {
        private int[] line=new int[9];
        private int[] column=new int[9];
        private int[][] block=new int[3][3];
        private boolean valid=false;
        private List<int[]> spaces=new ArrayList<int[]>();
        public void solveSudoku(char[][] board) {
            for(int i=0;i<9;i++){
                for(int j=0;j<9;j++){
                    if(board[i][j]=='.'){
                        spaces.add(new int[]{i,j});
                    }else{
                        int digit=board[i][j]-'0'-1;
                        flip(i,j,digit);
                    }
                }
            }
            dfs(board,0);
        }
        public void dfs(char[][] board,int pos){
            if(pos==spaces.size()){
                valid=true;
                return;
            }
            int[] space=spaces.get(pos);
            int i=space[0],j=space[1];
            int mask=~(line[i]|column[j]|block[i/3][j/3])&0x1ff;
            for(;mask!=0&&!valid;mask&=(mask-1)){
                int digitMask=mask&(-mask);
                int digit=Integer.bitCount(digitMask-1);
                flip(i,j,digit);
                board[i][j]=(char)(digit+'0'+1);
                dfs(board,pos+1);
                flip(i,j,digit);
            }
        }
        public void flip(int i,int j,int digit){
            line[i]^=(1<<digit);
            column[j]^=(1<<digit);
            block[i/3][j/3]^=(1<<digit);
        }
    }

方法3 枚举优化

在数独问题中,总有一些格子只能填入一个数字,此时该格子能填入的数字为确定的,所以可以在递归开始前将所有确定的数字填入。

    class Solution {
        private int[] line=new int[9];
        private int[] column=new int[9];
        private int[][] block=new int[3][3];
        private boolean valid=false;
        private List<int[]> spaces=new ArrayList<int[]>();
        public void solveSudoku(char[][] board) {
            for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] != '.') {
                    int digit = board[i][j] - '0' - 1;
                    flip(i, j, digit);
                }
            }
        }

        while (true) {
            boolean modified = false;
            for (int i = 0; i < 9; ++i) {
                for (int j = 0; j < 9; ++j) {
                    if (board[i][j] == '.') {
                        int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
                        if ((mask & (mask - 1)) == 0) {
                            int digit = Integer.bitCount(mask - 1);
                            flip(i, j, digit);
                            board[i][j] = (char) (digit + '0' + 1);
                            modified = true;
                        }
                    }
                }
            }
            if (!modified) {
                break;
            }
        }

        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] == '.') {
                    spaces.add(new int[]{i, j});
                }
            }
        }
            dfs(board,0);
        }
        public void dfs(char[][] board,int pos){
            if(pos==spaces.size()){
                valid=true;
                return;
            }
            int[] space=spaces.get(pos);
            int i=space[0],j=space[1];
            int mask=~(line[i]|column[j]|block[i/3][j/3])&0x1ff;
            for(;mask!=0&&!valid;mask&=(mask-1)){
                int digitMask=mask&(-mask);
                int digit=Integer.bitCount(digitMask-1);
                flip(i,j,digit);
                board[i][j]=(char)(digit+'0'+1);
                dfs(board,pos+1);
                flip(i,j,digit);
            }
        }
        public void flip(int i,int j,int digit){
            line[i]^=(1<<digit);
            column[j]^=(1<<digit);
            block[i/3][j/3]^=(1<<digit);
        }
    }

总结

太难了

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

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

相关文章

LangChain入门:18.使用ReAct 框架进行生成推理痕迹和任务特定行动来实现更大的协同作用

在这篇技术博文中&#xff0c;我们将深入探讨LangChain框架中的ReAct对话模型&#xff0c;以及如何利用它构建高效的智能对话系统。ReAct模型通过反应堆&#xff08;Reactor&#xff09;处理对话中的各种情况&#xff0c;实现了对复杂对话场景的有效解构。结合思维链&#xff0…

基于Java+Vue的校园交友系统(源码+文档+包运行)

一.系统概述 选题背景&#xff1a; 在大学校园中&#xff0c;学生们面临着新的环境和人际关系的挑战。有些学生可能感到孤独或者希望扩展自己的社交圈子&#xff0c;寻找志同道合的朋友或者潜在的伴侣。因此&#xff0c;设计一款校园交友平台具有重要意义。 研究意义&#xff1…

「51媒体」权重高新闻源央级媒体邀约资料有哪些?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 权重高的央级媒体邀约资源包括了中国一些最具影响力和权威性的新闻机构。具体如下&#xff1a; 人民日报&#xff1a;作为中国共产党中央委员会的机关报&#xff0c;人民日报具有极高的权…

Spring学习(三)——AOP

AOP是在不改原有代码的前提下对其进行增强 AOP(Aspect Oriented Programming)面向切面编程&#xff0c;在不惊动原始设计的基础上为其进行功能增强&#xff0c;前面咱们有技术就可以实现这样的功能即代理模式。Java设计模式——代理模式-CSDN博客 基础概念 连接点&#xff08…

SAM5716B 法国追梦DREAM 音频DSP芯片

法国追梦/DERAM SAM5504/5704/5716/5808音频DSP芯片,开发板&#xff0c;方案 可用于电子鼓、电子琴、电吉他、效果器、均衡器、啸叫抑制器等电声产品领域 全系列芯片&#xff1a; SAM2634 SAM2695 SAM5504B SAM5704B SAM5708B SAM5808B SAM5716B SAM5916B... 原厂开发…

【Qt】Qt Hello World 程序

文章目录 1、Qt Hello World 程序1.1 使用按钮实现1.1.1 使用可视化方式实现 1.1.2 纯代码方式实现 label创建堆&#xff08;内存泄漏&#xff09;或者栈问题Qt基础类&#xff08;Qstring、Qvector、Qlist&#xff09;乱码问题零散知识 1、Qt Hello World 程序 1.1 使用按钮实…

算法学习笔记:Bi-LSTM和Bi-GRU

这篇文章的作为前几篇RNN\LSTM\RNN的后续之作&#xff0c;主要就是补充一个这两个哥的变体&#xff0c;想详细了解RNN\LSTM\GRU的详细理论和公式推导以及代码的请前往下面链接&#xff1a; 算法学习笔记&#xff1a;循环神经网络&#xff08;Recurrent Neural Network)-CSDN博…

udemy视频教程下载:AI和ChatGPT提示工程精通指南

欢迎来到 ChatGPT 大师班&#xff01; 这个 ChatGPT 大师班&#xff1a;AI 和提示工程指南是您通往 AI 未来的全通道通行证。 以下是您的学习旅程&#xff1a; 理解和掌握 ChatGPT&#xff1a;您将深入了解 AI 和语言模型&#xff0c;重点是 ChatGPT。我们设计了这个部分&am…

前端三大件速成 01 HTML

文章目录 一、前端基础知识二、标签1、什么是标签2、标签的属性3、常用标签&#xff08;1&#xff09;声明&#xff08;2&#xff09;注释&#xff08;3&#xff09;html 根标签&#xff08;3&#xff09;head标签&#xff08;4&#xff09;body标签 三、特殊字符四、其他标签1…

java方法递归

简介 案例&#xff1a;阶乘 // 计算一个数的阶乘 public static int factorial(int n) {if (n 1) {return 1;}return n * factorial(n - 1); }案例 猴子吃桃子 // 猴子吃桃子问题 // 第一天吃了一半多一个 第十天剩一个 求第一天有多少个桃子 // 因为 f(x1) f(x)/2 - 1 // 所…

STL库 —— priority_queue 的编写

目录 一、 优先级队列的介绍 二、优先级队列的使用 2.1 建大堆 less 2.2 建小堆 greater 2.3 详解 greater 与 less 三、 priority_queue 的模拟实现 3.1 编写框架 3.2 编写简单函数 3.2 进堆 向上调整 3.3 出堆 向下调整 四、完整代码 一、 优先级队列的介绍 1.…

【Python系列】非异步方法调用异步方法

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

浅谈数据结构---红黑树、二叉树

红黑树简介 红黑树&#xff1a;在本质上还是二叉树&#xff0c;是一种高效的查找树。 特点 一边的数比另一边的数高太多时&#xff0c;自动旋转平衡 当数据量比较大时&#xff0c;层级比较多&#xff0c;查询效率低 如下图所示&#xff1a; 如果一边的数比另一边高太多时&…

AI智能电销机器人是什么?能给我们带来哪些便利?

科技的飞速发展&#xff0c;让很多“懒人”的幻想变成了现实&#xff0c;越来越多的人工智能产品被发明出来甚至完全替代日常生活中的工作。比如在电销行业&#xff0c;很多企业选择AI智能电销机器人进行外呼。那么你了解多少AI智能电销机器人呢&#xff1f;和小编kelaile520一…

前端js控制元素移动

背景 页面中有多个表格&#xff0c;每个表格中均有一从右到左匀速移动的元素&#xff0c;随着元素移动需要在表格中增减数据&#xff0c;由于使用css3动画无法捕捉元素移动位置&#xff0c;所以这里采用js控制dom的写法 解决办法 最终代码放在文章的最后&#xff0c;各位看官…

热塑性聚氨酯TPU的特性有哪些?UV胶水能够粘接热塑性聚氨酯TPU吗?又有哪些优势呢?

热塑性聚氨酯&#xff08;Thermoplastic Polyurethane&#xff0c;TPU&#xff09;是一种具有多种优异性能的弹性塑料&#xff0c;广泛用于各种应用领域。以下是TPU的一些主要特性&#xff1a; 弹性和柔软性&#xff1a; TPU具有良好的弹性和柔软性&#xff0c;能够在受力后迅速…

现在给政府机关医院学校部队供货的方式有哪些?

给政府机关、医院、学校和部队供货的方式主要包括以下几种&#xff1a; 直接采购&#xff1a;政府机关、医院、学校和部队通过招标或直接与供应商进行谈判&#xff0c;确定采购的产品和价格。这种方式常见于大宗或重要物资的采购&#xff0c;能够确保采购过程的透明度和公正性…

林草资源管理系统:构筑绿色长城,守护自然之美

在全球气候变化和生态环境恶化的背景下&#xff0c;森林和草原资源的保护、恢复和合理利用显得尤为重要。林草资源管理系统的建立&#xff0c;旨在通过现代信息技术手段&#xff0c;提升林草资源管理的效率和质量&#xff0c;确保自然资源的可持续发展。 项目背景 森林和草原…

Nacos—配置管理

简介&#xff1a; Nacos是阿里巴巴开发的&#xff0c;它旨在帮助用户更敏捷和容易地构建、交付和管理微服务平台。Nacos的主要功能和特性包括&#xff1a; 动态服务发现。Nacos支持基于DNS和RPC的服务发现&#xff0c;允许服务提供者和消费者之间的高效交互。动态配置管理。…

2024华中杯ABC题完1-3小问py代码+完整思路16页+后续参考论文

A题太阳能路灯光伏板朝向问题 &#xff08;完整版获取在文末&#xff09; 第1小问&#xff1a;计算每月15日的太阳直射强度和总能量 1. 理解太阳直射辐射和光伏板的关系**&#xff1a;光伏板接收太阳辐射并转化为电能&#xff0c;直射辐射对光伏板的效率影响最大。 2. 收集数…