• N皇后问题


    1、题目

    N 皇后问题是指在 N ∗ N N * N NN 的棋盘上要摆 N N N 个皇后,要求任何两个皇后不同行、不同列,也不在同一条斜线上。

    给定一个整数 n,返回 n 皇后的摆法有多少种。

    Leetcode 题源:N 皇后II

    例:

    n = 1
    返回 1
    
    • 1
    • 2
    n = 2 或 3
    2 皇后和3皇后无论怎么摆都不行,返回0
    
    • 1
    • 2
    n = 8
    返回 92
    
    • 1
    • 2

    2、思路

    只能用暴力。

    规定皇后按行放,即每行只能放一个皇后,则免去了皇后是否共行的判断。

    准备轨迹信息记录(一维数组record),每放置一个皇后,就在轨迹信息中记录,放置下一个皇后的时候,判断和轨迹信息中的位置是否冲突。

    若之前的某个皇后在 (x,y) 位置,需要判断当前位置 (m,n) 能否放置皇后,就需要判断该位置是否和 (x,y) 冲突,即判断 y == n || |m - x| == |n - y|,当前位置需要和之前所有已经放置的皇后位置进行冲突判断,才能确定是否可以放皇后。

    2.1 暴力递归版本

    //时间复杂度 O(n^n),每一行都要进行n次尝试
    public class NQueens {
        public static int num(int n) {
            if (n < 1) return 0;
            
            int[] record = new int[n];
            return process(0, record, n);
        }
        
        //当前来到第i行,一共是0 ~ n - 1行
        //在i行上放皇后,所有列都尝试
        //必须保证和之前所有的皇后不冲突
        //int[] record中保存之前的皇后存放的位置,之所以可以用一维,可以用下标表示行,数组中的数即表示所在的列
        //record[x] = y 之前的第x行的皇后,放在了y列
        //返回:不关心 i 以上发生了什么,i....后续有多少合法的方法数
        public static int process(int i, int[] record, int n) {
            if (i == n) { //所有皇后填完了,那么说明找到了1种有效方法
                return 1;
            }
            
            int res = 0;
            //i行的皇后,放哪列呢?逐列试
            for (int j = 0; j < n; j++) {
                if (isValid(record, i, j)) {
                    record[i] = j;
                    res += process(i + 1, record, n); 
                    //record数组只关心前面的数据,所以当回溯回去的时候,当前的脏数据是用不到的
                	//record数组不需要恢复前一个状态,即不需要擦,数据脏了就脏了,不影响计算
                }
            }
            
            return res;
        }
        
        public static boolean isValid(int[] record, int i, int j) {
            for (int k = 0; k < i; k++) { //讨论与之前的0~i-1行放置的所有皇后是否冲突,从第0行的皇后开始讨论
                //共列或者共斜线则冲突
                if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) {
                    return false;
                }
            }
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    2.2 位运算方案

    该方案的时间复杂度和暴力递归版本是一致的,只是优化了常数时间。

    优化思路:用 int 状态位来替换 record 数组。

    public class NQueens {
        // 请不要超过32皇后问题
    	public static int num(int n) {
    		if (n < 1 || n > 32) {
    			return 0;
    		}
    		// 如果你是13皇后问题,limit 最右13个1,其他都是0
            // limit 使用其位信息,而非其数值:
            // 如果是4皇后,则00000000 00000000 00000000 00001111;
            // 如果是k皇后,则从右往左数k位为1,其它位为0 , 实现方式: (1 << n) - 1 
    		int limit = n == 32 ? -1 : (1 << n) - 1;
    		return process(limit, 0, 0, 0);
    	}
    
    	// 7皇后问题,则limit : 00000000 00000000 00000000 01111111 (固定不变)
    	// 之前皇后的列影响:colLim
    	// 之前皇后的左下对角线影响:leftDiaLim
    	// 之前皇后的右下对角线影响:rightDiaLim
        // 所有参数都只用状态,不用原始数值
        // 无需关心放置到第几个皇后了,只要某个时刻 "列限制 = limit" 就找到了一种方法
    	public static int process(int limit, int colLim, int leftDiaLim, int rightDiaLim) {
    		if (colLim == limit) { //列限制 = limit,说明所有皇后都填满了,发现了一种方法
    			return 1;
    		}
            
            //假设:
            //列的限制:0001000 (1位置表示该位置不能放皇后)
            //左下限制:0010000  
            //右下限制:0000100
            //“列 | 左下 | 右下”或运算后的结果是总限制:0011100, 在总限制中,是1的位置不能放皇后,是0的位置可以放
            //该结果取反是 1... 1100011 (之所以取反,是因为可以加工出所有可以放皇后的位置)
            //取反结果与limit进行 与运算:0... 1100011, 该值就是pos的值
    		// pos中所有是1的位置,是你可以去尝试皇后的位置
    		int pos = limit & (~(colLim | leftDiaLim | rightDiaLim));
    		int mostRightOne = 0;
    		int res = 0;
    		while (pos != 0) { //尝试可能的1,将方法数累加返回
    			mostRightOne = pos & (~pos + 1); //提取出最右侧的1,即提取出一个可以放置皇后的位置。上面的例子,mostRightOne = 0000001
    			pos = pos - mostRightOne; //将该位置减掉,说明提取过了
    			res += process(limit, colLim | mostRightOne, (leftDiaLim | mostRightOne) << 1,
    					(rightDiaLim | mostRightOne) >>> 1); //实参就是在调整列、左下和右下的限制
    		}
    		return res;
    	}
    
    	public static void main(String[] args) {
    		int n = 15;
    
    		long start = System.currentTimeMillis();
    		System.out.println(num2(n));
    		long end = System.currentTimeMillis();
    		System.out.println("cost time: " + (end - start) + "ms");
    
    		start = System.currentTimeMillis();
    		System.out.println(num1(n));
    		end = System.currentTimeMillis();
    		System.out.println("cost time: " + (end - start) + "ms");
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
  • 相关阅读:
    字符编码的介绍
    华为帐号多端协同,打造美好互联生活
    多线程和并发问题详解
    机械工程英语第二版叶邦彦-汉语翻译最多单元版
    CRM帮助企业解决客户关系管理难题
    Python之字符串格式化
    HTML基础
    基于大数据的时间序列股价预测分析与可视化 - lstm 计算机竞赛
    [Ubuntu18.10] 给docker容器加上gpu加速
    13.接口自动化学习-Pytest结合Yaml使用
  • 原文地址:https://blog.csdn.net/u011386173/article/details/127704618