Frog Jump
Can a frog reach the last stone with jump-size constraints? Hash-map per-stone reachable jumps.
3 min read
dp hash-set
Problem#
Stones at given positions; frog at first stone with jump size 1 initially. After a jump of size k, the next must be k-1, k, or k+1. Return true iff the last stone is reachable.
Examples#
stones = [0,1,3,5,6,8,12,17]→true
Constraints#
2 <= n <= 2000.
Approach#
reach[pos] = set of possible jump sizes that reach stone at pos. Initialize reach[0] = {0}. For each stone in order, for each k in its set, propagate to stones at pos + k - 1, pos + k, pos + k + 1.
Solution#
class Solution {public: bool canCross(vector<int>& stones) { unordered_map<int, unordered_set<int>> reach; for (int s : stones) reach[s] = {}; reach[0].insert(0); for (int s : stones) { for (int k : reach[s]) { for (int next : {k - 1, k, k + 1}) { if (next > 0 && reach.count(s + next)) reach[s + next].insert(next); } } } return !reach[stones.back()].empty(); }};func canCross(stones []int) bool { reach := map[int]map[int]bool{} for _, s := range stones { reach[s] = map[int]bool{} } reach[0][0] = true for _, s := range stones { for k := range reach[s] { for _, next := range []int{k - 1, k, k + 1} { if next > 0 { if _, ok := reach[s+next]; ok { reach[s+next][next] = true } } } } } return len(reach[stones[len(stones)-1]]) > 0}from typing import Listfrom collections import defaultdict
class Solution: def canCross(self, stones: List[int]) -> bool: reach = defaultdict(set) for s in stones: reach[s] # ensure key exists reach[0].add(0) stone_set = set(stones) for s in stones: for k in list(reach[s]): for nxt in (k - 1, k, k + 1): if nxt > 0 and (s + nxt) in stone_set: reach[s + nxt].add(nxt) return bool(reach[stones[-1]])var canCross = function(stones) { const reach = new Map(); for (const s of stones) reach.set(s, new Set()); reach.get(0).add(0); for (const s of stones) { for (const k of reach.get(s)) { for (const next of [k - 1, k, k + 1]) { if (next > 0 && reach.has(s + next)) { reach.get(s + next).add(next); } } } } return reach.get(stones[stones.length - 1]).size > 0;};class Solution { public boolean canCross(int[] stones) { Map<Integer, Set<Integer>> reach = new HashMap<>(); for (int s : stones) reach.put(s, new HashSet<>()); reach.get(0).add(0); for (int s : stones) { for (int k : new ArrayList<>(reach.get(s))) { for (int next : new int[]{k - 1, k, k + 1}) { if (next > 0 && reach.containsKey(s + next)) { reach.get(s + next).add(next); } } } } return !reach.get(stones[stones.length - 1]).isEmpty(); }}function canCross(stones: number[]): boolean { const reach = new Map<number, Set<number>>(); for (const s of stones) reach.set(s, new Set<number>()); reach.get(0)!.add(0); for (const s of stones) { for (const k of reach.get(s)!) { for (const next of [k - 1, k, k + 1]) { if (next > 0 && reach.has(s + next)) { reach.get(s + next)!.add(next); } } } } return reach.get(stones[stones.length - 1])!.size > 0;}Editorial#
A pure 1D position DP doesn’t capture state — the jump size at each position matters. So the state is (position, last jump size). Hashing each position’s reachable jump sizes is the cleanest representation.
Complexity#
- Time: O(n²).
- Space: O(n²).
Concept revision#
Related#