title: Jump Game VII slug: jump-game-vii difficulty: medium leetcode_id: 1871 leetcode_url: https://leetcode.com/problems/jump-game-vii/ categories: - strings - dynamic-programming patterns: - slug: bfs is_optimal: false - slug: sliding-window is_optimal: true - slug: dynamic-programming is_optimal: false function_signature: "def can_reach(s: str, min_jump: int, max_jump: int) -> bool:" test_cases: visible: - input: { s: "011010", min_jump: 2, max_jump: 3 } expected: true - input: { s: "01101110", min_jump: 2, max_jump: 3 } expected: false hidden: - input: { s: "00", min_jump: 1, max_jump: 1 } expected: true - input: { s: "01", min_jump: 1, max_jump: 1 } expected: false - input: { s: "0000", min_jump: 1, max_jump: 3 } expected: true - input: { s: "00000000", min_jump: 3, max_jump: 5 } expected: true - input: { s: "0100000", min_jump: 2, max_jump: 5 } expected: true - input: { s: "01111110", min_jump: 1, max_jump: 6 } expected: false description: | 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*. constraints: | - `2 <= s.length <= 10^5` - `s[i]` is either `'0'` or `'1'` - `s[0] == '0'` - `1 <= minJump <= maxJump < s.length` examples: - input: 's = "011010", minJump = 2, maxJump = 3' output: "true" explanation: "In the first step, move from index 0 to index 3. In the second step, move from index 3 to index 5." - input: 's = "01101110", minJump = 2, maxJump = 3' output: "false" explanation: "There is no way to reach the last index starting from index 0." explanation: intuition: | Imagine you're hopping across stepping stones in a river, where `'0'` represents a safe stone and `'1'` represents water. From any stone, you can only jump forward between `minJump` and `maxJump` steps. The naive approach would be to try every possible jump from every reachable position — but with up to 10^5 characters and a jump range that could span thousands of indices, this becomes extremely slow. The key insight is that we need to efficiently track **which positions are reachable** and then, for each new position, check if **any** reachable position can jump to it. Instead of checking every source position individually, we can use a **sliding window** or **prefix sum** to answer "is there any reachable position in the valid jump range?" in O(1) time. Think of it like this: as you scan left to right, you maintain a count of how many reachable positions fall within the "jump window" for the current index. If that count is positive and the current character is `'0'`, you can reach it. approach: | We solve this using **Dynamic Programming with Prefix Sum optimization**: **Step 1: Set up the DP array** - Create a boolean array `dp` where `dp[i]` indicates whether index `i` is reachable - Set `dp[0] = True` since we start at index 0 - Initialize a counter `reachable` to track reachable positions in the current window   **Step 2: Iterate through the string** - For each index `i` from 1 to `n-1`: - First, check if a new position has entered our "from" window: if `i >= minJump` and `dp[i - minJump]` is true, increment `reachable` - Then, check if a position has left the window: if `i > maxJump` and `dp[i - maxJump - 1]` is true, decrement `reachable` - If `reachable > 0` and `s[i] == '0'`, mark `dp[i] = True`   **Step 3: Return the result** - Return `dp[n - 1]` — whether the last index is reachable   This approach works because the sliding window maintains a count of all reachable positions that could potentially jump to the current index. We add positions as they enter the jump range and remove them as they exit. common_pitfalls: - title: The BFS/DFS Timeout description: | A natural approach is to use BFS or DFS to explore all reachable positions. However, with a string of length `10^5` and a jump range that could span thousands of indices, this approach degenerates to O(n * (maxJump - minJump)) which is potentially O(n^2). For example, with `n = 100000`, `minJump = 1`, and `maxJump = 50000`, each position could have 50,000 neighbors to explore! wrong_approach: "Plain BFS/DFS exploring all neighbors in jump range" correct_approach: "BFS with visited tracking and early termination, or DP with sliding window" - title: Checking Every Source Position description: | For each index `i`, you might think to loop through all `j` from `i - maxJump` to `i - minJump` to check if any `dp[j]` is true. This is O(n * range) which is too slow. The sliding window/prefix sum optimization reduces this to O(1) per index by maintaining a running count of reachable positions in the valid range. wrong_approach: "For each i, loop through all j in jump range" correct_approach: "Maintain sliding window count of reachable positions" - title: Off-by-One Errors in Window Bounds description: | The jump constraints are `i + minJump <= j <= i + maxJump`. When working backwards (asking "can I reach index j?"), the valid source range is `j - maxJump <= i <= j - minJump`. Be careful when a position enters and exits the window: - Position `j - minJump` enters the window when we reach index `j` - Position `j - maxJump - 1` exits the window when we reach index `j` wrong_approach: "Incorrect window boundary calculations" correct_approach: "Carefully track when positions enter (at i - minJump) and exit (at i - maxJump - 1)" key_takeaways: - "**Sliding window for range queries**: When you need to check if *any* value in a range satisfies a condition, maintain a running count as the window slides" - "**Prefix sum for cumulative queries**: This pattern appears frequently — counting elements in ranges can be done in O(1) with preprocessing" - "**Optimizing DP transitions**: When DP transitions involve checking a range of previous states, look for ways to avoid the inner loop" - "**Jump Game series**: This problem extends the classic Jump Game pattern — earlier versions use greedy, this one requires DP with optimization" time_complexity: "O(n). We iterate through the string once, and each position enters and exits the sliding window exactly once." space_complexity: "O(n). We use a DP array of size `n` to track reachability of each position." solutions: - approach_name: DP with Sliding Window is_optimal: true code: | def can_reach(s: str, min_jump: int, max_jump: int) -> bool: n = len(s) # dp[i] = True if we can reach index i dp = [False] * n dp[0] = True # Start at index 0 # Count of reachable positions in the current jump window reachable = 0 for i in range(1, n): # Position (i - min_jump) just entered our "can jump from" window if i >= min_jump and dp[i - min_jump]: reachable += 1 # Position (i - max_jump - 1) just left the window if i > max_jump and dp[i - max_jump - 1]: reachable -= 1 # If any reachable position can jump here and it's a '0', mark reachable if reachable > 0 and s[i] == '0': dp[i] = True return dp[n - 1] explanation: | **Time Complexity:** O(n) — Single pass through the string with O(1) work per index. **Space Complexity:** O(n) — DP array to track reachability. The sliding window maintains a count of positions that could jump to the current index. As we move right, positions enter the window when they're exactly `minJump` away, and exit when they're more than `maxJump` away. - approach_name: BFS with Optimization is_optimal: false code: | from collections import deque def can_reach(s: str, min_jump: int, max_jump: int) -> bool: n = len(s) if s[n - 1] == '1': return False queue = deque([0]) # Track the farthest index we've added to avoid duplicates farthest = 0 while queue: i = queue.popleft() # Start of jump range: don't re-explore already visited indices start = max(i + min_jump, farthest + 1) end = min(i + max_jump, n - 1) for j in range(start, end + 1): if s[j] == '0': if j == n - 1: return True queue.append(j) # Update farthest to avoid revisiting farthest = max(farthest, i + max_jump) return False explanation: | **Time Complexity:** O(n) — Each index is added to the queue at most once due to the `farthest` optimization. **Space Complexity:** O(n) — Queue can hold up to n positions in the worst case. This BFS approach uses a `farthest` pointer to avoid re-exploring indices. When processing position `i`, we only explore indices beyond what we've already added. This ensures each index is visited at most once, giving linear time. - approach_name: Brute Force DP is_optimal: false code: | def can_reach(s: str, min_jump: int, max_jump: int) -> bool: n = len(s) dp = [False] * n dp[0] = True for i in range(1, n): if s[i] == '1': continue # Check all positions that could jump to i for j in range(max(0, i - max_jump), i - min_jump + 1): if dp[j]: dp[i] = True break return dp[n - 1] explanation: | **Time Complexity:** O(n * (maxJump - minJump)) — For each position, we check up to `maxJump - minJump + 1` previous positions. **Space Complexity:** O(n) — DP array. This approach is correct but too slow for large inputs. With `n = 10^5` and a large jump range, this becomes O(n^2) and will TLE. Included to illustrate why the sliding window optimization is necessary.