Skip to content

Commit 0c54f9a

Browse files
author
robot
committed
feat: $1871
1 parent e3d3fff commit 0c54f9a

File tree

3 files changed

+263
-0
lines changed

3 files changed

+263
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ leetcode 题解,记录自己的 leetcode 解题之路。
493493
- [1723. 完成所有工作的最短时间](./problems/1723.find-minimum-time-to-finish-all-jobs.md)
494494
- [1787. 使所有区间的异或结果为零](./problems/1787.make-the-xor-of-all-segments-equal-to-zero.md)
495495
- [1835. 所有数对按位与结果的异或和](./problems/1835.find-xor-sum-of-all-pairs-bitwise-and.md)
496+
- [1871. 跳跃游戏 VII](./problems/1871.jump-game-vii.md) 👍
496497
- [1872. 石子游戏 VIII](./problems/1872.stone-game-viii.md)
497498
- [1883. 准时抵达会议现场的最小跳过休息次数](./problems/5775.minimum-skips-to-arrive-at-meeting-on-time.md)
498499

SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@
324324
- [1723. 完成所有工作的最短时间](./problems/1723.find-minimum-time-to-finish-all-jobs.md) 🆕
325325
- [1787. 使所有区间的异或结果为零](./problems/1787.make-the-xor-of-all-segments-equal-to-zero.md) 🆕
326326
- [1835. 所有数对按位与结果的异或和](./problems/1835.find-xor-sum-of-all-pairs-bitwise-and.md) 🆕
327+
- [1871. 跳跃游戏 VII](./problems/1871.jump-game-vii.md) 👍
327328
- [1872. 石子游戏 VIII](./problems/1872.stone-game-viii.md)
328329
- [1883. 准时抵达会议现场的最小跳过休息次数](./problems/5775.minimum-skips-to-arrive-at-meeting-on-time.md) 🆕
329330

