问题背景
给你一个整数 n n n,返回 和为 n n n 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如, 1 , 4 , 9 1,4,9 1,4,9 和 16 16 16 都是完全平方数,而 3 3 3 和 11 11 11 不是。
数据约束
- 1 ≤ n ≤ 1 0 4 1 \le n \le 10 ^ 4 1≤n≤104
解题过程
动态规划,用两个状态: i i i 表示当前枚举的完全平方数的算数平方根, j j j 表示剩余的和。
这样一来,状态转移方程就可以表示为 d f s ( i , j ) = { d f s ( i − 1 , j ) j < i 2 m i n ( d f s ( i − 1 , j ) , d f s ( j − i 2 ) + 1 ) j ≤ i 2 \ dfs(i, j) = \begin{cases} dfs(i−1,j) & j \lt i ^ 2 \\ min(dfs(i - 1, j), dfs(j - i ^ 2) + 1) & j \le i ^ 2 \\ \end{cases} dfs(i,j)={dfs(i−1,j)min(dfs(i−1,j),dfs(j−i2)+1)j<i2j≤i2。
递归入口 是 d f s ( ⌊ n ⌋ , n ) dfs(\lfloor \sqrt n \rfloor, n) dfs(⌊n⌋,n),表示初始枚举的最大数为 n \sqrt n n,剩余的和为 n n n。
递归边界 是 d f s ( 0 , 0 ) = 0 dfs(0, 0) = 0 dfs(0,0)=0,表示没有剩余的数可选,且和刚好为零。
翻译成递推之后,由于计算时只用到前一个状态,记忆化数组可以去掉一个维度。
递推的计算可以放在静态代码块里,给所有测试用例公用。
具体实现
递归
class Solution {private static final int[][] memo = new int[110][10010];static {for (int[] row : memo) {Arrays.fill(row, -1);}}public int numSquares(int n) {return dfs((int) Math.sqrt(n), n); }private int dfs(int i, int j) {if (i == 0) {return j == 0 ? 0 : Integer.MAX_VALUE;}if (memo[i][j] != -1) {return memo[i][j];}if (j < i * i) {return memo[i][j] = dfs(i - 1, j);}return memo[i][j] = Math.min(dfs(i - 1, j), dfs(i, j - i * i) + 1); }
}
递推
class Solution {private static final int MAX_N = 10000;private static final int[][] dp = new int[110][MAX_N + 1];static {Arrays.fill(dp[0], Integer.MAX_VALUE);dp[0][0] = 0;for (int i = 1; i * i <= MAX_N; i++) {for (int j = 0; j <= MAX_N; j++) {if (j < i * i) {dp[i][j] = dp[i - 1][j];} else {dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - i * i] + 1);}}}}public int numSquares(int n) {return dp[(int) Math.sqrt(n)][n]; }
}
空间优化
class Solution {private static final int MAX_N = 10000;private static final int[] dp = new int[MAX_N + 1];static {Arrays.fill(dp, Integer.MAX_VALUE);dp[0] = 0;for (int i = 1; i * i <= MAX_N; i++) {for (int j = i * i; j <= MAX_N; j++) {dp[j] = Math.min(dp[j], dp[j - i * i] + 1);}}}public int numSquares(int n) {return dp[n]; }
}