zoukankan      html  css  js  c++  java
  • 笔试题计算组合数

    写程序,能够计算组合数 

    首先想到的是写一个求阶乘的函数,然后再另一个函数中对其调用以计算该组合数。这个思路没有问题,但是能写好代码也不是件容易的事,而且我们应该意识到,招聘方的这道简单的问题很显然是需要我们尽量写得完整的,而不是象征性写出思路,因此,必要的健壮性是要体现出来的。很多人的程序如下:

        private static int factorial(int n){

           int ret=1;

           for(int i=2;i<=n;ret*=i++);

           return ret;

        }

       

        public static int combination1(int all,int select){

           return factorial(all)/factorial(select)/factorial(all-select);

        }

    在这段程序中,由于没有任何容错机制,所以肯定是得不到高分的。另外还有个别人在第二个函数中的语句写成了:

           return factorial(all)/factorial(select)*factorial(all-select);

    这样粗心的错误直接会被判0分,基本上就不会再进入下一轮了。

    加入了容错检查的程序如下:

        private static int factorial(int n){

           if (n<0)

               throw new InvalidParameterException();

          

           int ret=1;

           if (n==0 || n==1)

               return ret;

           else

               for(int i=2;i<=n;ret*=i++);

          

           return ret;

        }

       

        public static int combination1(int all,int select){

           if (select>all || all<=0 || select<=0)

               throw new InvalidParameterException();

          

           if (select==all)

               return 1;

          

           return factorial(all)/factorial(select)/factorial(all-select);

        }

    这样这个基本思路的代码就算差不多了,这道题的及格分也就够了。

    但是,在众多及格分的成绩中要想获得机会,还是需要再继续改进的。也有的是面试官不断的提出新的需求,比如:如果保持程序中使用int类型不变,那么上述阶乘的计算函数只能算到大约12!,还能进一步提高这个数值吗?或者如果让你提高这段代码的运行效率,你要用什么思路优化?

    类似这样问题的提出,就是考验应聘者的时候了,经过分析,优化的空间可以通过原表达式约分实现。在原表达式中,必然有一部分阶乘是重复计算的,而且n!决定了可计算的最大值,如果能把这些重复的计算去掉,把阶乘运算去掉,很显然不仅效率能提高,而且可计算的范围也将变大。

    参考程序如下:

        public static int combination2(int all,int select){

           if (select>all || all<=0 || select<=0)

               throw new InvalidParameterException();

          

           int ret=1;

           int begin=0;

           if (all==select)

               return ret;

          

           //从1-begin之间的数被约分掉以简化计算

           if (select<all-select)

               begin=all-select+1;

           else

               begin=select+1;

          

           for(int i=begin;i<=all;ret*=i++);    //计算分子

           for(int i=all-begin+1;i>1;ret/=i--); //被分母除

          

           return ret;

        }

    如果能第一次就写成这种思路,很显然,我们的机会会更多。

    再来接受一下考验,如果面试官要求你再提供思路来优化运行效率,这时我们还要继续思考,比如把已经运算的结果缓冲起来,当再有相同的参数要求运算时直接从缓冲区返回算好的结果,将比重新运算要快。利用该思路,我们改造上边的函数,形成如下代码:

        private static Map<String,Integer> mapTable=new HashMap<String,Integer>();

    ……

        public static int combination3(int all,int select){

           if (select>all || all<=0 || select<=0)

               throw new InvalidParameterException();

          

           int ret=1;

           int begin=0;

           if (all==select){

               return ret;

           }

          

           String tableKey=new String(all+","+select);

           Integer tableValue=mapTable.get(tableKey);

           if (tableValue!=null)

               return tableValue.intValue();

          

           if (select<all-select){

               begin=all-select+1;

           }

           else{

               begin=select+1;

           }

          

           for(int i=begin;i<=all;ret*=i++);

           for(int i=all-begin+1;i>1;ret/=i--);

          

           mapTable.put(tableKey, Integer.valueOf(ret));

          

           return ret;

    }

    利用一个Map作为缓冲区以保存曾经计算好的结果,典型的空间换时间的思路,但已经能让面试官满意的了。

    但是,面试官的问题是会一直延续下去的,他思路的过程也应该是我们写代码思路的过程,因此,新的问题可能是:用HashMap不一定能提高效率,反而会降低效率,能说说是什么原因吗?如果不用HashMap,那用什么容器做缓冲区能更高效?

    其实,用HashMap,如果像上文程序那样使用String作为Key,则每次检索时的Hash运算都会耗掉很多时间,自然,如果不采用这种额外的寻址算法,才能达到预期的目的。常用的方式就是采用表格,将算好的结果以某种简单的方式存入到表格中,或者如果运算的数据范围在合适的范围内,也可以预先把表格定好,这样就减少了寻址带来的损失了。为了简单起见,下面的代码使用了数组代替前面的Map。

        private static int[] intTable;

        ……

        public static int combination4(int all,int select){

           if (select>all || all<=0 || select<=0)

               throw new InvalidParameterException();

          

           int ret=1;

           int begin=0;

           if (all==select){

               return ret;

           }

          

           if (intTable!=null && intTable[select]!=-1)

               return intTable[select];

          

           if (select<all-select){

               begin=all-select+1;

           }

           else{

               begin=select+1;

           }

          

           for(int i=begin;i<=all;ret*=i++);

           for(int i=all-begin+1;i>1;ret/=i--);

          

           if (intTable==null){

               intTable=new int[all+1];

               for(int i=0;i<all+1;intTable[i++]=-1);

           }

           intTable[select]=ret;

          

           return ret;

        }

    面试官的问题又来了:还能再优化吗?你可以与面试官讨论更多的细节以决定不同的优化方案,也可以如实告诉面试官你没有想到别的办法。我想,这时面试官期待的已经不是你能给他新的代码了,更多的是在考察你有没有独立解决难题这样代码之外的能力了。

    对于前文的代码,最后通过对比发现,使用数组缓存的实现其运行效率最高,然后是改良算法2,接下来是阶乘的实现方案,但比combination2要慢上1-2倍,最后是使用HashMap的实现效率最低。

    通过上述解析,可以看到,貌似简单的面试题,其实可以考察到很多内容,所以,求职时要认真准备,认真对待。

    附完整的测试代码。

    1. import java.security.InvalidParameterException;  
    2. import java.util.HashMap;  
    3. import java.util.Map;  
    4.   
    5.   
    6. /** 
    7.  * @author caowenbin 
    8.  * wenbin1027@hotmail.com 
    9.  * 
    10.  */  
    11. public class Example {  
    12.     private static Map<String,Integer> mapTable=new HashMap<String,Integer>();  
    13.     private static int[] intTable;  
    14.       
    15.     private static int factorial(int n){  
    16.         if (n<0)  
    17.             throw new InvalidParameterException();  
    18.           
    19.         int ret=1;  
    20.         if (n==0 || n==1)  
    21.             return ret;  
    22.         else  
    23.             for(int i=2;i<=n;ret*=i++);  
    24.           
    25.         return ret;  
    26.     }  
    27.       
    28.     public static int combination1(int all,int select){  
    29.         if (select>all || all<=0 || select<=0)  
    30.             throw new InvalidParameterException();  
    31.           
    32.         if (select==all)  
    33.             return 1;  
    34.           
    35.         return factorial(all)/factorial(select)/factorial(all-select);  
    36.     }  
    37.       
    38.     public static int combination2(int all,int select){  
    39.         if (select>all || all<=0 || select<=0)  
    40.             throw new InvalidParameterException();  
    41.           
    42.         int ret=1;  
    43.         int begin=0;  
    44.         if (all==select)  
    45.             return ret;  
    46.           
    47.         //从1-begin之间的数被约分掉以简化计算  
    48.         if (select<all-select)  
    49.             begin=all-select+1;  
    50.         else  
    51.             begin=select+1;  
    52.           
    53.         for(int i=begin;i<=all;ret*=i++);    //计算分子  
    54.         for(int i=all-begin+1;i>1;ret/=i--); //被分母除  
    55.           
    56.         return ret;  
    57.     }  
    58.       
    59.     public static int combination3(int all,int select){  
    60.         if (select>all || all<=0 || select<=0)  
    61.             throw new InvalidParameterException();  
    62.           
    63.         int ret=1;  
    64.         int begin=0;  
    65.         if (all==select){  
    66.             return ret;  
    67.         }  
    68.           
    69.         String tableKey=new String(all+","+select);  
    70.         Integer tableValue=mapTable.get(tableKey);  
    71.         if (tableValue!=null)  
    72.             return tableValue.intValue();  
    73.           
    74.         if (select<all-select){  
    75.             begin=all-select+1;  
    76.         }  
    77.         else{  
    78.             begin=select+1;  
    79.         }  
    80.           
    81.         for(int i=begin;i<=all;ret*=i++);  
    82.         for(int i=all-begin+1;i>1;ret/=i--);  
    83.           
    84.         mapTable.put(tableKey, Integer.valueOf(ret));  
    85.           
    86.         return ret;  
    87.     }  
    88.       
    89.     public static int combination4(int all,int select){  
    90.         if (select>all || all<=0 || select<=0)  
    91.             throw new InvalidParameterException();  
    92.           
    93.         int ret=1;  
    94.         int begin=0;  
    95.         if (all==select){  
    96.             return ret;  
    97.         }  
    98.           
    99.         if (intTable!=null && intTable[select]!=-1)  
    100.             return intTable[select];  
    101.           
    102.         if (select<all-select){  
    103.             begin=all-select+1;  
    104.         }  
    105.         else{  
    106.             begin=select+1;  
    107.         }  
    108.           
    109.         for(int i=begin;i<=all;ret*=i++);  
    110.         for(int i=all-begin+1;i>1;ret/=i--);  
    111.           
    112.         if (intTable==null){  
    113.             intTable=new int[all+1];  
    114.             for(int i=0;i<all+1;intTable[i++]=-1);  
    115.         }  
    116.         intTable[select]=ret;  
    117.           
    118.         return ret;  
    119.     }  
    120.     /** 
    121.      * @param args 
    122.      */  
    123.     public static void main(String[] args) {  
    124.         final int repeat=500000;  
    125.   
    126.         System.out.println("==========================");  
    127.         long time=System.currentTimeMillis();  
    128.         for(int i=0;i<repeat;i++){  
    129.             for(int j=1;j<=10;j++)  
    130.                 Example.combination1(10,j);  
    131.         }  
    132.         System.out.println("TimeSpan1="+(System.currentTimeMillis()-time));  
    133.         System.out.println("==========================");  
    134.           
    135.         time=System.currentTimeMillis();  
    136.         for(int i=0;i<repeat;i++){  
    137.             for(int j=1;j<=10;j++)  
    138.                 Example.combination2(10,j);  
    139.         }  
    140.         System.out.println("TimeSpan2="+(System.currentTimeMillis()-time));  
    141.         System.out.println("==========================");  
    142.           
    143.         time=System.currentTimeMillis();  
    144.         for(int i=0;i<repeat;i++){  
    145.             for(int j=1;j<=10;j++)  
    146.                 Example.combination3(10,j);  
    147.         }  
    148.         System.out.println("TimeSpan3="+(System.currentTimeMillis()-time));  
    149.         System.out.println("==========================");  
    150.   
    151.         time=System.currentTimeMillis();  
    152.         for(int i=0;i<repeat;i++){  
    153.             for(int j=1;j<=10;j++)  
    154.                 Example.combination4(10,j);  
    155.         }  
    156.         System.out.println("TimeSpan4="+(System.currentTimeMillis()-time));  
    157.         System.out.println("==========================");  
    158.     }  
    159. }  
  • 相关阅读:
    java安全编码指南之:Number操作
    java安全编码指南之:声明和初始化
    java安全编码指南之:Mutability可变性
    巧用HashMap一行代码统计单词出现次数
    java安全编码指南之:基础篇
    drf 路由生成
    drf 视图使用及源码分析
    drf Response浅析
    drf Serializer基本使用
    drf APIView源码浅析
  • 原文地址:https://www.cnblogs.com/jerryxing/p/2450741.html
Copyright © 2011-2022 走看看