对问题建模:
整个问题转化为一个图论问题。
从n到0,每个数字表示一个节点;
如果两个数字x到y相差一个完全平方数,则连接一条边。
我们得到了一个无权图。
原问题转化成,求这个无权图中从n到0的最短路径。
package leetcode.search;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class NumSquares_279 {
int solution(int n){
List<Integer> squares = generateSquares(n);
//两个整数之间差值是平方数,从动态规划角度,或者从两个数间就有一条边的搜索.广度优先遍历即最短路径算法角度
//广度优先遍历,能平方数组成的两个之间就有一条边,类似于最短路径问题
Queue<Integer> queue = new LinkedList<>();
int ret = 0;
queue.add(n);
boolean[] marked = new boolean[n+1];//标记已访问,不重复入队
marked[n] = true;
while (!queue.isEmpty()){
ret +=1;
int size = queue.size();
while (size-->0){
int i = queue.poll();
for (int s : squares){
if (i-s<0){
break;
}
else if (i-s==0){
return ret;
}
else if (marked[i-s]){//标注
continue;
}
marked[i-s] = true;
queue.add(i);
}
}
}
return n;//负数?
}
private List<Integer> generateSquares(int n) {
//f(n+1) = f(n) + 2(n-1)+3 = (n+1)^2 = (n)^2 + 2n+1
List<Integer> ret = new ArrayList<>();
int square = 1;
int diff = 3;
while (square <= n) {
ret.add(square);
square += diff;
diff += 2;
}
return ret;
}
//dp[n] 表示以n为和的最少平方的和的个数(所求)。
//
//dp 数组所有下标已经为完全平方数的数(如1,4,9...)置为 1,加一层 j 遍历找到当前 i 下长度最小的组合。
//
//动态方程的意思是:对于每个 i ,比 i 小一个完全平方数的那些数中最小的个数+1就是所求,也就是 dp [ i - j * j ] + 1 。
int solution2(int n){
int[] dp = new int[n+1];
for(int i=1;i*i<=n;i++){
dp[i*i] = 1;
}
for (int j = 1;j<=n;j++){
for (int k=1;k*k<j;k++){
dp[j] = Math.min(dp[j],dp[j-k*k]+1);
}
}
return dp[n];
}
//除8余7 必为4 判断能否有两个或一个,否则返回3 一个正数除4取余数,余数除8余7,必为4平方数
}