Skip to content

Commit efeb0a6

Browse files
committed
translate SuperEggDrop(Advanced).md
1 parent 7078491 commit efeb0a6

File tree

8 files changed

+262
-268
lines changed

8 files changed

+262
-268
lines changed
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
# Super Egg Drop(Advanced)
2+
3+
**Translator: [Jieyixia](https://github.com/Jieyixia)**
4+
**Author: [labuladong](https://github.com/labuladong)**
5+
6+
The Super Egg Drop problem (Leetcode 887) has been discussed in the last article using the classic dynamic programming method. If you are not very familiar with this problem and the classic method, please read「Super Egg Drop」, which is the basic of following contents.
7+
8+
In this article, we will optimize this problem with other two more efficient methods. One is adding binary search into the classic dynamic programming method, the other one is redefining state transition equation.
9+
10+
### Binary Search Optimization
11+
We want to find the floor `F` for a building with `N` floors using **minimum** number of moves (Each move means dropping an egg from a certain floor). Any egg dropped at a floor higher than `F` will break, and any egg dropped at or below floor `F` will not break. First, let's review the classic dynamic programming method:
12+
13+
1、To know `F`, we should traverse the situations that we drop an egg from floor `i`, `1 <= i <= N` and find the situation that costs minimum number of moves;
14+
15+
2、Anytime we drop an egg, there are two possible outcomes: the egg is broken or not broken;
16+
17+
3、If the egg is broken, `F` <= `i`; else, `F` > `i`;
18+
19+
4. Whether the egg is broken or not depends on which outcome causes **more** moves, since the goal is to know with certainty what the value of `F` is, regardless of its initial value.
20+
21+
The code for state transition:
22+
23+
```python
24+
# current state: K eggs, N floors
25+
# return the optimal results under current state
26+
def dp(K, N):
27+
for 1 <= i <= N:
28+
# the mininum moves
29+
res = min(res,
30+
max(
31+
dp(K - 1, i - 1), # the egg is broken
32+
dp(K, N - i) # the egg is not broken
33+
) + 1 # drop an egg at floor i
34+
)
35+
return res
36+
```
37+
38+
The above code reflects the following state transition equation:
39+
40+
$$ dp(K, N) = \min_{0 <= i <= N}\{\max\{dp(K - 1, i - 1), dp(K, N - i)\} + 1\}$$
41+
42+
If you can understand the state transition equation, it is not difficult to understand how to use binary search to optimize the process.
43+
44+
From the definition of `dp(K, N)` array (the minimum number of moves with `K` eggs and `N` floors), we know that when `K` is fixed, `dp(K, N)` will increase monotonically as `N` increases. In the above state transition equation, `dp(K - 1, i - 1)` will increase monotonically and `dp(K, N - i)` will decrease monotonically as `i` increases from 1 to `N`.
45+
46+
![](../pictures/SuperEggDrop/2.jpg)
47+
48+
We need to find the maximum between `dp(K - 1, i - 1)` and `dp(K, N - i)`, and then choose the minimum one among those maximum values. This means that we should get the intersection of the two straight lines (the lowest points of the red polyline).
49+
50+
In other article, we have mentioned that binary search is widely used in many cases, for example:
51+
52+
```java
53+
for (int i = 0; i < n; i++) {
54+
if (isOK(i))
55+
return i;
56+
}
57+
```
58+
59+
In the above case, it is likely to use binary search to optimize the complexity of linear search. Review the two `dp` functions, the lowest point satisfies following condition:
60+
61+
```java
62+
for (int i = 1; i <= N; i++) {
63+
if (dp(K - 1, i - 1) == dp(K, N - i))
64+
return dp(K, N - i);
65+
}
66+
```
67+
68+
If you are familiar with binary search, it is easy to know that what we need to search is the valley value. Let's look at the following code:
69+
70+
```python
71+
def superEggDrop(self, K: int, N: int) -> int:
72+
73+
memo = dict()
74+
def dp(K, N):
75+
if K == 1: return N
76+
if N == 0: return 0
77+
if (K, N) in memo:
78+
return memo[(K, N)]
79+
80+
# for 1 <= i <= N:
81+
# res = min(res,
82+
# max(
83+
# dp(K - 1, i - 1),
84+
# dp(K, N - i)
85+
# ) + 1
86+
# )
87+
88+
res = float('INF')
89+
# use binary search to replace linear search
90+
lo, hi = 1, N
91+
while lo <= hi:
92+
mid = (lo + hi) // 2
93+
broken = dp(K - 1, mid - 1) # the egg is broken
94+
not_broken = dp(K, N - mid) # the egg is not broken
95+
# res = min(max(broken, not broken) + 1)
96+
if broken > not_broken:
97+
hi = mid - 1
98+
res = min(res, broken + 1)
99+
else:
100+
lo = mid + 1
101+
res = min(res, not_broken + 1)
102+
103+
memo[(K, N)] = res
104+
return res
105+
106+
return dp(K, N)
107+
```
108+
The time complexity for dynamic programming problems is **the number of sub-problems × the complexity of function**.
109+
110+
Regardless of the recursive part, the complexity of `dp` function is O(logN), since binary search is used.
111+
112+
The number of sub-problems equals to the number of different states, which is O(KN).
113+
114+
Therefore, the time complexity of the improved method is O(K\*N\*logN), which is more efficient than O(KN^2) of the classic dynamic programming method. The space complexity is O(KN).
115+
116+
117+
### Redefine State Transition Equation
118+
119+
It has been mentioned in other article that the state transition equation for the same problem is not unique, resulting in different methods with different complexity.
120+
121+
Review the definition of the `dp` function:
122+
123+
```python
124+
def dp(k, n) -> int
125+
# current state: k eggs, n floors
126+
# return the optimal results under current state
127+
```
128+
129+
Or the `dp` array:
130+
131+
```python
132+
dp[k][n] = m
133+
# current state: k eggs, n floors
134+
# return the optimal results under current state
135+
```
136+
137+
Based on this definition, the expected answer is `dp(K, N)`. The method of exhaustion is necessary, we have to compare the results under different situations `1<=i<=N` to find the minimum. Binary search helps to reduce the search space.
138+
139+
Now, we make some modifications to the definition of `dp` array, current states are `k` eggs and allowed maximum number of moves `m`. `dp[k][m] = n` represents that we can accurately determine a floor `F` for a building with at most `n` floors. More specifically:
140+
141+
```python
142+
dp[k][m] = n
143+
# current state: k eggs, at most m moves
144+
# `F` can be determined for a building with at most n floors
145+
146+
# For example: dp[1][7] = 7 represents:;
147+
# one egg is given and you can drop an egg at certain floor 7 times;
148+
# you can determine floor `F` for a building with at most 7 floors;
149+
# any egg dropped at a floor higher than `F` will break;
150+
# any egg dropped at or below floor `F` will not break.
151+
# (search linearly from the first floor)
152+
```
153+
154+
This is actually a reverse version of our original definition. We want to know the number of moves at last. But under this new definition, it is one state of the `dp` array instead of the result. This is how we deal with this problem:
155+
156+
```java
157+
int superEggDrop(int K, int N) {
158+
159+
int m = 0;
160+
while (dp[K][m] < N) {
161+
m++;
162+
// state transition...
163+
}
164+
return m;
165+
}
166+
```
167+
168+
The `while` loop ends when `dp[K][m] == N`, which means that given `K` eggs and at most `m` moves, floor `F` can be accurately determined for a building with `N` floors. This is exactly the same as before.
169+
170+
Then how to find the state transition equation? Let's start from the initial idea:
171+
172+
![](../pictures/SuperEggDrop/1.jpg)
173+
174+
You have to traverse `1<=i<=N` to find the minimum. But these are not necessary under the new definition of `dp` array. This is based on the following two facts:
175+
176+
**1、There are only two possible outcomes when you drop an egg at any floor: the egg is broken or not broken. If the egg is broken, go downstairs. If the egg is not broken, go upstairs**
177+
178+
**2、No matter which outcome, total number of floors = the number of floors upstairs + the number of floors downstairs + 1(current floor)**
179+
180+
Base on the two facts, we can write the following state transition equation:
181+
182+
`dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1`
183+
184+
**`dp[k][m - 1]` is the number of floors upstairs**. `k` keeps unchanged since the egg is not broken, `m` minus one;
185+
186+
**`dp[k - 1][m - 1]` is the number of floors downstairs**. `k` minus one since the egg is broken, `m` minus one.
187+
188+
PS: why `m` minus one instead of plus one? According to the definition, `m` is the upper bound of the number of moves, instead of the number of moves。
189+
190+
![](../pictures/SuperEggDrop/3.jpg)
191+
192+
The code is
193+
194+
```java
195+
int superEggDrop(int K, int N) {
196+
// m will not exceed N (linear search)
197+
int[][] dp = new int[K + 1][N + 1];
198+
// base case:
199+
// dp[0][..] = 0
200+
// dp[..][0] = 0
201+
// Java intializes the array to be all 0 by default
202+
int m = 0;
203+
while (dp[K][m] < N) {
204+
m++;
205+
for (int k = 1; k <= K; k++)
206+
dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1;
207+
}
208+
return m;
209+
}
210+
```
211+
212+
which equals to:
213+
214+
```java
215+
for (int m = 1; dp[K][m] < N; m++)
216+
for (int k = 1; k <= K; k++)
217+
dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1;
218+
```
219+
220+
It seems more familiar. Since we need to get a certain index `m` of the `dp` array, `while` loop is used.
221+
222+
The time complexity of this algorithm is apparently O(KN), two nested `for` loop。
223+
224+
Moreover, `dp[m][k]` only relates to the left and left-top states, it is easy to simplify `dp` array to one dimension.
225+
226+
### More Optimization
227+
228+
In this section, we will introduce some mathematical methods without specific details.
229+
230+
Based on the `dp` definition in the previous section, **`dp(k, m)` increases monotonically when `m` increases. When `k` is fixed, a bigger `m` will cause a bigger `N`**
231+
232+
We can also use binary search to optimize the process, `dp[K][m] == N` is the stop criterion. Time complexity further decreases to O(KlogN), we can assume `g(k, m) =`……
233+
234+
All right, let's stop. I think it's enough to understand the binary search method with O(K\*N\*logN) time complexity.
235+
236+
It is certain that we should change the for loop to find `m`:
237+
238+
```java
239+
// change the linear search to binary search
240+
// for (int m = 1; dp[K][m] < N; m++)
241+
int lo = 1, hi = N;
242+
while (lo < hi) {
243+
int mid = (lo + hi) / 2;
244+
if (... < N) {
245+
lo = ...
246+
} else {
247+
hi = ...
248+
}
249+
250+
for (int k = 1; k <= K; k++)
251+
// state transition equation
252+
}
253+
```
254+
In conclusion, the first optimization using binary search is based on the monotonicity of the `dp` function; the second optimization modifies the state transition function. For most of us, it is easier to understand the idea of binary search instead of different forms of `dp` array.
255+
256+
If the you have grasp the basic methods well, the methods in the last section are good challenges for you.
257+
258+
259+
260+
261+
262+

0 commit comments

Comments
 (0)