经典算法应用之六---过桥问题和过河问题

过桥问题和倒水问题都是笔试面试中的热门智力题,不但微软、GOOGLE、百度、腾讯等公司采用,甚至在IQ测试与公务员考试中都能见到。本文不但教你如何快速用手算来解决这两种问题,并且教你如何用程序代码来计算这两种问题。绝对让你大有收获。
一.过桥问题
在漆黑的夜里,四位旅行者来到了一座狭窄而且没有护栏的桥边。如果不借助手电筒的话,大家是无论如何也不敢过桥去的。不幸的是,四个人一共只带了一只手电筒,而桥窄得只够让两个人同时通过。如果各自单独过桥的话,四人所需要的时间分别是1,2,5,8分钟;而如果两人同时过桥,所需要的时间就是走得比较慢的那个人单独行动时所需的时间。问题是,你如何设计一个方案,让用的时间最少。
这个题目被微软、GOOGLE、百度、华硕、建设银行等很多公司用作笔试题或面试题。当然也有用在IQ测试中。
解答其实也容易,能者多劳这四个字就足以形容解答方案了——用时短的人必须要多跑几趟以便传递手电筒。
设这四个人叫做A,B,C,D,他们所需要的时间分别是1,2,5,8分钟。
第一步:A和B过桥,花费2分钟。
第二步:A回来,花费1分钟。
第三步:C和D过桥,花费8分钟。
第四步:B回来,花费2分钟。
第五步:A和B过桥,花费2分钟。
这样只要花费2+1+8+2+2=15分钟,下面再来考虑如何用程序来解决这类问题,在写程序之前还有个细节要考虑下,比如A,B,C,D四个人所需要的时间分别是1,8,9,10分钟。
方案一
第一步:A和B过桥,花费8分钟。
第二步:A回来,花费1分钟。
第三步:C和D过桥,花费10分钟。
第四步:B回来,花费8分钟。
第五步:A和B过桥,花费8分钟。
一共要8+1+10+8+8=35分钟。
方案二
第一步:A和B过桥,花费8分钟。
第二步:A回来,花费1分钟。
第三步:A和C过桥,花费9分钟。
第四步:A回来,花费1分钟。
第五步:A和D过桥,花费10分钟。
一共要8+1+9+1+10=29分钟。
因此可以得出更加细化的解决方案——要么是最快者将最慢的2个送过桥,要么是最快的2个将最慢的2个送过桥。即将过桥的人按其过桥的时间从小到大排列,设为A,B,……Y,Z。其中A和B是最快的二个,Y和Z是最慢的二个。那么就有二种方案:
方案一 最快者将最慢的2个送过桥
第一步:A和Z过桥,花费Z分钟。
第二步:A回来,花费A分钟。
第三步:A和Y过桥,花费Y分钟。
第四步:A回来,花费A分钟。
这四步后总人数就减小2个,花费时间为A + A + Y + Z分钟。
方案二 最快的2个将最慢的2个送过桥
第一步:A和B过桥,花费B分钟。
第二步:A回来,花费A分钟。
第三步:Y和Z过桥,花费Z分钟。
第四步:B回来,花费B分钟。
这四步后总人数同样减小2个,花费时间为A + B + B + Z分钟。
这样,每次比较一下这二种方案就能将总人数减小2。然后我们再考虑一些边界情况:
有三个人过桥设为A,B,C(已经排好序,下同)。应该花费A + B + C分钟。
有二个人过桥设为A,B。那么肯定是花费B分钟。
有一个人过桥设为A。肯定花费A分钟。

分析至此,代码很容易写出,对输入的数据可以用快速排序来排下序(关于qsort的使用,可以参见《使用VC库函数中的快速排序函数》)。

//热门智力题 - 过桥问题  
const int MAXN = 1000;  
int cmp(const void *x,const void *y)  
{  
    return *(int *)x-*(int *)y;  
}  
int main()  
{  
    cout<<"热门智力题 - 过桥问题"<<endl;  
    cout<<"  --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n"<<endl;    
  
    int n, i, sum, a[MAXN];  
    cout<<"请输入人数:";  
    cin>>n;  
    cout<<"请输入每个人过桥时间,以空格分开"<<endl;  
    for (i = 0; i < n; i++)  
        cin>>a[i];  
    qsort(a, n, sizeof(a[0]), cmp);  
    sum = 0;  
    for (i = n - 1; i > 2; i = i - 2)  
    {    
        //最小者将最大2个送走或最小2个将最大2个送走  
        if (a[0] + a[1] + a[1] + a[i] < a[0] + a[0] + a[i - 1] + a[i])  
            sum = sum + a[0] + a[1] + a[1] + a[i];  
        else  
            sum = sum + a[0] + a[0] + a[i - 1] + a[i];    
    }  
    if (i == 2)  
        sum = sum + a[0] + a[1] + a[2];  
    else if (i == 1)   
        sum = sum + a[1];  
    else   
        sum = a[0];  
    cout<<"最短过桥时间为:"<<sum<<endl;  
    return 0;  
}  

程序运行结果如下:

