题目
某幢大楼有100层。你手里有两颗一模一样的玻璃珠。当你拿着玻璃珠在某一层往下扔的时候,一定会有两个结果,玻璃珠碎了或者没碎。这幢大楼有个临界楼层。低于它的楼层,往下扔玻璃珠,玻璃珠不会碎,等于或高于它的楼层,扔下玻璃珠,玻璃珠一定会碎。玻璃珠碎了就不能再扔。现在让你设计一种方式,使得在该方式下,最坏的情况扔的次数比其他任何方式最坏的次数都少。也就是设计一种最有效的方式。
分析
1)最笨的办法:从第一层开始试,每次增加一层,当哪一层扔下玻璃珠后碎掉了,也就知道了。不过最坏的情况扔的次数可能为100。
2)随便挑一层,假如为N层,扔下去后,如果碎了,那就只能从第一层开始试了,最坏的情况可能为N;假如没碎,就一次增加一层继续扔吧,这时最坏的情况为100-N。也就是说,采用这种办法,最坏的情况为max{N, 100-N+1}。之所以要加一,是因为第一次是从第N层开始扔。
3)假如没摔的话,不如不要一次增加一层继续扔吧,而是采取另外一种方式:把问题转换为100-N,在这里面找临界楼层,这样不就把问题转换成用递归的方式来解决。
定义一个函数F(N),表示N层楼最有效方式最坏情况的次数;
通过上面的分析,有
F(N)=Min(
Max(1,1+F(N-1)),
Max(2,1+F(N-2))
…
Max(N-1,1+F(1))
);
F(1)=1;
求F(100);
int floor1(int N){
int F[N+1];
F[0]=F[1]=1;
for(int i=2;i<=N;i++){
F[i]=i;
for(int j=1;j<i;j++){
int tmp=max(j, F[i-j]+1);
if(tmp<F[i]){
F[i]=tmp;
}
}
}
return F[N];
}
int main()
{
int res=floor1(100);
cout<<res;
system("pause");
return 0;
}
将此题扩展为N个球,M层:
我们可以把M层楼 / N个鸡蛋的问题转化成一个函数 F(M,N),其中楼层数M和鸡蛋数N是函数的两个参数,而函数的值则是最优解的最大尝试次数。
假设我们第一个鸡蛋扔出的位置在第X层(1<=X<=M),会出现两种情况:
1.第一个鸡蛋没碎
那么剩余的M-X层楼,剩余N个鸡蛋,可以转变为下面的函数:
F(M-X,N)+ 1,1<=X<=M
2.第一个鸡蛋碎了
那么只剩下从1层到X-1层楼需要尝试,剩余的鸡蛋数量是N-1,可以转变为下面的函数:
F(X-1,N-1) + 1,1<=X<=M
整体而言,我们要求出的是 M层楼 / N个鸡蛋 条件下,最大尝试次数最小的解,所以这个题目的状态转移方程式如下:
F(M,N)= Min(Max( F(M-X,N)+ 1, F(X-1,N-1) + 1)),1<=X<=M
int getMinSteps(int eggNum, int floorNum){
if(eggNum < 1 || floorNum < 1) {
return 0;
}
//上一层备忘录,存储鸡蛋数量-1的floorNum层楼条件下的最优化尝试次数
int preCache[floorNum+1];
//当前备忘录,存储当前鸡蛋数量的floorNum层楼条件下的最优化尝试次数
int currentCache[floorNum+1];
//把备忘录每个元素初始化成最大的尝试次数
for(int i=1;i<=floorNum; i++){
currentCache[i] = i;
}
for(int n=2; n<=eggNum; n++){
//当前备忘录拷贝给上一次备忘录,并重新初始化当前备忘录
for(int i=1;i<=floorNum; i++){
preCache[i] = currentCache[i];
currentCache[i] = i;
}
for(int m=1; m<=floorNum; m++){
for(int k=1; k<m; k++){
//扔鸡蛋的楼层从1到m枚举一遍,如果当前算出的尝试次数小于上一次算出的尝试次数,则取代上一次的尝试次数。
//这里可以打印k的值,从而知道第一个鸡蛋是从第几次扔的。
currentCache[m] = min(currentCache[m], 1+max(preCache[k-1],currentCache[m-k]));
}
}
}
return currentCache[floorNum];
}
int main() {
cout<<getMinSteps(2,2080)<<endl;
system("pause");
return 0;
}