题目(中等)
给你一个下标从 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 <= 105
s[i] 要么是 ‘0’ ,要么是 ‘1’
s[0] == ‘0’
1 <= minJump <= maxJump < s.length
思路
常规的思路是动态规划,dp[i]代表第i个位置是否能达到,
对于dp[i]来说,能到达的条件为该位置是0以及[i-maxjump, i-minjump]之间有位置能达到,这样才能跳到i位置,最后判断结尾能否达到。但看数据量1e5,这样O(n^2)会超时。
//超时
class Solution {
public:
bool canReach(string s, int minJump, int maxJump) {
int n = s.size();
if(s[n-1] == '1') return false;
vector<bool> dp(n); //该下标是否可达到
dp[0] = true;
for(int i = 1; i < n; i++) {
if(s[i] == '1') continue;
if(i - minJump < 0) continue;
for(int j = i - minJump; j >= i - maxJump && j >= 0; j--) {
if(dp[j] == true) {
dp[i] = true;
break;
}
}
}
return dp[n-1];
}
};
因此需要优化,注意到每次判断该位置是否可行时,只需要判断[i-maxjump, i-minjump]这个区间是否有可行解,可以用前缀和来快速得到这个区间的结果,用1表示可行,0表示不可行,如果这个区间的和不为0,即说明这个区间存在可行解。
而这个区间的和就是区间首尾的前缀和之差sum[i - maxJump - 1] - sum[i - minJump];
实现上,先处理掉各种边界,之后对每个位置进行判断,并累加到前缀和中,dp数组最后一位即是答案;
另外,dp数组其实只在计算自身前缀和时用到了,可以只用一个int表示省时省事。
class Solution {
public:
bool canReach(string s, int minJump, int maxJump) {
int n = s.size();
if(s[n-1] == '1') return false;
if(minJump > n - 1) return false;
if(maxJump > n - 1) return true;
int dp;
vector<int> sum(n);
fill(sum.begin(), sum.begin() + minJump, 1); //minJump之前都不能到达
for(int i = minJump; i <= maxJump; i++) {
dp = s[i] == '0' ? 1 : 0;
sum[i] = sum[i-1] + dp;
}
if(sum[maxJump] == 1) return false;
for(int i = maxJump + 1; i < n; i++) {
int total = sum[i - maxJump - 1] - sum[i - minJump];
dp = (total != 0 && s[i] == '0') ? 1 : 0;
sum[i] = sum[i-1] + dp;
}
return dp;
}
};
此外,由于区间大小是固定的,还可以用滑动窗口的方法,更新位置i对应的窗口[i-maxjump, i-minjump]内可取点的个数,如果区间内有可取点,则则i可取,滑动时判断边界更新区间内可取点个数。注意边界,最好用例子写来确定准确表达式。
class Solution {
public:
bool canReach(string s, int minJump, int maxJump) {
int n = s.size();
if(s[n-1] == '1') return false;
if(minJump > n - 1) return false;
if(maxJump > n - 1) return true;
vector<int> dp(n);
dp[0] = 1;
for(int i = minJump; i <= maxJump; i++) {
dp[i] = s[i] == '0' ? 1 : 0;
}
int aviable = count(dp.begin(), dp.begin() + maxJump + 1 - minJump, 1);
for(int i = maxJump + 1; i < n; i++) {
int right = i - minJump, left = i - maxJump - 1;
if(dp[left] == 1) aviable--;
if(dp[right] == 1) aviable++;
dp[i] = (aviable > 0 && s[i] == '0') ? 1 : 0;
}
return dp[n-1];
}
};