A frog wants to climb a staircase with n steps. Given an integer array heights, where heights[i] contains the height of the ith step, and an integer k.
To jump from the ith step to the jth step, the frog requires abs(heights[i] - heights[j]) energy, where abs() denotes the absolute difference. The frog can jump from the ith step to any step in the range [i + 1, i + k], provided it exists. Return the minimum amount of energy required by the frog to go from the 0th step to the (n-1)th step.
Input: heights = [10, 5, 20, 0, 15], k = 2
Output: 15
Explanation:
0th step -> 2nd step, cost = abs(10 - 20) = 10
2nd step -> 4th step, cost = abs(20 - 15) = 5
Total cost = 10 + 5 = 15.
Input: heights = [15, 4, 1, 14, 15], k = 3
Output: 2
Explanation:
0th step -> 3rd step, cost = abs(15 - 14) = 1
3rd step -> 4th step, cost = abs(14 - 15) = 1
Total cost = 1 + 1 = 2.
Input: heights = [15, 4, 1, 14, 15], k = 4
As the problem states to find the minimum energy required, the first approache that comes to our mind is greedy.
In greedy we tend to make our moves greedily on each step, here the greedy move will be to select the path with minimum energy. But this approach will fail here as total energy required by the frog depends upon the path taken by the frog. If the frog just takes the minimum energy path in every stage it can happen that it eventually takes a path with greater energy after a certain number of jumps.
The problem here is asking for the minimum possible energy and as discussed in the previous problem, this kind of problem can be solved using recusion.
Using the steps described in previous problem, write the recursive solution:
/*It is pseudocode and not tied to
any specific programming language.*/
f(ind, height){
if(ind == 0) return 0
mmSteps = INT_MAX
for(int j = 1; j <= K; j++){
if((ind-j) >= 0){
jump = f(ind-j), height) + abs(a[ind] - a[ind-j]);
mmSteps = min(mmSteps, jump);
}
}
return mmSteps;
}
#include <bits/stdc++.h>
using namespace std;
class Solution {
private:
//Helper function for recursion
int func(int ind, vector<int> &heights, int k){
//Base case
if(ind == 0){
return 0;
}
//Initialize mmStep to INT_MAX
int mmStep = INT_MAX;
//Try all possible steps
for(int j = 1; j <= k; j++){
//Check if ind-j is non negative
if(ind-j >= 0){
int jump = func(ind-j, heights, k) + abs(heights[ind] - heights[ind-j]);
//Update the mimimum energy
mmStep = min(jump, mmStep);
}
}
//Return the answer
return mmStep;
}
public:
/* Function to find the mimimum
energy to reach last stair*/
int frogJump(vector<int> &heights, int k) {
int ind = heights.size()-1;
//Return the mimimum energy
return func(ind, heights, k);
}
};
int main() {
vector<int> height{15, 4, 1, 14, 15};
int k = 3;
// Create an instance of Solution class
Solution sol;
// Print the answer
cout << "Minimum energy : " << sol.frogJump(height, k) << endl;
return 0;
}
import java.util.*;
class Solution {
//Helper function for recursion
private int func(int ind, int[] heights, int k) {
//Base case
if (ind == 0) {
return 0;
}
//Initialize mmStep to INT_MAX
int mmStep = Integer.MAX_VALUE;
//Try all possible steps
for (int j = 1; j <= k; j++) {
//Check if ind-j is non negative
if (ind - j >= 0) {
int jump = func(ind - j, heights, k) + Math.abs(heights[ind] - heights[ind - j]);
//Update the mimimum energy
mmStep = Math.min(jump, mmStep);
}
}
//Return the answer
return mmStep;
}
/* Function to find the mimimum
energy to reach last stair*/
public int frogJump(int[] heights, int k) {
int ind = heights.length - 1;
//Return the mimimum energy
return func(ind, heights, k);
}
public static void main(String[] args) {
int[] heights = {15, 4, 1, 14, 15};
int k = 3;
// Create an instance of Solution class
Solution sol = new Solution();
// Print the answer
System.out.println("Minimum energy: " + sol.frogJump(heights, k));
}
}
class Solution:
#Helper function for recursion
def func(self, ind, heights, k):
#Base case
if ind == 0:
return 0
#Initialize mmStep to INT_MAX
mmStep = float('inf')
#Try all possible steps
for j in range(1, k + 1):
#Check if ind-j is non negative
if ind - j >= 0:
jump = self.func(ind - j, heights, k) + abs(heights[ind] - heights[ind - j])
#Update the mimimum energy
mmStep = min(mmStep, jump)
#Return the answer
return mmStep
"""Function to find the mimimum
energy to reach last stair"""
def frogJump(self, heights, k):
ind = len(heights) - 1
#Return the mimimum energy
return self.func(ind, heights, k)
if __name__ == "__main__":
heights = [15, 4, 1, 14, 15]
k = 3
# Create an instance of Solution class
sol = Solution()
# Print the answer
print("Minimum energy:", sol.frogJump(heights, k))
class Solution {
//Helper function for recursion
func(ind, heights, k) {
//Base case
if (ind === 0) {
return 0;
}
//Initialize mmStep to INT_MAX
let mmStep = Infinity;
//Try all possible steps
for (let j = 1; j <= k; j++) {
//Check if ind-j is non negative
if (ind - j >= 0) {
let jump = this.func(ind - j, heights, k) + Math.abs(heights[ind] - heights[ind - j]);
//Update the mimimum energy
mmStep = Math.min(jump, mmStep);
}
}
//Return the answer
return mmStep;
}
/* Function to find the mimimum
energy to reach last stair*/
frogJump(heights, k) {
let ind = heights.length - 1;
//Return the mimimum energy
return this.func(ind, heights, k);
}
}
let heights = [15, 4, 1, 14, 15];
let k = 3;
// Create an instance of Solution class
let sol = new Solution();
// Print the answer
console.log("Minimum energy:", sol.frogJump(heights, k));
Memoization tends to store the value of subproblems in some map/ table. As, here we have only one parameter in recursive function, so we can create an 1D array to store the values of subproblems.
Steps to convert Recursive code to memoization solution:
#include <bits/stdc++.h>
using namespace std;
class Solution {
private:
// Function to recursion with memoization
int func(int ind, vector<int> &heights, int k, vector<int>& dp){
//Base case
if(ind == 0){
return 0;
}
/* If the result for this index has
been previously calculated, return it */
if (dp[ind] != -1) return dp[ind];
//Initialize mmStep to INT_MAX
int mmStep = INT_MAX;
//Try all possible steps
for(int j = 1; j <= k; j++){
//Check if ind-j is non negative
if(ind-j >= 0){
int jump = func(ind-j, heights, k, dp) + abs(heights[ind] - heights[ind-j]);
//Update the mimimum energy
mmStep = min(jump, mmStep);
}
}
//Return the answer
return dp[ind] = mmStep;
}
public:
/* Function to find the mimimum
energy to reach last stair*/
int frogJump(vector<int> &heights, int k) {
int ind = heights.size()-1;
/*Initialize a memoization array
to store calculated results*/
vector<int> dp(ind+1, -1);
//Return the mimimum energy
return func(ind, heights, k, dp);
}
};
int main() {
vector<int> height{15, 4, 1, 14, 15};
int k = 3;
// Create an instance of Solution class
Solution sol;
// Print the answer
cout << "Minimum energy : " << sol.frogJump(height, k) << endl;
return 0;
}
import java.util.*;
class Solution {
// Helper function for recursion with memoization
private int func(int ind, int[] heights, int k, int[] dp) {
// Base case
if (ind == 0) {
return 0;
}
/* If the result for this index has been
previously calculated, return it*/
if (dp[ind] != -1) {
return dp[ind];
}
// Initialize mmStep to Integer.MAX_VALUE
int mmStep = Integer.MAX_VALUE;
// Try all possible jumps
for (int j = 1; j <= k; j++) {
// Check if ind - j is non-negative
if (ind - j >= 0) {
int jump = func(ind - j, heights, k, dp) + Math.abs(heights[ind] - heights[ind - j]);
//Update the minimum energy
mmStep = Math.min(jump, mmStep);
}
}
// Store the result in dp array and return
dp[ind] = mmStep;
return dp[ind];
}
/* Function to find the minimum
energy to reach the last stair*/
public int frogJump(int[] heights, int k) {
int ind = heights.length - 1;
/* Initialize a memoization array
to store calculated results*/
int[] dp = new int[ind + 1];
Arrays.fill(dp, -1);
// Return the minimum energy
return func(ind, heights, k, dp);
}
public static void main(String[] args) {
int[] heights = {15, 4, 1, 14, 15};
int k = 3;
// Create an instance of Solution class
Solution sol = new Solution();
// Print the answer
System.out.println("Minimum energy: " + sol.frogJump(heights, k));
}
}
class Solution:
#Function for recusion using memoization
def func(self, ind, heights, k, dp):
# Base case
if ind == 0:
return 0
""" If the result for this index has been
previously calculated, return it"""
if dp[ind] != -1:
return dp[ind]
# Initialize mmStep to float('inf')
mmStep = float('inf')
# Try all possible jumps
for j in range(1, k + 1):
# Check if ind - j is non-negative
if ind - j >= 0:
jump = self.func(ind - j, heights, k, dp) + abs(heights[ind] - heights[ind - j])
#Update the minimum energy
mmStep = min(mmStep, jump)
# Store the result in dp array and return
dp[ind] = mmStep
return dp[ind]
def frogJump(self, heights, k):
ind = len(heights) - 1
""" Initialize a memoization array
to store calculated results"""
dp = [-1] * (ind + 1)
# Return the minimum energy
return self.func(ind, heights, k, dp)
if __name__ == "__main__":
heights = [15, 4, 1, 14, 15]
k = 3
# Create an instance of Solution class
sol = Solution()
# Print the answer
print("Minimum energy:", sol.frogJump(heights, k))
class Solution {
// Helper function for recursion with memoization
func(ind, heights, k, dp) {
// Base case
if (ind === 0) {
return 0;
}
/* If the result for this index has been
previously calculated, return it*/
if (dp[ind] !== -1) {
return dp[ind];
}
// Initialize mmStep to Infinity
let mmStep = Infinity;
// Try all possible jumps
for (let j = 1; j <= k; j++) {
// Check if ind - j is non-negative
if (ind - j >= 0) {
let jump = this.func(ind - j, heights, k, dp) + Math.abs(heights[ind] - heights[ind - j]);
//Update the minimum energy
mmStep = Math.min(jump, mmStep);
}
}
// Store the result in dp array and return
dp[ind] = mmStep;
return dp[ind];
}
/* Function to find the minimum
energy to reach the last stair*/
frogJump(heights, k) {
let ind = heights.length - 1;
/* Initialize a memoization array
to store calculated results*/
let dp = new Array(ind + 1).fill(-1);
// Return the minimum energy
return this.func(ind, heights, k, dp);
}
}
let heights = [15, 4, 1, 14, 15];
let k = 3;
// Create an instance of Solution class
let sol = new Solution();
// Print the answer
console.log("Minimum energy:", sol.frogJump(heights, k));
In order to convert a recursive code to tabulation code, we try to convert the recursive code to tabulation and here are the steps:
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
/* Function to find the mimimum
energy to reach last stair*/
int frogJump(vector<int> &heights, int k) {
int ind = heights.size();
/*Initialize a memoization array
to store calculated results*/
vector<int> dp(ind + 1, -1);
dp[0] = 0;
// Loop through the array
for (int i = 1; i < ind; i++) {
int mmSteps = INT_MAX;
// Loop to try all possible jumps from '1' to 'k'
for (int j = 1; j <= k; j++) {
if (i - j >= 0) {
int jump = dp[i - j] + abs(heights[i] - heights[i - j]);
//Update the Minimum energy
mmSteps = min(jump, mmSteps);
}
}
dp[i] = mmSteps;
}
//Result is stored in the last element of dp
return dp[ind - 1];
}
};
int main() {
vector<int> height {15,4,1,14,15};
int k = 3;
// Create an instance of Solution class
Solution sol;
// Print the answer
cout << "Minimum energy : " << sol.frogJump(height, k) << endl;
return 0;
}
import java.util.*;
class Solution {
/* Function to find the minimum
energy to reach the last stair*/
public int frogJump(int[] heights, int k) {
int ind = heights.length;
/* Initialize a memoization array
to store calculated results*/
int[] dp = new int[ind + 1];
Arrays.fill(dp, -1);
dp[0] = 0;
// Loop through the array
for (int i = 1; i < ind; i++) {
int mmSteps = Integer.MAX_VALUE;
// Loop to try all possible jumps from 1 to k
for (int j = 1; j <= k; j++) {
if (i - j >= 0) {
int jump = dp[i - j] + Math.abs(heights[i] - heights[i - j]);
//Update the minimum energy
mmSteps = Math.min(jump, mmSteps);
}
}
dp[i] = mmSteps;
}
// Result is stored in the last element of dp
return dp[ind - 1];
}
public static void main(String[] args) {
int[] heights = {15, 4, 1, 14, 15};
int k = 3;
// Create an instance of Solution class
Solution sol = new Solution();
// Print the answer
System.out.println("Minimum energy: " + sol.frogJump(heights, k));
}
}
class Solution:
def frogJump(self, heights, k):
ind = len(heights)
""" Initialize a memoization array
to store calculated results"""
dp = [-1] * (ind + 1)
dp[0] = 0
# Loop through the array
for i in range(1, ind):
mmSteps = float('inf')
# Loop to try all possible jumps from 1 to k
for j in range(1, k + 1):
if i - j >= 0:
jump = dp[i - j] + abs(heights[i] - heights[i - j])
#Update the minimum energy
mmSteps = min(jump, mmSteps)
dp[i] = mmSteps
# Result is stored in the last element of dp
return dp[ind - 1]
if __name__ == "__main__":
heights = [15, 4, 1, 14, 15]
k = 3
# Create an instance of Solution class
sol = Solution()
# Print the answer
print("Minimum energy:", sol.frogJump(heights, k))
class Solution {
/* Function to find the minimum
energy to reach the last stair*/
frogJump(heights, k) {
let ind = heights.length;
/* Initialize a memoization array
to store calculated results*/
let dp = new Array(ind + 1).fill(-1);
dp[0] = 0; // Base case
// Loop through the array
for (let i = 1; i < ind; i++) {
let mmSteps = Infinity;
// Loop to try all possible jumps from 1 to k
for (let j = 1; j <= k; j++) {
if (i - j >= 0) {
let jump = dp[i - j] + Math.abs(heights[i] - heights[i - j]);
mmSteps = Math.min(jump, mmSteps);
}
}
dp[i] = mmSteps;
}
// Result is stored in the last element of dp
return dp[ind - 1];
}
}
let heights = [15, 4, 1, 14, 15];
let k = 3;
// Create an instance of Solution class
let sol = new Solution();
// Print the answer
console.log("Minimum energy:", sol.frogJump(heights, k));
If we observe the recurrence relation, we notice that to calculate the current value at index i, we only need the previous last k values, where k is the number of steps the frog can jump. So, we can use a list data structure and store the last k values and in each iteration we will delete the last value and add the current value to the list. As the current value will be coming under the last k values for (i+1)th index element.
By doing so we can optimize the space complexity of the tabulation approach which was O(N) to O(k).
Q: Why is the recurrence dp[i] = min(dp[j] + |heights[i] - heights[j]|) necessary?
A: Since the frog can jump anywhere within [i-k, i-1], we need to evaluate all possible jumps and pick the one that requires the least energy.
Q: How does this problem change if k becomes dynamic (i.e., k changes at every step)?
A: The recurrence must be modified so that at every step i, the allowed jump range is [i+1, i+k[i]], where k[i] is an array representing the allowed jump size at step i.
Q: What if there was an additional constraint that the frog had a limited energy budget?
A: Instead of finding the minimum energy required, we must check if dp[n-1] <= budget, turning it into a bounded DP problem.
Q: Can this problem be solved using a greedy approach instead of DP?
A: No, a greedy approach fails because choosing the lowest energy jump at each step does not guarantee the global minimum.