N皇后问题-leetcode51-java回溯解+详细优化过程

48 篇文章 0 订阅
9 篇文章 0 订阅

说明:问题描述来源leetcode

一、问题描述:

51. N 皇后

难度困难1592

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

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

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

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

示例 1:

img

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

示例 2:

输入:n = 1
输出:[["Q"]]

提示:

  • 1 <= n <= 9

二、题解:

做题过程:

题解1:

class Solution {
    List<List<String>> res = new LinkedList<>();
    char[][] chars ;
    private int n;

    public List<List<String>> solveNQueens(int n) {
        chars= new char[n][n];
        this.n = n;
        backTracking(0,0,0);
        return res;
    }

    private void backTracking(int row, int column,int count) {
        if (count == n){
            // 说明已经达到了不会相互攻击的n皇后,可以通过char获取一个结果
            addToRes();
            return;
        }
        for (int i = row; i < n; i++) {
            for (int j = column; j < n; j++) {
                if (chars[i][j] == 0){
                    char[][] arrays = getNewArrays();
                    doIAndJ(i,j);
                    count++;
                    backTracking(i + 1,0,count);
                    count--;
                    chars = arrays;

                }
            }
        }
    }

    private void addToRes() {
        List<String> list = new ArrayList<>();

        for (int i = 0; i < chars.length; i++) {
            String s = String.valueOf(chars[i]);
            list.add(s);
        }
        res.add(new ArrayList<>(list));

    }

    private char[][] getNewArrays() {
        char[][] result = new char[n][n];
        for (int i = 0; i < chars.length; i++) {
            for (int j = 0; j < chars[i].length; j++) {
                result[i][j] = chars[i][j];
            }
        }

        return result;
    }

    private void doIAndJ(int row, int column) {
        chars[row][column] = 'Q';
        for (int i = 0; i < n; i++) {
            if (chars[row][i] == 0) chars[row][i] = '.'; 
            
            if (chars[i][column] == 0) chars[i][column] = '.'; 
            
            if (column - i >= 0){//左边
                if (row - i >=0 && chars[row - i][column - i] == 0) chars[row - i][column - i] = '.';//左上 
                if (row + i < n && chars[row + i][column - i] == 0) chars[row + i][column - i] = '.';//左下 
            }
            
            if (column + i < n){//右边
                if (row - i >=0 && chars[row - i][column + i] == 0) chars[row - i][column + i] = '.';//左上 
                if (row + i < n && chars[row + i][column + i] == 0) chars[row + i][column + i] = '.';//左下 
            }
            
            
        }
    }
}

这个可以通过n为4的情况

后面这个题解通过了!!!

居然通过了哈哈!

还以为会超出时间复杂度。

还可以继续优化:

题解2:

再分析:

每一行必定会有一个皇后。

为什么?我们拆解这句话应该是等价于:每一行最多只可以有一个皇后,最少也是只有一个皇后。

先证明前半部分每一行最多只可以有一个皇后,①反证法:假如存在一行有2个皇后,那么不就和题目所说的同一行的皇后会相互攻击的条件所矛盾了吗?

再证明后半部分最少也是只有一个皇后,②反证法:假如最少是0个,那么存在一行没有皇后,那么肯定会有某一行会有多个皇后,这不就又回到①了吗

因此每一行必定有一个皇后。也不知道证明得够不够说服力,目前就感觉证明得没什么问题。

那么当我们即将遍历到n+1行就退出即可。

相应的,根据对称性,每一列也必定会存在一个皇后。

找到了这个条件,我们可以改造下:

class Solution {
    List<List<String>> res = new LinkedList<>();
    char[][] chars;
    private int n;

    public List<List<String>> solveNQueens(int n) {
        chars = new char[n][n];
        this.n = n;
        backTracking(0);
        return res;
    }

    private void backTracking(int row) {
        if (row == n) {
            // 说明已经达到了不会相互攻击的n皇后,可以通过char获取一个结果
            addToRes();
            return;
        }
        for (int i = 0; i < n; i++) {//对于每一列
            if (chars[row][i] == 0) {
                char[][] arrays = getNewArrays();
                doIAndJ(row,i);
                backTracking(row + 1);
                chars = arrays;
            }
        }
    }


    private void addToRes() {
        List<String> list = new ArrayList<>();

        for (int i = 0; i < chars.length; i++) {
            String s = String.valueOf(chars[i]);
            list.add(s);
        }
        res.add(new ArrayList<>(list));

    }

    private char[][] getNewArrays() {
        char[][] result = new char[n][n];
        for (int i = 0; i < chars.length; i++) {
            for (int j = 0; j < chars[i].length; j++) {
                result[i][j] = chars[i][j];
            }
        }

        return result;
    }

