您的位置:首页 > 职场人生

汽车加油问题——一道迷惑的面试题

2010-01-19 09:25 197 查看
问题

一辆载油500升的汽车从A开往1000公里外的B,已知汽车每公里耗油量为1升,A处有无穷多的油,其他任何地点都没有油,但该车可以在任何地点存放油以备中转,问从A到B最少需要多少油?

分析
这道题目初一看觉得方案太灵活,有点晕,网上所有对此题目的答案千篇一律的是:

题目可归结为求数列 an=500/(2n+1) n=0,1,2,3......的和Sn什么时候大于等于1000,解得n>6

当n=6时,S6=977.57

所以第一个中转点离起始位置距离为1000-977.57=22.43公里

所以第一次中转之前共耗油 22.43*(2*7+1)=336.50升

此后每次中转耗油500升

所以总耗油量为7*500+336.50=3836.50升

看起来很有道理,但是又没有说明为什么?害我抓破脑皮去想:这是为什么?怎么证明的?

不过我们简单推下:3836.50 - 7*(500 - 2*22.43) = 650.52,也就是说第一次中转跑7次会剩下650.52,证明这个答案是错误的!

后来我猜想出一种解释,首先我们需要抓住几个关键点:

1. 运输距离要尽量短,也就是说,我们的方案肯定是先从起点将所有需要的油搬到第一个x公里外的转运点,然后再以此类推,不能来回奔波于多个转运点。

2. 运输效率要尽可能高,也就是说,尽量以最大运输能力去跑——不要因为1、2公升油再跑一趟。

3. 相邻转运点之间跑的次数一定是奇数次。

由此,我们可以倒过来考虑:

1. 车到1000公里外的终点的时候,只需要放0的油在那里,转运次数1,可以求得距离是500;

2. 车到500公里外的转运点的时候,需要放500的油在那里,转运次数3,那么如何保证3次都是运输效率最高的呢?通过保证运运输效率最高,可以求得转运点的距离。上面也是这样求的。

3. 以此类推… 中转次数 1 3 5 7 …

解法一
/// <summary>

///计算在距离x的地方放a的油,跑2n+1次运输效率最高的距离

/// </summary>

double Distance(double a, int n)

{

return (tankLimit * (n + 1) - a) / (2 * n + 1);

}//x为距离,a = k(tankLimit - 2x) + 500 – x

/// <summary>

/// 在距离x的地方,存放amount的油需要跑的总次数

/// </summary>

int Trip(double x, double amount)

{

int n = 0;

while (true)

{

var a1 = 2 * n * (tankLimit - 2 * x);//来回运的油量

var a2 = tankLimit - x;//最后一次运的油量

var diff = a1 + a2 - amount;

if (diff >= 0 || -diff <= 0.0001) return 2 * n + 1;//避免计算误差

n++;

}

}

void Transport()//倒推中转

{

int k = 0;

double a = 0, d = 0;//a是每次中转的总耗油量,d是距离累计

while (d < 1000)

{

var last_a = a;

var x = Distance(a, 2 * k);

if (d + x <= 1000)

{

a += (2 * k + 1) * x;

k++;

}

else

{

x = 1000 - d;

a += Trip(x, a) * x;

}

d += x;

Console.WriteLine("a = {0:F} x = {1:F} Trip = {2}", a, x, Trip(x, last_a));

}

}

运行程序,可以求得:

a = 500.00 x = 500.00 Trip = 1

a = 1100.00 x = 200.00 Trip = 3

a = 1877.78 x = 155.56 Trip = 5

a = 2751.28 x = 124.79 Trip = 7

a = 2888.89 x = 19.66 Trip = 7

可以看出,如果采用这种运输方法,已经比上面网上流传的答案3836.50升少了很多了。但是,这种1 3 5 7…的运输方法就是最优的么?

解法二
或许我们并不需要1 3 5 7…这样递增这么快,1 3 3 3 5 5 5也可能是很好的方法,因此将程序小改动下:

double Transport(int factor)

{



var lastA = a;

var x = Distance(a, 2 * k);

if (x == 0) { k++; continue; }

if (d + x <= 1000)

{

a += (2 * k + 1) * x;

if (cnt++ % factor == 0) k++;

}



然后去计算factor从1~100的情况下耗油量分别是:

1 2888.88888888889

2 2507.65432098765

3 2376

4 2350.4

5 2340.16

6 2336.064

7 2334.4256

8 2333.77024

9 2333.508096

10 2333.4032384

11 2333.36129536

12 2333.344518144

13 2333.3378072576

14 2333.33512290304

15 2333.33404916122

可以看出,factor >15以后就趋于稳定了。

讨论
由此可以看出,不可以盲目迷信别人的解法(当然包括我现在的解法:)做到这里我们可以猜测,解法二也不能说就是最优了,可能存在一些更加优秀的序列——哈哈,说到这里,大家应该想到,这种问题最适合遗传算法了!我暂时没时间了,如果有兴趣的同学找到更优秀的解法麻烦通知我:)

作者:Silver 原文链接:http://yishan.cc/blogs/gpww/archive/2009/10/28/1-1.aspx
-------------------------------------

《编程之美——微软技术面试心得》源代码下载地址:

http://download.csdn.net/source/1883633或者http://www.broadview.com.cn/06074

另外,请大家帮个忙,来投票吧,看看《编程之美》第1章“游戏之乐——游戏中碰到的题目”大家最喜欢哪个题目,或者大家认为哪个题目最有趣。

在此先谢过大家了^_^ (可以选择以下任何网址参与投票)

CSDN:http://vote.csdn.net/VotePost.aspx?voteid=6419

开心网:http://www.kaixin001.com/~vote/detail.php?vid=6324994&uid=57936198

QQ空间:http://user.qzone.qq.com/80754098/vote/1021116304
其他文章:

连载1:卡特兰数(Catalan)

连载2:序列 ABAB对应字符串集合

连载3:最长公共子序列

连载4:计算字符串的相似度

连载5:寻找符合条件的整数

连载6:数组循环位移

连载7:天平秤球

连载8:动态有序集合——挑战红黑树

连载9:寻找最大的k 个数

连载10:一摞饼的排序

连载11:离散优化问题搜索框架 overview

连载12:时分秒针重合——看似简单的面试题
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: