题意整理
- 给定整数n作为楼层数,再给定整数k作为棋子数。
- 如果想找到棋子不会摔碎的最高层数,考虑最差的情况下,至少需要扔多少次棋子。
方法一(暴力递归)
1.解题思路
-
递归终止条件:0层时,不需要确定,返回0次;一枚棋子时,需要一个一个地试,返回n。
-
递归如何推进:假设当前为第i层,要么棋子碎了,需要扔dfs(i-1,k-1)+1次,要么棋子没碎,需要扔dfs(n-i,k)+1次,取较大者。并且统计所有层的情况,取最小的次数。
-
每一层返回什么:返回当前层的最小次数。
当层数n较大时,这种方法会报栈深度溢出的错误。
2.代码实现
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* 返回最差情况下扔棋子的最小次数
* @param n int整型 楼层数
* @param k int整型 棋子数
* @return int整型
*/
public int solve (int n, int k) {
//递归
return dfs(n,k);
}
private int dfs(int n,int k){
//0层时,不需要确定,返回0次
if(n==0) return 0;
//一枚棋子时,一个一个试,返回n
if(k==1) return n;
int min=Integer.MAX_VALUE;
for(int i=1;i<=n;i++){
/*第i层时,如果棋子碎了则需要扔dfs(i-1,k-1)+1次,如果没碎,则
需要扔dfs(n-i,k)+1次,取较大者,记录所有可能的最小次数*/
min=Math.min(min,Math.max(dfs(i-1,k-1),dfs(n-i,k))+1);
}
return min;
}
}
3.复杂度分析
- 时间复杂度:最坏情况下,每次大问题的规模减1,递归里还嵌套着一个循环,所以时间复杂度为。
- 空间复杂度:最坏情况下,递归的层数为n,所以空间复杂度是。
方法二(动态规划)
1.解题思路
首先求出层数n需要的二分次数cnt,如果k大于等于cnt,则可以通过二分的策略确定最高层数,直接返回cnt。如果k小于cnt,可以先求出i枚棋子能确定的最高层数,一旦这个数大于等于n,直接返回对应扔棋子的次数。
- 状态定义:表示棋子数为i时,可确定的最高层数。
- 状态初始化:只有一枚棋子时,仍多少次确定多少层,即。
- 状态转移:扔第i枚棋子时,如果棋子碎了,向下可以确定层,如果没碎,向上可以确定层,再加上当前层,所以。
图解展示:
2.代码实现
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* 返回最差情况下扔棋子的最小次数
* @param n int整型 楼层数
* @param k int整型 棋子数
* @return int整型
*/
public int solve (int n, int k) {
//只有1个棋子时,需要一个一个试,返回n
if(k==1) return n;
//棋子数足够多时,每仍一枚棋子可排除一半的层数,理想情况下正好是对应二分的次数
int cnt=(int)(Math.log(n)/Math.log(2))+1;
if(k>=cnt) return cnt;
//dp[i]表示棋子数为i时,可确定的最高层数
int[] dp=new int[k+1];
//记录仍棋子的最小次数
int time=1;
while(true){
for(int i=k;i>=1;i--){
/*如果棋子碎了,向下可以确定dp[i-1]层,如果没碎,
向上可以确定dp[i]层,再加上当前层,所以dp[i]+=dp[i-1]+1*/
dp[i]+=dp[i-1]+1;
if(dp[i]>=n){
return time;
}
}
//只有一枚棋子时,仍多少次确定多少层
dp[1]=time++;
}
}
}
3.复杂度分析
- 时间复杂度:外层循环最多执行次,内层循环执行k次,所以时间复杂度为。
- 空间复杂度:需要额外大小为k+1的dp数组,所以空间复杂度是。