House Robber II
Introduction
You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. All houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, adjacent houses have a security system connected, and it will automatically contact the police if two adjacent houses were broken into on the same night.
Given an integer array nums
Example 1
Output: 3
Explanation: You cannot rob house 1 (money = 2) and house 3 (money = 2) as these two are adjascent.
So, the total amount will be 3.
Example 2
Output: 4
Explanation: You can rob house 1 (money = 1), house 3 (money = 3).
So, the total amount will be 4
Example 3
Output: 3
Explanation: You can rob house 3 (money = 3)
So, the total amount will be 3
Note:
This problem is similar to
Contents
Solution intution from previous house robber problem
This problem is a similar to the previous
-
First time using houses from
, i.e. excluding last house, this way if you consider 1st house then you are ignoring last house since they are adjascent to each other.1,......n-1
-
Second time using houses from
, i.e. excluding first house, this way you are considering last indexed house, but ignoring the first house since they are adjascent to each other.2,.....n
Then, finally take the maximum from these two results.
Solution using brute force approach
In this approach, we will apply the solution intution discussed above to the brute force approach, that is to
compute all possible combinations with conditions given in the problem, we will run this process on sub arrays
0...n-1
1....n
-
We will create a function
that takesbruteForceApproach
array as input argument.nums
-
Create another helper function
that takebruteForceApproachRec
andnums
variable to indicate the start index of the sub array we should be considering andstart
variable to indicate the end index of sub array we should be considering.end
- Call this function recursively, by moving start to +2 index considering you want to rob current house and +1 index considering you dont want to rob the current house, and take the maximum out of these two possibilities.
- Base case will be, when it reaches end of array or goes out of bounds as we are trying with +1 and +2 index from every start index, and when start index becomes greater than end index, then we simply return 0.
-
Call
two times. First, withbruteForceApproachRec
, i.e. ignoring last indexed house since we are considering first house. Second, withstart =0, end = nums.length-2
, i.e. ignoring first indexed house since we are considering last house.start =1, end = nums.length--1
public class HouseRobber2 {
/* brute force approach*/
static int bruteForceApproach(int[] nums) {
int one = bruteForceApproachRec(nums, 0, nums.length-2);
int two = bruteForceApproachRec(nums, 1, nums.length-1);
return Math.max(one, two);
}
static int bruteForceApproachRec(int[] nums, int start, int end) {
if(start>=nums.length) {
return 0;
}
if(start > end) {
return 0;
}
return Math.max(nums[start] + bruteForceApproachRec(nums, start+2, end), bruteForceApproachRec(nums, start+1, end));
}
}
Complexity Analysis:
Time complexity: Above code runs in O(2n) time where n
nums
Space complexity: O(n) as we are making recursive loops n
Solution using DP memoization
From the above brute force approach, we can notice that there are some overlapping problems.
-
So, in this approach, we will be caching sub problem result into a
withHashMap
index as key and its result as value. When a sub problem is repeated and found in cache, we will return it, otherwise we will call the function recursively and cache its result.start
import java.util.HashMap;
public class HouseRobber2 {
/* DP memoization*/
static int dpMemoizationApproach(int[] nums) {
HashMap<Integer, Integer> cache = new HashMap<>();
int one = dpMemoizationApproachRec(nums, 0, nums.length-2, cache);
int two = dpMemoizationApproachRec(nums, 1, nums.length-1, cache);
return Math.max(one, two);
}
static int dpMemoizationApproachRec(int[] nums, int start, int end, HashMap<Integer, Integer> cache) {
if(start>=nums.length) {
return 0;
}
if(start > end) {
return 0;
}
if(cache.containsKey(start)) {
return cache.get(start);
}
int result = Math.max(nums[start] + bruteForceApproachRec(nums, start+2, end), bruteForceApproachRec(nums, start+1, end));
cache.put(start, result);
return result;
}
}
Complexity Analysis:
Time complexity: Above code runs in O(n) time where n
nums
Space complexity: O(n) for the cache we are maintaining and stack for recursive calls.
Solution using DP Bottom-Up approach using tabulation
In this approach we will look at solving the problem using Dynamic programming using tabulation caching mechanism.
The time and memory complexity will be same as above solution using memoization,
but we can notice a pattern to improve memory complexity to constant in later solution.
Intution behind this solution is, when we are at ith
dp
Max(nums[i] + dp[i-2], dp[i-1])
dp[i-2]
dp[i-1]
We will repeat this solution for house indexed 0....n-1
1......n
Base cases for this solution will be
-
, because at 0th index, maximum amount you can rob is just that house.dp[0] = nums[0]
-
, because at 0th indexed house has higher amount, you can rob that house and skip index 1 house or rob index 1 house and skip index 0 house.dp[1] = Max(nums[0], nums[1])
Finally, take the last element from dp
Since we need to call this for houses indexed 0.....n-1
1.....n
public class HouseRobber2 {
/* DP tabulation approach */
static int dpTabulationApproach(int[] nums) {
int[] excl_last_house = new int[nums.length -1];
System.arraycopy(nums, 0, excl_last_house,0,nums.length-1);
int max1 = dpTabulationApproachHelper(excl_last_house);
int[] excl_first_house = new int[nums.length-1];
System.arraycopy(nums,1,excl_first_house,0, nums.length-1);
int max2 = dpTabulationApproachHelper(excl_first_house);
return Math.max(max1, max2);
}
static int dpTabulationApproachHelper(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for(int i=2;i<nums.length; i++) {
dp[i] = Math.max(nums[i]+dp[i-2], dp[i-1]);
}
return dp[dp.length-1];
}
}
Complexity Analysis:
Time complexity: Above code runs in O(n) time where n
nums
Space complexity: O(n) since we have a dp
n
Solution using DP Bottom-Up approach memory optimized
If we look at above DP solution using tabulation, when calculating maximum amount you can rob at index i
i-2
i-1
-
In this approach, we will create two variables
andm1
to hold previous values for i-2nd step and i-1st indexed houses.m2
-
We will initialize
andm1 = 0
.m1 = 0
-
Next, for each index
compute the maximum that can be robbed usingi
, store this into a temporary variable, then make this value as newMax(nums[i]+m1, m2)
and current m2 tom2
for next iteration.m1
Since we need to call this for houses indexed 0.....n-1
1.....n
public class HouseRobber2 {
/* DP optimized */
static int dpOptimized(int[] nums) {
int max1 = dpOptimizedHelper(nums, 0, nums.length-2); // excluding the last house
int max2 = dpOptimizedHelper(nums, 1, nums.length-1); // excluding the first house
return Math.max(max1, max2);
}
static int dpOptimizedHelper(int[] nums, int start, int end) {
int m1 = 0;
int m2 = 0;
for(int i=start;i<=end; i++) {
int temp = Math.max(nums[i]+m1, m2);
m1 = m2;
m2 = temp;
}
return m2;
}
}
Complexity Analysis:
Time complexity: Above code runs in O(n) time where n
nums
Space complexity: O(1) since we are only using m1, m2 and a temp variables.
Above implementations source code can be found at