[算法专题] N皇后问题|回溯算法|简单易懂效率还高

N皇后

难度:困难

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

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

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

**注意:**leetcode上有两个n皇后,难度都是困难,其实处理没什么区别,都可以采用回溯法,一个是返回List,一个是返回数字

这里将题目修改为输出:个数 + List,如果你只需要返回List或者返回数字,可以将代码删去不需要的,进一步提高效率节省内存。

示例:

输入: 4
输出: 
2
[
 [".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]
# 解释: 4 皇后问题存在两个不同的解法。

提示:

  • 皇后,是国际象棋中的棋子,意味着国王的妻子。皇后只做一件事,那就是“吃子”。当她遇见可以吃的棋子时,就迅速冲上去吃掉棋子。当然,她横、竖、斜都可走一到七步,可进可退。(引用自 百度百科 - 皇后

官方解法的速度

在这里插入图片描述

本次解法(单纯返回List)的速度
在这里插入图片描述

思路介绍

理解该题解法之前,你需要对深度优先搜索有一定了解。

深度优先搜索:从一个顶点V0开始,沿着一条路一直走到底,如果发现不能到达目标解,那就返回到上一个节点,然后从另一条路开始走到底,这种尽量往深处走的概念即是深度优先的概念。

三个判断条件来决定该位置是否可以放置皇后:

假设一个前提,坐标为 [ i , j ]

  1. 该列 没有皇后:列i的坐标即可表示,很直观
  2. 左下 到 右上 没有皇后:使用i + j作为下标判断是否有皇后
  3. 左上 到 右下 没有皇后:使用i - j + n作为下标判断是否有皇后

注意:由于我们是一行一行找,所以不需要判断改行是否有皇后了

存储数据说明:

boolean[] down; //左下 到 右上
boolean[] up;//左上 到 右下
boolean[] col;//列
int n;
boolean[][] visited;//是否有皇后
//List版本
List<List<String>> res = new ArrayList<>();
//数字版
int sum = 0;

因为是N个棋盘及N个皇后,所以每一行都会有一个皇后,首先初始化第一行的不同放置方式:

void start(int n) {
    this.n = n;
    down = new boolean[2 * n + 1];//防止越界
    up = new boolean[2 * n + 1];//防止越界
    col = new boolean[n + 1];
    visited = new boolean[n + 1][n + 1];
    //初始化
    Arrays.fill(down, false);
    Arrays.fill(up, false);
    Arrays.fill(col, false);
    for (int j = 1; j <= n; j++) {
        Arrays.fill(visited[j], false);
    }
    //第一行的不同防止方式
    for (int i = 1; i <= n; i++) {
        visited[1][i] = true;
        col[i] = true;
        down[1 + i] = true;
        up[1 - i + n] = true;
        dfs(2);//从第二行开始,不断深入!
        visited[1][i] = false;
        col[i] = false;
        down[1 + i] = false;
        up[1 - i + n] = false;
    }
}

接下来我们要寻找第二行是否有可以放置的,如果有就继续进入k+1(下一行),继续寻找。直到第n行都完成了摆放,那么就可以对需要返回的结果作出修改

void dfs(int k) {
    if (k == n + 1) {
        //List版本
        ArrayList<String> strings = new ArrayList<>();
        for (int i = 1; i <= n; i++) {
            //采用StringBuilder又快又好,可以深入了解一下java中String不可变性
            StringBuilder s = new StringBuilder();
            for (int j = 1; j <= n; j++) {
                if (visited[i][j]) s.append('Q');
                else s.append('.');
            }
            strings.add(s.toString());
        }
        res.add(strings);
        //数字版本
        sum += 1;
        return;
    }
    for (int i = 1; i <= n; i++) {
        if (col[i] || down[k + i] || up[k - i + n]) continue;
        visited[k][i] = true;
        col[i] = true;
        down[k + i] = true;
        up[k - i + n] = true;
        dfs(k + 1);
        visited[k][i] = false;
        col[i] = false;
        down[k + i] = false;
        up[k - i + n] = false;
    }
}

详细代码(附带输入测试):

只需要删除测试模块即可通过leetcode中两道N皇后的题目,不过理解后自己写一遍才有用,复制黏贴不可取!

/***
 * @author: G_night
 * 转载请声明作者
 * Reprint please state the author
 ***/

import java.util.*;

class Solution {
    boolean[] down;
    boolean[] up;
    boolean[] col;
    int n;
    boolean[][] visited;
    //List版本
    List<List<String>> res = new ArrayList<>();
    //数字版
    int sum = 0;

    //返回数字版
    public int totalNQueens(int n) {
        //初始化数据并设置最初的位置
        start(n);
        return sum;
    }

    //返回List版本
    public List<List<String>> solveNQueens(int n) {
        //初始化数据并设置最初的位置
        start(n);
        return res;
    }

    void start(int n) {
        this.n = n;
        down = new boolean[2 * n + 1];
        up = new boolean[2 * n + 1];
        col = new boolean[n + 1];
        visited = new boolean[n + 1][n + 1];
        //初始化
        Arrays.fill(down, false);
        Arrays.fill(up, false);
        Arrays.fill(col, false);
        for (int j = 1; j <= n; j++) {
            Arrays.fill(visited[j], false);
        }
        for (int i = 1; i <= n; i++) {
            visited[1][i] = true;
            col[i] = true;
            down[1 + i] = true;
            up[1 - i + n] = true;
            dfs(2);
            //重新恢复false防止之前的造成影响
            visited[1][i] = false;
            col[i] = false;
            down[1 + i] = false;
            up[1 - i + n] = false;
        }
    }

    void dfs(int k) {
        if (k == n + 1) {
            //List版本
            ArrayList<String> strings = new ArrayList<>();
            for (int i = 1; i <= n; i++) {
                StringBuilder s = new StringBuilder();
                for (int j = 1; j <= n; j++) {
                    if (visited[i][j]) s.append('Q');
                    else s.append('.');
                }
                strings.add(s.toString());
            }
            res.add(strings);
            //数字版本
            sum += 1;
            return;
        }
        for (int i = 1; i <= n; i++) {
            if (col[i] || down[k + i] || up[k - i + n]) continue;
            visited[k][i] = true;
            col[i] = true;
            down[k + i] = true;
            up[k - i + n] = true;
            dfs(k + 1);
            visited[k][i] = false;
            col[i] = false;
            down[k + i] = false;
            up[k - i + n] = false;
        }
    }
}

//测试
class try {
    public static void main(String[] args) {
        int n=4;
        System.out.println(new Solution().totalNQueens(n));
        System.out.println(new Solution().solveNQueens(n));
    }
}

后记

这个只是一种解法,其实还有更简单的判断方式(使用bigmap思维),不过面试中这个方法好理解也容易想到并解释。这道题在以前大一的时候作为学校的选修考题之一,虽然写着困难的难度,其实理解和实操都是比较简单的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值