    private void doIAndJ(int row, int column) {
        chars[row][column] = 'Q';
        for (int i = 0; i < n; i++) {
            if (chars[row][i] == 0) chars[row][i] = '.';

            if (chars[i][column] == 0) chars[i][column] = '.';

            if (column - i >= 0) {//左边
                if (row - i >= 0 && chars[row - i][column - i] == 0) chars[row - i][column - i] = '.';//左上 
                if (row + i < n && chars[row + i][column - i] == 0) chars[row + i][column - i] = '.';//左下 
            }

            if (column + i < n) {//右边
                if (row - i >= 0 && chars[row - i][column + i] == 0) chars[row - i][column + i] = '.';//左上 
                if (row + i < n && chars[row + i][column + i] == 0) chars[row + i][column + i] = '.';//左下 
            }


        }
    }
}

end

这里的效率较之题解1已经提高了很多了,剩下的应该是对数据的处理的部分的优化了。

而题解1和题解2相比较,题解1走多了什么步骤呢?题解1有2层循环,我们可以设想当第一层递归遍历到第二行的chars数组时,是不是已经算是找到了所有结果了呢?

事实上按照推理是这样的,当第一行全部遍历后已经是找到所有的结果了,而其他的递归也存在遍历第二次的场景。

也就是当那一行确定后,再遍历到下一行进行递归时就已经出现了重复了,不行可以改一下题解1的代码,让它每次递归都不走下一行的遍历:

class Solution {
List<List<String>> res = new LinkedList<>();
    char[][] chars ;
    private int n;

    public List<List<String>> solveNQueens(int n) {
        chars= new char[n][n];
        this.n = n;
        backTracking(0,0,0);
        return res;
    }

    private void backTracking(int row, int column,int count) {
        if (count == n){
            // 说明已经达到了不会相互攻击的n皇后,可以通过char获取一个结果
            addToRes();
            return;
        }
        for (int i = row; i < n; i++) {
            for (int j = column; j < n; j++) {
                if (chars[i][j] == 0){
                    char[][] arrays = getNewArrays();
                    doIAndJ(i,j);
                    count++;
                    backTracking(i + 1,0,count);
                    count--;
                    chars = arrays;

                }
            }
            break;
        }
    }

    private void addToRes() {
        List<String> list = new ArrayList<>();

        for (int i = 0; i < chars.length; i++) {
            String s = String.valueOf(chars[i]);
            list.add(s);
        }
        res.add(new ArrayList<>(list));

    }

    private char[][] getNewArrays() {
        char[][] result = new char[n][n];
        for (int i = 0; i < chars.length; i++) {
            for (int j = 0; j < chars[i].length; j++) {
                result[i][j] = chars[i][j];
            }
        }

        return result;
    }

    private void doIAndJ(int row, int column) {
        chars[row][column] = 'Q';
        for (int i = 0; i < n; i++) {
            if (chars[row][i] == 0) chars[row][i] = '.';

            if (chars[i][column] == 0) chars[i][column] = '.';

            if (column - i >= 0){//左边
                if (row - i >=0 && chars[row - i][column - i] == 0) chars[row - i][column - i] = '.';//左上
                if (row + i < n && chars[row + i][column - i] == 0) chars[row + i][column - i] = '.';//左下
            }

            if (column + i < n){//右边
                if (row - i >=0 && chars[row - i][column + i] == 0) chars[row - i][column + i] = '.';//左上
                if (row + i < n && chars[row + i][column + i] == 0) chars[row + i][column + i] = '.';//左下
            }


        }
    }
}

这里和题解1的区别就是只完成内部的一层遍历就break了,无法继续走下面的行的遍历,效率和题解2几乎一样。

我们剩下的就对题解2进行优化数据处理就可以了:

但是本题解2的数据处理部分很难处理,这里对数据的操作实在是够多了的,比如getNewArrays这个函数每次都要创建和修改,其他的也是,总之很难修改了,是因为选定了默认(数值表示为0)的char作为判断条件.

可以换另一种思路,

比如说让是否为目标值作为判断条件。

题解3:

下面是替换了要判断的目标值,同时对数据的操作也减少了,不需要很多次地进行数组的创建

class Solution {
    List<List<String>> res = new LinkedList<>();
    char[][] chars;
    private int n;

    public List<List<String>> solveNQueens(int n) {
        chars = new char[n][n];
        for (int i = 0; i < n; i++) {
            Arrays.fill(chars[i],'.');
        }
        this.n = n;
        backTracking(0);
        return res;
    }

