本文出自《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.}
本文来自博客,转载请标明出处:http://blog.csdn.net/caowenbin/archive/2011/03/02/6217976.aspx