Topics › Dynamic Programming › 1D DP
Frog jump with K distances
Java source ↗ Edit this article ↗
Jump to code:
Problem
There are n stairs with heights h[0..n-1]. A frog starts on stair 0 and wants to reach stair n - 1. From stair i it may jump to any of the stairs i + 1, i + 2, ..., i + k. A jump from stair i to stair j costs |h[i] - h[j]| energy. Return the minimum total energy to reach the last stair. (GeeksforGeeks calls this problem “Minimal Cost”.)
Typical constraints: n up to about 10^5, 1 ≤ k ≤ 100, heights up to 10^4. With k = 2 this is exactly the plain Frog Jump.
Example 1
h = [10, 30, 40, 50, 20], k = 3
answer = 30 // 0 -> 1 costs 20, then 1 -> 4 (a jump of 3) costs |30 - 20| = 10
Example 2
h = [10, 20, 10], k = 1
answer = 20 // k = 1 forces 0 -> 1 -> 2: 10 + 10
Approach 1: Recursion
Intuition. Generalise the Frog Jump argument. Let f(i) be the minimum energy to reach stair i. The last jump into stair i had some length j between 1 and k, so it came from stair i - j (which must exist, so i - j ≥ 0). The cost after that jump does not depend on how the frog reached i - j, so the route to i - j should be the cheapest one, f(i - j). Try every j and keep the best:
f(0) = 0
f(i) = min over j = 1..k with i - j ≥ 0 of f(i - j) + |h[i] - h[i - j]|
The two hand-written options of Frog Jump simply become a loop.
Algorithm.
- If
i == 0, return 0. - For each
jfrom 1 tokwhilei - j ≥ 0, computef(i - j) + |h[i] - h[i - j]|. - Return the minimum of those values.
static int recursive(int[] h, int k) {
return solve(h.length - 1, h, k);
}
static int solve(int i, int[] h, int k) {
if (i == 0) return 0;
int best = Integer.MAX_VALUE;
for (int j = 1; j <= k && i - j >= 0; j++) { // last jump came from i - j
best = Math.min(best, solve(i - j, h, k) + Math.abs(h[i] - h[i - j]));
}
return best;
}
Complexity. Time up to O(k^n): each call can branch k ways, and the same stairs are solved repeatedly. Space O(n) for the recursion depth.
Approach 2: Memoization
Intuition. The state is still just the stair index, so there are only n different subproblems. Cache them and each is solved once, with k work for its loop.
Algorithm.
- Create
dpof sizenfilled with-1(costs are never negative). - In the helper, return the cached value when present; otherwise run the loop from Approach 1 and store the result.
static int memoization(int[] h, int k) {
int[] dp = new int[h.length];
Arrays.fill(dp, -1);
return memo(h.length - 1, h, k, dp);
}
static int memo(int i, int[] h, int k, int[] dp) {
if (i == 0) return 0;
if (dp[i] != -1) return dp[i];
int best = Integer.MAX_VALUE;
for (int j = 1; j <= k && i - j >= 0; j++) {
best = Math.min(best, memo(i - j, h, k, dp) + Math.abs(h[i] - h[i - j]));
}
return dp[i] = best;
}
Complexity. Time O(n · k): n states, k transitions each. Space O(n) for the table plus O(n) stack, which is too deep for Java’s default stack once n reaches tens of thousands.
Approach 3: Tabulation
Intuition. Every f(i) reads only smaller indices, so fill the table from stair 0 upward. By the time we reach i, all of dp[i - k..i - 1] are final.
Algorithm.
dp[0] = 0.- For
ifrom 1 ton - 1:dp[i] = min over valid j of dp[i - j] + |h[i] - h[i - j]|. - Return
dp[n - 1].
static int tabulation(int[] h, int k) {
int n = h.length;
int[] dp = new int[n];
dp[0] = 0;
for (int i = 1; i < n; i++) {
int best = Integer.MAX_VALUE;
for (int j = 1; j <= k && i - j >= 0; j++) {
best = Math.min(best, dp[i - j] + Math.abs(h[i] - h[i - j]));
}
dp[i] = best;
}
return dp[n - 1];
}
Dry run on h = [10, 30, 40, 50, 20], k = 3:
| i | h[i] | j = 1 | j = 2 | j = 3 | dp[i] |
|---|---|---|---|---|---|
| 0 | 10 | - | - | - | 0 |
| 1 | 30 | 0 + 20 = 20 | - | - | 20 |
| 2 | 40 | 20 + 10 = 30 | 0 + 30 = 30 | - | 30 |
| 3 | 50 | 30 + 10 = 40 | 20 + 20 = 40 | 0 + 40 = 40 | 40 |
| 4 | 20 | 40 + 30 = 70 | 30 + 20 = 50 | 20 + 10 = 30 | 30 |
The answer dp[4] = 30 comes from the jump of length 3 out of stair 1.
Complexity. Time O(n · k), space O(n).
Approach 4: Ring buffer
Intuition. In plain Frog Jump we kept two variables because dp[i] read only the last two cells. Here it reads the last k cells, so keep exactly those. A ring buffer of size k stores dp[i] at position i % k. When we are about to write dp[i] into slot i % k, that slot holds dp[i - k], the oldest value the current step needed, and it has already been read because the loop finishes before the write. So the overwrite is safe.
If k ≥ n, a buffer of size n is enough, since no stair index reaches n; the code uses size = min(n, k).
Algorithm.
size = min(n, k),ring[0] = 0.- For
ifrom 1 ton - 1: computebestby readingring[(i - j) % size]for each validj, then writering[i % size] = best. - Return
ring[(n - 1) % size].
static int ringBuffer(int[] h, int k) {
int n = h.length;
int size = Math.min(n, k);
int[] ring = new int[size]; // ring[i % size] holds dp[i]
ring[0] = 0;
for (int i = 1; i < n; i++) {
int best = Integer.MAX_VALUE;
for (int j = 1; j <= k && i - j >= 0; j++) {
best = Math.min(best, ring[(i - j) % size] + Math.abs(h[i] - h[i - j]));
}
ring[i % size] = best; // overwrites dp[i - size], which is no longer needed
}
return ring[(n - 1) % size];
}
Complexity. Time O(n · k), space O(min(n, k)). This only matters when k is much smaller than n; in interviews the tabulation is the expected answer and the ring buffer is a follow-up.
You might hope for something faster than O(n · k), as in “sliding window minimum” problems. That trick needs the cost of a jump to depend on only one endpoint. Here the cost |h[i] - h[j]| mixes both stairs, so the minimum over the window changes with i. With small k, O(n · k) is the standard solution.
Edge cases and pitfalls
- One stair. No jumps are needed; all methods return 0, and the ring buffer has size 1.
k = 1. The frog must visit every stair, and the answer is the sum of adjacent differences.k ≥ n - 1. The frog can jump from stair 0 straight to the end, and by the triangle inequality|a - c| ≤ |a - b| + |b - c|no route with stops is cheaper, so the answer is|h[0] - h[n - 1]|. The tests check this on[10, 100, 10, 50]withk = 5(answer 40) and[1, 1000, 1000, 1000, 1]withk = 4(answer 0). It is a handy sanity check, but it only applies when the end is one jump away.- Bounds in the loop. The condition
i - j ≥ 0must be part of the loop test; otherwise smallireads negative indices. - Infinity.
beststarts atInteger.MAX_VALUEand is only compared. At least one validj(namelyj = 1) always exists fori ≥ 1, so it is always replaced. - Consistency with Frog Jump. Setting
k = 2must reproduce the Frog Jump answers; the test on[30, 10, 60, 10, 60, 50]checks this, plus 200 seeded random inputs on which all four methods must agree.
Related problems
- Frog Jump is the
k = 2special case, with anO(1)space solution. - Climbing stairs counts routes with the same “which last move” reasoning.
- Introduction to DP explains memoization, tabulation and space optimization in general.
- Jump Game II is a jumping problem where each jump has unit cost, so a greedy works instead of DP.