Skip to content

Commit 8afb11d

Browse files
committed
feat: add solutions to lc problem: No.0837
No.0837.New 21 Game
1 parent d0ca1e8 commit 8afb11d

File tree

7 files changed

+582
-26
lines changed

7 files changed

+582
-26
lines changed

solution/0800-0899/0837.New 21 Game/README.md

Lines changed: 287 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,38 +53,317 @@
5353

5454
<!-- 这里可写通用的实现逻辑 -->
5555

56+
**方法一:记忆化搜索**
57+
58+
我们设计一个函数 $dfs(i)$,表示当前分数为 $i$ 时,到最终停止抽取数字时,分数不超过 $n$ 的概率。那么答案就是 $dfs(0)$。
59+
60+
函数 $dfs(i)$ 的计算方法如下:
61+
62+
- 如果 $i \ge k$,那么停止抽取数字,如果 $i \le n$,返回 $1$,否则返回 $0$;
63+
- 否则,可以在 $[1,..maxPts]$ 范围内抽取下一个数字 $j$,那么 $dfs(i) = \frac{1}{maxPts} \sum_{j=1}^{maxPts} dfs(i+j)$。
64+
65+
这里我们可以使用记忆化搜索来加速计算。
66+
67+
以上方法的时间复杂度为 $O(k \times maxPts)$,会超出时间限制,我们需要优化一下。
68+
69+
当 $i \lt k$ 时,以下等式成立:
70+
71+
$$
72+
\begin{aligned}
73+
dfs(i) &= (dfs(i + 1) + dfs(i + 2) + \cdots + dfs(i + maxPts)) / maxPts & (1)
74+
\end{aligned}
75+
$$
76+
77+
当 $i \lt k - 1$ 时,以下等式成立:
78+
79+
$$
80+
\begin{aligned}
81+
dfs(i+1) &= (dfs(i + 2) + dfs(i + 3) + \cdots + dfs(i + maxPts + 1)) / maxPts & (2)
82+
\end{aligned}
83+
$$
84+
85+
因此,当 $i \lt k-1$ 时,我们将等式 $(1)$ 减去等式 $(2)$,得到:
86+
87+
$$
88+
\begin{aligned}
89+
dfs(i) - dfs(i+1) &= (dfs(i + 1) - dfs(i + maxPts + 1)) / maxPts
90+
\end{aligned}
91+
$$
92+
93+
即:
94+
95+
$$
96+
\begin{aligned}
97+
dfs(i) &= dfs(i + 1) + (dfs(i + 1) - dfs(i + maxPts + 1)) / maxPts
98+
\end{aligned}
99+
$$
100+
101+
如果 $i=k-1$,有:
102+
103+
$$
104+
\begin{aligned}
105+
dfs(i) &= dfs(k - 1) &= dfs(k) + dfs(k + 1) + \cdots + dfs(k + maxPts - 1) / maxPts & (3)
106+
\end{aligned}
107+
$$
108+
109+
我们假设有 $i$ 个数不超过 $n$,那么 $k+i-1 \leq n$,又因为 $i\leq maxPts$,所以 $i \leq \min(n-k+1, maxPts)$,因此等式 $(3)$ 可以写成:
110+
111+
$$
112+
\begin{aligned}
113+
dfs(k-1) &= \min(n-k+1, maxPts) / maxPts
114+
\end{aligned}
115+
$$
116+
117+
综上所述,有以下状态转移方程:
118+
119+
$$
120+
\begin{aligned}
121+
dfs(i) &= \begin{cases}
122+
1, & i \geq k, i \leq n \\
123+
0, & i \geq k, i \gt n \\
124+
\min(n-k+1, maxPts) / maxPts, & i = k - 1 \\
125+
dfs(i + 1) + (dfs(i + 1) - dfs(i + maxPts + 1)) / maxPts, & i < k - 1
126+
\end{cases}
127+
\end{aligned}
128+
$$
129+
130+
时间复杂度 $O(k)$,空间复杂度 $O(k)$。其中 $k$ 为最大分数。
131+
132+
**方法二:动态规划**
133+
134+
我们可以将方法一中的记忆化搜索改成动态规划。
135+
136+
定义 $f[i]$ 表示当前分数为 $i$ 时,到最终停止抽取数字时,分数不超过 $n$ 的概率。那么答案就是 $f[0]$。
137+
138+
当 $k \leq i \leq \min(n, k + maxPts - 1)$ 时,有 $f[i] = 1$。
139+
140+
当 $i = k - 1$ 时,有 $f[i] = \min(n-k+1, maxPts) / maxPts$。
141+
142+
当 $i \lt k - 1$ 时,有 $f[i] = f[i + 1] + (f[i + 1] - f[i + maxPts + 1]) / maxPts$。
143+
144+
时间复杂度 $O(k)$,空间复杂度 $O(k)$。其中 $k$ 为最大分数。
145+
56146
<!-- tabs:start -->
57147

