leetcode完全平方数的三种解法

探讨了如何找到构成任意正整数n的最少完全平方数个数的方法,包括BFS算法、动态规划及数学规律三种解法,旨在提供解决此类问题的多种思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

示例 1:

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

首先是可以用BFS算法来做:

  • count= 1 时,可以走到1*1,2*2,3*3,。。。i*i;将n-1*1,n-2*2,n-3*3....加入队列
  • count= 2 时,继续把未访问过的加入队列

当从队列中的值tmp取出来且tmp-i*i==0时,说明,已经按最短路径走到了。输出count即可。

以下是我写的代码:

public static int numSquares(int n){
        Boolean[] visited=new Boolean[n+1];
        Arrays.fill(visited,false);
        Queue<Integer> q=new LinkedList<>();
        q.offer(n);
        int count=0;
        while(!q.isEmpty()){
            int size=q.size();
            count++;
            for(int i=0;i<size;i++){
                int tmp=q.poll();
                visited[tmp]=true;
                for(int j=1;j*j<=n;j++){
                    if(tmp-j*j==0){
                        return count;
                    }else if(tmp-j*j<0){
                        break;
                    }else{
                        if(!visited[tmp-j*j]){
                            q.offer(tmp-j*j);
                        }
                    }

                }
            }
        }
        return count;
    }

第二种解法是使用动态规划:

状态转移方程是dp[i]=min(dp[i],dp[i-j*j]+1),求得dp[n]即可。代码为:

public static int numSquares(int n){
        int[] dp=new int[1000000];
        Arrays.fill(dp,10);
        dp[0]=0;
        for(int i=1;i<=n;i++){
            int j=1;
            while(j*j<=i){
                dp[i]=dp[i]<(dp[i-j*j]+1)?dp[i]:dp[i-j*j]+1;
                j++;
            }
        }
        return dp[n];
    }

第三种解法是使用数学规律,因为:Lagrange 四平方定理: 任何一个正整数都可以表示成不超过四个整数的平方之和。

所以最多只能是1,2,3,4这4种答案。

算法是如果改数字可以整除4,则一直整除下去直到无法整除为止,此时不会影响count大小,接着是如果该数n%8==7,则返回4,否则测试一下是否可以有两个数组成,然后就只剩3这种答案了。代码如下:

public static int numSquares(int n){
        while(n%4==0){
            n/=4;
        }
        if(n%8==7){
            return 4;
        }
        for(int i=1;i*i<=n;i++){
            if(i*i==n){
                return 1;
            }
        }
        for(int i=1;i*i<=n;i++){
            int b=(int)Math.sqrt(n-i*i);
            if(b*b+i*i==n){
                return 2;
            }
        }
        return 3;
    }

这种算法是最快的。还有一种算法是用dfs来做,奈何本人水平不够,数字较小时可以返回正确值,数字较大时就超时了,总共488个测试案例通过了458个,代码也放上去吧。

    public static  int numSquares(int n) {
        int half=(int)Math.sqrt(n);
        int count=0;
        int[] arr=new int[half+1];
        for(int i=1;i<arr.length;i++){
            arr[i]=n/(i*i);
            count+=arr[i];
        }
        count=dfs(arr,arr.length-1,arr[arr.length-1],0,0,n,arr[1]);
        return count;
    }
    public static int dfs(int[] arr,int x,int t,int count,int sum,int n,int min){
        if(count>=min){
            return min;
        }
        int tmpCount=0;
        for(int i=t;i>=0;i--){
            sum+=i*x*x;
            count+=i;
            if(sum==n){
                if(count<min){
                    min=count;
                }
                return min;
            }else if(sum>n){
                sum-=x*x*i;
                count-=i;
            }else{
                if(x-1>=1&&count<min){
                    tmpCount= dfs(arr,x-1,arr[x-1],count,sum,n,min);
                    if(tmpCount<min){
                        min=tmpCount;
                    }
                }
                sum-=x*x*i;
                count-=i;
            }
        }
        return min;

    }

整体而言就是这些,以后再改进。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值