题目:
标题:测试次数
x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。
各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。
x星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的2楼。
如果手机从第7层扔下去没摔坏,但第8层摔坏了,则手机耐摔指数=7。
特别地,如果手机从第1层扔下去就坏了,则耐摔指数=0。
如果到了塔的最高层第n层扔没摔坏,则耐摔指数=n
为了减少测试次数,从每个厂家抽样3部手机参加测试。
某次测试的塔高为1000层,如果我们总是采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢?
请填写这个最多测试次数。
注意:需要填写的是一个整数,不要填写任何多余内容。
解题思路:
题目问题:1000层楼,三部手机,最坏的运气下最多测试多少次能得到摔坏前的最高层。将问题进行转换:3部手机,测试k次,最多能测试多少层?
首先,先考虑两部手机,测试k次,最多能测试多少层?
进行第一次测试,先测第k层,消耗了一次测试机会,剩下k-1次测试机会。手机摔坏了,那么剩下的另一部手机只能从第一层开始一层一层的测试,测到第k-1层(共测了k-1层)。加上第一次测的那一层,这样测试k次,最多能测(k-1)+1层。
如果第一次测试时,手机没坏,那么第k层及其以下层都是安全的。
进行第二次测试,手机坏了,此时剩下k-2次测试机会,另一部手机测k-2次,只能测k-2层,所以第二次测试应该测第[(k-1)+1]+[(k-2)+1]层,最多能测[(k-1)+1]+[(k-2)+1]层。
如果第二次测试时,没有坏。
进行第三次测试,手机坏了,此时剩下k-3次测试机会,另一部手机测k-3次,只能测k-3层,所以第三次测试应该测第[(k-1)+1]+[(k-2)+1]+[(k-3)+1]层,最多能测[(k-1)+1]+[(k-2)+1]+[(k-3)+1]层。
......
如果第j-1次测试时,没有坏。
进行第j测试,手机坏了,此时剩下k-j次测试机会,另一部手机测k-j次,只能测k-j层,所以第j次测试应该测第[(k-1)+1]+[(k-2)+1]+[(k-3)+1]+......+[(k-j)+1]层,最多能测[(k-1)+1]+[(k-2)+1]+[(k-3)+1]+......+[(k-j)+1]层。
......
如果上述测试过程中,手机一直没有摔坏,直到最后进行第k次测试,应该测试第[(k-1)+1]+[(k-2)+1]+[(k-3)+1]+......+[(k-k)+1]层。
那么两部手机,1000层楼,需要测k次才能完成测试,那么:
[(k-1)+1]+[(k-2)+1]+[(k-3)+1]+......+[(k-k)+1]
=k+(k-1)+(k-2)+......+1
=k(k+1)/2 ≥ 1000
解得:k≥44.22415.....
k需要向上取整,k最少为45。
如果将一部手机进行k次测试的结果用数组保存起来,下角标表示进行第k测试,数组内容表示能够测的最大楼层数,即:
pre[k] = k
则两部手机进行k次测试时,能够测的最大楼层数为:
[(k-1)+1]+[(k-2)+1]+[(k-3)+1]+......+[(k-k)+1]
=(pre[k-1]+1)+(pre[k-2]+1)+......+(pre[0]+1) ≥ 1000
如果将两部手机进行k次测试时的结果用数组保存起来,下角标表示进行第k测试,数组内容表示能够测的最大楼层数,即:
current[k] = (pre[k-1]+1)+(pre[k-2]+1)+......+(pre[0]+1)
那么current[k-1] = (pre[k-2]+1)+(pre[k-3]+1)+......+(pre[0]+1)
则两部手机进行k次测试时,能够测的最大楼层数为:
current[k] = (pre[k-1]+1)+(pre[k-2]+1)+(pre[k-3]+1)......+(pre[0]+1)
= pre[k-1] + 1 + current[k-1] ≥ 1000
然后,回到正题,三部手机的情况下呢?
先记住两部手机可以推出的结论:两部手机,k次测试,最多能测k(k+1)/2层
三部手机,思路同前面一样,先确定第一次测试时的楼层位置,第一次测试,不能太高也能太矮,必须恰到好处,也就是第一部手机如果摔坏,剩余k-1次机会,用两部手机能够将剩余楼层给测试完。
因为两部手机k-1次测试,最多能测试k(k-1)/2层,所以第一部手机第一次测试时,先测第k(k-1)/2+1层。
进行第一次测试,先测第k(k-1)/2+1层,消耗了一次测试机会,剩下k-1次测试机会。手机摔坏了,那么剩下的另两部手机只能从第一层开始一层一层的测试,测到第k(k-1)/2层(共测了k(k-1)/2层)。加上第一次测的那一层,这样测试k次,最多能测(k(k-1)/2+1层。
如果第一部手机第一次测试时,如果第一部手机没有摔坏,那么第k(k-1)/2+1层及其以下层都是安全的。
进行第二次测试,手机坏了,此时剩下k-2次测试机会,两部手机进行k-2次测试最多能测(k-1)(k-2)/2层,所以第二次测试应该测第[k(k-1)/2+1]+[(k-1)(k-2)/2+1]层,最多能测[k(k-1)/2+1]+[(k-1)(k-2)/2+1]层。
如果第二次测试时,没有坏。
进行第三次测试,手机坏了,此时剩下k-3次测试机会,两部手机进行k-3次测试最多能测(k-2)(k-3)/2层,所以第三次测试应该测第[k(k-1)/2+1]+[(k-1)(k-2)/2+1]+[(k-2)(k-3)/2+1]层,最多能测[k(k-1)/2+1]+[(k-1)(k-2)/2+1]+[(k-2)(k-3)/2+1]层。
......
如果第j-1次测试时,没有坏。
进行第j次测试,手机坏了,此时剩下k-j次测试机会,两部手机进行k-j次测试最多能测(k-j)(k-j+1)/2层,所以第三次测试应该测第[k(k-1)/2+1]+[(k-1)(k-2)/2+1]+[(k-2)(k-3)/2+1]+......+[(k-j)(k-j+1)/2+1]层。
最多能测[k(k-1)/2+1]+[(k-1)(k-2)/2+1]+[(k-2)(k-3)/2+1]+......+[(k-j)(k-j+1)/2+1]层。
......
进行第k次测试,此时剩下0次测试机会,两部手机进行0次测试最多能测0层,所以第k次测试应该测第[k(k-1)/2+1]+[(k-1)(k-2)/2+1]+[(k-2)(k-3)/2+1]+......+[(1*0)/2+1]层。
最多能测[k(k-1)/2+1]+[(k-1)(k-2)/2+1]+[(k-2)(k-3)/2+1]+......+[(1*0)/2+1]层。
则三部手机进行k次测试时,能够测的最大楼层数为:
[k(k-1)/2+1]+[(k-1)(k-2)/2+1]+[(k-2)(k-3)/2+1]+......+[(1*0)/2+1]
如果将两部手机进行k次测试的结果用数组保存起来,下角标表示进行第k测试,数组内容表示能够测的最大楼层数,即:
pre[k] = k(k+1)/2
则三部手机进行k次测试时,能够测的最大楼层数为:
(pre[k-1]+1)+(pre[k-2]+1)+(pre[k-3]+1)......+(pre[0]+1) ≥ 1000
如果将三部手机进行k次测试时的结果用数组保存起来,下角标表示进行第k测试,数组内容表示能够测的最大楼层数,即:
current[k] = (pre[k-1]+1)+(pre[k-2]+1)+......+(pre[0]+1)
那么current[k-1] = (pre[k-2]+1)+(pre[k-3]+1)+......+(pre[0]+1)
则三部手机进行k次测试时,能够测的最大楼层数为:
current[k] = (pre[k-1]+1)+(pre[k-2]+1)+(pre[k-3]+1)......+(pre[0]+1)
= pre[k-1] + 1 + current[k-1] ≥ 1000
由此看出用三部手机进行k次测试时,需要用到三部手机进行k-1次测试和两部手机进行k-1次测试的数据。
可以推广得到,对于n部手机进行k次测试时,需要用到n部手机进行k-1次测试和n-1部手机进行k-1次测试的数据。
有重叠子问题(current[k-1]),子问题有最优解或可以用有更优解的新子问题(复杂程度更小的pre[k-1] )代替。而动态规划常被用于有重叠子问题和最优子结构性子的问题。可以使用动态规划求解k值,先介绍一下动态规划基本步骤:
- 将复杂的问题分解成简单的问题,求解简单的问题,以求解复杂的问题。
- 自底向上的求解最小规模子问题的最优解,用一张表将结果记录下来。
- 遇到相同子问题,直接查表得到答案。通过一步一步的迭代,就可以得到复杂问题的答案。
动态规划在分解问题的时候,往往可以用递归问题描述,然而递归存在多次重复计算,采用自底上求解最小规模子问题的最优解并记录结果,可以省去重复计算。这样就将递归问题转换成了迭代问题。
这里n部手机k次测试,自底向上求解,用数组记录计算结果:
用数组pre[]存放n-1部手机的测试结果,数组current[]用于存放当前n部手机测试结果。
n=1时:
- current[k] = k;
n=2时:
- pre[] = current[];
- current[k] = pre[k-1]+1+current[k-1];
- current[k]>=1000?
n=3时:
- pre[] = current[];
- current[k] = pre[k-1]+1+current[k-1];
- current[k]>=1000?
......
用JAVA代码描述:
//存放当前n部手机测试结果
int current[] = new int[m+1];
//存放n-1部手机的测试结果
int pre[] = new int[m+1];
//current初始化,同时存入一部手机的测试数据
for(int i=0;i<m+1;i++){
current[i] = i;
}
//两部及两部以上的测试情况
for(int i=2;i<=n;i++){
pre = current.clone();
current[k] = pre[k-1]+1+current[k-1];
current[k] >= m?
}
我们要寻找的是,k取何值时,current[k]>=m?
k的取值范围应该是1到m(一部手机测试时,k=m时,才满足条件),循环遍历k的所有可能取值,找到满足条件的k值,则跳出循环,并将k值返回,问题得以解决。
//存放当前n部手机测试结果
int current[] = new int[m+1];
//存放n-1部手机的测试结果
int pre[] = new int[m+1];
//current初始化,同时存入一部手机的测试数据
for(int i=0;i<m+1;i++){
current[i] = i;
}
int times = 0;
//两部及两部以上的测试情况
for(int i=2;i<=n;i++){
pre = current.clone();
for (int k = 1; k <= m; k++) {
current[k] = pre[k-1]+1+current[k-1];
if (current[k] >= m) {
//使用变量times存放k值
times = k;
//跳出循环块
break;
}
}
}
将以上代码封装成函数,供main函数调用:
public class Main {
public static void main(String[] args) {
System.out.println(getTimes(1000, 3));
}
/**
* 获得测试次数
* @param m 楼层数
* @param n 手机数量
* @return times 测试次数k
*/
public static int getTimes(int m,int n) {
//存放当前n部手机测试结果
int current[] = new int[m+1];
//存放n-1部手机的测试结果
int pre[] = new int[m+1];
//current初始化,同时存入一部手机的测试数据
for(int i=0;i<m+1;i++){
current[i] = i;
}
int times = 0;
//两部及两部以上的测试情况
for(int i=2;i<=n;i++){
pre = current.clone();
for (int k = 1; k <= m; k++) {
current[k] = pre[k-1]+1+current[k-1];
if (current[k] >= m) {
//使用变量times存放k值
times = k;
//跳出循环块
break;
}
}
}
return times;
}
}
运行结果截图: