Skip to content

Commit 8ea2ca8

Browse files
committed
✨feat: Add 464
1 parent e6807e1 commit 8ea2ca8

File tree

4 files changed

+148
-11
lines changed

4 files changed

+148
-11
lines changed

Index/博弈论.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
| 题目 | 题解 | 难度 | 推荐指数 |
22
| ------------------------------------------------------------ | ------------------------------------------------------------ | ---- | -------- |
33
| [292. Nim 游戏](https://leetcode-cn.com/problems/nim-game/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/nim-game/solution/gong-shui-san-xie-noxiang-xin-ke-xue-xi-wmz2t/) | 中等 | 🤩🤩🤩🤩 |
4+
| [464. 我能赢吗](https://leetcode.cn/problems/can-i-win/) | [LeetCode 题解链接](https://leetcode.cn/problems/can-i-win/solution/by-ac_oier-0ed9/) | 中等 | 🤩🤩🤩🤩 |
45
| [810. 黑板异或游戏](https://leetcode-cn.com/problems/chalkboard-xor-game/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/chalkboard-xor-game/solution/gong-shui-san-xie-noxiang-xin-ke-xue-xi-ges7k/) | 困难 | 🤩🤩🤩🤩 |
56
| [877. 石子游戏](https://leetcode-cn.com/problems/stone-game/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/stone-game/solution/gong-shui-san-xie-jing-dian-qu-jian-dp-j-wn31/) | 中等 | 🤩🤩🤩🤩 |
67
| [1728. 猫和老鼠 II](https://leetcode.cn/problems/cat-and-mouse-ii/) | [LeetCode 题解链接](https://leetcode.cn/problems/cat-and-mouse-ii/solution/by-ac_oier-gse8/) | 困难 | 🤩🤩🤩🤩 |

Index/记忆化搜索.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
| [87. 扰乱字符串](https://leetcode-cn.com/problems/scramble-string/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/scramble-string/solution/gong-shui-san-xie-yi-ti-san-jie-di-gui-j-hybk/) | 困难 | 🤩🤩🤩 |
44
| [375. 猜数字大小 II](https://leetcode-cn.com/problems/guess-number-higher-or-lower-ii/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/guess-number-higher-or-lower-ii/solution/gong-shui-san-xie-yi-ti-shuang-jie-ji-yi-92e5/) | 中等 | 🤩🤩🤩🤩🤩 |
55
| [403. 青蛙过河](https://leetcode-cn.com/problems/frog-jump/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/frog-jump/solution/gong-shui-san-xie-yi-ti-duo-jie-jiang-di-74fw/) | 困难 | 🤩🤩🤩🤩 |
6+
| [464. 我能赢吗](https://leetcode.cn/problems/can-i-win/) | [LeetCode 题解链接](https://leetcode.cn/problems/can-i-win/solution/by-ac_oier-0ed9/) | 中等 | 🤩🤩🤩🤩 |
67
| [494. 目标和](https://leetcode-cn.com/problems/target-sum/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/target-sum/solution/gong-shui-san-xie-yi-ti-si-jie-dfs-ji-yi-et5b/) | 中等 | 🤩🤩🤩🤩 |
78
| [552. 学生出勤记录 II](https://leetcode-cn.com/problems/student-attendance-record-ii/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/student-attendance-record-ii/solution/gong-shui-san-xie-yi-ti-san-jie-ji-yi-hu-fdfx/) | 困难 | 🤩🤩🤩🤩 |
89
| [576. 出界的路径数](https://leetcode-cn.com/problems/out-of-boundary-paths/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/out-of-boundary-paths/solution/gong-shui-san-xie-yi-ti-shuang-jie-ji-yi-asrz/) | 中等 | 🤩🤩🤩🤩 |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[464. 我能赢吗](https://leetcode.cn/problems/can-i-win/solution/by-ac_oier-0ed9/)** ,难度为 **中等**
4+
5+
Tag : 「博弈论 DP」、「记忆化搜索」、「状态压缩」
6+
7+
8+
9+
在 "100 game" 这个游戏中,两名玩家轮流选择从 $1$ 到 $10$ 的任意整数,累计整数和,先使得累计整数和 达到或超过  $100$ 的玩家,即为胜者。
10+
11+
如果我们将游戏规则改为 “玩家 不能 重复使用整数” 呢?
12+
13+
例如,两个玩家可以轮流从公共整数池中抽取从 $1$ 到 $15$ 的整数(不放回),直到累计整数和 >= $100$。
14+
15+
给定两个整数 `maxChoosableInteger` (整数池中可选择的最大数)和 `desiredTotal`(累计和),若先出手的玩家是否能稳赢则返回 `true` ,否则返回 `false` 。假设两位玩家游戏时都表现 最佳 。
16+
17+
示例 1:
18+
```
19+
输入:maxChoosableInteger = 10, desiredTotal = 11
20+
21+
输出:false
22+
23+
解释:
24+
无论第一个玩家选择哪个整数,他都会失败。
25+
第一个玩家可以选择从 1 到 10 的整数。
26+
如果第一个玩家选择 1,那么第二个玩家只能选择从 2 到 10 的整数。
27+
第二个玩家可以通过选择整数 10(那么累积和为 11 >= desiredTotal),从而取得胜利.
28+
同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。
29+
```
30+
示例 2:
31+
```
32+
输入:maxChoosableInteger = 10, desiredTotal = 0
33+
34+
输出:true
35+
```
36+
示例 3:
37+
```
38+
输入:maxChoosableInteger = 10, desiredTotal = 1
39+
40+
输出:true
41+
```
42+
43+
提示:
44+
* $1 <= maxChoosableInteger <= 20$
45+
* $0 <= desiredTotal <= 300$
46+
47+
---
48+
49+
### 二维博弈论 DP(TLE)
50+
51+
这是一道博弈论 DP 的题,为了方便,我们使用 $n$ 来表示 $maxChoosableInteger$,使用 $t$ 来表示 $desiredTotal$。
52+
53+
由于 $n$ 数据范围为 $20$,且每个数只能选一次,我们可以使用一个二进制数 $state$ 来表示 $[1, n]$ 范围内的被选择的数的情况:二进制表示中 $1$ 的位置代表数已被选择,否则代表尚未选择。
54+
55+
首先朴素二维状态表示相对容易想到:**定义 $f[statue][k]$ 为当前已被选择的数为 $state$,轮数为 $k$ 时,「原始回合的先手」能否获胜($1$ 代表能,$-1$ 代表不能),其中 $k$ 从 $0$ 开始,通过 $k$ 的奇偶性可知是原始回合的先手还是后手。**
56+
57+
设计递归函数来实现「记忆化搜索」,函数 `int dfs(int state, int tot, int k)` 表示当前状态为 $state$,$tot$ 对应累计和,$k$ 代表轮数,最终答案通过判断 `dfs(0, 0, 0)` 是否为 $1$ 来得知。
58+
59+
**转移过程中,如果发现当前回合的决策,能够直接使得累积和超过 $t$,说明当前回合玩家获胜;或者如果当前决策能够导致下一回合的玩家失败的话,当前回合玩家也获胜,否则当前玩家失败。**
60+
61+
代码:
62+
```Java
63+
class Solution {
64+
int n, t;
65+
int[][] f = new int[1 << 20][2];
66+
// 1 true / -1 false
67+
int dfs(int state, int tot, int k) {
68+
if (state == ((1 << n) - 1) && tot < t) return -1;
69+
if (f[state][k % 2] != 0) return f[state][k % 2];
70+
int hope = k % 2 == 0 ? 1 : -1;
71+
for (int i = 0; i < n; i++) {
72+
if (((state >> i) & 1) == 1) continue;
73+
if (tot + i + 1 >= t) return f[state][k % 2] = hope;
74+
if (dfs(state | (1 << i), tot + i + 1, k + 1) == hope) return f[state][k % 2] = hope;
75+
}
76+
return f[state][k % 2] = -hope;
77+
}
78+
public boolean canIWin(int _n, int _t) {
79+
n = _n; t = _t;
80+
if (t == 0) return true;
81+
return dfs(0, 0, 0) == 1;
82+
}
83+
}
84+
```
85+
* 时间复杂度:共有 $2^{n} \times 2$ 个状态,每个状态转移需要 $O(n)$ 复杂度,整体复杂度为 $O(2^{n + 1} \times n)$
86+
* 空间复杂度:$O(2^{n + 1})$
87+
88+
---
89+
90+
### 优化状态表示
91+
92+
进一步发现,若能优化轮数维度,可以有效减少一半的计算量,我们调整状态定义为:**定义 $f[state]$ 为当前状态为 $state$,「当前先手」能否获胜($1$ 代表能,$-1$ 代表不能)。**
93+
94+
同时调整递归函数为 $int dfs(int state, int tot)$,最终答案通过判断 `dfs(0, 0)` 是否为 $1$ 来得知。
95+
96+
注意这里调整的重点在于:将记录「原始回合的先后手发起 和 原始回合的先后手获胜情况」调整为「当前回合发起 和 当前回合获胜情况」。
97+
98+
代码:
99+
```Java
100+
class Solution {
101+
int n, t;
102+
int[] f = new int[1 << 20];
103+
// 1 true / -1 false
104+
int dfs(int state, int tot) {
105+
if (f[state] != 0) return f[state];
106+
for (int i = 0; i < n; i++) {
107+
if (((state >> i) & 1) == 1) continue;
108+
if (tot + i + 1 >= t) return f[state] = 1;
109+
if (dfs(state | (1 << i), tot + i + 1) == -1) return f[state] = 1;
110+
}
111+
return f[state] = -1;
112+
}
113+
public boolean canIWin(int _n, int _t) {
114+
n = _n; t = _t;
115+
if (n * (n + 1) / 2 < t) return false;
116+
if (t == 0) return true;
117+
return dfs(0, 0) == 1;
118+
}
119+
}
120+
```
121+
* 时间复杂度:共有 $2^{n}$ 个状态,每个状态转移需要 $O(n)$ 复杂度,整体复杂度为 $O(2^{n} \times n)$
122+
* 空间复杂度:$O(2^{n})$
123+
124+
---
125+
126+
### 最后
127+
128+
这是我们「刷穿 LeetCode」系列文章的第 `No.464` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
129+
130+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
131+
132+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
133+
134+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
135+

LeetCode/951-960/954. 二倍数对数组(中等).md

+11-11
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Tag : 「优先队列」、「堆」、「构造」、「哈希表」、「拓
66

77

88

9-
给定一个长度为偶数的整数数组 `arr`,只有对 `arr` 进行重组后可以满足 “对于每个$ 0 <= i < len(arr) / 2$,都有 $arr[2 * i + 1] = 2 * arr[2 * i]$” 时,返回 `true`;否则,返回 `false`
9+
给定一个长度为偶数的整数数组 `arr`,只有对 `arr` 进行重组后可以满足 “对于每个$ 0 <= i < len(arr) / 2$,都有 $arr[2 \times i + 1] = 2 \times arr[2 \times i]$” 时,返回 `true`;否则,返回 `false`
1010

1111
示例 1:
1212
```
@@ -30,28 +30,28 @@ Tag : 「优先队列」、「堆」、「构造」、「哈希表」、「拓
3030
```
3131

3232
提示:
33-
* $0 <= arr.length <= 3 * 10^4$
34-
* arr.length 是偶数
33+
* $0 <= arr.length <= 3 \times 10^4$
34+
* `arr.length` 是偶数
3535
* $-10^5 <= arr[i] <= 10^5$
3636

3737
---
3838

3939
### 逐个构造 + 优先队列
4040

41-
整理一下题意:是否能对 `arr` 进行重组,使得每一个奇数位置的值均是前一个位置的值的两倍,即凑成 $\frac{n}{2}$ 组形如 $(x, 2 * x)$ 的数对。
41+
整理一下题意:是否能对 `arr` 进行重组,使得每一个奇数位置的值均是前一个位置的值的两倍,即凑成 $\frac{n}{2}$ 组形如 $(x, 2 \times x)$ 的数对。
4242

4343
对于一个任意的有理数而言,对其乘 $2$ 仅会改变数值的大小,而不会改变其方向(正负性质)。
4444

4545
因此如果我们每次都拿最接近 $0$ 的值作为起点,整个构造过程就是唯一确定的。
4646

4747
具体的,我们可以借助优先队列(堆)来实现,构造一个以与 $0$ 值距离作为基准的小根堆。每次从堆中取出元素 $x$,根据当前元素 $x$ 是否被「预定」过进行分情况讨论:
4848

49-
* 当前值 $x$ 没有被预定过,说明 $x$ 必然是数对中的「绝对值」的较小值,此时给 $x$ 并预定一个 $x * 2$,即对 $x * 2$ 的预定次数加一;
49+
* 当前值 $x$ 没有被预定过,说明 $x$ 必然是数对中的「绝对值」的较小值,此时给 $x$ 并预定一个 $x \times 2$,即对 $x \times 2$ 的预定次数加一;
5050
* 当前值 $x$ 已经被预定过,说明 $x$ 和此前的某个数 $\frac{x}{2}$ 组成过数对,对 $x$ 的预定次数减一。
5151

52-
当且仅当构成过程结束后,所有数的「预定」次数为 $0$ 时,`arr` 可以凑成 $\frac{n}{2}$ 组形如 $(x, 2 * x)$ 的数对。
52+
当且仅当构成过程结束后,所有数的「预定」次数为 $0$ 时,`arr` 可以凑成 $\frac{n}{2}$ 组形如 $(x, 2 \times x)$ 的数对。
5353

54-
> 一些细节:由于 $arr[i]$ 的数值范围为 $[-10^5, 10^5]$,同时存在乘 $2$ 操作,因此我们需要对计算结果进行 $2 * 10^5$ 的偏移操作,确保其为正数。
54+
> 一些细节:由于 $arr[i]$ 的数值范围为 $[-10^5, 10^5]$,同时存在乘 $2$ 操作,因此我们需要对计算结果进行 $2 \times 10^5$ 的偏移操作,确保其为正数。
5555
5656
代码:
5757
```Java
@@ -83,9 +83,9 @@ class Solution {
8383

8484
上述解法中,我们不可避免的对 `arr` 进行一遍完成的尝试构造,并且在尝试构造结束后再进行一次性的合法性检查。
8585

86-
事实上,如果 `arr` 能够凑成 $\frac{n}{2}$ 组形如 $(x, 2 * x)$ 的数对,并且对于某个 $x$ 可能会出现多次,我们可以统计 $arr[i]$ 的数量,并根据绝对值大小进行排序,进行成组构造:$cnts[x]$ 个 $x$ 消耗 $cnts[x]$ 个 $2 * x$。
86+
事实上,如果 `arr` 能够凑成 $\frac{n}{2}$ 组形如 $(x, 2 \times x)$ 的数对,并且对于某个 $x$ 可能会出现多次,我们可以统计 $arr[i]$ 的数量,并根据绝对值大小进行排序,进行成组构造:$cnts[x]$ 个 $x$ 消耗 $cnts[x]$ 个 $2 \times x$。
8787

88-
同时由于我们提前预处理了每个 $arr[i]$ 的出现次数,我们可以提前知道是否有 $cnts[x]$ 个 $2 * x$ 和 $x$ 成组,从而可以边构造边检查合法性。
88+
同时由于我们提前预处理了每个 $arr[i]$ 的出现次数,我们可以提前知道是否有 $cnts[x]$ 个 $2 \times x$ 和 $x$ 成组,从而可以边构造边检查合法性。
8989

9090
代码:
9191
```Java
@@ -116,7 +116,7 @@ class Solution {
116116

117117
对于上述两种解法,要么利用「优先队列」要么利用「排序」,目的都是为了找到数对中的「绝对值较小」的那一位,然后开始往后构造。
118118

119-
**事实上,我们可以利用任意 $x$ 只可能与 $\frac{x}{2}$ 或者 $2 * x$ 组成数对来进行建图,通过对图跑拓扑序来验证是否能够凑成 $\frac{n}{2}$ 组形如 $(x, 2 * x)$ 的数对。**
119+
**事实上,我们可以利用任意 $x$ 只可能与 $\frac{x}{2}$ 或者 $2 \times x$ 组成数对来进行建图,通过对图跑拓扑序来验证是否能够凑成 $\frac{n}{2}$ 组形如 $(x, 2 \times x)$ 的数对。**
120120

121121
> 不了解「拓扑排序」的同学可以看前置 🧀:[图论拓扑排序入门](https%3A//mp.weixin.qq.com/s?__biz%3DMzU4NDE3MTEyMA%3D%3D%26mid%3D2247489706%26idx%3D1%26sn%3D771cd807f39d1ca545640c0ef7e5baec),里面通过图解演示了何为拓扑序,以及通过「反证法」证明了为何有向无环图能够能够进行拓扑排序。
122122
@@ -127,7 +127,7 @@ class Solution {
127127
* $x$ 为奇数,由于 $\frac{x}{2}$ 不为整数,因此 $x$ 只能作为数对中绝对值较小的那个(即 $x$ 入度为 $0$),加入队列;
128128
* $x$ 为偶数,首先令 $x$ 的入度 $in[x] = cnts[\frac{x}{2}]$,代表有 $cnts[\frac{x}{2}]$ 个 $\frac{x}{2}$ 与其对应。当 $in[x] = 0$ 时,说明没有 $\frac{x}{2}$ 与其成对,此时 $x$ 只能作为数对中绝对值较小的那个(即 $x$ 入度为 $0$),加入队列。
129129

130-
跑一遍拓扑排序,假设当前出队值为 $t$,此时需要消耗掉 $cnts[t]$ 个 $t * 2$ 与其形成数对(即 $cnts[t * 2] -= cnts[t]$ ),同时 $t * 2$ 的入度也要更新(即 $in[t * 2] -= cnts[t]$ ),若 $in[t * 2] = 0$ 且此时 $cnts[t * 2] > 0$,将 $t * 2$ 进行入队。同时由于我们明确减少了 $t * 2$ 的数量,因此需要同步更新 $t * 4$ 的入度,同理,当 $t * 4$ 的入度 $in[t * 4] = 0$,同时 $cnts[t * 4] > 0$ 时,需要将 $t * 4$ 进行入队。
130+
跑一遍拓扑排序,假设当前出队值为 $t$,此时需要消耗掉 $cnts[t]$ 个 $t \times 2$ 与其形成数对(即 $cnts[t \times 2] -= cnts[t]$ ),同时 $t \times 2$ 的入度也要更新(即 $in[t \times 2] -= cnts[t]$ ),若 $in[t \times 2] = 0$ 且此时 $cnts[t \times 2] > 0$,将 $t \times 2$ 进行入队。同时由于我们明确减少了 $t \times 2$ 的数量,因此需要同步更新 $t \times 4$ 的入度,同理,当 $t \times 4$ 的入度 $in[t \times 4] = 0$,同时 $cnts[t \times 4] > 0$ 时,需要将 $t \times 4$ 进行入队。
131131

132132

133133
代码:

0 commit comments

Comments
 (0)