一座大楼有 n+1 层,地面算作第0层,最高的一层为第 n 层。已知棋子从第0层掉落肯定不会摔碎,从第 i 层掉落可能会摔碎,也可能不会摔碎。
给定整数 n 作为楼层数,再给定整数 k 作为棋子数,返回如果想找到棋子不会摔碎的最高层数,即使在最差的情况下扔的最小次数。一次只能扔一个棋子。
数据范围:
要求:空间复杂度 ,时间复杂度 (m是最终返回的结果)
10,1
10
因为只有1棵棋子,所以不得不从第1层开始一直试到第10层,在最差的情况下,即第10层是不会摔坏的最高层,最少也要扔10次
3,2
2
先在2层扔1棵棋子,如果碎了,试第1层,如果没碎,试第3层
105,2
14
第一个棋子先在14层扔,碎了则用仅存的一个棋子试1~13层
若没碎,第一个棋子继续在27层扔,碎了则用仅存的一个棋子试15~26层
若没碎,第一个棋子继续在39层扔,碎了则用仅存的一个棋子试28~38层
若没碎,第一个棋子继续在50层扔,碎了则用仅存的一个棋子试40~49层
若没碎,第一个棋子继续在60层扔,碎了则用仅存的一个棋子试51~59层
若没碎,第一个棋子继续在69层扔,碎了则用仅存的一个棋子试61~68层
若没碎,第一个棋子继续在77层扔,碎了则用仅存的一个棋子试70~76层
若没碎,第一个棋子继续在84层扔,碎了则用仅存的一个棋子试78~83层
若没碎,第一个棋子继续在90层扔,碎了则用仅存的一个棋子试85~89层
若没碎,第一个棋子继续在95层扔,碎了则用仅存的一个棋子试91~94层
若没碎,第一个棋子继续在99层扔,碎了则用仅存的一个棋子试96~98层
若没碎,第一个棋子继续在102层扔,碎了则用仅存的一个棋子试100、101层
若没碎,第一个棋子继续在104层扔,碎了则用仅存的一个棋子试103层
若没碎,第一个棋子继续在105层扔,若到这一步还没碎,那么105便是结果
class Solution { public: int solve(int n, int k) { if( n<=1 ) return n; if(k==1) return n; // 棋子完全够用的情况下,那么最多要best个棋子和best步就可以搞定n层楼 int best=log2f(n)+1; if(k>=best) return best; vector< vector<int> > dp; for(int i=0;i<=k;i++){ vector<int> temp; if(i==0){ dp.push_back(temp); continue; } if(i==1){ for(int j=0;j<=n;j++) temp.push_back(j); dp.push_back(temp); continue; } temp.push_back(0); temp.push_back(1); for(int j=2;j<=n;j++){ ////int dpij=1+dp[i-1][j-1]+dp[i][j-1]; int dpij=1+dp[i-1][j-1]+temp[j-1]; temp.push_back(dpij); if(i==k && dpij>=n ) return j; if(dpij>=n) break; } dp.push_back(temp); } } };
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * 返回最差情况下扔棋子的最小次数 * @param n int整型 楼层数 * @param k int整型 棋子数 * @return int整型 */ int solve(int n, int k) { // write code here int maxn = 0, times = 1; vector<int> tmp(k + 1, 0); vector<int> last(k + 1, 0); while (maxn < n) { for (int num = 1; num <= k; num++) { if (times ==1) tmp[num] = 1; else if (num == 1) tmp[num] = times; else tmp[num] = last[num - 1] + last[num] + 1; } maxn = tmp.back(); last = tmp; times++; } return times - 1; } };
python解法
思想:dp[i][j]=dp[i][j-1]+ dp[i-1][j-1]+1
dp[i][j-1]:当前棋子没有碎
dp[i-1][j-1]:当前棋子碎了
1:当前棋子占用1次
from functools import lru_cache class Solution: def solve(self , n , k ): # write code here @lru_cache() def fun(qi, n): if qi == 1: return n if n == 1: return 1 return fun(qi,n-1) + fun(qi-1,n-1) + 1 res = 1 while fun(k, res) < n: res += 1 return res
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * 返回最差情况下扔棋子的最小次数 * @param n int整型 楼层数 * @param k int整型 棋子数 * @return int整型 */ public int solve (int n, int k) { // 状态转移方程:dp[i][j]=dp[i][j-1]+dp[i-1][j-1]+1 if(k==1) return n; int h=(int)(Math.log(n)/Math.log(2))+1; if(k>h) return h; int[] dp=new int[k]; int i=1; while(true){ int p=0; for(int j=0;j<k;j++){ int temp=dp[j]; dp[j]+=p+1; p=temp; if(dp[j]>=n) return i; } i++; } } }
class Solution: def solve(self,n ,k ): if n<1&nbs***bsp;k<1: return 0 return self.helper(n,k) def helper(self,n,k): if n==0:return 0 if k==1:return n mins = 0x7fffffff for i in range(1,n): mins = min(mins,max(self.helper(i-1,k-1),self.helper(n-i,k))) return mins+12.动态规划
class Solution1: def solve(self , n , k ): # write code here if n<1&nbs***bsp;k<1: return 0 if k==1: return n dp = [[0]*(k+1) for i in range(n+1)] for i in range(1,n+1): dp[i][1] = i for i in range(1,k+1): dp[1][i] = 1 for i in range(2,n+1): for j in range(2,k+1): mins = 0x7fffffff for m in range(1,i+1): mins = min(mins,max(dp[m-1][j-1],dp[i-m][j])) dp[i][j] = mins+1 return dp3.最优解法
import math class Solution: def solve(self , n , k ): if n<1&nbs***bsp;k<1: return 0 if k==1: return n bsTimes = math.log2(n)+1 if k>bsTimes:return bsTimes dp = [0]*k res = 0 while True: res = res+1 previous = 0 for i in range(len(dp)): tmp = dp[i] dp[i] = dp[i]+previous+1 previous = tmp if (dp[i]>=n): return res
public int solve(int n, int k) { // dp数组,用来存储在m次尝试和k个棋子情况下可以确定的最高楼层数 int[] dp = new int[k + 1]; int m = 0; // 不断增加尝试次数m,直到dp[m][k] >= n while (dp[k] < n) { m++; for (int j = k; j > 0; j--) dp[j] += dp[j - 1] + 1; } return m; }
这是根据各位大神的解法,拼凑出来的一种我可以看懂,而且可以通过的解法(动态规划+二分优化+空间优化)
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * 返回最差情况下扔棋子的最小次数 * @param n int整型 楼层数 * @param k int整型 棋子数 * @return int整型 */ public int solve (int n, int k) { int[] pre = new int[n + 1]; int[] dp = new int[n + 1]; int res = (int) (Math.log(n) / Math.log(2)) + 1; if (res <= k) return res; for (int i = 1; i <= n; i++) { dp[i] = i; } for (int i = 2; i <= k; i++) { pre = dp; dp = new int[n + 1]; for (int j = 1; j <= n; j++) { int l = 0; int r = j; while (l < r) { int mid = (l + r) / 2; int idx = j - 1 - mid; if (r - l <= 1) { dp[j] = 1 + Math.max(dp[l], pre[r]); break; } if (pre[mid] == dp[idx]) { dp[j] = dp[idx] + 1; break; } if (pre[mid] > dp[idx]) { r = mid; } else { l = mid; } } } } return dp[n]; } }
# -*- coding: utf-8 -*- import math # # 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 # 返回最差情况下扔棋子的最小次数 # @param n int整型 楼层数 # @param k int整型 棋子数 # @return int整型 # class Solution1: """ 题目: https://www.nowcoder.com/practice/d1418aaa147a4cb394c3c3efc4302266?tpId=196&tqId=37178&rp=1&ru=/exam/oj&qru=/exam/oj&sourceUrl=%2Fexam%2Foj%3FjudgeStatus%3D2%26page%3D1%26pageSize%3D50%26search%3D%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196&difficulty=undefined&judgeStatus=2&tags=&title= 算法1: # 超时 !!!!! 设dp[i][j]表示i层楼,j个棋子,在最差情况下的最少试验次数 初始化: dp[0][0] = 0 状态转移方程: dp[i][0] = 0 # i层楼,0个棋子,无须试验 dp[i][1] = i # i层楼,1个棋子,试验 i 次 dp[0][j] = 0 # 0层楼,j个棋子,无须试验 dp[1][j] = 1 # 1层楼,j个棋子,试验 1 次 dp[i][j] = min(1 + max(dp[k - 1][j - 1], dp[i - k][j])), 1 <= k <= i,这里选取从第k层扔下一个棋子,如果碎了,那么大于k的楼层必然会碎,问题变为 k - 1 层楼,j - 1个棋子进行试验;如果没碎,那么小于k的楼层必然不碎,问题转变为i - k层楼,j个棋子进行试验。另外需要注意的是,没经过一次试验,次数+1。同时我们取得是最少试验次数。 返回值: dp[n][k] 复杂度: 时间复杂度:O(n^2 * k) 空间复杂度:O(n * k) """ def solve(self, n, k): # write code here dp = [[10 ** 6] * (k + 1) for _ in range(n + 1)] for i in range(1, n + 1): dp[i][1] = i for j in range(1, k + 1): dp[1][j] = 1 for i in range(2, n + 1): for j in range(2, k + 1): for l in range(1, i + 1): dp[i][j] = min(dp[i][j], 1 + max(dp[l - 1][j - 1], dp[i - l][j])) return dp[n][k] class Solution: """ 参考: 大神:棒棒糖🍭201906101800876 算法: 设dp[i][j]表示i个棋子扔j次, 最多能测多少层。考虑第i个棋子在最优的楼层扔下去, 有两种情况: a. 碎了, 那么上面就不用测了, 下面能测的是dp[i-1][j-1]层 b. 没碎, 那么下面就不用测了, 上面能测dp[i][j-1]层 c. 第i个棋子扔掉的那一层也能测. 综上,状态转移方程: dp[i][j] = dp[i-1][j-1] + dp[i][j-1] + 1 显然dp[i][j]是单调递增的, 那么我们要求的答案就是在i不超过k的情况下, 使得dp[i][j] >= n的最小的j。 不难发现,dp[i][j]只和上面元素和左上面元素有关系, 故可以压缩一维,并且需要逆向计算。在计算dp[i][j]的时候,先从右侧开始计算,算到i的时候,一开始dp[i]处为黄色,存储的是上一次迭代的结果dp[i][j-1], i-1处也为黄色,因此dp[i] = dp[i] + dp[i-1] + 1就等价于dp[i][j] = dp[i-1][j-1] + dp[i][j-1] + 1。如果反了,那么算到i的时候, i-1处就变成了粉色,转移方程就相当于dp[i][j] = dp[i-1][j] + dp[i][j-1] + 1, 导致wa 复杂度: 时间复杂度:O(klogn) 空间复杂度:O(k) """ def solve(self, n, k): # write code here if n == 0: return 0 if k == 1: return n b = int(math.log10(n) / math.log10(2)) + 1 # 优化性质, 如果k充分大, 二分的扔即可,python27没有math.log2(x), 数学替代:log2(n) = log10(n)/log10(2) if k >= b: return b dp = [1] * (k + 1) # 初始值, 不管有几个棋子, 扔1次只能测1层 res = 1 # 最少扔1次 while True: res += 1 # 每一轮迭代, j增加1次 for i in range(k, 1, -1): dp[i] = dp[i] + dp[i - 1] + 1 if dp[i] >= n: # 当前尝试的楼层到达n,找到解 return res dp[1] = res # dp[1][j] = j,1个棋子扔j次,测得楼层为j if __name__ == '__main__': s = Solution() # n, k = 10, 1 # n, k = 3, 2 # n, k = 105, 2 # n, k = 874520, 7 n, k = 1, 1000000 res = s.solve(n, k) print res
public int solve (int n, int k) { return bestWay(n,k); // write code here } public static int bestWay(int remain,int k){//最吊的方法 int[] dp = new int[k + 1]; int step = 1; while(true){ for(int i = k;i > 0;i--){ dp[i] = dp[i-1] + dp[i] + 1; } if(dp[k] >= remain){ return step; } step++; } }令人自豪四边形不等式:
public int dpWay(int n,int sum){//四边形不等式 int[][] dp = new int[n+1][sum+1]; int[][] s = new int[n+1][sum+1]; for(int i = 0;i < dp.length;i++){ s[i][1] = 1; dp[i][1] = i; } for(int i = 1;i < dp[0].length;i++){ dp[1][i] = 1; s[1][i] = 1; } for(int i = 2;i < dp.length;i++){ for(int j = sum;j >= 2;j--){ int low = s[i-1][j]; int up = j < sum ? s[i][j+1] : i; dp[i][j] = i; s[i][j] = i; for(int k = low;k <= up;k++){ int max = Math.max(dp[k-1][j-1],dp[i-k][j]) + 1; if(max < dp[i][j]){ dp[i][j] = max; s[i][j] = k; } } } } return dp[n][sum]; }
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * 返回最差情况下扔棋子的最小次数 * @param n int整型 楼层数 * @param k int整型 棋子数 * @return int整型 */ public int solve (int n, int k) { // write code here /******************************************************************************************************/ // 最原始的方法(最好理解) /* int[][] dp = new int[k + 1][n + 1]; for (int j = 1; j <= n; j++) { dp[1][j] = j; } for (int i = 2; i <= k; i++) { for (int j = 1; j <= n; j++) { dp[i][j] = j; for (int x = 1; x <= j; x++) { dp[i][j] = Math.min(Math.max(dp[i - 1][x - 1], dp[i][j - x]) + 1, dp[i][j]); } } } return dp[k][n]; */ /******************************************************************************************************/ // 使用二分查找 /* int[][] dp = new int[k + 1][n + 1]; for (int j = 1; j <= n; j++) { dp[1][j] = j; } for (int i = 2; i <= k; i++) { for (int j = 1; j <= n; j++) { dp[i][j] = j; int l = 1; int r = j; int mid; while (l < r) { mid = l + ((r - l) >> 1); if (dp[i - 1][mid - 1] < dp[i][j - mid]) { l = mid + 1; } else { r = mid; } } dp[i][j] = Math.min(Math.max(dp[i - 1][l - 1], dp[i][j - l]) + 1, dp[i][j]); } } return dp[k][n]; */ /******************************************************************************************************/ // 楼层高肯定比楼层低所需要的查找次数要多 /* int[][] dp = new int[k + 1][n + 1]; for (int j = 1; j <= n; j++) { dp[1][j] = j; } for (int i = 2; i <= k; i++) { int x = 1; for (int j = 1; j <= n; j++) { dp[i][j] = j; while (dp[i - 1][x - 1] < dp[i][j - x]) { x++; } dp[i][j] = Math.min(Math.max(dp[i - 1][x - 1], dp[i][j - x]) + 1, dp[i][j]); } } return dp[k][n]; */ /******************************************************************************************************/ // 使用滚动数组的方式,优化存储空间 int[][] dp = new int[2][n + 1]; for (int j = 1; j <= n; j++) { dp[1][j] = j; } int cur; int pre; for (int i = 2; i <= k; i++) { int x = 1; cur = i % 2; pre = i % 2 == 0 ? 1 : 0; for (int j = 1; j <= n; j++) { dp[cur][j] = j; while (dp[pre][x - 1] < dp[cur][j - x]) { x++; } dp[cur][j] = Math.min(Math.max(dp[pre][x - 1], dp[cur][j - x]) + 1, dp[cur][j]); } } return dp[k % 2][n]; } }
class Solution: def solve(self , n: int, k: int) -> int: # write code here memo={} def dp(n,k): if k==1:return n if n==0:return 0 if (n,k) in memo.keys(): return memo[(n,k)] res=float('Inf') left=1 right=n while left<=right: mid=(left+right)//2 broken=dp(mid-1,k-1) no_broken=dp(n-mid,k) if broken>no_broken: res=min(res,broken+1) right=mid-1 else: res=min(res,no_broken+1) left=mid+1 memo[(n,k)]=res return res return dp(n,k)超时了