-
Difficulty: Medium
-
Related Topics: Math, Dynamic Programming, Breadth-first Search
Description
Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...
) which sum to n.
给定一个正整数 n,现需要用完全平方数(1
, 4
, 9
, 16
)的和凑出 n,问所需平方数的最小值。
Examples
Example 1
Input: n = 12
Output: 3
Explanation: 12 = 4 + 4 + 4.
Example 2
Input: n = 13
Output: 2
Explanation: 13 = 4 + 9.
Solution
先给出一个 naive 的做法,暴力搜索,大致基于下式进行计算(将题目所求的函数记为 (f(n))):
[f(n) = egin{cases}
1& n 是完全平方数\
min(f(i) + f(n - i)) ; { i in [1, n / 2] } & else
end{cases}
]
为了防止重复计算,还是采用了记忆化搜索的方法,结果华丽丽的 TLE 了(input = 6175),而我本地跑这组测试数据的时候直接爆栈了……
import kotlin.math.floor
import kotlin.math.min
import kotlin.math.sqrt
class Solution {
private val memo = hashMapOf<Int, Int>()
fun numSquares(n: Int): Int {
if (memo.containsKey(n)) {
return memo.getValue(n)
}
if (n.isPerfectSquare()) {
memo[n] = 1
return 1
}
var result = Int.MAX_VALUE
for (i in 1..(n / 2)) {
result = min(result, numSquares(i) + numSquares(n - i))
}
memo[n] = result
return result
}
private fun Int.isPerfectSquare(): Boolean {
val root = floor(sqrt(this.toDouble())).toInt()
return root * root == this
}
}
自然只能找其它解法了,以下一个解法来自 discussion。考虑以下等式(还是一样,将原函数记为 (f(n))):
[f(0) = 0 \
f(1) = f(0) + 1 = 1 \
f(2) = f(1) + 1 = 2 \
f(3) = f(2) + 1 = 3 \
f(4) = min(f(4 - 1 imes 1) + 1, f(4 - 2 imes 2) + 1) = min(f(3) + 1, f(0) + 1) = 1 \
f(5) = min(f(5 - 1 imes 1) + 1, f(5 - 2 imes 2) + 1) = min(f(4) + 1, f(1) + 1) = 2 \
... \
f(13) = min(f(13 - 1 imes 1) + 1, f(13 - 2 imes 2) + 1, f(13 - 3 imes 3) + 1) = min(f(12) + 1, f(9) + 1, f(4) + 1) = 2
]
所以最后的状态转移方程是:
[f(n) = min(f(n - i imes i) + 1)(其中 n - i imes i geq 0 且 i geq 1)
]
代码如下:
import kotlin.math.min
class Solution {
fun numSquares(n: Int): Int {
val dp = IntArray(n + 1) { Int.MAX_VALUE }
dp[0] = 0
for (i in 1..n) {
var min = Int.MAX_VALUE
var j = 1
while (i - j * j >= 0) {
min = min(min, dp[i - j * j] + 1)
j++
}
dp[i] = min
}
return dp.last()
}
}