58148
### **Python3**
59149

60150
<!-- 这里可写当前语言的特殊实现逻辑 -->
61151

62152
```python
153+
class Solution:
154+
def new21Game(self, n: int, k: int, maxPts: int) -> float:
155+
@cache
156+
def dfs(i: int) -> float:
157+
if i >= k:
158+
return int(i <= n)
159+
if i == k - 1:
160+
return min(n - k + 1, maxPts) / maxPts
161+
return dfs(i + 1) + (dfs(i + 1) - dfs(i + maxPts + 1)) / maxPts
162+
163+
return dfs(0)
164+
```
63165

166+
```python
167+
class Solution:
168+
def new21Game(self, n: int, k: int, maxPts: int) -> float:
169+
f = [0] * (k + maxPts)
170+
for i in range(k, min(n + 1, k + maxPts)):
171+
f[i] = 1
172+
f[k - 1] = min(n - k + 1, maxPts) / maxPts
173+
for i in range(k - 2, -1, -1):
174+
f[i] = f[i + 1] + (f[i + 1] - f[i + maxPts + 1]) / maxPts
175+
return f[0]
64176
```
65177

66178
### **Java**
67179

68180
<!-- 这里可写当前语言的特殊实现逻辑 -->
69181

70182
```java
183+
class Solution {
184+
private double[] f;
185+
private int n, k, maxPts;
186+
187+
public double new21Game(int n, int k, int maxPts) {
188+
f = new double[k];
189+
this.n = n;
190+
this.k = k;
191+
this.maxPts = maxPts;
192+
return dfs(0);
193+
}
194+
195+
private double dfs(int i) {
196+
if (i >= k) {
197+
return i <= n ? 1 : 0;
198+
}
199+
if (i == k - 1) {
200+
return Math.min(n - k + 1, maxPts) * 1.0 / maxPts;
201+
}
202+
if (f[i] != 0) {
203+
return f[i];
204+
}
205+
return f[i] = dfs(i + 1) + (dfs(i + 1) - dfs(i + maxPts + 1)) / maxPts;
206+
}
207+
}
208+
```
209+
210+
```java
211+
class Solution {
212+
public double new21Game(int n, int k, int maxPts) {
213+
if (k == 0) {
214+
return 1.0;
215+
}
216+
double[] f = new double[k + maxPts];
217+
for (int i = k; i < Math.min(n + 1, k + maxPts); ++i) {
218+
f[i] = 1;
219+
}
220+
f[k - 1] = Math.min(n - k + 1, maxPts) * 1.0 / maxPts;
221+
for (int i = k - 2; i >= 0; --i) {
222+
f[i] = f[i + 1] + (f[i + 1] - f[i + maxPts + 1]) / maxPts;
223+
}
224+
return f[0];
225+
}
226+
}
227+
```
228+
229+
### **C++**
230+
231+
```cpp
232+
class Solution {
233+
public:
234+
double new21Game(int n, int k, int maxPts) {
235+
vector<double> f(k);
236+
function<double(int)> dfs = [&](int i) -> double {
237+
if (i >= k) {
238+
return i <= n ? 1 : 0;
239+
}
240+
if (i == k - 1) {
241+
return min(n - k + 1, maxPts) * 1.0 / maxPts;
242+
}
243+
if (f[i]) {
244+
return f[i];
245+
}
246+
return f[i] = dfs(i + 1) + (dfs(i + 1) - dfs(i + maxPts + 1)) / maxPts;
247+
};
248+
return dfs(0);
249+
}
250+
};
251+
```
252+
253+
```cpp
254+
class Solution {
255+
public:
256+
double new21Game(int n, int k, int maxPts) {
257+
if (k == 0) {
258+
return 1.0;
259+
}
260+
double f[k + maxPts];
261+
memset(f, 0, sizeof(f));
262+
for (int i = k; i < min(n + 1, k + maxPts); ++i) {
263+
f[i] = 1;
264+
}
265+
f[k - 1] = min(n - k + 1, maxPts) * 1.0 / maxPts;
266+
for (int i = k - 2; i >= 0; --i) {
267+
f[i] = f[i + 1] + (f[i + 1] - f[i + maxPts + 1]) / maxPts;
268+
}
269+
return f[0];
270+
}
271+
};
272+
```
273+
274+
### **Go**
275+
276+
```go
277+
func new21Game(n int, k int, maxPts int) float64 {
278+
f := make([]float64, k)
279+
var dfs func(int) float64
280+
dfs = func(i int) float64 {
281+
if i >= k {
282+
if i <= n {
283+
return 1
284+
}
285+
return 0
286+
}
287+
if i == k-1 {
288+
return float64(min(n-k+1, maxPts)) / float64(maxPts)
289+
}
290+
if f[i] > 0 {
291+
return f[i]
292+
}
293+
f[i] = dfs(i+1) + (dfs(i+1)-dfs(i+maxPts+1))/float64(maxPts)
294+
return f[i]
295+
}
296+
return dfs(0)
297+
}
71298

299+
func min(a, b int) int {
300+
if a < b {
301+
return a
302+
}
303+
return b
304+
}
305+
```
306+
307+
```go
308+
func new21Game(n int, k int, maxPts int) float64 {
309+
if k == 0 {
310+
return 1
311+
}
312+
f := make([]float64, k+maxPts)
313+
for i := k; i < min(n+1, k+maxPts); i++ {
314+
f[i] = 1
315+
}
316+
f[k-1] = float64(min(n-k+1, maxPts)) / float64(maxPts)
317+
for i := k - 2; i >= 0; i-- {
318+
f[i] = f[i+1] + (f[i+1]-f[i+maxPts+1])/float64(maxPts)
319+
}
320+
return f[0]
321+
}
322+
323+
func min(a, b int) int {
324+
if a < b {
325+
return a
326+
}
327+
return b
328+
}
72329
```
73330

