Fork me on GitHub

《吃豆子过桥问题》——经典智力题、面试题

吃豆子过桥问题

  本题来自于百度校招面试题,通过一个简单的智力问题理解递归问题的解法。

  一:问题描述

  一个人要过一座80米的桥,每走一米需要吃一颗豆子,他最多可以装60颗豆子,问最少需要吃多少颗豆子才能走完桥?

 

  二:初步分析

  1.一趟(不折回)最多只能走60米豆子就会被吃完;

  2.如果有折回,必须保证能够返回到有豆子的地点,且在折回点放下的豆子尽量多;

  3.尽可能少的折回(次数和折回的距离都要少,毕竟一趟折回就要多消耗一个来回的豆子);

  4.折回点的豆子数量要求至少能够走完剩余的部分;

 

  三:具体分析

  1.由条件1得一趟走不完,由条件3我们可以考虑折回尽量少的次数能否走完。

  先考虑只折回一次,那么此时就要求得折回点的位置。

  2.由条件3得折回的距离尽量短,那么可以考虑最短的折回距离,很明显应该是20米处;设折回点距离起始位置x米,则此时x=20;那么在x处,需要至少有60颗豆子,一次性拿60颗豆子走完最后一程。

  那么问题就来了,怎么在x处囤积60颗豆子呢,假使他第一次信心满满的拿着60颗豆子,走到x处就只剩下40颗了,现在不够走完剩余的全程啊,只能放下一部分折回去再取了,那放多少呢,很明显由条件2得我们需要放下20颗,拿着20颗刚好可以折回到起点,继续拿着60颗豆子第二次来到x处,此时手上还有40颗,再捡起第一次放的20颗,共60颗豆子刚好可以走完全程。

  在这种情况下,我们易知,他共走了(20*3+60= 120米,故吃掉120颗豆子。

 

  四:问题拓展

  如果桥长81米呢?

  此时我们如果还是只折回一次的话,在21米处还是得有60颗豆子,也就是需要搬运60颗豆子到21米处;分析得不可能只折回一次就搬运60颗豆子到21米处,因此需要再设置一个折返点y,此时x=210<y<x

  刚才考虑了80米的情况,那么我们可以假设现在在80米的基础上加了1米,因此可以设y=1,即先折回两趟到1米处,这样y处就有60*3-5*(1)=175颗豆子,再从yx折返1趟,x处就有60*2-3*(20)=60颗豆子,最后从x出发拿着60颗豆子就可以愉快的走过桥了。

  因此他共走过5*(1)+3*(20)+(60) = 125米,故吃掉125颗豆子。

 

  五:再次拓展

  如果桥长n米,最多装m颗豆子,最少消耗f(n,m)nm的关系是什么呢?

  此时问题突然就变得很复杂了,别急,我们分析一下刚才的思路,桥长从80米到81米,就是要在1米处放足够多豆子(当然只要不小于桥长80米时的消耗就行),那么这些豆子又需要从起点处运到1米处,那么在这1米内又需要消耗多少豆子呢?

  我们可以考虑先把可装的最大豆子数m固定(假设还是60),当桥长0<n<=60米时,f(n,m) = n

  当n=61时,需要在1米处囤积f(n-1,6)f(60,60)=60颗豆子,囤积过程中需要往返一次,第一次到达1米处留下58颗豆子赶紧回去再取60颗到达1米处还剩59颗,现在有117颗足够走完最后60米。消耗豆子1*2+(1)+60 = 63颗豆子;

  当n=62时,需要在1米处囤积f(n-1,m)f(61,60)=63颗豆子,囤积过程中需要往返一次,第一次到达1米处留下58颗豆子赶紧回去再取60颗到达1米处还剩59颗,现在有117颗足够走完最后61米。消耗豆子1*2+(1)+63 = 66颗豆子;

  以此类推...

  那么问题来了,细心的你肯定发现了上面那种情况都是往返一次的,但是不是每次都只往返一次,比如当n=81时,该怎么确定往返的次数呢?

  分析上面的递推关系我们可以知道,每次往返的趟数与f(n-1,m)的大小有关。分析往返过程易知:最后一次到达1米处剩59颗豆子,之前每次到达1米处可以放下58颗豆子,假设之前到达了1米处T次,则有:

  59 + 58 * T >= f(n-1,60)  ==>  T >= [f(n-1,60)-59]/58

  由于T必须是整数,故T = ceil((f(n-1,60)-59) / 58)ceil(x)表示对x向上取整,即不小于x的最小整数

  另一种表示方式为T = floor( (f(n-1,60)-2) / 58 ),这种写法是为了方便编程实现,整型数的除法会自动向下取整

  之前往返T次消耗掉2T颗豆子,最后一次到达1米处消耗1颗豆子,故总消耗:f(n-1,60) + 2T + 1颗豆子

  这时候我们考虑将固定为60m扩展为任意m,则有:f(n,m) = f(n-1,m) + ceil( f(n-1,m)-(m-1)) / (m-2) ) * 2+ 1 = f(n-1,m) + (f(n-1,m)-2) / (m-2) * 2+ 1 ;

  好了到这里,这个问题也彻底解决完了,现在就让我们用简短的代码来实现这个过程吧!

 

  六:编程实现   

 1 #include<cmath>
 2 #include<iostream>
 3 using namespace std;
 4 typedef unsigned long long int64;
 5 
 6 //参数说明:length为桥的长度,maxNum为最大可带的豆子数
 7 //递归实现
 8 int64 getMinConsume(int length, int maxNum) {
 9     if(length <= maxNum)
10         return length;
11     int64 get_n_1 = getMinConsume(length-1,maxNum); //上一次的豆子消耗
12     return get_n_1 + (get_n_1-2)/(maxNum-2) * 2 + 1;
13 }
14 //第二种公式实现——递归
15 int64 getMinConsume2(int length, int maxNum) {
16     if(length <= maxNum)
17         return length;
18     int64 get_n_1 = getMinConsume2(length-1,maxNum);  //上一次的豆子消耗
19     return get_n_1 + ceil((double)(get_n_1-maxNum+1)/(maxNum-2)) * 2 + 1;
20 }
21 //非递归的实现方式
22 int64 getMinConsume_NoRecursive(int length, int maxNum) {
23     if(length <= maxNum)
24         return length;
25     int64 result = maxNum, i;
26     for(i=maxNum; i<length; i++)
27         result += (result-2)/(maxNum-2) * 2 + 1;
28     return result;
29 }
30 
31 int main()
32 {
33     int maxNum=60, length;
34     for(length=50; length<201; length++)
35         cout<<length<<"米至少消耗"<<getMinConsume2(length,maxNum)<<"颗豆子!"<<endl;
36     return 0;
37 }
38 
39 int main2()
40 {
41     while(1){
42         cout<<"请输入最大可带的豆子数量和桥的长度: ";
43         int maxNum, length;
44         if(!(cin>>maxNum>>length))
45             break;
46         cout<<"至少消耗"<<getMinConsume_NoRecursive(length,maxNum)<<"颗豆子!"<<endl;
47     }
48     return 0;
49 }

  最后,感谢百度三面面试官耐心的引导我思考这道题,并不断加大难度把这道题理解透彻,还监督我完成代码的实现,在此谢谢温和友善的刘面试官~

  更多关于百度校招面试经历请参见:百度校招面试经历及总结(全部通过等offer中)

posted @ 2015-09-21 23:53  闻波  阅读(9318)  评论(29编辑  收藏  举报
友情链接: