Given an integer array nums of size n. Return the maximum sum possible using the elements of nums such that no two elements taken are adjacent in nums.
A subsequence of array is a sequence that can be derived from the array by deleting some or no elements without changing the order of the remaining elements.
Input: nums = [1, 2, 4]
Output: 5
Explanation: [1, 2, 4], the underlined elements are taken to get the maximum sum.
Input: nums = [2, 1, 4, 9]
Output: 11
Explanation: [2, 1, 4, 9], the underlined elements are taken to get the maximum sum.
Input: nums = [1, 7, 16, 8]
As discussed in the previous problems that rercursion can be used when the problem is asking for minimum/maximum of something. Here in this problem it is asking for maximum sum of subsequences, one approach that comes to our mind is to generate all subsequences and pick the one with the maximum sum.
To generate all the subsequences, we can use the pick/non-pick technique. This technique can be briefly explained as follows:
Now, we will try to form the recursive solution to the problem with the pick/non-pick technique. There is one more catch, the problem wants us to have only non-adjacent elements of the array in the subsequence, therefore we need to address that too. In order to do that, while moving ahead, we can keep a track if we took the last index or not.
/*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 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 max(pick, nonPick);
}
public:
/*Function to calculate the maximum
sum of nonAdjacent elements d*/
int nonAdjacent(vector<int>& nums) {
int ind = nums.size()-1;
//Return the maximum sum
return func(ind, nums);
}
};
int main() {
vector<int> arr{2, 1, 4, 9};
//Create an instance of Solution class
Solution sol;
// Call the solve function and print the result
cout << sol.nonAdjacent(arr);
return 0;
}
import java.util.*;
class Solution {
// Function to solve the problem using recursion
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 calculate the maximum
sum of non-adjacent elements*/
public int nonAdjacent(int[] nums) {
int ind = nums.length - 1;
// Return the maximum sum
return func(ind, nums);
}
public static void main(String[] args) {
int[] arr = {2, 1, 4, 9};
// Create an instance of Solution class
Solution sol = new Solution();
// Call the nonAdjacent function and print the result
System.out.println(sol.nonAdjacent(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)
""" Function to calculate the maximum
sum of non-adjacent elements"""
def nonAdjacent(self, nums):
ind = len(nums) - 1
# Return the maximum sum
return self.func(ind, nums)
if __name__ == "__main__":
arr = [2, 1, 4, 9]
# Create an instance of Solution class
sol = Solution()
# Call the nonAdjacent function and print the result
print(sol.nonAdjacent(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);
}
/* Function to calculate the maximum
sum of non-adjacent elements */
nonAdjacent(nums) {
let ind = nums.length - 1;
// Return the maximum sum
return this.func(ind, nums);
}
}
let arr = [2, 1, 4, 9];
// Create an instance of Solution class
let sol = new Solution();
// Call the nonAdjacent function and print the result
console.log(sol.nonAdjacent(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.
Steps to convert Recursive code to memoization solution:
#include <bits/stdc++.h>
using namespace std;
class Solution{
private:
// Function to solve the problem using memoization
int func(int ind, vector<int> &arr, vector<int> &dp) {
// Base cases
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 calculate the maximum
sum of nonAdjacent elements d*/
int nonAdjacent(vector<int>& nums) {
int ind = nums.size()-1;
//Initialize the dp array with -1
vector<int> dp(ind+1, -1);
//Return the maximum sum
return func(ind, nums, dp);
}
};
int main() {
vector<int> arr{2, 1, 4, 9};
//Create an instance of Solution class
Solution sol;
// Call the solve function and print the result
cout << sol.nonAdjacent(arr);
return 0;
}
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;
/* Check if the result for 'ind'
has already been computed*/
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 calculate the maximum
sum of non-adjacent elements */
public int nonAdjacent(int[] nums) {
int ind = nums.length - 1;
// Initialize the dp array with -1
int[] dp = new int[ind + 1];
Arrays.fill(dp, -1);
// Return the maximum sum
return func(ind, nums, dp);
}
public static void main(String[] args) {
int[] arr = {2, 1, 4, 9};
// Create an instance of Solution class
Solution sol = new Solution();
// Call the nonAdjacent function and print the result
System.out.println(sol.nonAdjacent(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
""" Check if the result for 'ind'
has already been computed"""
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]
""" Function to calculate the maximum
sum of non-adjacent elements """
def nonAdjacent(self, nums):
ind = len(nums) - 1
# Initialize the dp array with -1
dp = [-1] * (ind + 1)
# Return the maximum sum
return self.func(ind, nums, dp)
if __name__ == "__main__":
arr = [2, 1, 4, 9]
# Create an instance of Solution class
sol = Solution()
# Call the nonAdjacent function and print the result
print(sol.nonAdjacent(arr))
class Solution {
// Function to solve the problem using memoization
func(ind, arr, dp) {
// Base cases
if (ind === 0) {
return arr[ind];
}
if (ind < 0) {
return 0;
}
/* Check if the result for 'ind'
has already been computed */
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 calculate the maximum
sum of non-adjacent elements*/
nonAdjacent(nums) {
let ind = nums.length - 1;
// Initialize the dp array with -1
let dp = new Array(ind + 1).fill(-1);
// Return the maximum sum
return this.func(ind, nums, dp);
}
}
let arr = [2, 1, 4, 9];
// Create an instance of Solution class
let sol = new Solution();
// Call the nonAdjacent function and print the result
console.log(sol.nonAdjacent(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 {
public:
/*Function to calculate the maximum
sum of nonAdjacent elements*/
int nonAdjacent(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];
}
};
int main() {
vector<int> arr {2,1,4,9};
//Create an instance of Solution class
Solution sol;
// Call the solve function and print the result
cout << sol.nonAdjacent(arr);
return 0;
}
import java.util.*;
class Solution {
/* Function to calculate the maximum
sum of nonAdjacent elements */
public int nonAdjacent(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];
}
public static void main(String[] args) {
int[] arr = {2, 1, 4, 9};
// Create an instance of Solution class
Solution sol = new Solution();
// Call the solve function and print the result
System.out.println(sol.nonAdjacent(arr));
}
}
class Solution:
""" Function to calculate the maximum
sum of nonAdjacent elements """
def nonAdjacent(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]
arr = [2, 1, 4, 9]
#Create an instance of Solution class
sol = Solution()
#Print the answer
print(sol.nonAdjacent(arr))
class Solution {
/* Function to calculate the maximum
sum of nonAdjacent elements*/
nonAdjacent(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];
}
}
let arr = [2, 1, 4, 9];
//Create an instance of Solution class
let sol = new Solution();
//Print the answer
console.log(sol.nonAdjacent(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 {
public:
/*Function to calculate the maximum
sum of nonAdjacent elements*/
int nonAdjacent(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;
}
};
int main() {
vector<int> arr {2,1,4,9};
//Create an instance of Solution class
Solution sol;
// Call the solve function and print the result
cout << sol.nonAdjacent(arr);
return 0;
}
import java.util.*;
class Solution {
/* Function to calculate the maximum
sum of nonAdjacent elements*/
public int nonAdjacent(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;
}
public static void main(String[] args) {
int[] arr = {2, 1, 4, 9};
// Create an instance of Solution class
Solution sol = new Solution();
// Call the solve function and print the result
System.out.println(sol.nonAdjacent(arr));
}
}
class Solution:
"""Function to calculate the maximum
sum of nonAdjacent elements"""
def nonAdjacent(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
arr = [2, 1, 4, 9]
#Create an instance of Solution class
sol = Solution()
#Print the answer
print(sol.nonAdjacent(arr))
class Solution {
/* Function to calculate the maximum
sum of nonAdjacent elements */
nonAdjacent(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;
}
}
let arr = [2, 1, 4, 9];
//Create an instance of Solution class
let sol = new Solution();
//Print the solution
console.log(sol.nonAdjacent(arr));
Q: Can we solve this problem using a greedy approach?
A: No, a greedy approach (always taking the largest available element) fails because it doesn’t consider future consequences (e.g., picking a large element might force skipping multiple good elements).
Q: Why is the recurrence relation based on dp[i-1] and dp[i-2]?
A: If we include nums[i], we must take dp[i-2] to ensure no adjacent elements are included. If we exclude nums[i], we take dp[i-1] to get the best sum without nums[i].
Q: What if there was an additional constraint that elements can only be picked if their value is even?
A: Instead of considering all elements, apply the same DP approach only to even numbers.
Q: What if the elements were arranged in a circular array (first and last elements are adjacent)?
A: This modifies the problem into the circular house robber problem, where we solve for two cases: Exclude the first element and solve normally. Exclude the last element and solve normally. Return the maximum of both cases.