74331
### **TypeScript**
75332

76333
```ts
77334
function new21Game(n: number, k: number, maxPts: number): number {
78-
if (!k) return 1.0;
79-
let dp = new Array(k + maxPts).fill(0.0);
80-
for (let i = k; i <= n && i < k + maxPts; i++) {
81-
dp[i] = 1.0;
335+
const f = new Array(k).fill(0);
336+
const dfs = (i: number): number => {
337+
if (i >= k) {
338+
return i <= n ? 1 : 0;
339+
}
340+
if (i === k - 1) {
341+
return Math.min(n - k + 1, maxPts) / maxPts;
342+
}
343+
if (f[i] !== 0) {
344+
return f[i];
345+
}
346+
return (f[i] =
347+
dfs(i + 1) + (dfs(i + 1) - dfs(i + maxPts + 1)) / maxPts);
348+
};
349+
return dfs(0);
350+
}
351+
```
352+
353+
```ts
354+
function new21Game(n: number, k: number, maxPts: number): number {
355+
if (k === 0) {
356+
return 1;
357+
}
358+
const f = new Array(k + maxPts).fill(0);
359+
for (let i = k; i < Math.min(n + 1, k + maxPts); ++i) {
360+
f[i] = 1;
82361
}
83-
dp[k - 1] = (1.0 * Math.min(n - k + 1, maxPts)) / maxPts;
84-
for (let i = k - 2; i >= 0; i--) {
85-
dp[i] = dp[i + 1] - (dp[i + maxPts + 1] - dp[i + 1]) / maxPts;
362+
f[k - 1] = Math.min(n - k + 1, maxPts) / maxPts;
363+
for (let i = k - 2; i >= 0; --i) {
364+
f[i] = f[i + 1] + (f[i + 1] - f[i + maxPts + 1]) / maxPts;
86365
}
87-
return dp[0];
366+
return f[0];
88367
}
89368
```
90369

0 commit comments

Comments
 (0)