An array heights stores the height of every stone, and an integer k gives the maximum jump distance.
A frog starts at index 0 and must reach index n - 1. From any index, a jump can be made to any later index within distance k, as long as the destination exists. The energy for one jump equals the absolute difference between both stone heights. Return the minimum total energy required to reach the final stone.
Example 1
Input: heights = [10, 30, 40, 50, 20], k = 3
Output: 30
Explanation: The route 0 -> 1 -> 4 costs |10 - 30| + |30 - 20| = 20 + 10 = 30.
Example 2
Input: heights = [10, 10], k = 5
Output: 0
Explanation: The direct route 0 -> 1 costs |10 - 10| = 0, even though k is larger than the required distance.
Recursion
The final stone can be reached from several earlier stones, not just one or two. The small aha moment is simple: before landing on a stone, the previous position must be one of the last k reachable stones. Each possible previous stone gives a complete route cost.
The same question appears again for every previous stone: minimum energy needed to reach the earlier position. Recursion fits because each choice leads to the same smaller problem. The state solve(index) represents the minimum energy required to reach stone index. The first helper call uses solve(n - 1) because the final stone is the destination and the helper works backward through all valid previous jumps.
For each state, every jump length from 1 to k is tested after confirming a valid previous index. The cheapest complete cost is returned. Direct recursion is easy to understand, but repeated states make the work grow very quickly.
Algorithm
Define
solve(index)as the minimum energy required to reach stoneindex, so every recursive call answers the same smaller question.Return
0at index0because the starting stone requires no jump and contributes no energy cost.Start recursion from
solve(n - 1)because the final stone represents the complete destination and earlier stones form smaller states.Keep
minimumEnergyat infinity before testing jumps so the first valid complete route can safely become the current best.Try every jump length from
1throughkbecause any reachable stone in the previous allowed window may produce the cheapest route.Add the height difference to
solve(previousIndex)only for a non-negative previous index because no stone exists before index0.Retain the smallest complete route in
minimumEnergyand return the value because the cheapest valid predecessor determines the state answer.
Dry Run
Diagram 1
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: // Computes the minimum energy required to reach one stone. int solve(int index, vector<int>& heights, int k) { // The starting stone needs no jump. if (index == 0) { return 0; } int minimumEnergy = INT_MAX; // Every allowed previous stone is tested as the jump source. for (int jump = 1; jump <= k; jump++) { int previousIndex = index - jump; // A jump can be used only when the previous stone exists. if (previousIndex >= 0) { int difference = abs(heights[index] - heights[previousIndex]); // A predecessor answer covers energy to the source. // The height difference adds the final jump cost. int jumpEnergy = solve(previousIndex, heights, k) + difference; minimumEnergy = min(minimumEnergy, jumpEnergy); } } return minimumEnergy; }public: // Returns minimum energy for the final stone. int frogJump(vector<int>& heights, int k) { int n = heights.size(); return solve(n - 1, heights, k); }};// Driver codeint main() { vector<int> heights = {10, 30, 40, 50, 20}; int k = 3; Solution obj; cout << obj.frogJump(heights, k) << endl; return 0;}Note: Direct recursion may fail for large input values. Repeated subproblems create exponential work, so an online judge may report Time Limit Exceeded.
Complexity Analysis
Time Complexity: O(kN), where N is the maximum recursion depth, because each recursive state can branch into at most k possible previous states.
Space Complexity: O(N), because a sequence of 1-step jumps can create a recursion stack of depth N.
Memoization
Direct recursion keeps asking for the same stone again. For example, several routes into later stones may need the answer for index 1 or index 2. A dp array saves each completed answer, so repeated work disappears.
The state remains solve(index), and dp[index] stores the minimum energy required to reach stone index. Before any state is expanded, the stored value is checked. The recurrence stays unchanged; only a memory layer is added.
Algorithm
Create a
dparray of sizenfilled with-1, so every untouched position clearly marks an uncalculated state.Preserve
solve(index)as the minimum energy for stoneindex, because memoization should reuse the recursive meaning without changing the choices.Return
0at index0because the starting stone needs no energy, and reusedp[index]whenever a stored answer exists to avoid repeated work.Keep
minimumEnergyat infinity before exploring predecessors so every valid route can compete for the state answer.Test every non-negative previous index within distance
kbecause each allowed predecessor can lead to a different total energy.Add the jump difference to the memoized predecessor answer and retain the smallest sum because the least costly complete route wins.
Store the result in
dp[index]before returning and callsolve(n - 1)from the public method so future visits reuse each completed state.
Dry Run
Diagram 1
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: // Computes and stores the minimum energy for one stone. int solve(int index, vector<int>& heights, int k, vector<int>& dp) { // The starting stone needs no jump. if (index == 0) { return 0; } // A stored answer avoids repeated recursion. if (dp[index] != -1) { return dp[index]; } int minimumEnergy = INT_MAX; // Every allowed previous stone is tested as the jump source. for (int jump = 1; jump <= k; jump++) { int previousIndex = index - jump; // A jump can be used only when the previous stone exists. if (previousIndex >= 0) { int difference = abs(heights[index] - heights[previousIndex]); // A stored answer covers energy to the source. // The height difference adds the final jump cost. int jumpEnergy = solve(previousIndex, heights, k, dp) + difference; minimumEnergy = min(minimumEnergy, jumpEnergy); } } // The best computed answer is stored for later requests. dp[index] = minimumEnergy; return dp[index]; }public: // Returns minimum energy for the final stone. int frogJump(vector<int>& heights, int k) { int n = heights.size(); vector<int> dp(n, -1); return solve(n - 1, heights, k, dp); }};// Driver codeint main() { vector<int> heights = {10, 30, 40, 50, 20}; int k = 3; Solution obj; cout << obj.frogJump(heights, k) << endl; return 0;}Complexity Analysis
Time Complexity: O(N × k), where N is the number of states, because each state checks at most k allowed predecessors once.
Space Complexity: O(N), because the dp array stores N values and the recursion stack can also grow to a depth of N.
Tabulation
Memoization solves states only after recursive demand appears. Tabulation builds the same answers in order from the starting stone to the final stone.
For a current index, all valid previous indices are smaller, so earlier table values are already available. The recursive call solve(previousIndex) becomes dp[previousIndex]. The same transition becomes an iterative loop over jump lengths.
Algorithm
Create a
dparray of sizensodp[index]stores the minimum energy for stoneindexand mirrors the recursive state.Set
dp[0]to0because the starting stone needs no jump and supplies the base answer for later states.Process indices from
1throughn - 1so every allowed predecessor has a completed table value before use.Keep
minimumEnergyat infinity for each current index so the first valid predecessor can establish a safe initial answer.Examine jump lengths from
1throughkand skip negative previous indices because only existing stones can start a jump.Add each height difference to
dp[previousIndex]and keep the smallest sum because every table state needs the cheapest complete route.Store the selected value in
dp[index]and returndp[n - 1]because the final table position represents the full journey.
Dry Run
Frog Jump with k Distances Tabulation
Solution
#include <bits/stdc++.h>using namespace std;class Solution {public: // Returns minimum energy with bottom-up DP. int frogJump(vector<int>& heights, int k) { int n = heights.size(); vector<int> dp(n, 0); // Earlier DP states are prepared before every current state. for (int index = 1; index < n; index++) { int minimumEnergy = INT_MAX; // Every allowed previous stone is tested as the jump source. for (int jump = 1; jump <= k; jump++) { int previousIndex = index - jump; // A jump can be used only when the previous stone exists. if (previousIndex >= 0) { int difference = abs(heights[index] - heights[previousIndex]); // A table value covers energy to the source. // The height difference adds the final jump cost. int jumpEnergy = dp[previousIndex] + difference; minimumEnergy = min(minimumEnergy, jumpEnergy); } } dp[index] = minimumEnergy; } return dp[n - 1]; }};// Driver codeint main() { vector<int> heights = {10, 30, 40, 50, 20}; int k = 3; Solution obj; cout << obj.frogJump(heights, k) << endl; return 0;}Complexity Analysis
Time Complexity: O(N × k), where N is the number of stones, because each stone checks at most k possible jump lengths.
Space Complexity: O(N), because the dp array stores one value for each of the N stones, while the iterative approach uses no recursion stack.
Space Optimization
The tabulation transition for an index reads only the previous k DP values. Older values are never needed again, because a jump longer than k is not allowed. So the full DP array can be replaced with a circular buffer of size k + 1.
The buffer position index % windowSize stores the answer for the current index. While calculating current, every previous index within distance k reads the stored answer from previousIndex % windowSize. After current is calculated, the same buffer slot is overwritten for the current index. The overwrite is safe because the value from index - windowSize is already outside the allowed jump window.
The variable current still means the minimum energy required to reach the present stone. The shift order is handled by the modulo write: all previous values are read first, then current is written into the circular slot for the current index.
Algorithm
Choose
windowSize = k + 1so the current circular slot cannot overwrite any of the previouskanswers before all required reads finish.Create a
dpwindow of lengthwindowSizeand setdp[0]to0because the starting stone needs a stored base answer.Process indices from
1throughn - 1so every allowed predecessor remains available in the circular window.Keep
currentat infinity before checking predecessors so the first valid route can safely become the best route.Test every non-negative previous index within distance
kbecause the transition still considers the same allowed jumps as tabulation.Read each predecessor through
previousIndex % windowSizeso the recent table state maps to the correct circular slot.Compare every predecessor cost and retain the smallest sum in
currentbecause the state still represents minimum energy.Write
currentintoindex % windowSizeonly after all reads finish, then return the final circular slot because older states are no longer needed.
Dry Run
Frog Jump with k Distances Space Optimization
Solution
#include <bits/stdc++.h>using namespace std;class Solution {public: // Returns the minimum energy using a circular DP window. int frogJump(vector<int>& heights, int k) { int n = heights.size(); int windowSize = k + 1; vector<int> dp(windowSize, 0); // Keeps only recent reachable DP answers. for (int index = 1; index < n; index++) { int current = INT_MAX; // Read every allowed predecessor before a write. // Early writes could erase a required DP value. for (int jump = 1; jump <= k; jump++) { int previousIndex = index - jump; // A jump can be used only when the previous stone exists. if (previousIndex >= 0) { int previousSlot = previousIndex % windowSize; int difference = abs(heights[index] - heights[previousIndex]); // Compare each complete predecessor cost. // Keep the cheapest cost for the current stone. int jumpEnergy = dp[previousSlot] + difference; current = min(current, jumpEnergy); } } // Write current after all predecessor reads. // The modulo shift advances the DP window. int currentSlot = index % windowSize; dp[currentSlot] = current; } return dp[(n - 1) % windowSize]; }};// Driver codeint main() { vector<int> heights = {10, 30, 40, 50, 20}; int k = 3; Solution obj; cout << obj.frogJump(heights, k) << endl; return 0;}Complexity Analysis
Time Complexity: O(N × k), where N is the number of stones, because each stone checks at most k previous stones.
Space Complexity: O(k), because the circular dp array stores only the most recent k + 1 states, which simplifies to O(k).
Interview follow-up Questions
Yes. A jump still needs an existing destination. When k is larger than the remaining distance, only available later stones are considered.
Be the first to add a comment.