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

先上题目:

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
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小马工匠坊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值