给你一个下标从 0 开始的二进制字符串 s 和两个整数 minJump 和 maxJump 。一开始,你在下标 0 处,且该位置的值一定为 '0' 。当同时满足如下条件时,你可以从下标 i 移动到下标 j 处:
i + minJump <= j <= min(i + maxJump, s.length - 1)且s[j] == '0'.
如果你可以到达 s 的下标 s.length - 1 处,请你返回 true ,否则返回 false 。
示例 1:
输入:s = “011010”, minJump = 2, maxJump = 3 **输出:**true 解释: 第一步,从下标 0 移动到下标 3 。 第二步,从下标 3 移动到下标 5 。
示例 2:
**输入:**s = “01101110”, minJump = 2, maxJump = 3 **输出:**false
提示:
2 <= s.length <= 10^5s[i]要么是'0',要么是'1's[0] == '0'1 <= minJump <= maxJump < s.length
思路一
带剪枝的BFS。注意几点,
- 从某个节点出发遍历所有可达的节点,需要记录这次遍历到哪儿了,避免下一次算出来的区间和这一次重叠。
class Solution {
public:
// cf. https://leetcode.cn/problems/jump-game-vii/description/comments/2994432/
bool canReach(string s, int minJump, int maxJump) {
const int n = s.size();
queue<int> q;
q.push(0);
int end = 0;
while (!q.empty()) {
int i = q.front();
q.pop();
if (i == n - 1) return true;
// 上一次已经考虑到end了,所以这次的beg可以从end+1开始,前面不用再考虑
int beg = max(i + minJump, end + 1);
end = min(i + maxJump, n - 1);
for (int j = beg; j <= end; ++j) {
if (s[j] == '0') q.push(j);
}
}
return false;
}
};思路二
不太相关的差分数组。设字符串长度为 ,对字符数组(字符串)做如下游戏:
- 遍历数组,从 开始(先不考虑
s[i]='0'的条件),给子区间 每个索引做一次标记,表示这个索引可以从下标 跳过来,当然,若遇到边界则以边界值为准; - 记数组 ,其中 表示原数组
s[i]被做标记的次数。显然, 表示s[i]可以跳到。 - 考虑
s[i] = '0'才能被跳到,结合起来就是遍历数组,初始值 ,- 若
s[i] = '0'且 ,给子区间 做标记; - 否则,跳过当前索引。
- 若
- 遍历完原数组, 并且
s[n-1] = '0'表示能跳到,否则不能。
抽象成上面的流程后,发现过程中涉及对子区间的批量增加,可以利用差分数组降低复杂度。
- 注意,初始化差分数组相当于对
[0,0]这个子区间批量加1,于是变成diff[0] += 1; diff[1] -= 1。
class Solution {
public:
// cf. https://leetcode.cn/problems/jump-game-vii/solutions/791314/qian-zhui-he-you-hua-dp-by-endlesscheng-k9d2
bool canReach(string s, int minJump, int maxJump) {
const int n = s.size();
vector<int> diff(n + 1, 0);
// 初始化差分数组
diff[0] += 1;
diff[1] -= 1;
int psum = 0; // 记录前缀和
for (int i = 0; i < n; ++i) {
psum += diff[i];
if (psum > 0 && s[i] == '0') { // 能跳到的条件
// 当区间越界,直接操作边界
diff[min(i + minJump, n)] += 1;
diff[min(i + maxJump + 1, n)] -= 1;
}
}
return psum > 0 && s.back() == '0';
}
};思路三
根据题意,坐标 j 可达的条件为,
s[j] = '0'- 在区间
[j - maxJump, j - minJump]中存在一个可达的坐标i
维护一个大小为 maxJump - minJump 的滑动窗口,记 cnt 为窗口中所有可达坐标数量之和。
- 遍历到坐标
i时,如果当前cnt > 0,则说明在闭区间[i - maxJump, i - minJump]中至少存在一个坐标能跳到i. 反之则说明此区间内不存在能移动到i的坐标。 - 每次遍历判断完当前是否可达后,需要更新滑窗的信息。每次遍历,滑窗整体向右移动一格,因此需要考虑左边界缩减和右边界扩展对
cnt的影响。当滑窗右移一格时,- 左边界
i - maxJump离开,如果i - maxJump是可达坐标,此时cnt需要减一 - 右边界
i - minJump + 1新加入,如果这个坐标可达,此时cnt需要加一
- 左边界
- 遍历时,直接从
i = minJump开始,因为当i < minJump时,滑窗右边界i - minJump < 0,即此时不存在对应的闭区间。
class Solution {
public:
bool canReach(string s, int minJump, int maxJump) {
const int n = s.size();
vector<int8_t> reachable(n, 0);
int cnt = 1; // 因为 s[0] 可达
reachable[0] = 1;
for (int i = minJump; i < n; ++i) {
if (s[i] == '0' && cnt > 0) {
reachable[i] = 1;
}
// 左边界痛失可达
if (i >= maxJump && reachable[i - maxJump]) {
--cnt;
}
// 右边界新增可达
if (reachable[i - minJump + 1]) {
++cnt;
}
}
return reachable[n-1] == 1;
}
};