problems/1871.jump-game-vii.md

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
## 题目地址(1871. 跳跃游戏 VII)
2+
3+
https://leetcode-cn.com/problems/jump-game-vii/
4+
5+
## 题目描述
6+
7+
```
8+
给你一个下标从 0 开始的二进制字符串 s 和两个整数 minJump 和 maxJump 。一开始,你在下标 0 处,且该位置的值一定为 '0' 。当同时满足如下条件时,你可以从下标 i 移动到下标 j 处:
9+
10+
i + minJump <= j <= min(i + maxJump, s.length - 1) 且
11+
s[j] == '0'.
12+
13+
如果你可以到达 s 的下标 s.length - 1 处,请你返回 true ,否则返回 false 。
14+
15+
 
16+
17+
示例 1:
18+
19+
输入:s = "011010", minJump = 2, maxJump = 3
20+
输出:true
21+
解释:
22+
第一步,从下标 0 移动到下标 3 。
23+
第二步,从下标 3 移动到下标 5 。
24+
25+
26+
示例 2:
27+
28+
输入:s = "01101110", minJump = 2, maxJump = 3
29+
输出:false
30+
31+
32+
 
33+
34+
提示:
35+
36+
2 <= s.length <= 105
37+
s[i] 要么是 '0' ,要么是 '1'
38+
s[0] == '0'
39+
1 <= minJump <= maxJump < s.length
40+
```
41+
42+
## 前置知识
43+
44+
- BFS
45+
- 动态规划
46+
- 前缀和
47+
48+
## 公司
49+
50+
- 暂无
51+
52+
## BFS(超时)
53+
54+
### 思路
55+
56+
我们可以对问题进行抽象。将 0 抽象为图中的点,将每一个 0 与**其能够到达的比它索引大的 0**抽象为边。那么问题转化为从索引为 0 的点与索引为 n - 1 的点**是否联通**。我们有很多方法能够解决这个问题,不妨使用 BFS。
57+
58+
### 关键点
59+
60+
- 将题目抽象为图的联通问题
61+
62+
### 代码
63+
64+
- 语言支持:Python3
65+
66+
Python3 Code:
67+
68+
```python
69+
70+
class Solution:
71+
def canReach(self, s: str, minJump: int, maxJump: int) -> bool:
72+
if s[-1] == '1': return False
73+
zeroes = set([i for i in range(len(s)) if s[i] == '0'])
74+
q = set([0])
75+
while q:
76+
cur = q.pop()
77+
if cur == len(s) - 1: return True
78+
for nxt in range(cur + minJump, min(cur + maxJump, len(s)) + 1):
79+
if nxt in zeroes and nxt not in q:
80+
q.add(nxt)
81+
return False
82+
83+
```
84+
85+
**复杂度分析**
86+
87+
令 n 为数组长度。
88+
89+
- 时间复杂度:$O(n^2)$
90+
- 空间复杂度:$O(n)$
91+
92+
## 动态规划
93+
94+
### 思路
95+
96+
定义 dp(i) 为从索引为 0 的点是否能够到达索引为 i 的点。显然 dp(i) 只有在满足以下两个条件才为 true:
97+
98+
- s[i] == '0'
99+
- s[j] == '0' 其中 max(0, i - maxJump) <= j <= max(0, i - minJump)
100+
101+
于是,我们可以枚举所有满足条件的 j ,并观察其是否满足上述条件即可。
102+
103+
代码:
104+
105+
```py
106+
class Solution:
107+
def canReach(self, s: str, minJump: int, maxJump: int) -> bool:
108+
def dp(pos):
109+
if pos == len(s) - 1: return True
110+
return s[pos] == '0' and any([dp(i) for i in range(pos + minJump, min(len(s), pos + maxJump + 1))])
111+
if s[-1] == '1': return False
112+
return dp(0)
113+
```
114+
115+
由于枚举 i 和 j 的复杂度为都为 $O(n)$,因此总的时间复杂度为 $O(n^2)$,代入题目会超时。
116+
117+
我们需要对其进行优化。我们发现算法条件在于寻找满足条件的 j,而满足条件的 j 实际上就是区间[max(0,i-maxJump), max(0, i-minJump)] **中可以从 0 点到达的点**
118+
119+
换句话说就是 **dp 数组的区间 [max(0,i-maxJump), max(0, i-minJump)] 中是否存在一个 true**
120+
121+
那么这该如何求呢?我举个例子你就懂了。
122+
123+
比如一个数组 [false, true, false, false, true],我想知道区间 [2,3] 是否有 true。
124+
125+
朴素的做法是遍历:
126+
127+
```js
128+
bools = [false, true, false, false, true];
129+
bools[2] || bools[3];
130+
```
131+
132+
如果我想知道任意合法区间 [s,e] 是否有 true。
133+
134+
则可以:
135+
136+
```js
137+
bools = [false, true, false, false, true]
138+
for(let i = s; i < min(e,len(bools)); i++) {
139+
if bools[i]: return true
140+
}
141+
return false
142+
143+
```
144+
145+
实际上,我们有可以将 bools 映射到整数,其中 false 映射为 0,true 映射为 1,并对 bools 求前缀和。这样就可以通过前缀和在 $O(1)$ 时间获取到任意区间的 true 的个数。
146+
147+
代码:
148+
149+
```js
150+
bools = [false, true, false, false, true];
151+
// bools 映射为 [0,1,0,0,1]
152+
// pres 为 [0,1,1,1,2]
153+
return pres[e] - s == 0 ? 0 : pres[s - 1];
154+
```
155+
156+
### 关键点
157+
158+
- 对 DP 数组本身求前缀和,而不是原数组
159+
160+
### 代码
161+
162+
- 语言支持:Python3
163+
164+
Python3 Code:
165+
166+
```python
167+
168+
169+
class Solution:
170+
def canReach(self, s: str, minJump: int, maxJump: int) -> bool:
171+
n = len(s)
172+
pres = [0] * n
173+
dp = [0] * n
174+
dp[0] = pres[0] = 1
175+
for i in range(1, n):
176+
l = i - maxJump - 1
177+
r = i - minJump
178+
dp[i] = s[i] == '0' and (0 if r < 0 else pres[r]) - (0 if l < 0 else pres[l]) > 0
179+
pres[i] = pres[i-1] + dp[i]
180+
return dp[-1]
181+
182+
```
183+
184+
**复杂度分析**
185+
186+
令 n 为数组长度。
187+
188+
- 时间复杂度:$O(n)$
189+
- 空间复杂度:$O(n)$
190+
191+
## 平衡二叉树
192+
193+
### 思路
194+
195+
我们可以将所有的 0 的索引按照升序顺序存起来,不妨令这个存储 0 索引的数据结构名为 zeros。
196+
197+
然后继续使用前面提到的动态规划思路,即 dp(i) 表示是否可从索引为 0 的点到达索引为 1 的点。唯一不同的是,这里不使用前缀和加速。
198+
199+
对于每一个可以到达的点(初始为索引为 0 的点),我们都执行一下判断:
200+
201+
1. 当前点 i 可以到跳的点为 j。其中 j 属于区间[i + minJump, i + maxJump]。判断 j 是否存在于 zeros。 (操作 1)
202+
2. 如果存在 zero[k] == j 。则令 dp[j] = true(操作 2)
203+
204+
最后返回最后 dp[n] 即可。
205+
206+
这种做法时间复杂度取决于 zeros 的数据结构。由于 zero 需要支持区间查找(操作 1),并且 zeros 是升序的,因此使用数组来存储就没问题。区间查找使用二分即可。
207+
208+
不过由于 zeros 最差的情况下可以到达 n 的数据规模,而此时操作 2 复杂度可以到达 $O(n)$,因此对于全为 0 的情况,时间复杂度为 $O(n^2)$。
209+
210+
我们可以使用平衡二叉树,并在每次操作 2 结束后将 v 从 zeros 移除来进行加速(平衡二叉树删除只需要 logn 时间)
211+
212+
并且由于 zeros 中的值都最多只会被访问一次,因此时间复杂度为 $O(n)$。但是我们使用二分查找时间复杂度是 $logn$,因此总的时间不大于 $nlogn$。之所以说不大于,是因为 zeros 在不断变小,因此每次二分时间也在不断缩减。
213+
214+
### 关键点
215+
216+
- 使用平衡二叉树不断执行删除以降低复杂度
217+
218+
### 代码
219+
220+
- 语言支持:Python3
221+
222+
Python3 Code:
223+
224+
```python
225+
226+
from sortedcontainers import SortedList
227+
class Solution:
228+
def canReach(self, s: str, minJump: int, maxJump: int) -> bool:
229+
if s[-1] == '1': return False
230+
zeroes = SortedList([i for i in range(len(s)) if s[i] == '0'])
231+
232+
dp = [False] * len(s)
233+
dp[0] = True
234+
235+
for i in range(len(s)):
236+
if dp[i]:
237+
l = zeroes.bisect_left(i + minJump)
238+
r = zeroes.bisect_right(i + maxJump)
239+
for v in [zeroes[i] for i in range(l, r)]:
240+
dp[v] = True
241+
zeroes.remove(v)
242+
return dp[-1]
243+
244+
```
245+
246+
**复杂度分析**
247+
248+
令 n 为数组长度。
249+
250+
- 时间复杂度:$O(nlogn)$
251+
- 空间复杂度:$O(n)$
252+
253+
> 此题解由 [力扣刷题插件](https://leetcode-pp.github.io/leetcode-cheat/?tab=solution-template) 自动生成。
254+
255+
力扣的小伙伴可以[关注我](https://leetcode-cn.com/u/fe-lucifer/),这样就会第一时间收到我的动态啦~
256+
257+
以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。
258+
259+
关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。
260+
261+
![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)

0 commit comments

Comments
 (0)