leetcode刷题之——N皇后

题目:

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

上图为 8 皇后问题的一种解法。

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

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

示例:

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

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

思路:在看到这类题目的时候,首先想到的就是:图搜索。这里我使用的就是图搜索,使用回溯算法进行解答。而在图搜索中选择深度优先搜索的难点主要在于以下几点:

 1、结束时,记录返回结果

 2、抉择之后对应状态的改变

 3、还原现场

其中对于困难点1,我们可以通过建立全局变量List> 对象来进行填充,而dfs()的返回值可以置为void。

对于困难点2是有一点麻烦的。我们可以通过对列进行搜索,每次dfs的时候将列+1(idx + 1),但是对于行,和斜线,我们需要专门考虑,为此我们可以再设立几个全局的数组来记录对应的行和斜线的状态。

困难点3,这个点是容易忽视掉的,类似于深搜和递归等,在改调用的方法结束的时候记得要还原现场,将状态还原,避免影响之后的操作·。

以下是伪代码:

List> ans = new ArrayList>();

int[] path = new int[n];

void dfs(int idx,int n)// idx当前遍历的列,n表示棋盘的边长
    if(idx >= n) // 边界条件,当搜索到边界的时候,也就是改输出结果的时候
        // 记录结果 并结束
        return ;
    for(i...n)//对于每一列,皇后所在的行都是未知的、
        if(状态是否符合搜索条件) //剪枝判断
            path[idx] = i; //表示该列所对应的行
            //记录状态
            dfs(idx+1,n);
            //还原状态

public List> solveNQueens(int n){
    dfs(0,n);
    return ans
}

观上面的伪代码,可以知道主要难点在于剪枝判断和记录结果。其实记录结果还是很简单的,只需要遍历整个棋盘,记录该列所对应的行并标记为Q就行。所以,对于整个算法的难点就落在了剪枝操作和记录状态上面了。因为剪枝操作其实就是判断对应的状态,所以所有的难点也就是记录状态。

记录状态的难点:由于皇后可以走八个方向,四条直线。因此可以归结为一下四种情况

1、行

2、列

3、正斜线

4、反斜线

由于我们的搜索是以列展开的,因此列这条直线可以直接省去。而行的考虑还是比较简单的,我们只需要在记录状态的时候标记改行已经被占用就可以了。可以定义一个全局变量rows[i] = true,来表示改行是否被占用,记得之后要还原现场。

对于正斜线和反斜线,我们就要仔细考虑了。我们可以参照行进行操作。由于这个状态的记录主要是表示那些未知被皇后占用了。所以我们可以将斜线也类似于行一样标记出来。对于一个n*n的棋盘来说正斜线和反斜线的数量都是 2n-1。而我们想要将被占用的斜线表示出来,可以才用数学中的思维来进行解决,构件坐标轴如下:

leetcode刷题之——N皇后_第1张图片

虽然丑了点,不过可以看= =。我们将正斜线作为x+y=k固定值,那么对于不同的k值,即可以表示为不同的正斜线。那么对于正斜线的全局变量slash数组的下标可以记为slash[x+y],即slash[idx + i] = true;而反斜线的方程是-x+y = k。同理可以记为backlash[idx-i] = true;不过idx - i可能是负数,所以我们要将其设置为正数,不然下标就出错了,因此可以定为backlash[idx-i+n-i] = true;记录完状态之后,再调用dfs之后需要还原现场。

由以上的思考方式最后可以得到最后的代码为以下:

    List> ans = new ArrayList>();

    int[] row;

    boolean[] line;

    boolean[] slash;

    boolean[] backlash;

    /**
     * 对列进行深度优先搜索
     * @param idx
     * @param n
     */
    void dfs(int idx,int n){
        //结束条件   记录结果
        if(idx >= n){
            List tmp = new ArrayList();
            for(int i = 0 ; i < n ; i++){
                String str = "";
                for(int j = 0 ; j < n ; j++){
                    if(row[i] == j){
                        str +="Q";
                    }else{
                        str +=".";
                    }
                }
                tmp.add(str);
            }
            ans.add(tmp);
            return ;
        }

        for(int i = 0 ; i < n ; i++){
            if(!line[i] && !slash[idx + i] && !backlash[idx-i+n-1]){
                row[idx] = i;
                line[i] = true;
                slash[i+idx] = true;
                backlash[idx-i+n-1] = true;
                dfs(idx + 1 , n);
                line[i] = false;  // 还原现场
                slash[i+idx] = false;
                backlash[idx-i+n-1] = false;
            }
        }
    }

    public List> solveNQueens(int n) {
        row = new int[n];
        line = new boolean[n];
        slash = new boolean[2*n];
        backlash = new boolean[2*n];
        dfs(0,n);
        return ans;
    }

  这道题目还是挺有意思的,主要是很有做的意义。在刚刚拿到题目的时候感觉考虑的点有很多,但是在一步一步解析之后,再看这道题目,就会发现并没有多难。而且这道题目很具有代表性,可以说是图的深搜的代表了。笔者目前也是慢慢在学习图的相关算法,解题思路比较简陋,代码还有很多可以优化的点。欢迎大家在阅读完之后,如果有什么想法和意见的话,能够及时告诉我,我好及时修正。

你可能感兴趣的:(leetcode,刷题,java语言,算法,搜索,算法,leetcode,图,深度优先搜索)