    private void backTracking(int row) {
        if (row == n) {
            // 说明已经达到了不会相互攻击的n皇后,可以通过char获取一个结果
            addToRes();
            return;
        }
        for (int i = 0; i < n; i++) {//对于每一列
            if (!isExistQ(row,i)){
                chars[row][i] = 'Q';
                backTracking(row + 1);
                chars[row][i] = '.';
            }
        }
    }

    private boolean isExistQ(int row, int column) {
        for (int i = 0; i < n; i++) {//判断列
            if ( chars[i][column] == 'Q') return true;

        }
        for (int i = 1; i < n; i++) {
            if (column - i >= 0) {//左边
                if (row - i >= 0 && chars[row - i][column - i] == 'Q') return true;//左上
                if (row + i < n && chars[row + i][column - i] == 'Q') return true;//左下
            }

            if (column + i < n) {//右边
                if (row - i >= 0 && chars[row - i][column + i] == 'Q') return true;//左上
                if (row + i < n && chars[row + i][column + i] == 'Q') return true;//左下
            }
        }
        return false;
    }


    private void addToRes() {
        List<String> list = new ArrayList<>();

        for (int i = 0; i < chars.length; i++) {
            String s = String.valueOf(chars[i]);
            list.add(s);
        }
        res.add(new ArrayList<>(list));

    }
}

但是还是不够!到底是哪里出错了呢,然后思考一下:

再来剪枝操作一下终于!!!

题解4:

对于斜边的操作其实只需要对左上角、右上角进行查询是否有目标值Q就可以了

class Solution {
    List<List<String>> res = new LinkedList<>();

    public List<List<String>> solveNQueens(int n) {
        char[][] chars = new char[n][n];
        for (int i = 0; i < n; i++) {
            Arrays.fill(chars[i], '.');
        }
        backTracking(chars, n, 0);
        return res;
    }

    private void backTracking(char[][] chars, int n, int row) {
        if (row == n) {
            // 说明已经达到了不会相互攻击的n皇后,可以通过char获取一个结果
            addToRes(chars);
            return;
        }
        for (int i = 0; i < n; i++) {//对于每一列
            if (!isExistQ(chars, n, row, i)) {
                chars[row][i] = 'Q';
                backTracking(chars, n, row + 1);
                chars[row][i] = '.';
            }
        }
    }

    private boolean isExistQ(char[][] chars, int n, int row, int column) {
        for (int i = 0; i < n; i++) {//判断列
            if (chars[i][column] == 'Q') return true;

        }

        for (int i = column - 1, j = 1; i >=0 && row - j >= 0; i--, j++) {
            if (chars[row - j][i] == 'Q') return true;//左上
        }
        
        for (int i = column + 1, j = 1; i < n && row - j >= 0; i++, j++) {
            if (chars[row - j][i] == 'Q') return true;//左上
        }

        return false;
    }


    private void addToRes(char[][] chars) {
        List<String> list = new ArrayList<>();

        for (int i = 0; i < chars.length; i++) {
            String s = String.valueOf(chars[i]);
            list.add(s);
        }
        res.add(new ArrayList<>(list));

    }
}

这个就成功优化到了2ms,而这样子实际上还是可以优化的,对于列的目标值Q的查询直接剔除在下方的那些就可以了

也就是对isExistQ函数的列操作进行稍作改变:

class Solution {
    List<List<String>> res = new LinkedList<>();

    public List<List<String>> solveNQueens(int n) {
        char[][] chars = new char[n][n];
        for (int i = 0; i < n; i++) {
            Arrays.fill(chars[i], '.');
        }
        backTracking(chars, n, 0);
        return res;
    }

    private void backTracking(char[][] chars, int n, int row) {
        if (row == n) {
            // 说明已经达到了不会相互攻击的n皇后,可以通过char获取一个结果
            addToRes(chars);
            return;
        }
        for (int i = 0; i < n; i++) {//对于每一列
            if (!isExistQ(chars, n, row, i)) {
                chars[row][i] = 'Q';
                backTracking(chars, n, row + 1);
                chars[row][i] = '.';
            }
        }
    }

    private boolean isExistQ(char[][] chars, int n, int row, int column) {
        for (int i = 0; i < row; i++) {//判断列
            if (chars[i][column] == 'Q') return true;

        }

        for (int i = column - 1, j = 1; i >=0 && row - j >= 0; i--, j++) {
            if (chars[row - j][i] == 'Q') return true;//左上
        }
        
        for (int i = column + 1, j = 1; i < n && row - j >= 0; i++, j++) {
            if (chars[row - j][i] == 'Q') return true;//左上
        }

        return false;
    }


    private void addToRes(char[][] chars) {
        List<String> list = new ArrayList<>();

        for (int i = 0; i < chars.length; i++) {
            String s = String.valueOf(chars[i]);
            list.add(s);
        }
        res.add(new ArrayList<>(list));

    }
}

end

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值