给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...
)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
广度优先搜索
class Solution(object):
def numSquares(self, n):
"""
:type n: int
:rtype: int
"""
if n == 0:
return 0
queue = deque([n])
visited = set()
path = 0
while queue:
path += 1
length = len(queue)
for i in range(length):
num = queue.popleft()
for j in range(1, int(num**0.5+1)):
x = num - j*j
if x == 0:
return path
if x not in set():
queue.append(x)
visited.add(x)
return None
遍历所有的可能组合是比较直观的一种方式。但是暴力的遍历显然是不行的,一旦当n较大时就肯定会耗费大量的时间。这里遍历设计的是采用队列的方式,用广度优先的方式来搜索。这样做的好处是一旦在某一层得到我们想要的答案,就能立马返回结果,不需要进行后面的计算。因为每一层列举了所有的组合可能,如果这一层的某一个路径符合答案,那么就不需要进行后面层的遍历。
这里用到的一个小技巧是用一个set来保存所有已经出现过的节点值,当遍历到的节点值是前面已经访问过的节点值,即具有相同的值,此时就不对其进行入队操作,因为相同节点一定是出现在当前节点之前,可能是当前层的,也可能是前面层的,那么该节点在前面都不符合要求,在这里更不会符合要求,因为层更深,如果该节点符合要求,选择的也不会是这个点而是前面的点。所以利用这个集合set可以减少大量的计算,这也是这种遍历方法可行的最大原因,相比于暴力解法每种都尝试。
动态规划
class Solution(object):
def numSquares(self, n):
"""
:type n: int
:rtype: int
"""
dp = [i for i in range(n+1)]
for i in range(2,n+1):
for j in range(1,int(i**0.5)+1):
dp[i] = min(dp[i],dp[i-j*j]+1)
return dp[-1]
想到动态规划解题是相对容易的,但是如何确定状态转移方程是比较难的。对于dp[i]而言表示的是对于数字i,其最少的完全平方数。那么i其实可以分解为两部分,i = x + j*j,其中j是完全平方数,那么此时的总数就变成了dp[x]+1,dp[x]是组成x的最少完全平方数。那么我们只需要遍历所有j的可能,就能获得dp[i]。
在初始化dp数组的时候,可以先将其设为最多的完全平方数,那么就是全部都由1组成。