279. 完全平方数
思路:给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
(方法1)由于最终结果是平方数不断累加而得的,所以自然联想到动态规划。->
令dp[i]表示组成数字i的最小平方数的个数,那么就有递推公式:
dp[i] = Math.min(dp[i], dp[i - j * j] + 1)
而我们所求即为dp[n],完整代码如下:
public int numSquares(int n) {
int[] dp = new int[n + 1]; // 默认初始化值都为0
for (int i = 1; i <= n; i++) {
dp[i] = Integer.MAX_VALUE; // 最坏的情况就是每次+1
for (int j = 1; i - j * j >= 0; j++) {
dp[i] = Math.min(dp[i], dp[i - j * j] + 1); // 动态转移方程
}
}
return dp[n];
}
public int numSquares(int n) {
int[] dp = new int[n+1];
for(int i=1;i<dp.length;i++){
dp[i] = i;
for(int j=1;j<=Math.sqrt(i);j++){
dp[i] = Math.min(dp[i],dp[i-j*j] + 1);
}
}
return dp[n];
}
这里dp[i]的初始化也可以写为dp[i] = i,因为最坏的情况就是由1不断累加而成的,这个i也就是最大的了。
(方法2)我们也可以转换一下思路,可以将每个整数看成图中的一个节点,如果两个整数之差为一个平方数,那么这两个整数所在的节点就有一条边。要求解最小的平方数数量,就是求解从节点 n 到节点 0 的最短路径。->
首先,我们构造一个生成小于n的平方数序列的函数:
public List<Integer> generateSquares(int n) {
List<Integer> squares = new ArrayList<>();
int square = 1;
int diff = 3;
while (square <= n) {
squares.add(square);
square += diff;
diff += 2;
}
return squares;
}
这里的diff的算法是根据(n+1)^2 - n^2 = 2n + 1而来的。->
接下来我们从节点n开始遍历其邻接节点,用mark数组标记该节点是否被访问过,访问过就直接跳过:
public int numSquares(int n) {
List<Integer> squares = generateSquares(n);
Queue<Integer> queue = new LinkedList<>();
boolean[] marked = new boolean[n + 1];
queue.add(n);
marked[n] = true;
int level = 0;
while (!queue.isEmpty()) {
int size = queue.size();
level++;
while (size-- > 0) {
int cur = queue.poll();
for (int s : squares) {
int next = cur - s;
if (next < 0) {
break;
}
if (next == 0) {
return level;
}
if (marked[next]) {
continue;
}
marked[next] = true;
queue.add(next);
}
}
}
return n;
}
根据BFS的特性,那么最先return的,一定是最小的level。