Problem: Minimum Jumps to Reach Home

A certain bug's home is on the x-axis at position `x`. Help them get there
from position `0`.

The bug jumps according to the following rules:

* It can jump exactly `a` positions forward (to the right).
* It can jump exactly `b` positions backward (to the left).
* It cannot jump backward twice in a row.
* It cannot jump to any `forbidden` positions.

The bug may jump forward beyond its home, but it cannot jump to positions
numbered with negative integers.

Given an array of integers `forbidden`, where `forbidden[i]` means that the
bug cannot jump to the position `forbidden[i]`, and integers `a`, `b`, and
`x`, return the minimum number of jumps needed for the bug to reach its home.
If there is no possible sequence of jumps that lands the bug on position `x`,
return `-1.`


Python solution:
import math
import itertools
import bisect
import collections
import string
import heapq
import functools
import sortedcontainers
from typing import List, Dict, Tuple, Iterator

from enum import Enum


class Direction(Enum):
  kForward = 0
  kBackward = 1


class Solution:
  def minimumJumps(self, forbidden: List[int], a: int, b: int, x: int) -> int:
    furthest = max(x + a + b, max(pos + a + b for pos in forbidden))
    seenForward = {pos for pos in forbidden}
    seenBackward = {pos for pos in forbidden}

    q = collections.deque([(Direction.kForward, 0)])

    ans = 0
    while q:
      for _ in range(len(q)):
        dir, pos = q.popleft()
        if pos == x:
          return ans
        forward = pos + a
        backward = pos - b
        if forward <= furthest and forward not in seenForward:
          seenForward.add(forward)
          q.append((Direction.kForward, forward))
        if dir == Direction.kForward and backward >= 0 and backward not in seenBackward:
          seenBackward.add(backward)
          q.append((Direction.kBackward, backward))
      ans += 1

    return -1


Java solution:
enum Direction { FORWARD, BACKWARD }

class Solution {
  public int minimumJumps(int[] forbidden, int a, int b, int x) {
    int furthest = x + a + b;
    Set<Integer> seenForward = new HashSet<>();
    Set<Integer> seenBackward = new HashSet<>();

    for (final int pos : forbidden) {
      seenForward.add(pos);
      seenBackward.add(pos);
      furthest = Math.max(furthest, pos + a + b);
    }

    // (direction, position)
    Queue<Pair<Direction, Integer>> q = new ArrayDeque<>(List.of(new Pair<>(Direction.FORWARD, 0)));

    for (int ans = 0; !q.isEmpty(); ++ans)
      for (int sz = q.size(); sz > 0; --sz) {
        Direction dir = q.peek().getKey();
        final int pos = q.poll().getValue();
        if (pos == x)
          return ans;
        final int forward = pos + a;
        final int backward = pos - b;
        if (forward <= furthest && seenForward.add(forward))
          q.offer(new Pair<>(Direction.FORWARD, forward));
        // It cannot jump backward twice in a row.
        if (dir == Direction.FORWARD && backward >= 0 && seenBackward.add(backward))
          q.offer(new Pair<>(Direction.BACKWARD, backward));
      }

    return -1;
  }
}


C++ solution:
enum class Direction { kForward, kBackward };

class Solution {
 public:
  int minimumJumps(vector<int>& forbidden, int a, int b, int x) {
    int furthest = x + a + b;
    unordered_set<int> seenForward;
    unordered_set<int> seenBackward;

    for (const int pos : forbidden) {
      seenForward.insert(pos);
      seenBackward.insert(pos);
      furthest = max(furthest, pos + a + b);
    }

    // (direction, position)
    queue<pair<Direction, int>> q{{{Direction::kForward, 0}}};

    for (int ans = 0; !q.empty(); ++ans)
      for (int sz = q.size(); sz > 0; --sz) {
        const auto [dir, pos] = q.front();
        q.pop();
        if (pos == x)
          return ans;
        const int forward = pos + a;
        const int backward = pos - b;
        if (forward <= furthest && seenForward.insert(forward).second)
          q.emplace(Direction::kForward, forward);
        // It cannot jump backward twice in a row.
        if (dir == Direction::kForward && backward >= 0 &&
            seenBackward.insert(backward).second)
          q.emplace(Direction::kBackward, backward);
      }

    return -1;
  }
};