求和为 n 的完全平方数的最小数量,可以根据和为 n-1x1, n-2x2, n-3x3... 的完全平方数的最小数量推导出来。
这个问题完全可以变化成零钱兑换问题:给你一个目标金额 n,和一个若干硬币的面额 coins = 1,4,9,16...,问最少需要几枚硬币凑出这个金额(因为包含面值为 1 的硬币,所以不存在凑不出来的情况)。
解法一:自顶向下的递归DP + 备忘录
func numSquares(n int) int {
// 把所有小于等于整数 n的完全平方数纳入选择列表
choices := make([]int, 0)
for i:=1; i*i<=n; i++{
choices = append(choices, i*i)
}
// 备忘录初始化为-1,不会和可能答案冲突
memo := make([]int, n+1)
for idx := range memo{
memo[idx] = -1
}
return dp(choices, n, memo)
}
func dp(choices []int, sum int, memo []int) int{
if sum == 0{
return 0
}
if memo[sum] != -1{
return memo[sum]
}
res := sum // 为了求min,初始化为最大数量,就是当全选择 1时
for _, choice := range choices{
if sum - choice < 0{
continue
}
res = min(res, dp(choices, sum-choice, memo)+1)
}
memo[sum] = res
return memo[sum]
}
func min(a, b int) int{
if a < b{
return a
}
return b
}
解法二:自底向上的递推DP
func numSquares(n int) int {
// dp[i]表示和为 i的完全平方数的最小数量
dp := make([]int, n+1)
// base case, 和为0不存在完全平方数
dp[0] = 0
for i := 1; i<len(dp); i++{
dp[i] = i // 最差情况是全部选1,总共需要 i个数
for j := 1; j*j <=i; j++{ // 在比 i小的完全平方数中做选择
dp[i] = min(dp[i], dp[i-j*j]+1)
}
}
return dp[n]
}
func min(a, b int) int{
if a < b{
return a
}
return b
}