House Robber: From recursion to memoization and ultimately space-optimized solution

A lifelong learner. Love to travel. Listen to music.
Search for a command to run...

A lifelong learner. Love to travel. Listen to music.
No comments yet. Be the first to comment.
Let’s evaluate the Classic Dynamic Programming problem, Fibonacci Number in a crispy way. We will start solving the problem from a recursive solution and later step by step we will use memoization and space optimization. The Fibonacci Number or Fibon...
Best Practices for Path Structure, Versioning, and Error Handling

Understanding the Performance Implications of async and defer Attributes

Overview static: Default position. It does not allow setting properties like top, bottom, left, right, z-index relative: Acts the same as static. but allows positioning it relatively by putting properties like top, bottom, left, right, z-index absolu...

Overview When an element in the browser has a conflict of styles, the browser uses a set of rules to determine which style should be rendered. This set of rules is defined as CSS Selector Specificity. For instance, for an element, if we have the foll...

Master block, inline, flex, and grid to build better web layouts

One of the most popular dynamic programming problems is the famous house robber. If we narrow down the category, it is the pick or not pick problem. Based on the basics of house robber problem, a few more problems are available are out there by adding more layers of complexity.
Today, we will get the most basic problem and solve it step by step. We will start by using the recursion and later on top of the recursion problem, we will use the tabulation of dynamic programming. Finally, we will optimize the space.
The house robber problem is explained, as follows,
There are a few houses in a row and as a robber, I can not rob adjacent houses. This implies, if I rob house 1, I can not rob house 2. After house 1, I have to choose house 3 or 4 or so on. However, If I do not rob house 1, then I may rob house 2.
With this in our mind, the simplest solution is, to write a recursion and try all the possibilities.
Recursion will be the brute force solution, where we will explore all the possibilities and figure out the maximum value. We can consider any of the sequences, from the first to the last index or vice-versa of the array. Here’s a solution, driving from the first to the last index.
function rob(nums: number[]): number {
const helper = (index) => {
if (index >= nums.length) {
return 0;
}
const pick = nums[index] + helper(index + 2);
const notPick = helper(index + 1);
return Math.max(pick, notPick);
}
return helper(0);
};
Now, we can see the sub-problems, where, we may calculate the index multiple times using the function. Instead, to persist the already calculated value, we can consider an array dp of the array size.
function rob(nums: number[]): number {
const dp = new Array(nums.length).fill(-1);
const helper = (index) => {
if (index >= nums.length) {
return 0;
}
if (dp[index] !== -1) {
return dp[index];
}
const pick = nums[index] + helper(index + 2);
const notPick = helper(index + 1);
dp[index] = Math.max(pick, notPick);
return dp[index];
}
return helper(0);
};
Now, if we already calculated the value, we skip the further calculation and get the calculated from the array.
We can calculate the the value from last to the first index as well as follows,
function rob(nums: number[]): number {
const dp = new Array(nums.length).fill(-1);
const helper = (index) => {
if (index === 0) {
return nums[index];
}
if (index < 0) {
return 0;
}
if (dp[index] !== -1) {
return dp[index];
}
const pick = nums[index] + helper(index - 2);
const notPick = helper(index - 1);
dp[index] = Math.max(pick, notPick);
return dp[index];
}
return helper(nums.length - 1);
};
If we observe closely, we can see, the base value of the recursion,
If there is only one house, the result is the first house's value
For the first two houses, the result is the maximum of the first two houses, since we can not take these consecutive two houses
For the rest of the houses, if we pick the house, just can not pick the immediate previous one
Considering these, can calculate the final maximum results as below,
function rob(nums: number[]): number {
const dp = new Array(nums.length).fill(0);
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (let i = 2; i < nums.length; i += 1) {
const take = nums[i] + dp[i - 2];
const notTake = dp[i - 1];
dp[i] = Math.max(take, notTake);
}
return dp[nums.length - 1];
};
From the last solution, it occurs, that the solution of the current index only depends on the previous two values. With this consideration, we can simply avoid the entire O(n) array with a just two variables,
function rob(nums: number[]): number {
let rob1 = 0;
let rob2 = 0;
for (let i = 0; i < nums.length; i += 1) {
const take = rob1 + nums[i];
const notTake = rob2;
rob1 = rob2
rob2 = Math.max(take, notTake)
}
return rob2;
};
This is the most optimized solution with constant space and O(n) runtime complexity.
For practice: https://leetcode.com/problems/house-robber/