leetcode 887 鸡蛋掉落
非常经典,综合的题目。这一道题想到二分查找是不困难的,但是要想把二分查找正确的应用起来则需要使用动态规划的方法。将动态规划函数设置为dp(k,n)。根据在某一点的扔鸡蛋结果,如果碎了,代表需要在当前楼层以下的楼中进行测试,并且可用的鸡蛋数量少了一个,所以递归的函数为dp(k-1,x-1),若是没碎,则代表所要找的楼层肯定在当前楼层之上,递归函数为dp(k,n-x)。接下来则是最为困难也关键的过程。我们需要寻找的是这两个函数中最大值的最小值,也就是分别考虑两种情况下的最坏情况,并且进行比较,其中较小的值才是我们想要的。而从两个函数的表达式可以看出,分别为增函数与减函数,而这两个函数较大值的最小值,就是这两个函数曲线的交点处取的。也就是说,要通过递归实现对这个点的不断逼近,由于这个最小值点不一定是整数,所以需要取到两边的两个整数进行比较。而这个逼近的过程就可以通过二分查找来实现。这么一想下来,逻辑还是较为清晰的。贴代码。
class Solution { public: unordered_map<int,int> memory; int superEggDrop(int k, int n) { return dp(k,n); } int dp(int k,int n) { if(memory.find(n*100 + k) == memory.end()) { int res; if(n == 0) res = 0; else if(k == 1) res = n; else { int l = 1; int h = n; while(l+1<h) { int mid = (l+h)/2; int x0 = dp(k-1,mid-1); int x1 = dp(k,n-mid); if(x0<x1) l = mid; else if(x0>x1) h = mid; else l = h = mid; } res = 1+ min(max(dp(k-1,l-1),dp(k,n-l)),max(dp(k-1,h-1),dp(k,h-1))); } memory[n*100 + k] = res; } return memory[n*100 + k]; } };
想了一晚上,有了一点关于双蛋问题的思路。该问题最重要的点在于,其所求的最小值,代表着即便每一步都是碎,也能完成所有楼层的测试。从这一个思路出发就会简单很多。首先假设最少的次数为k,第一次扔鸡蛋的楼层为m,因为只有两个鸡蛋,所以在最坏的情况下,也就是第一次碎了,剩下的k-1次机会也一定能够将剩下的m-1个楼层测试完成。在这个思路上,因为最后只剩一个鸡蛋,所以最稳妥的做法是从第一层开始一楼一楼往上爬,而一楼一楼爬最高能爬k-1楼,因为最多也只剩k-1次机会,当然也可以从更低的楼开始爬,但是就浪费了两个蛋的潜力。也就是说,这里的k = m。接下来,考虑第一次没碎的情况,这种情况下,可以把k层楼往下的所有楼层当成平地,这样一来,又变成了两个鸡蛋,k-1次机会的问题,这里能爬最高k-1楼。以此类推,为了能完全发挥两个蛋的潜力,自然要让这里的k变成1,而在k变成1之后,所有爬楼的总和一定大于楼层的总和,如果取100,那k就等于14,这样就完成了双蛋问题。
想明白了,官方题解中的逆向的方法,为什么状态转移方程会那样写,因为如果在某一层碎了,代表这一层网上的所有楼层都变成了天空,而剩下的k-1个蛋和t-1次机会是留给往下的楼层的。没碎同理,代表该楼层往下的所有楼层都是平地,剩下的k个蛋和t-1次机会是留给网上的楼层的,完成。贴代码
1 int calcF(int K, int T) 2 { 3 if (T == 1 || K == 1) return T + 1; 4 return calcF(K - 1, T - 1) + calcF(K, T - 1); 5 } 6 7 int superEggDrop(int K, int N) 8 { 9 int T = 1; 10 while (calcF(K, T) < N + 1) T++; 11 return T; 12 }
又想到了一个点,在这一公式中,碎裂所递归的式子的结果一定等于当前楼层的高度,因为这是蓄谋已久的,而不碎往上,则是意外之喜,在往上的过程中,同样需要考虑下一次碎了怎么办,或者说从下一次开始一直碎,碎到全部都碎了,这是最坏的情况,然而即便是最坏的情况也能够将该楼层往下的所有楼层全部查清。也就是说,当鸡蛋来到某一层,就代表着这一层往下的已经有十足的把握能够查清,如果运气好没碎,那就再按照规律往上爬,calcf(k-1,t-1)这个式子是来到这一层就已经获得了的,calcf(k-1,t-1)则是往上爬按照最稳妥的规律能够得到的最大提升。

浙公网安备 33010602011771号