騰訊筆試題:小Q硬幣組合


騰訊有一道機試題: 
大概意思是: 
小Q非常富有,擁有非常多的硬幣,小Q的擁有的硬幣是有規律的,對於所有的非負整數K,小Q恰好> 各有兩個數值為2^k,的硬幣,所以小Q擁有的硬幣是1,1,2,2,4,4……,小Q賣東西需要支付元錢,請問小Q想知道有多少種組合方案。 
輸入:一個n (1<=n<=10^18),代表要付的錢 
輸出:表示小Q可以拼湊的方案數目

 

輸入樣例:6
輸出樣例:3
即:4+2,4+1+1,2+2+1+1

暴力解法

容易得知,對於輸入N,所需硬幣最大值不會超過N,即只需從1~2^logN這些硬幣拼湊。每種硬幣可選0~2個,共三種選法。排列組合共3^logN種。

回溯法:耗費略優於暴力解法

 

import java.util.Scanner;
public class Main {
    private static  int n;  //支付數
    private static int count=0;  
    private static int[] p=null;  //p[i]記錄2^i元的硬幣用了多少個,取值0~2
    
    //初始化數組大小
    private static void init(){
        double lo=Math.log(n)/Math.log(2);
        int length=(int)lo+1;
        p=new int[length];
    }
    
    //取值並回溯
    private static final void solve(int i){
        if(i>=p.length) return;
        for(int t=0;t<=2;t++){
            p[i]=t;
            if(isOK()) count++;
            else if(isPart()) solve(i+1);
        }
        p[i]=0;
    }
    
    //判斷是否當前是否等於n
    private static boolean isOK(){
        int sum=0;
        for(int i=0;i<p.length;i++){
            sum+=Math.pow(2, i)*p[i];
        }
        if(sum==n) return true;
        else return false;
    }
    
    //是否進行延伸
    private static boolean isPart(){
        int sum=0;
        for(int i=0;i<p.length;i++){
            sum+=Math.pow(2, i)*p[i];
        }
        if(sum<n) return true;
        else return false;
    }
    
    public static void main(String[] args){
        Scanner scanner=new Scanner(System.in);
        n=scanner.nextInt();
        scanner.close();
        double start=System.currentTimeMillis();
        init();
        solve(0);
        System.out.println(count);
        System.out.println("use time="+(System.currentTimeMillis()-start));
    }
}

動態規划:耗費遠小於回溯

使用res[n,i]表示:使用1,1,2,2,4,4,...,2^i,2^i可以組合出n的方案數
可見

res[n,i]=1,當n=0,即所有面值的硬幣所取數目都為0
res[n,i]=1,當n=1,即只取一個一元的硬幣
res[2,0]=1,即只取兩個一元硬幣
res[n,0]=0,當n>=3,因為無法只使用1,1組成大於等於3的組合
res[n,i]=sum(res[n-2^i*m,i-1]) n,i取其他,0=<m<=2
import java.util.Scanner;
public class Main {
    private static  int n;  //支付數
    private static int count=0;  
    private static int[][]res=null;
    
    //初始化數組
    private static void init(){
        double lo=Math.log(n)/Math.log(2);
        int length=(int)lo+1;
        res=new int[n+1][length];
        for(int i=0;i<res[0].length;i++){
            res[0][i]=1;
            res[1][i]=1;
        }
        
        res[1][0]=1;
        res[2][0]=1;
    }

    //動態規划
    private static final int solve(){
        if(n==0) return 1;
        if(n==1) return 1;
        
        init();
        for(int i=1;i<n+1;i++){
            for(int j=1;j<res[0].length;j++){
                int sum=0;
                for(int m=0;m<3;m++){
                    int rest=(int) (i-Math.pow(2, j)*m);
                    if(rest>=0)
                    {
                        sum+=res[rest][j-1];
                    }
                }
                res[i][j]=sum;
            }
        }
        return res[n][res[0].length-1];
    }
    
    
    public static void main(String[] args){
        Scanner scanner=new Scanner(System.in);
        n=scanner.nextInt();
        scanner.close();
        double start=System.currentTimeMillis();
        int result=solve();
        System.out.println(result);
        System.out.println("use time="+(System.currentTimeMillis()-start));
    }
}
結果分析:
回溯:測試通過,n=10000時,耗費15s
動態規划:測試通過,n=10000時,耗費32ms

第四種方法:一種很有趣的思路

將硬幣分為兩份:1,2,4,8,16,.....和1,2,4,8,16....
組成兩個數值為a,b的兩個數字,他們的和是a+b=n; 
a在每一份中只可能有一種組合方式(二進制的思想)。
將a和b使用二進制表示,那么對於n=11,有a=101,b=110這種組合,即a=1+0+4=5,b=0+2+4=6。但是,請注意,對於a和b,在相同位取不同值,只有一種組合方法。
如111+100和101+110(即交換中間位)本質上都是同一種組合方法,因此對於該類型可以使用二進制異或進行去重。
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
         Scanner scanner=new Scanner(System.in);
         int n=scanner.nextInt();
         if(n<=2) {
             System.out.println(n);
             return;
         }
         Set<Integer> countset=new HashSet<>();
         int stop=n/2;
         for(int i=1;i<=stop;i++) {
             int result=(i)^(n-i);//異或a和b
             countset.add(result);
         }
        System.out.println(countset.size());
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM