A robber is targeting to rob houses from a street. Each house has security measures that alert the police when two adjacent houses are robbed. The houses are arranged in a circular manner, thus the first and last houses are adjacent to each other.
Given an integer array money, where money[i] represents the amount of money that can be looted from the (i+1)th house. Return the maximum amount of money that the robber can loot without alerting the police.
Input: money = [2, 1, 4, 9]
Output: 10
Explanation: [2, 1, 4, 9] The underlined houses would give the maximum loot.
Note that we cannot loot the 1st and 4th houses together.
Input: money = [1, 5, 2, 1, 6]
Output: 11
Explanation: [1, 5, 2, 1, 6] The underlined houses would give the maximum loot.
Input: money = [9, 4, 1, 8]
Here, the problem is asking for maximum money the theif can rob, so based on the discussion in previous articles, it is obvious that we think of recursion to solve this problem.
This problem can be tackled using the method outlined in the article on Maximum Sum of non-adjacent elements. It is strongly recommended that readers review that article before proceeding with this one.
In previous questions, the focus was on finding the maximum sum of non-adjacent elements. As the arrangement was linear, the first and last elements were not adjacent to each other. In this case of a circular street where the first and last houses are adjacent, it is certain that both cannot be included in the answer simultaneously, as they are adjacent to each other.
There are two possible choices that can be made for each index. If we pick an element then, pick = arr[ind] + f(ind-2). The reason we are doing f(ind-2) is because we have picked the current index(ind) element so we need to pick a non-adjacent element so we choose the index ‘ind-2’ instead of ‘ind-1’.
The other chice can be to ignore the current element at index(ind) in our subsequence. So nonPick= 0 + f(ind-1). As we don’t pick the current element, we can consider the adjacent element in the subsequence.
/*It is pseudocode and it is not tied to
any specific programming language*/
f(ind, arr[]){
//base condition
pick = arr[ind] + find(ind-2, arr)
notPick = 0 + f(ind-1, arr)
}
/*It is pseudocode and it is not tied to
any specific programming language*/
f(ind, arr[]){
//base condition
pick = arr[ind] + f(ind-2, arr)
notPick = 0 + f(ind - 1, arr)
return max(pick, notPick)
}
#include <bits/stdc++.h>
using namespace std;
class Solution {
private:
//Function to solve the problem using recursion
int func(int ind, vector<int> &arr){
//Base case
if (ind == 0)
return arr[ind];
if (ind < 0)
return 0;
// Choosing the current element
int pick = arr[ind] + func(ind - 2, arr);
// Skipping the current element
int nonPick = 0 + func(ind - 1, arr);
// Return the maximum
return max(pick, nonPick);
}
public:
//Function to find the maximum money robber can rob
int houseRobber(vector<int>& money) {
int n = money.size();
vector<int> arr1;
vector<int> arr2;
//If array has only one element, return that
if(n==1)
return money[0];
for(int i=0; i<n; i++){
/*Store every element in
arr1 except the last element*/
if(i!=n-1) arr1.push_back(money[i]);
/*Store every element in
arr2 except the first element*/
if(i!=0) arr2.push_back(money[i]);
}
int ans1 = func(arr1.size()-1, arr1);
int ans2 = func(arr2.size()-1, arr2);
//Return the maximum of an1 and ans2
return max(ans1,ans2);
}
};
int main(){
vector<int> arr{1,5,1,2,6};
//Create an instance of Solution class
Solution sol;
//Print the solution
cout<<sol.houseRobber(arr);
}
import java.util.*;
class Solution {
// Recursive function to calculate the maximum money robbed
private int func(int ind, int[] arr) {
// Base cases
if (ind == 0) {
return arr[ind];
}
if (ind < 0) {
return 0;
}
// Choosing the current element
int pick = arr[ind] + func(ind - 2, arr);
// Skipping the current element
int nonPick = 0 + func(ind - 1, arr);
// Return the maximum
return Math.max(pick, nonPick);
}
// Function to find the maximum money robber can rob
public int houseRobber(int[] money) {
int n = money.length;
int[] arr1, arr2;
// If array has only one element, return that
if (n == 1)
return money[0];
// Create arr1 (excluding last element)
arr1 = new int[n - 1];
for (int i = 0; i < n - 1; i++) {
arr1[i] = money[i];
}
// Create arr2 (excluding first element)
arr2 = new int[n - 1];
for (int i = 1; i < n; i++) {
arr2[i - 1] = money[i];
}
int ans1 = func(arr1.length - 1, arr1);
int ans2 = func(arr2.length - 1, arr2);
// Return the maximum of ans1 and ans2
return Math.max(ans1, ans2);
}
public static void main(String[] args) {
int[] arr = {1, 5, 1, 2, 6};
// Create an instance of Solution class
Solution sol = new Solution();
// Print the solution
System.out.println(sol.houseRobber(arr));
}
}
class Solution:
#Function to solve the problem using recursion
def func(self, ind, arr):
# Base cases
if ind == 0:
return arr[ind]
if ind < 0:
return 0
# Choosing the current element
pick = arr[ind] + self.func(ind - 2, arr)
# Skipping the current element
nonPick = 0 + self.func(ind - 1, arr)
# Return the maximum
return max(pick, nonPick)
def houseRobber(self, money):
n = len(money)
arr1 = []
arr2 = []
# If array has only one element, return that
if n == 1:
return money[0]
for i in range(n):
""" Store every element in
arr1 except the last element"""
if i != n - 1:
arr1.append(money[i])
""" Store every element in
arr2 except the first element"""
if i != 0:
arr2.append(money[i])
ans1 = self.func(len(arr1) - 1, arr1)
ans2 = self.func(len(arr2) - 1, arr2)
# Return the maximum of ans1 and ans2
return max(ans1, ans2)
arr = [1, 5, 1, 2, 6]
#Create an instance of Solution class
sol = Solution()
print(sol.houseRobber(arr))
class Solution {
//Function to solve the problem using recursion
func(ind, arr) {
// Base cases
if (ind === 0) {
return arr[ind];
}
if (ind < 0) {
return 0;
}
// Choosing the current element
let pick = arr[ind] + this.func(ind - 2, arr);
// Skipping the current element
let nonPick = 0 + this.func(ind - 1, arr);
// Return the maximum
return Math.max(pick, nonPick);
}
houseRobber(money) {
let n = money.length;
let arr1 = [];
let arr2 = [];
// If array has only one element, return that
if (n === 1) {
return money[0];
}
for (let i = 0; i < n; i++) {
/* Store every element in
arr1 except the last element*/
if (i !== n - 1) {
arr1.push(money[i]);
}
/* Store every element in arr2
except the first element*/
if (i !== 0) {
arr2.push(money[i]);
}
}
let ans1 = this.func(arr1.length - 1, arr1);
let ans2 = this.func(arr2.length - 1, arr2);
// Return the maximum of ans1 and ans2
return Math.max(ans1, ans2);
}
}
let arr = [1, 5, 1, 2, 6];
//Create an instance of Solution class
let sol = new Solution();
console.log(sol.houseRobber(arr));
If we observe the recursion tree, we will observe a number of overlapping subproblems. Therefore the recursive solution can be memoized to reduce the time complexity.
#include <bits/stdc++.h>
using namespace std;
class Solution {
private:
//Function to solve the problem using recursion
int func(int ind, vector<int> &arr, vector<int> &dp){
//Base case
if (ind == 0)
return arr[ind];
if (ind < 0)
return 0;
if(dp[ind] != -1){
return dp[ind];
}
// Choosing the current element
int pick = arr[ind] + func(ind - 2, arr, dp);
// Skipping the current element
int nonPick = 0 + func(ind - 1, arr, dp);
/* Store the result in dp
array and return the maximum */
return dp[ind] = max(pick, nonPick);
}
public:
//Function to find the maximum money robber can rob
int houseRobber(vector<int>& money) {
int n = money.size();
vector<int> arr1;
vector<int> arr2;
//If array has only one element, return that
if(n==1)
return money[0];
for(int i=0; i<n; i++){
/*Store every element in
arr1 except the last element*/
if(i!=n-1) arr1.push_back(money[i]);
/*Store every element in
arr2 except the first element*/
if(i!=0) arr2.push_back(money[i]);
}
//Initialize the dp array with -1
vector<int> dp(n, -1);
int ans1 = func(arr1.size()-1, arr1, dp);
vector<int> dp1(n, -1);
int ans2 = func(arr2.size()-1, arr2, dp1);
//Return the maximum of an1 and ans2
return max(ans1,ans2);
}
};
int main(){
vector<int> arr{1,5,1,2,6};
//Create an instance of Solution class
Solution sol;
//Print the solution
cout<<sol.houseRobber(arr);
}
import java.util.*;
class Solution {
// Function to solve the problem using memoization
private int func(int ind, int[] arr, int[] dp) {
// Base cases
if (ind == 0)
return arr[ind];
if (ind < 0)
return 0;
// If dp[ind] already has a value, return it
if (dp[ind] != -1) {
return dp[ind];
}
// Choosing the current element
int pick = arr[ind] + func(ind - 2, arr, dp);
// Skipping the current element
int nonPick = func(ind - 1, arr, dp);
/* Store the result in dp
array and return the maximum*/
return dp[ind] = Math.max(pick, nonPick);
}
// Function to find the maximum money robber can rob
public int houseRobber(int[] money) {
int n = money.length;
if (n == 0)
return 0;
if (n == 1)
return money[0];
int[] arr1 = new int[n - 1];
int[] arr2 = new int[n - 1];
// Populate arr1 and arr2
for (int i = 0; i < n; i++) {
if (i != n - 1)
arr1[i] = money[i];
if (i != 0)
arr2[i - 1] = money[i];
}
// Initialize the dp arrays with -1
int[] dp1 = new int[n];
Arrays.fill(dp1, -1);
int ans1 = func(arr1.length - 1, arr1, dp1);
int[] dp2 = new int[n];
Arrays.fill(dp2, -1);
int ans2 = func(arr2.length - 1, arr2, dp2);
// Return the maximum of ans1 and ans2
return Math.max(ans1, ans2);
}
public static void main(String[] args) {
int[] arr = {1, 5, 1, 2, 6};
//Create an instance of Solution class
Solution sol = new Solution();
System.out.println(sol.houseRobber(arr));
}
}
class Solution:
#Function to solve the problem using memoization
def func(self, ind, arr, dp):
# Base cases
if ind == 0:
return arr[ind]
if ind < 0:
return 0
# If dp[ind] already has a value, return it
if dp[ind] != -1:
return dp[ind]
# Choosing the current element
pick = arr[ind] + self.func(ind - 2, arr, dp)
# Skipping the current element
nonPick = self.func(ind - 1, arr, dp)
""" Store the result in dp
array and return the maximum"""
dp[ind] = max(pick, nonPick)
return dp[ind]
def houseRobber(self, money):
n = len(money)
if n == 0:
return 0
if n == 1:
return money[0]
# Exclude last element
arr1 = money[:-1]
# Exclude first element
arr2 = money[1:]
# Initialize the dp arrays with -1
dp1 = [-1] * n
ans1 = self.func(len(arr1) - 1, arr1, dp1)
dp2 = [-1] * n
ans2 = self.func(len(arr2) - 1, arr2, dp2)
# Return the maximum of ans1 and ans2
return max(ans1, ans2)
arr = [1, 5, 1, 2, 6]
#Create an instance of Solution class
sol = Solution()
print(sol.houseRobber(arr))
class Solution {
/* Function to solve the problem
using with memoization*/
func(ind, arr, dp) {
// Base cases
if (ind === 0)
return arr[ind];
if (ind < 0)
return 0;
// If dp[ind] already has a value, return it
if (dp[ind] !== -1) {
return dp[ind];
}
// Choosing the current element
let pick = arr[ind] + this.func(ind - 2, arr, dp);
// Skipping the current element
let nonPick = this.func(ind - 1, arr, dp);
/* Store the result in dp
array and return the maximum*/
dp[ind] = Math.max(pick, nonPick);
return dp[ind];
}
// Function to find the maximum money robber can rob
houseRobber(money) {
let n = money.length;
if (n === 0)
return 0;
if (n === 1)
return money[0];
// Exclude last element
let arr1 = money.slice(0, n - 1);
// Exclude first element
let arr2 = money.slice(1);
// Initialize the dp arrays with -1
let dp1 = new Array(n).fill(-1);
let ans1 = this.func(arr1.length - 1, arr1, dp1);
let dp2 = new Array(n).fill(-1);
let ans2 = this.func(arr2.length - 1, arr2, dp2);
// Return the maximum of ans1 and ans2
return Math.max(ans1, ans2);
}
}
let arr = [1, 5, 1, 2, 6];
//Create an instance of Solution class
let sol = new Solution();
console.log(sol.houseRobber(arr));
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 {
private:
//Function to solve the problem using tabulation
int func(vector<int> &nums){
int ind = nums.size();
vector<int> dp(ind, 0);
// Base case
dp[0] = nums[0];
// Iterate through the elements of the array
for (int i = 1; i < ind; i++) {
/* Calculate maximum value by either picking
the current element or not picking it*/
int pick = nums[i];
if (i > 1)
pick += dp[i - 2];
int nonPick = dp[i - 1];
// Store the maximum value in dp array
dp[i] = max(pick, nonPick);
}
/* The last element of the dp array
will contain the maximum sum*/
return dp[ind-1];
}
public:
//Function to find the maximum money robber can rob
int houseRobber(vector<int>& money) {
int n = money.size();
vector<int> arr1;
vector<int> arr2;
//If array has only one element, return that
if(n==1)
return money[0];
for(int i=0; i<n; i++){
/*Store every element in
arr1 except the last element*/
if(i!=n-1) arr1.push_back(money[i]);
/*Store every element in
arr2 except the first element*/
if(i!=0) arr2.push_back(money[i]);
}
int ans1 = func(arr1);
int ans2 = func(arr2);
//Return the maximum of an1 and ans2
return max(ans1,ans2);
}
};
int main(){
vector<int> arr{1,5,1,2,6};
//Create an instance of Solution class
Solution sol;
//Print the solution
cout<<sol.houseRobber(arr);
}
import java.util.*;
class Solution {
// Function to solve the problem using memoization
private int func(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
// Base case
dp[0] = nums[0];
// Iterate through the elements of the array
for (int i = 1; i < n; i++) {
/* Calculate maximum value by either picking
the current element or not picking it */
int pick = nums[i];
if (i > 1)
pick += dp[i - 2];
int nonPick = dp[i - 1];
// Store the maximum value in dp array
dp[i] = Math.max(pick, nonPick);
}
/* The last element of the dp array
will contain the maximum sum */
return dp[n - 1];
}
// Function to find the maximum money robber can rob
public int houseRobber(int[] money) {
int n = money.length;
if (n == 0)
return 0;
if (n == 1)
return money[0];
int[] arr1 = new int[n - 1];
int[] arr2 = new int[n - 1];
// Populate arr1 and arr2
for (int i = 0; i < n; i++) {
if (i != n - 1)
arr1[i] = money[i];
if (i != 0)
arr2[i - 1] = money[i];
}
int ans1 = func(arr1);
int ans2 = func(arr2);
// Return the maximum of ans1 and ans2
return Math.max(ans1, ans2);
}
public static void main(String[] args) {
int[] arr = {1, 5, 1, 2, 6};
//Create an instance of Solution class
Solution sol = new Solution();
System.out.println(sol.houseRobber(arr));
}
}
class Solution:
#Function to solve the problem using tabulation
def func(self, nums):
n = len(nums)
dp = [0] * n
# Base case
dp[0] = nums[0]
# Iterate through the elements of the array
for i in range(1, n):
""" Calculate maximum value by either picking
the current element or not picking it"""
pick = nums[i]
if i > 1:
pick += dp[i - 2]
nonPick = dp[i - 1]
# Store the maximum value in dp array
dp[i] = max(pick, nonPick)
""" The last element of the dp array
will contain the maximum sum"""
return dp[-1]
def houseRobber(self, money):
n = len(money)
if n == 0:
return 0
if n == 1:
return money[0]
# Exclude last element
arr1 = money[:-1]
# Exclude first element
arr2 = money[1:]
ans1 = self.func(arr1)
ans2 = self.func(arr2)
# Return the maximum of ans1 and ans2
return max(ans1, ans2)
arr = [1, 5, 1, 2, 6]
#Create an instance of Solution class
sol = Solution()
print(sol.houseRobber(arr))
class Solution {
/* Function to solve the problem
using with memoization*/
func(nums) {
let n = nums.length;
let dp = new Array(n).fill(0);
// Base case
dp[0] = nums[0];
// Iterate through the elements of the array
for (let i = 1; i < n; i++) {
/* Calculate maximum value by either picking
the current element or not picking it*/
let pick = nums[i];
if (i > 1)
pick += dp[i - 2];
let nonPick = dp[i - 1];
// Store the maximum value in dp array
dp[i] = Math.max(pick, nonPick);
}
/* The last element of the dp array
will contain the maximum sum*/
return dp[n - 1];
}
// Function to find the maximum money robber can rob
houseRobber(money) {
let n = money.length;
if (n === 0)
return 0;
if (n === 1)
return money[0];
// Exclude last element
let arr1 = money.slice(0, n - 1);
// Exclude first element
let arr2 = money.slice(1);
let ans1 = this.func(arr1);
let ans2 = this.func(arr2);
// Return the maximum of ans1 and ans2
return Math.max(ans1, ans2);
}
}
let arr = [1, 5, 1, 2, 6];
//Create an instance of Solution class
let sol = new Solution();
console.log(sol.houseRobber(arr));
If we do some observation, we find that the values required at every iteration for dp[i] are dp[i-1] and dp[i-2]. we see that for any i, we do need only the last two values in the array. So is there is no need to maintain a whole array for it.
Let us call dp[i-1] as prev and dp[i-2] as prev2. Now understand the following illustration:
#include <bits/stdc++.h>
using namespace std;
class Solution {
private:
//Function to solve the problem using tabulation
int func(vector<int> &nums){
int n = nums.size();
int prev = nums[0];
int prev2 = 0;
for (int i = 1; i < n; i++) {
// Maximum sum if we pick current element
int pick = nums[i];
if (i > 1){
//Add the maximum sum two elements ago
pick += prev2;
}
// Maximum sum if we don't pick current element
int nonPick = 0 + prev;
// Maximum at the current element
int cur_i = max(pick, nonPick);
prev2 = prev;
prev = cur_i;
}
// Return the maximum sum
return prev;
}
public:
//Function to find the maximum money robber can rob
int houseRobber(vector<int>& money) {
int n = money.size();
vector<int> arr1;
vector<int> arr2;
//If array has only one element, return that
if(n==1)
return money[0];
for(int i=0; i<n; i++){
/*Store every element in
arr1 except the last element*/
if(i!=n-1) arr1.push_back(money[i]);
/*Store every element in
arr2 except the first element*/
if(i!=0) arr2.push_back(money[i]);
}
int ans1 = func(arr1);
int ans2 = func(arr2);
//Return the maximum of an1 and ans2
return max(ans1,ans2);
}
};
int main(){
vector<int> arr{1,5,1,2,6};
//Create an instance of Solution class
Solution sol;
//Print the solution
cout<<sol.houseRobber(arr);
}
import java.util.*;
class Solution {
// Function to solve the problem using memoization
private int func(int[] nums) {
int n = nums.length;
int prev = nums[0];
int prev2 = 0;
for (int i = 1; i < n; i++) {
// Maximum sum if we pick current element
int pick = nums[i];
if (i > 1) {
// Add the maximum sum two elements ago
pick += prev2;
}
// Maximum sum if we don't pick current element
int nonPick = 0 + prev;
// Maximum at the current element
int cur_i = Math.max(pick, nonPick);
prev2 = prev;
prev = cur_i;
}
// Return the maximum sum
return prev;
}
// Function to find the maximum money robber can rob
public int houseRobber(int[] money) {
int n = money.length;
if (n == 0)
return 0;
if (n == 1)
return money[0];
int[] arr1 = new int[n - 1];
int[] arr2 = new int[n - 1];
// Populate arr1 and arr2
for (int i = 0; i < n; i++) {
if (i != n - 1)
arr1[i] = money[i];
if (i != 0)
arr2[i - 1] = money[i];
}
int ans1 = func(arr1);
int ans2 = func(arr2);
// Return the maximum of ans1 and ans2
return Math.max(ans1, ans2);
}
public static void main(String[] args) {
int[] arr = {1, 5, 1, 2, 6};
//Create an instance of Solution class
Solution sol = new Solution();
System.out.println(sol.houseRobber(arr));
}
}
class Solution:
#Function to solve the problem using tabulation
def func(self, nums):
n = len(nums)
prev = nums[0]
prev2 = 0
for i in range(1, n):
# Maximum sum if we pick current element
pick = nums[i]
if i > 1:
# Add the maximum sum two elements ago
pick += prev2
# Maximum sum if we don't pick current element
nonPick = 0 + prev
# Maximum at the current element
cur_i = max(pick, nonPick)
prev2 = prev
prev = cur_i
# Return the maximum sum
return prev
def houseRobber(self, money):
n = len(money)
if n == 0:
return 0
if n == 1:
return money[0]
# Exclude last element
arr1 = money[:-1]
# Exclude first element
arr2 = money[1:]
ans1 = self.func(arr1)
ans2 = self.func(arr2)
# Return the maximum of ans1 and ans2
return max(ans1, ans2)
arr = [1, 5, 1, 2, 6]
#Create an instance of Solution class
sol = Solution()
print(sol.houseRobber(arr))
class Solution {
/* Function to solve the problem
using with memoization*/
func(nums) {
let n = nums.length;
let prev = nums[0];
let prev2 = 0;
for (let i = 1; i < n; i++) {
// Maximum sum if we pick current element
let pick = nums[i];
if (i > 1) {
// Add the maximum sum two elements ago
pick += prev2;
}
// Maximum sum if we don't pick current element
let nonPick = 0 + prev;
// Maximum at the current element
let cur_i = Math.max(pick, nonPick);
prev2 = prev;
prev = cur_i;
}
// Return the maximum sum
return prev;
}
// Function to find the maximum money robber can rob
houseRobber(money) {
let n = money.length;
if (n === 0)
return 0;
if (n === 1)
return money[0];
// Exclude last element
let arr1 = money.slice(0, n - 1);
// Exclude first element
let arr2 = money.slice(1);
let ans1 = this.func(arr1);
let ans2 = this.func(arr2);
// Return the maximum of ans1 and ans2
return Math.max(ans1, ans2);
}
}
let arr = [1, 5, 1, 2, 6];
//Create an instance of Solution class
let sol = new Solution();
console.log(sol.houseRobber(arr));
Q: Why do we need to consider two cases (exclude first and exclude last)?
A: If we allow robbing all houses normally, the robber could pick both first and last houses, violating the circular constraint.
Q: What happens if all houses contain the same value?
A: The robber picks alternate houses for the highest total sum, ensuring no two adjacent houses are robbed.
Q: What if the robber could skip at most k houses instead of 1?
A: The recurrence relation changes to: dp[i]=max(dp[i−1],dp[i−2]+money[i],…,dp[i−k]+money[i]) A sliding window approach optimizes this to O(n).
Q: How can we optimize this for extremely large inputs (n > 10^6)?
A: Instead of a dp[] array (O(n) space), use two rolling variables (O(1)) to store only the last two computed values.