《吃豆子過橋問題》——經典智力題、面試題


吃豆子過橋問題

  本題來自於百度校招面試題,通過一個簡單的智力問題理解遞歸問題的解法。

  一:問題描述

  一個人要過一座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中)


免責聲明!

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



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