算法题解记录17+++完全平方数

        这是楼主第一次不靠题解,挑战动态规划的中等题目,虽然最终结果只超过了5%的人,不过我也很满意了。

        本题楼主首先采用朴素的递归型动态规划,接着优化算法,使用借助HashMap存储临时数据的递归型动态规划,几次时间复杂度都很高,最后优化成借助数组存储数据的迭代型动态规划。

题目描述:

        给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

        完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,149 和 16 都是完全平方数,而 3 和 11 不是。

示例 1:

输入:n = 12
输出:3 
解释:12 = 4 + 4 + 4

示例 2:

输入:n = 13
输出:2
解释:13 = 4 + 9

提示:

  • 1 <= n <= 10^4

解题准备:

        1.了解可能存在的基础操作:首先,输入只有一个数,应该不会有查找【事实证明,还是有,而且得自己建立数据结构】;;其余看不出来。

        2.理解题意:其实用朴素的观点,很容易想到一种解法---对于数据i,先找到i以下的最大平方数(比如10,找到9;或者17,找到16),接着,用i-这个数得到新的i,再用新的i继续此过程,直到i==1,返回一共执行的次数。

                不过,这个思路是错误的!

                假设i==12,按照此思路,第一步是i-9,得到i=3,接着i==3依次-1,一共执行了4次,返回4;;然而,答案是12=4+4+4,只需要执行3次。

        那么,还有其它思路吗?

        我们想处理i的完全平方数,需不需要借助i-1、i-2……的完全平方数数据呢?

        我们直接处理i,可能有几种情况?

解题难点1分析:

        对解题准备的问题进行思考,我们可以发现:

        处理i的情况:

                1.如果i是完全平方数,比如1,4,9,16,25……那么直接返回1;

                2.如果i不是完全平方数,那么可能会有无数种情况(比如刚刚的12=4+4+4),一下子没法处理。

                作图如下:

                我们可以发现,2的最少完全平方数,就是1+1;

                3的最少完全平方数,是1+1+1,形式上等于1+2,并且数目上,1的最少完全平方数和2的最少完全平方数之和,就等于3的最少完全平方数。

                不过,对于4不同,对于5好像也不太符合。

                继续观察,我们发现,5的最少完全平方数,是4+1,形式上和数目上,都等于1的最少完全平方数+4的最少完全平方数。

                其实,如果i不是完全平方数,那么i可以转化为k+n(i=k+n,k、n>=1),如果使k、n最少完全平方数的和最小,就能得到i的最少完全平方数。

                比如i是100,那么就从k=1,一直遍历到k=99【当然,为了省时、避免重复,遍历到k=50就差不多】

                即i=100, k=1, n=99;

                i=100, k=2, n=98;

                i=100, k=3, n=97;

                ……

                i=100, k=50, n=50;

                这本质上是一种暴力搜索,把所有情况枚举出来,然后选择最少的一个。

        原始动态规划递归代码:

                我们既然知道了,求i,就得求k+n,又因为k、n>=1,所以k、n必然小于i,那么,只要得到从1到i-1的所有最少完全平方数,就能枚举得到i的最少完全平方数。

                求i-1、i-2的最少完全平方数,其结构与求i的类似,所以我们可以使用递归的方法解决。

class Solution {
   public int numSquares(int n) {
        int res=n;

        // 判断是否完全平方数
        double temp=Math.sqrt(n);
        int l=(int)temp;
        if(l-temp==0){
            return 1;
        }

        // 遍历得到从1到i-1的所有最少完全平方数
        for(int i=1; i<n/2+1; i++){
            res=Math.min(res, numSquares(n-i)+numSquares(i));
        }
    
        return res;
    }
}

        这个算法,时间复杂度奇高,由于需要从1到n/2+1,每次调用两个自身(n-i和i),所以可以把递归树看成一个二叉树,该树的深度约为n,且基本上是满二叉树,其时间复杂度可想而知。

        我也不确定时间复杂度,不过我猜是O(n/2 * 2^n);

        HashMap临时存储动态规划代码:

class Solution {
   // 存储临时数据
   Map<Integer, Integer> data=new HashMap<>(); 
   public int numSquares(int n) {
        int res=n;

        // 判断是否为完全平方数
        double temp=Math.sqrt(n);
        int l=(int)temp;
        if(l-temp==0){
            return 1;
        }

        // 同样的递归,实质一样,不过先从临时数据中查看有无
        for(int i=1; i<n/2+1; i++){
            if(data.get(i)!=null){
                if(data.get(n-i)!=null){         
                    res=Math.min(res, data.get(i)+data.get(n-i));
                }else{
                    res=Math.min(res, data.get(i)+numSquares(n-i));
                }
            }else if(data.get(n-i)!=null){
                res=Math.min(res, data.get(n-i)+numSquares(i));
            }else{
                res=Math.min(res, numSquares(n-i)+numSquares(i));
            }
        }

        data.put(n, res);
        return res;
    }
}

                这个算法,节省至少一半的时间,实质上,由于把从1到i-1的数据都保存了,应该能使复杂度到O(n/2 * n!);

解题难点2分析:

        不过,即使是竭尽所能,时间上仍然超出限制。

        所以,不得不考虑更加复杂的迭代型动态规划。

        根据上述思路,我们已经知道如何处理i,那么,怎么处理1到i-1,并存储起来呢?

        首先,已知,对于i这个大问题,只要它不是完全平方数,解法就是拆分成两个数,然后求出所有解,拿到最小的一个。

        那么,第一步,至少得有两个基本数据,一般选取1、2作为基本数据。

        接着,我们需要一个数据结构来存储基本数据,比如HashMap,我采用的是数组,其随机访问的特性非常有用。

        声明数组dp,就得知道它多大,从1存储到i-1,所以是i-1这么大。为了保险,选取i作为大小。

        此时,满足dp[0]=1, dp[1]=2;

        接下来,就是遍历运算。

        按理说,对于数据i,i=k+n,所以只需要从k=1,遍历到i/2+1即可得到其最少完全平方数。

        数组存储的动态规划代码:

class Solution {
   public int numSquares(int n) {
        int[] dp=new int[n]; // 声明数组
        int res=n; // 保险起见

        // 判断是否为完全平方数
        double temp=Math.sqrt(n);
        int l=(int)temp;
        if(l-temp==0){
            return 1;
        }

        // 从3开始,因为1、2已经得到
        int i=3;
        dp[0]=1;
        dp[1]=2;

        // i<n,即遍历到n-1
        while(i<n){
            // 在遍历时,不可避免地可能存在完全平方数
            temp=Math.sqrt(i);
            l=(int)temp;
            if(l-temp==0){
                dp[i-1]=1;
                i++;
                // 记得continue,否则执行下面的语句后,出错
                continue;
            }

            // 保险
            res=i;

            // 迭代得到最少完全平方数
            for(int k=1; k<i/2+1; k++){
                res=Math.min(res, dp[k-1]+dp[i-k-1]);
            }
            // 记录
            dp[i-1]=res;
            i++;
        }

        // 拿到真实结果
        res=n;
        for(int ln=1; ln<n/2+1; ln++){
            res=Math.min(res, dp[ln-1]+dp[n-ln-1]);
        }
        return res;
    }
}

        题解的思路则非常简洁,我会在接下来的算法补充中说明。

        以上内容即我想分享的关于力扣热题17的一些知识。

        我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。

  • 36
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值