二.倒水问题
这个题目的版本非常之多,有微软版的,腾讯版的,新浪版的等等,最常见的是给你一个容量为5升的桶和一个容量为3升的桶,水不限使用,要求精确得到4升水。
解法肯定有很多,可以用宽度优先搜索(BFS),也可以用穷举法。穷举法实现比较方便,其基本思想是用:用小桶容量的倍数对大桶的容量进行取余。比如3升的桶和5升的桶得到4升水可以这样做:
3 % 5 = 3
6 % 5 = 1
9 % 5 = 4
成功得到4升水。(PS:上面的过程用如何用文字描述了?)
同样,用7升的桶和11升的桶得到2升水可以这样做:
7 % 11 = 7
14 % 11 = 3
21 % 11 = 10
28 % 11 = 6
35 % 11 = 2
成功得到2升水。
哈哈,有了这个基本思想在用笔算答案时简直是遇神杀神,遇佛杀佛,又方便又快速!如果要求用程序来实现如何做了?easy,将倒水问题的基本思想用易于编程的话来翻译下——不断用小桶装水倒入大桶,大桶满了立即清空,每次判断下二个桶中水的容量是否等于指定容量。有了这个倒水问题的编程指导方针后代码非常容易写出:

//热门智力题 - 打水问题  
//基本思想:用小桶容量的倍数对大桶的容量进行取余。  
//指导方针:不断用小桶装水倒入大桶,大桶满了立即清空,  
//每次判断下二个桶中水的容量是否等于指定容量。  
const string OPERATOR_NAME[7] = {  
    "装满A桶","装满B桶",  
    "将A桶清空","将B桶清空",  
    "A桶中水倒入B桶","B桶中水倒入A桶",  
    "成功"  
};  
int main()  
{  
    cout<<"热门智力题 - 打水问题"<<endl;  
    cout<<"  --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n"<<endl;  
  
    int    a_volume, b_volume, goal_volume;  
    vector record;       //记录操作步数  
    int    ai;  
    int    i, a_water, b_water;  
  
    cout<<"请输入A桶容量,B桶容量,目标容量:";  
    cin>>a_volume>>b_volume>>goal_volume;  
    a_water = b_water = 0; //A桶,B桶中有多少升水  
    char szTemp[30];  
    while (true)  
    {  
        if (a_water == 0)//A桶没水,就装满水  
        {  
            a_water = a_volume;  
            sprintf(szTemp, "         A:%d  B:%d", a_water, b_water);   
            record.push_back(OPERATOR_NAME[0] + szTemp);//fill A  
        }  
        else  
        {  
            //如果A桶的水比(B桶容量-B桶的水)要多  
            if (a_water > b_volume - b_water)  
            {  
                //A桶的水==A桶的水+B桶的水-B桶容量  
                a_water = a_water + b_water- b_volume;  
                b_water = b_volume;      //B桶的水装满了  
                sprintf(szTemp, "  A:%d  B:%d", a_water, b_water);   
                record.push_back(OPERATOR_NAME[4] + szTemp);//A->B     
                if (a_water == goal_volume)  
                    break;  
                b_water = 0;            //将B桶清空  
                sprintf(szTemp, "       A:%d  B:%d", a_water, b_water);   
                record.push_back(OPERATOR_NAME[3] + szTemp);  
            }  
            else  
            {  
                //B桶的水==A桶的水+B桶的水  
                b_water += a_water;   
                a_water = 0;  
                sprintf(szTemp, "  A:%d  B:%d", a_water, b_water);  
                record.push_back(OPERATOR_NAME[4] + szTemp);//A->B  
                if (b_water == goal_volume)   
                    break;  
            }  
        }  
    }  
    record.push_back(OPERATOR_NAME[6]); //success  
    cout<<"\n---------------------------------------------------"<<endl;  
    cout<<"一个可行的倒水方案如下"<<endl;  
    vector::iterator pos;  
    for (pos = record.begin(); pos != record.end(); pos++)  
        cout<<*pos<<endl;  
    cout<<"---------------------------------------------------"<<endl;  
    return 0;  
}  

程序运行结果如下:



注意这里只是给出一个可行的倒水方案,不一定是最优解。另外倒水问题要注意下像用2升的桶和4升的桶得到3升水这种不可解的情况,这种不可解情况在用本文中对倒水问题所总结的基本思想计算时会得到循环数列。其根本原因是二个桶容量的最大公约数无法被目标容量所整除,如6升的桶和9升的桶无法得到2升水是因为6和9的最大公约数是3即GCD(6,9)=3而3无法整除2。
倒水问题也也容易推广到多个桶的情况,分析方法和上文差不多,这里就不再赘述了。

过桥问题和倒水问题就讲解到此,相信以后这二类问题都不会再困扰大家了_


推荐阅读:
经典算法应用之一----归并排序(微软笔试题)
经典算法应用之二----基数排序(google笔试题)
经典算法应用之三----应用二中题目的升华
经典算法应用之四(上)---基本位操作之算法篇
经典算法应用之四(中)---基本位操作之算法篇
经典算法应用之四(下)---百度面试题
经典算法应用之五---随机生成和为S的N个正整数
经典算法应用之六---过桥问题和过河问题
经典算法应用之七----10亿数据中取最大的100个数据

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • 有句话叫做“也许你现在的生活不是你想要的,但一定是你自找的"而这,就是源于错误的认识,做了错误的决定,让你觉得自己...
    若流水阅读 302评论 0 0
  • 欢迎前往个人博客驽马点滴和视频空间哔哩哔哩-《挨踢日志》 个人比较倾向于把内容的制作权限开放出去。 的制作过程中,...
    KomalZheng阅读 165评论 0 0
  • 家乡桃花开了阅读 269评论 0 0