DP

class Solution:
    def canReach(self, s: str, a: int, b: int) -> bool:
        """ tc O(N) sc O(N)
        dp[i]: start from idx= 0, there are at least 1 way to jump to s[i-1]        
        """
        n = len(s)
        dp = [0] * (n+1)
        psum = [0] *(n+1)
        dp[1] = 1
        psum[1] = 1
        for i in range(2,n+1):
            # i is next stop  before jump from [l,r]
            if s[i-1] == '0' and i - a >= 1 :#(i-1)-a >=0:
                l = max(1,i-b) 
                r = i - a  
        # means from [i-b,i-a] there at least one case, k   where dp[k] == 1  
                if psum[r]- psum[l-1]>0: 
                    dp[i] = 1
            psum[i] = psum[i-1] + dp[i]
        return dp[n]
                

BFS with queue

class Solution:
    def canReach(self, s: str, a: int, b: int) -> bool:  #minJump  a   maxJump b
        """tc O(N) sc O(N)
        """
        mx = 0
        n = len(s)
        q = collections.deque([0]) # 0: start
        while q:
            i = q.popleft()
            l = max(i+a,mx+1)
            r = min(i+b,n-1)
            for j in range(l,r+1):
                if s[j] == '0':
                    if j == n-1: return True
                    q.append(j)
            # mx = max(mx,i+b)
            mx = i + b # it's like topological sort, means mx will always larger while doing BFS 
        return False 

Greedy

class Solution:
    def canReach(self, s: str, a: int, b: int) -> bool:
        """
        main idea: sliding window  
        tc O(N)  sc O(1)
        """
        n = len(s)
        minv=maxv = 0
        # edge case:
        if s[-1] == '1':
            return False 
        for i in range(n-1):
            if s[i] == '1' or i < minv :
                continue
            if i > maxv:
                return False
            
            if s[i] == '0':
                minv = i+a
                maxv = min(i+b,n-1)
                if minv<=n-1<=maxv:
                    return True 
        return False