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

来源:互联网 发布:能看禁播视频的软件 编辑:程序博客网 时间:2024/06/05 14:18

先上题目:

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[n]!=-1){            return list[n];        }        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[n]=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[n];}}

其使用的是自底向上的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[n]!=-1){           // System.out.format("list[%d] have directly returned %d\n",n,list[n]);            return list[n];        }        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[n]=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类型

总结

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

还有一些广度优先搜索和数学算法,过后将继续研究。

0 0
原创粉丝点击