力扣1871——跳跃游戏 VII(动态规划+前缀和/滑动窗口)

题目(中等)

给你一个下标从 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];
    }
};

你可能感兴趣的:(算法,动态规划,leetcode,算法,前缀和,滑动窗口)