解析面试题的背后

本文出自《http://blog.csdn.net/caowenbin

无论是技术笔试还是面试,通常会要求写一点代码或详细描述程序思路,很多应试者都认为自己的回答还算过得去,但往往还是没有了下文。究其原因,既有对试题的轻视,也有平时没有养成良好的思维习惯。其实无论试题的难易程度如何,其背后都可以考核出很多知识点来,比如我们来看这样一道简单的笔试题:

写程序,能够计算组合数 。

 

这道题太简单了,当我们第一次学程序设计语言的时候可能都写过这个程序,我们一起来看一下这道题的思路过程。

 

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

    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的实现效率最低。

 

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

 

附完整的测试代码。

 

view plaincopy to clipboardprint?
01.import java.security.InvalidParameterException;  
02.import java.util.HashMap;  
03.import java.util.Map;  
04. 
05. 
06./** 
07. * @author caowenbin 
08. * wenbin1027@hotmail.com 
09. * 
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.} 


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/caowenbin/archive/2011/03/02/6217976.aspx

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值