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

    &nbsp;

    **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`

    &nbsp;

    **Step 3: Return the result**

    - Return `dp[n - 1]` — whether the last index is reachable

    &nbsp;

    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.
