您的位置:首页 > Web前端

279. Perfect Squares-Leetcode(关于DP的再深入研究)

2015-12-24 01:27 330 查看
先上题目:

Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, …) which sum to n.

For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.

Credits:

Special thanks to @jianchao.li.fighter for adding this problem and creating all test cases.

Subscribe to see which companies asked this question

一开始直接想到了递归的DP算法,一些用例通过,整体测试的时候超时。

(最最一开始我把题目理解错了,以为用尽可能大的平方数之和来表示所给给数n,后来发现使用最大的得出的平方数个数并不是最小的。如12=9+1+1+1;而正确的结果应该是12=4+4+4;最小的平方数个数是3而不是4。)

使用的还是分铁棒的办法进行分割,i++每一个单位长度都进行遍历。这是我代码的重大缺陷。因为题目中要求所有的组成部分都是平方数。

public class Solution {
int [] list;
public int numSquares(int n) {
list=new int [n+1];
list[0]=0;
int bound=(int)Math.sqrt(n);

for(int i=1;i<=n;i++){
list[i]=-1;

}
for(int i=1;i<=bound;i++){
list[(int)Math.pow(i,2)]=1;
}
return getNum(n);
}
private int getNum(int n){
if(list
!=-1){
return list
;
}
int min=n;
for(int i=1;i<(n/2+1);i++){
int temp=getNum(i)+getNum(n-i);
if(min>temp){
min=temp;
}
}
list
=min;
return min;
// return 1+getNum(n-(int)Math.pow(getLessSquares(n),2));
}
private int getLessSquares(int n){
for(int i=0;;i++){
if(Math.pow(i,2)>n){
System.out.println("squars:"+(i-1));

return i-1;
}
}
}
}


一开始找不到超时的原因。于是在网上看前辈的算法:

public class Solution {
public int numSquares(int n) {
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for(int i = 1; i <= n; ++i) {
int min = Integer.MAX_VALUE;
int j = 1;
while(i - j*j >= 0) {
min = Math.min(min, dp[i - j*j] + 1);
++j;
}
dp[i] = min;
}
return dp
;
}
}


其使用的是自底向上的dp,最后返回数组的最后一位。其运行效率也很客观。

下图中左侧是他的代码,右侧是我改良过以后的递归DP。



改到最后出了上下的方向不一样,我是递归它是双重循环以外,其余的都一样,可是我们的时间复杂度还是有很大差距。按照算法导论上说应该相差一个常数。这里先不去验证,因为相差的不是不可接受。

下面是右侧的算法代码:

public class Solution {
int [] list;
public int numSquares(int n) {
list=new int [n+1];

//int bound=(int)Math.sqrt(n);

/* for(int i=1;i<=n;i++){
list[i]=-1;

}*/
Arrays.fill(list, -1);
list[0]=0;
/* for(int i=1;i<=bound;i++){
list[i*i]=1;
}*/
return getNum(n);
}
private int getNum(int n){
if(list
!=-1){
// System.out.format("list[%d] have directly returned %d\n",n,list
);
return list
;

}
int min=n;
for(int i=1;i*i<=n;i++){

min=Math.min(min,getNum(n-i*i)+1);
//int temp=getNum(n-i*i)+1;

//  System.out.format("getNum(i:%d)+getNum(n-i:%d)=%d+%d=%d\n",i,n-i,getNum(i),getNum(n-i),temp);
// if(min>temp){
//    min=temp;
//}
}
list
=min;
//for(int i=0;i<n;i++){
//  System.out.format("%d ",list[i]);
//}
//  System.out.format("\n");
return min;
// return 1+getNum(n-(int)Math.pow(getLessSquares(n),2));
}
/*private int getLessSquares(int n){
for(int i=0;;i++){
if(Math.pow(i,2)>n){
//    System.out.println("squars:"+(i-1));

return i-1;
}
}
}*/
}


说一下我改进的过程:

原来思想是按照铁棒思路均匀扫描,现在按照平方数进行扫描。

同时之前的平方数索引的元素赋值为1的步骤可以省略了。

几个小点注意的地方:

使用Math.min/max() 寻找最大最小比起自己写要高效一点点,但是使用起来很简洁方便。

平方的时候直接写i*i比用pow函数简洁一些。

使用Array.fill(数组名,初始值)可以简洁地初始化数组为某一个值。和自己写for循环效率相当,但是更加简洁。

使用Array.asList(数组名)将一个数组转化为List对象,以便可以调用高级函数。各种类的静态方法可以留意一下,它们会很有用。

format输出的时候%b为输出boolean类型

总结

这里面抓住平方分割是问题的本质。知道一个问题的框架以后(如铁棒分割),要从一般的角度再次审视这个问题(平方分割)。

还有一些广度优先搜索和数学算法,过后将继续研究。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  square numbers dp