一道有趣的广度优先搜索/动态规划的题

Posted 鑫的感悟和分享

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一道有趣的广度优先搜索/动态规划的题相关的知识,希望对你有一定的参考价值。

leetcode279 完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
输入: n = 13
输出: 2
解释: 13 = 4 + 9.

这是一道很有意思的题,我本来是搜索广度优先搜索的题目看到这道题的,但是乍一眼看上去这道题似乎和广度优先搜索没有关系,于是我就瞄了一眼题解,题解还有动态规划的解法,我用动态规划写出来之后发现时间效率只打败了5%的人,高达7000ms,我很疑惑,以为自己写错了,然后用高频题解的代码跑出来依然是这种情况,这就说明了这道题动态规划不是最优解,还有其他的解法。

先看动规的解法:

class Solution:

    def numSquares(self, n: int) -> int:

        dp=[0]*(n+1)

        for i in range(n+1):

            dp[i]=i

            j=0

            while (j+1)*(j+1)<=i:

                j+=1

                dp[i]=min(dp[i-j*j]+1,dp[i])

        return dp[-1]

其实很好理解,就不在这解释了。

下面看广度优先搜索的解法,很有意思。


因为是广度优先遍历,顺序遍历每一行,所以当节点差出现0时,此时一定是最短的路径。

如绿色所示,若此时节点为0,表示根节点可以由路径上的平方数「1,1,9」构成,返回此时的路径长度3,后续不再执行。

如红色所示,若节点值在之前已经出现,则不需要再计算,一定不会是最短路径,就算有也是在前面先出现不是。

广度优先搜索一般要借助队列,这个题是一个层次遍历。

class Solution:

    def numSquares(self, n: int) -> int:

        from collections import deque

        if n == 0return 0

        queue = deque([n])

        step = 0

        visited = set()

        while(queue):

            step+=1

            l=len(queue)

            for _ in range(l):

                tmp=queue.pop()

                for i in range(1,int(tmp**0.5)+1):

                    x=tmp-i**2

                    if(x==0):

                        return step

                    if(x not in visited):

                        queue.appendleft(x)

                        visited.add(x)

        return step

我们来剖析一下这个代码,我们其实是要按照题意首要构建一棵树,子节点呢是根结点减去一些平方数之后的值,因为是广度优先,所以我们希望的是一层一层的来,也就是说我们希望先统计一层的信息,再把这一层的节点逐个pop出来,然后根据pop出来的结果呢来构建下一层的节点,所以我们就清楚了,广度优先搜索的时候我们氛围两个步骤,一个是统计本层的信息,一个是构建下一层的信息。广度优先搜索一般是用队列这种数据结构来构建的。如果是一颗已知的树,那么终止条件可以是把所有节点遍历完,但是我们需要考虑这个题的终止条件,因为所有的节点都是我们现场构造的,我们希望的又是出现一个结果0结束,那么我们的终止条件就是我们添加新节点时出现一个0,而且我们统计信息也已经在第一块内统计出来了,所以我们的统计信息直接返回回来就可以了,我们的最大的循环呢,也就是整个队列,我们是把一层都结束了,构建出下一层了,才进入下一个循环,也就是说最外面的while(queue)是针对每个循环的,此外,还有最重要的一点,这个题目要有剪枝操作,也就是说已经出现过的节点我们就不再考虑了,因为我们是按顺序来的,如果后面又出现已经出现过的节点,势必不能带来最佳的结果。


注意一下,写的时候出错了一个地方,就是对集合的定义:

s=set()而不是s=set{}

以上是关于一道有趣的广度优先搜索/动态规划的题的主要内容,如果未能解决你的问题,请参考以下文章

episode13:广度优先搜索

动画:如何用广度和深度优先搜索找到女朋友?

数据结构算法设计模式

Leetcode 广搜动态规划01 矩阵(542)

LeetCode 1129颜色交替的最短路径广度优先HERODING的LeetCode之路

动态规划初探 -- 背包问题