给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
思路一:动态规划
本来我也是自己在想动态规划的做法,有思路了,可是过于复杂。在写代码的时候就在想,动态规划不是都挺短的代码吗,怎么我的逻辑这么长,会不会是我错了。看了看题解,发现确实是错了(倒也没错,就是太复杂,没找对正确的状态方程)。
- 首先初始化长度为n+1的数组dp,每个位置都为0
- 如果n为0,则结果为0
- 对数组进行遍历,下标为i,每次都将当前数字先更新为最大的结果,即dp[i]=i,比如i=4,最坏结果为4=1+1+1+1即为4个数字
- 动态转移方程为:dp[i] = MIN(dp[i], dp[i - j * j] + 1),i表示当前数字,jj表示平方数(加上的1,就是jj的情况)。任何一个数,都会被另一个数加上一个完全平方数而得到。
时间复杂度:O(n*sqrt(n)),sqrt为平方根
class Solution {
public int numSquares(int n) {
int[] dp = new int[n+1];
for(int i=1;i<n+1;i++){
dp[i] = i;
for(int j=1;i-j*j>=0;j++){
dp[i] = Math.min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
}
思路二:广度优先遍历
当看到便签有广度优先遍历,我是很懵的。这既不是个图,又不是个数,怎么广度优先遍历?
直到在题解中看了这位老哥的图我才明白:
思想和动态规划相仿,都是表示成多种情况的完全平方数之和。最先一层出现0的就是最终结果。
class Solution {
public int numSquares(int n) {
Queue<Integer> queue = new LinkedList<Integer>();
boolean[] visit = new boolean[n+1]; //标志是否被访问
for(boolean v:visit) v = false;
visit[n] = true;
queue.offer(n);
int res = 0;
while(!queue.isEmpty()){
res++;
int length = queue.size();
for(int i=0;i<length;i++){
int num = queue.poll();
for(int j=1;num-j*j>=0;j++){
if(num-j*j==0) return res;
if(!visit[num-j*j]){
queue.offer(num-j*j);
visit[num-j*j] = true;
}
}
}
}
return res;
}
}
}
}