​LeetCode刷题实战52:N皇后 II

算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试。所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 !

今天和大家聊的问题叫做 N皇后 II,我们先来看题面:

https://leetcode-cn.com/problems/n-queens-ii/

The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens attack each other.

题意

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

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

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

提示:皇后,是国际象棋中的棋子,意味着国王的妻子。皇后只做一件事,那就是“吃子”。当她遇见可以吃的棋子时,就迅速冲上去吃掉棋子。当然,她横、竖、斜都可走一或 N-1 步,可进可退。

样例

输入: 4
输出: 2

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

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]

解题

https://www.cnblogs.com/techflow/p/12782020.html

思路分析

八皇后问题已经是老生常谈了,在我们探讨解法之前,先来思考一个问题,用递归或者搜索解决的问题很多,为什么只有N皇后问题如此经典呢?

是因为国际象棋比较流行吗?还是因为这个问题很困难吗?还是它的思路很巧妙吗?由于这个问题是我自己提出的,书本上并没有相关的答案,需要我们自己思考。我们先不公布答案,带着这个问题来分析一下这道题的思路。

我个人在解题的时候喜欢问问题,很多时候看似破朔迷离找不到头绪的题目,多问几个问题也许就能找到灵感。如果我们对数字敏感的话,很容易发现一个大问题,为什么题目会让我们在n*n的棋盘上摆放n个皇后呢?为什么是n个,而不是2n个,也不是n+1个呢?

这个问题不难回答,因为题目当中规定了皇后不能同行摆放,所以每一行最多摆放一个皇后,一共有n行,那么显然最多只能摆放n个皇后。但是如果我们继续提问,既然这么多限制条件,那为什么一定能找到答案呢,会不会摆放n个皇后的解不存在呢?这当然是有可能的,我们很容易发现,当n=2和3的时候就没有解法。

如果我们顺着这个思路问下去,还可以挖掘出许多问题来。比如到底什么样的n可以使得一定有解呢?每一个n对应的解有没有规律呢?这样一直问下去,如果所有的问题都能解答,就说明这个问题就彻底吃透了。实际上这个问题背后是一个非常复杂的数学问题,会在之后开一篇文章单独讲解。

虽然不去深究这些问题,也一样可以把题目做出来,但很多时候思维和能力的差距就体现在这些看似无用的思考上。

我们先把问题简化,把解的存在性问题先放一放,既然题目要我们求,一定会给我们有解的n。而且我们也可以大概猜测得出结论,当n大于等于4的时候,N皇后问题一定有解。那么,我们怎么求解呢?

问题建模

我们干想是没有结果的,要对问题进行建模。建模这个词很玄乎,听起来很高端,而且在很多场景当中都会出现。比如机器学习当中,我们需要建模,还有专门的数学建模大赛,但是少有人会对建模这个词进行解释。

经过了一系列思考,我个人总结,所谓的建模,其实就是一个寻找和设计适应问题的解法的过程。模型就是从问题当中抽象出来的逻辑,比如N皇后摆放是问题本身,但是摆放的方法的逻辑才是模型。模型不是凭空出现的,是我们一点点构建的。这个过程有点像是搭积木,从无到有,从易到难,一点点将模型完善。

n*n的棋盘上摆放n个皇后,这个是问题本身,我们做第一层抽象。显然,由于皇后之间不能同行也不能同列,那么每一行和每一列只能摆放一个皇后。我们不能同时枚举一个皇后摆放的行和列,我们优先考虑其中的行。不如做一个假设,由于皇后之间没有差别,我们可以假设每一行摆放的皇后是固定的。第一个皇后就摆放在第一行,第二个皇后就摆放在第二行。

进行了第一层抽象之后,问题清晰了许多,但是还是无法得出答案。所以我们还需要做第二层抽象和分析,每行固定一个皇后之后可以保证皇后之间不会同行发生冲突,但是不能保证不同列以及不同对角线。所以我们必须设计一个机制,来保证这一点。我们需要枚举皇后所有摆放的情况,所以不能再固定皇后摆放的列,既然不能固定,但是可以记录。由于我们已经确定了每一个皇后摆放的行,只要记录下它们摆放的列,就可以判断是否会构成同列以及同对角线。

到这里,我们已经找到解法了,但是我们还可以再做第三层抽象。由于皇后已经固定了行号,我们可以用数组当中的下标代替皇后。下标0存储的位置就是皇后0摆放的列号,0就是皇后0的行号,那么我们用一个一维数组就存储了皇后摆放的二维信息。

也就是说我们在递归的时候,只用一个数组就记录了整个棋盘的情况,这个时候用代码实现起来就要容易很多。

# code for leetcode 51
class Solution:
    
    def dfs(self, n, queen, ret):
        if len(queen) == n:
            ret.append(queen[:])
            return 
        
        for i in range(n):
            # 如果同列
            if i in queen:
                continue
            flag = False
            # 判断是否存在同对角线
            for j, idx in enumerate(queen):
                # len(queen)表示当前是第几个皇后
                if abs(len(queen) - j) == abs(idx - i):
                    flag = True
                    break
            if flag:
                continue
            # 合法则放入i列
            queen.append(i)
            self.dfs(n, queen, ret)
            queen.pop()
            
    def transform(self, n, ret):
        res = []
        # 根据每个皇后摆放的列号还原棋盘
        for arr in ret:
            s = []
            for i, idx in enumerate(arr):
                row = ['.' for _ in range(n)]
                row[idx] = 'Q'
                s.append(''.join(row))
            res.append(s)
        return res
        
    def solveNQueens(self, n: int) -> List[List[str]]:
        ret = []
        self.dfs(n, [], ret)
        return self.transform(n, ret)

总结

最后,我们再回到一开始的问题,为什么递归求解的问题这么多,只有N皇后成为经典呢?

我觉得很重要的一个原因就在于这道题对应的建模过程,我们从无到有,抽丝剥茧,一点点将整个问题搭建起来,构建出了一个适配于当前问题的模型。并且经过我们的优化,这也是用递归实现的最佳模型。对于我们而言,把问题AC了其实并不重要,重要的是能够掌握这个思路构建的能力,这样以后我们就可以很方便地迁移到其他的问题场景当中,这才是学习的精髓。

好了,今天的文章就到这里,如果觉得有所收获,请顺手点个在看或者转发吧,你们的支持是我最大的动力。

上期推文:

LeetCode1-50题汇总,速度收藏!

LeetCode刷题实战51:N 皇后

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值