You are given a 0-indexed binary string s and two integers minJump and maxJump. In the beginning, you are standing at index 0, which is equal to '0'. You can move from index i to index j if the following conditions are fulfilled:
i + minJump <= j <= min(i + maxJump, s.length - 1), and
s[j] == '0'.
Return true if you can reach index s.length - 1 in s, or false otherwise.
Example 1:
Input: s ="00110010", minJump =2, maxJump =4Output:true
Explanation: The order of jumps is: indices 0 -> 4 -> 7.
Example 2:
Input: s ="0010", minJump =1, maxJump =1Output:false
Breadth First Search (BFS) - Level-by-level traversal for reachability problems
Sliding Window Technique - Maintaining a window of valid elements as you iterate
Two Pointers - Using pointers to track ranges efficiently in linear time
1. Brute Force (Memoization)
Intuition
From each position, we can jump to any position within the range [i + minJump, i + maxJump] if that position contains '0'. We use recursion with memoization to explore all valid jumps. Starting from index 0, we try every reachable position and recursively check if we can reach the end. Memoization prevents recalculating the same positions.
Algorithm
Create a memoization array initialized to null/unknown.
Define a recursive function dfs(i):
If already computed, return the cached result.
Mark the current position as unreachable initially.
Try all positions j in range [i + minJump, i + maxJump].
If s[j] == '0' and dfs(j) returns true, mark current as reachable.
publicclassSolution{publicboolCanReach(string s,int minJump,int maxJump){int n = s.Length;bool?[] dp =newbool?[n];
dp[n -1]=true;boolDfs(int i){if(dp[i].HasValue){return dp[i].Value;}
dp[i]=false;for(int j = i + minJump; j <= Math.Min(n -1, i + maxJump); j++){if(s[j]=='0'&&Dfs(j)){
dp[i]=true;break;}}return dp[i].Value;}if(s[n -1]=='1'){returnfalse;}returnDfs(0);}}
funccanReach(s string, minJump int, maxJump int)bool{
n :=len(s)
dp :=make([]*bool, n)
t :=true
dp[n-1]=&t
var dfs func(i int)bool
dfs =func(i int)bool{if dp[i]!=nil{return*dp[i]}
f :=false
dp[i]=&f
for j := i + minJump; j <=min(n-1, i+maxJump); j++{if s[j]=='0'&&dfs(j){
t :=true
dp[i]=&t
break}}return*dp[i]}if s[n-1]=='1'{returnfalse}returndfs(0)}funcmin(a, b int)int{if a < b {return a
}return b
}
class Solution {funcanReach(s: String, minJump: Int, maxJump: Int): Boolean {val n = s.length
val dp = arrayOfNulls<Boolean>(n)
dp[n -1]=truefundfs(i: Int): Boolean {
dp[i]?.let{return it }
dp[i]=falsefor(j in i + minJump..minOf(n -1, i + maxJump)){if(s[j]=='0'&&dfs(j)){
dp[i]=truebreak}}return dp[i]!!}if(s[n -1]=='1'){returnfalse}returndfs(0)}}
classSolution{funccanReach(_ s:String,_ minJump:Int,_ maxJump:Int)->Bool{let sArr =Array(s)let n = sArr.count
var dp =[Bool?](repeating:nil, count: n)
dp[n -1]=truefuncdfs(_ i:Int)->Bool{iflet val = dp[i]{return val
}
dp[i]=falsefor j in(i + minJump)...min(n -1, i + maxJump){if sArr[j]=="0"&&dfs(j){
dp[i]=truebreak}}return dp[i]!}if sArr[n -1]=="1"{returnfalse}returndfs(0)}}
Where n is the length of the string s and m is the given range of the jump (maxJump−minJump+1).
2. Breadth First Search
Intuition
BFS naturally explores positions level by level, where each level represents positions reachable in one jump. The key optimization is tracking the farthest index we've already processed. When processing position i, we only need to check new positions starting from max(i + minJump, farthest + 1) to avoid revisiting positions already added to the queue.
Algorithm
Initialize a queue with position 0 and track farthest = 0.
While the queue is not empty:
Dequeue position i.
Compute start = max(i + minJump, farthest + 1).
For each j from start to min(i + maxJump, n - 1):
If s[j] == '0', enqueue j. If j is the last index, return true.
Update farthest = i + maxJump.
Return false if the queue empties without reaching the end.
publicclassSolution{publicboolCanReach(string s,int minJump,int maxJump){int n = s.Length;Queue<int> q =newQueue<int>();
q.Enqueue(0);int farthest =0;while(q.Count >0){int i = q.Dequeue();int start = Math.Max(i + minJump, farthest +1);for(int j = start; j <= Math.Min(i + maxJump, n -1); j++){if(s[j]=='0'){if(j == n -1)returntrue;
q.Enqueue(j);}}
farthest = i + maxJump;}returnfalse;}}
funccanReach(s string, minJump int, maxJump int)bool{
n :=len(s)
q :=[]int{0}
farthest :=0forlen(q)>0{
i := q[0]
q = q[1:]
start :=max(i+minJump, farthest+1)for j := start; j <min(i+maxJump+1, n); j++{if s[j]=='0'{if j == n-1{returntrue}
q =append(q, j)}}
farthest = i + maxJump
}returnfalse}funcmax(a, b int)int{if a > b {return a
}return b
}funcmin(a, b int)int{if a < b {return a
}return b
}
class Solution {funcanReach(s: String, minJump: Int, maxJump: Int): Boolean {val n = s.length
val q: java.util.Queue<Int>= java.util.LinkedList()
q.add(0)var farthest =0while(q.isNotEmpty()){val i = q.poll()val start =maxOf(i + minJump, farthest +1)for(j in start..minOf(i + maxJump, n -1)){if(s[j]=='0'){if(j == n -1)returntrue
q.add(j)}}
farthest = i + maxJump
}returnfalse}}
classSolution{funccanReach(_ s:String,_ minJump:Int,_ maxJump:Int)->Bool{let sArr =Array(s)let n = sArr.count
var q =[0]var farthest =0while!q.isEmpty {let i = q.removeFirst()let start =max(i + minJump, farthest +1)for j in start..<min(i + maxJump +1, n){if sArr[j]=="0"{if j == n -1{returntrue}
q.append(j)}}
farthest = i + maxJump
}returnfalse}}
implSolution{pubfncan_reach(s:String, min_jump:i32, max_jump:i32)->bool{let s = s.as_bytes();let n = s.len();let min_j = min_jump asusize;let max_j = max_jump asusize;letmut q =VecDeque::new();
q.push_back(0usize);letmut farthest =0usize;whileletSome(i)= q.pop_front(){let start =(i + min_j).max(farthest +1);let end =(i + max_j +1).min(n);for j in start..end {if s[j]==b'0'{if j == n -1{returntrue;}
q.push_back(j);}}
farthest = i + max_j;}false}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
3. Dynamic Programming (Sliding Window)
Intuition
Position i is reachable if any position in [i - maxJump, i - minJump] is reachable (and s[i] == '0'). Instead of checking all positions in this range for each i, we maintain a running count of reachable positions within the window. As we move forward, we add newly entering positions to the count and remove positions that exit the window.
Algorithm
Create a DP array where dp[i] indicates if position i is reachable.
Set dp[0] = true and initialize count cnt = 0.
For each position i from 1 to n - 1:
If i >= minJump and dp[i - minJump] is true, increment cnt.
If i > maxJump and dp[i - maxJump - 1] is true, decrement cnt.
Instead of tracking a count, we use a pointer j to remember the farthest position we've marked as reachable so far. From each reachable position i, we can mark all positions in [i + minJump, i + maxJump] as reachable. The pointer j ensures we never mark the same position twice, achieving linear time.
Algorithm
Create a DP array with dp[0] = true. Initialize pointer j = 0.
For each position i from 0 to n - 1:
If dp[i] is false, skip to the next iteration.
Update j = max(j, i + minJump) to start from where we left off.
Mark all positions from j to min(i + maxJump, n - 1) where s[j] == '0' as reachable.
classSolution:defcanReach(self, s:str, minJump:int, maxJump:int)->bool:
n =len(s)if s[n -1]=='1':returnFalse
dp =[False]* n
dp[0]=True
j =0for i inrange(n):if dp[i]==False:continue
j =max(j, i + minJump)while j <min(i + maxJump +1, n):if s[j]=='0':
dp[j]=True
j +=1return dp[n -1]
publicclassSolution{publicbooleancanReach(String s,int minJump,int maxJump){int n = s.length();if(s.charAt(n -1)=='1'){returnfalse;}boolean[] dp =newboolean[n];
dp[0]=true;int j =0;for(int i =0; i < n; i++){if(!dp[i]){continue;}
j =Math.max(j, i + minJump);while(j <Math.min(i + maxJump +1, n)){if(s.charAt(j)=='0'){
dp[j]=true;}
j++;}}return dp[n -1];}}
classSolution{public:boolcanReach(string s,int minJump,int maxJump){int n = s.size();if(s[n -1]=='1'){returnfalse;}
vector<bool>dp(n,false);
dp[0]=true;int j =0;for(int i =0; i < n; i++){if(!dp[i]){continue;}
j =max(j, i + minJump);while(j <min(i + maxJump +1, n)){if(s[j]=='0'){
dp[j]=true;}
j++;}}return dp[n -1];}};
classSolution{/**
* @param {string} s
* @param {number} minJump
* @param {number} maxJump
* @return {boolean}
*/canReach(s, minJump, maxJump){const n = s.length;if(s[n -1]==='1'){returnfalse;}const dp =newArray(n).fill(false);
dp[0]=true;let j =0;for(let i =0; i < n; i++){if(!dp[i]){continue;}
j = Math.max(j, i + minJump);while(j < Math.min(i + maxJump +1, n)){if(s[j]==='0'){
dp[j]=true;}
j++;}}return dp[n -1];}}
publicclassSolution{publicboolCanReach(string s,int minJump,int maxJump){int n = s.Length;if(s[n -1]=='1'){returnfalse;}bool[] dp =newbool[n];
dp[0]=true;int j =0;for(int i =0; i < n; i++){if(!dp[i])continue;
j = Math.Max(j, i + minJump);while(j <= Math.Min(i + maxJump, n -1)){if(s[j]=='0'){
dp[j]=true;}
j++;}}return dp[n -1];}}
funccanReach(s string, minJump int, maxJump int)bool{
n :=len(s)if s[n-1]=='1'{returnfalse}
dp :=make([]bool, n)
dp[0]=true
j :=0for i :=0; i < n; i++{if!dp[i]{continue}
j =max(j, i+minJump)for j <min(i+maxJump+1, n){if s[j]=='0'{
dp[j]=true}
j++}}return dp[n-1]}funcmax(a, b int)int{if a > b {return a
}return b
}funcmin(a, b int)int{if a < b {return a
}return b
}
class Solution {funcanReach(s: String, minJump: Int, maxJump: Int): Boolean {val n = s.length
if(s[n -1]=='1'){returnfalse}val dp =BooleanArray(n)
dp[0]=truevar j =0for(i in0 until n){if(!dp[i])continue
j =maxOf(j, i + minJump)while(j <=minOf(i + maxJump, n -1)){if(s[j]=='0'){
dp[j]=true}
j++}}return dp[n -1]}}
classSolution{funccanReach(_ s:String,_ minJump:Int,_ maxJump:Int)->Bool{let sArr =Array(s)let n = sArr.count
if sArr[n -1]=="1"{returnfalse}var dp =[Bool](repeating:false, count: n)
dp[0]=truevar j =0for i in0..<n {if!dp[i]{continue}
j =max(j, i + minJump)while j <=min(i + maxJump, n -1){if sArr[j]=="0"{
dp[j]=true}
j +=1}}return dp[n -1]}}
implSolution{pubfncan_reach(s:String, min_jump:i32, max_jump:i32)->bool{let s = s.as_bytes();let n = s.len();let min_j = min_jump asusize;let max_j = max_jump asusize;if s[n -1]==b'1'{returnfalse;}letmut dp =vec![false; n];
dp[0]=true;letmut j =0usize;for i in0..n {if!dp[i]{continue;}
j = j.max(i + min_j);while j < n && j <= i + max_j {if s[j]==b'0'{
dp[j]=true;}
j +=1;}}
dp[n -1]}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
Common Pitfalls
Forgetting to Check the Last Character
Before starting any traversal, verify that s[n-1] == '0'. If the destination is blocked, immediately return false. Skipping this check wastes computation on an impossible path.
Off-by-One Errors in Jump Range
The valid jump range from position i is [i + minJump, i + maxJump] inclusive. Common mistakes include using i + maxJump - 1 or forgetting to clamp the upper bound to n - 1. Always verify your loop bounds match the problem constraints.
Revisiting Already Processed Positions
In BFS or DP approaches, failing to track which positions have been visited or marked leads to exponential blowup. Use a farthest pointer or visited array to ensure each index is processed at most once.
Sign in to join the discussion