A non-empty array nums of positive integers is given. Select a subsequence with no two selected elements at adjacent positions in nums.
Return the maximum possible sum among all valid subsequences.
Example 1
Input: nums = [2, 1, 4, 9]
Output: 11
Explanation: Selecting 2 and 9 produces a valid sum of 11. No other valid selection produces a larger sum.
Example 2
Input: nums = [7]
Output: 7
Explanation: A single element has no adjacent partner, so selecting 7 gives the maximum sum.
Recursion
Each array position offers a small choice: take the value or leave the value. Taking nums[index] blocks nums[index - 1], so the next available prefix ends at index - 2. Skipping nums[index] leaves the prefix ending at index - 1 available.
The state solve(index) stores the best valid sum inside positions 0 through index. The first helper call starts at index n - 1 because the prefix then covers the complete array. Both choices reduce the prefix, so recursion eventually reaches a crossed boundary or a one-element prefix.
Algorithm
Begin with
solve(index)as the maximum valid sum inside positions0throughindex, so every recursive call carries one clear prefix meaning.Return
0afterindexcrosses the left boundary because no array value remains available for selection.Return
nums[0]at index0because a one-element positive prefix has only one best selection.Take
nums[index]together withsolve(index - 2)because selecting the current value makes the adjacent previous position unavailable.Skip
nums[index]throughsolve(index - 1)because the previous prefix then remains completely available.Compare the take and skip sums so the larger valid choice becomes the answer for the current prefix.
Start recursion from
n - 1because the final prefix represents every position in the input array.
Dry Run
Maximum Sum of Non Adjacent Element Recursion
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: // Returns the best sum through a chosen index. int solve(int index, vector<int>& nums) { // A crossed boundary contributes no selected value. if (index < 0) { return 0; } // A one-value prefix has one best selection. if (index == 0) { return nums[0]; } // Taking the value forces one adjacent skip. int take = nums[index] + solve(index - 2, nums); // Skipping the value keeps the previous prefix. int skip = solve(index - 1, nums); // The larger valid choice gives the prefix answer. return max(take, skip); }public: // Returns the maximum valid non-adjacent sum. int maximumNonAdjacentSum(vector<int>& nums) { int n = nums.size(); // The final index represents the complete array. return solve(n - 1, nums); }};// Driver codeint main() { vector<int> nums = {2, 1, 4, 9}; Solution obj; cout << obj.maximumNonAdjacentSum(nums); return 0;}Note: Direct recursion may fail for large input values. Repeated subproblems create exponential work, so an online judge may report Time Limit Exceeded.
Complexity Analysis
Time Complexity: O(2N), where N is the number of indices, because each index can create two recursive choices: take or skip.
Space Complexity: O(N), because the recursion depth can grow to at most N calls and no additional growing data structure is used.
Memoization
Direct recursion reaches the same prefix through different decision paths. For example, solve(1) can appear below both solve(3) and solve(2). Recalculating an already solved prefix adds work without adding new information.
Memoization keeps the recursive take-or-skip idea unchanged and adds an array named dp. A saved dp[index] value answers every later request for the same prefix immediately, reducing the recursion tree to one calculation per index.
Algorithm
Keep
solve(index)as the best valid sum throughindexso the recursive state remains identical to direct recursion.Create a
dparray filled with-1because every valid answer is positive and the sentinel clearly marks an unfinished state.Return the boundary answers before a cache lookup because crossed and one-element prefixes need no stored calculation.
Reuse
dp[index]after a cache hit so repeated paths avoid rebuilding the same recursive subtree.Take
nums[index]withsolve(index - 2)because selecting the current value rules out the adjacent previous position.Skip
nums[index]withsolve(index - 1)because leaving the current value preserves the entire previous prefix.Store the larger branch in
dp[index]so every future request receives the finished prefix answer in constant time.Start with
solve(n - 1)because the last prefix contains the complete input array.
Dry Run
maximum-sum-of-non-adjacent-elements-memoization-dp-minus-one.png
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: // Returns the cached best sum through an index. int solve(int index, vector<int>& nums, vector<int>& dp) { // A crossed boundary contributes no selected value. if (index < 0) { return 0; } // A one-value prefix has one best selection. if (index == 0) { return nums[0]; } // A saved answer avoids rebuilding a subtree. if (dp[index] != -1) { return dp[index]; } // Taking the value forces one adjacent skip. int take = nums[index] + solve(index - 2, nums, dp); // Skipping the value keeps the previous prefix. int skip = solve(index - 1, nums, dp); // Saving the larger choice supports later reuse. dp[index] = max(take, skip); return dp[index]; }public: // Returns the maximum valid non-adjacent sum. int maximumNonAdjacentSum(vector<int>& nums) { int n = nums.size(); // Negative entries mark unfinished prefix states. vector<int> dp(n, -1); // The final index represents the complete array. return solve(n - 1, nums, dp); }};// Driver codeint main() { vector<int> nums = {2, 1, 4, 9}; Solution obj; cout << obj.maximumNonAdjacentSum(nums); return 0;}Complexity Analysis
Time Complexity: O(N), where N is the number of prefix states, because each state is computed once and reused through memoization.
Space Complexity: O(N), because the dp array stores N values and the recursion stack can contain at most N calls.
Tabulation
Memoization proves every state depends only on smaller prefix states. Tabulation fills the same answers from left to right, so each required value already exists before a later prefix is processed.
The meaning stays unchanged: dp[index] stores the best valid sum through index. The first two prefix answers provide a stable starting point, and a loop replaces recursive calls with direct table lookups.
Algorithm
Create an array named
dpwith one entry per input position so every prefix answer has a dedicated location.Store
nums[0]indp[0]because a one-element positive prefix has one best selection.Return
dp[0]for an array of length one because no larger prefix needs construction.Store
max(nums[0], nums[1])indp[1]because adjacent positions cannot both contribute to the second prefix.Move from index
2toward the final position so both required earlier answers exist before each transition.Add
nums[index]todp[index - 2]for the take sum, preserving the required gap from the current position.Compare the take sum with
dp[index - 1]for the skip sum, then store the larger result sodp[index]preserves the best prefix answer.Return
dp[n - 1]because the final table entry represents the complete array.
Dry Run
Maximum Sum of Non Adjacent Element Tabulation
Solution
#include <bits/stdc++.h>using namespace std;class Solution {public: // Returns the maximum valid non-adjacent sum. int maximumNonAdjacentSum(vector<int>& nums) { int n = nums.size(); // Each entry stores one finished prefix answer. vector<int> dp(n, 0); dp[0] = nums[0]; // A one-value array already has a final answer. if (n == 1) { return dp[0]; } // Adjacent first values cannot both be selected. dp[1] = max(nums[0], nums[1]); // Later states need two finished earlier states. for (int index = 2; index < n; index++) { // Taking the value uses the two-back answer. int take = nums[index] + dp[index - 2]; // Skipping the value keeps the prior answer. int skip = dp[index - 1]; // The larger valid choice completes the state. dp[index] = max(take, skip); } // The last state covers the complete input array. return dp[n - 1]; }};// Driver codeint main() { vector<int> nums = {2, 1, 4, 9}; Solution obj; cout << obj.maximumNonAdjacentSum(nums); return 0;}Complexity Analysis
Time Complexity: O(N), where N is the number of input positions, because each prefix state is computed once with constant work.
Space Complexity: O(N), because the dp array stores one value for each input position, while the iterative approach uses no recursion stack.
Space Optimization
Tabulation reveals a small memory pattern: the current prefix answer needs only the previous prefix answer and the answer from two positions earlier. Older table entries never appear in a later transition.
The variable previous represents dp[index - 1], while secondPrevious represents dp[index - 2]. First, current combines the take and skip choices. Next, shifting secondPrevious before previous preserves both old values in the required order for the following index.
Algorithm
Store
nums[0]inpreviousbecause the first prefix answer equals the only available positive value.Store
0insecondPreviousbecause the imaginary prefix before index0contributes no selected value.Move from index
1toward the end so each current answer can use the two retained earlier states.Add
nums[index]tosecondPreviousfor the take sum because the two-back prefix respects the non-adjacent rule.Keep
previousas the skip sum because ignoring the current value preserves the best earlier prefix.Set
currentto the larger sum so the new prefix receives the best valid choice.Shift
secondPreviousto the oldprevious, then shiftprevioustocurrent, preserving both required states for the next index.Return
previousafter the loop because the variable then represents the final prefix answer.
Dry Run
Maximum Sum of Non Adjacent Element Space Optimization
Solution
#include <bits/stdc++.h>using namespace std;class Solution {public: // Returns the maximum valid non-adjacent sum. int maximumNonAdjacentSum(vector<int>& nums) { int n = nums.size(); int secondPrevious = 0; int previous = nums[0]; // Each step needs only two earlier answers. for (int index = 1; index < n; index++) { // Taking the value uses the two-back answer. int take = nums[index] + secondPrevious; // Skipping the value keeps the prior answer. int skip = previous; // Current keeps the larger valid choice. int current = max(take, skip); // Ordered shifts preserve both needed states. secondPrevious = previous; previous = current; } // Previous now represents the complete array. return previous; }};// Driver codeint main() { vector<int> nums = {2, 1, 4, 9}; Solution obj; cout << obj.maximumNonAdjacentSum(nums); return 0;}Complexity Analysis
Time Complexity: O(N), where N is the number of input positions, because each position is processed once with constant work.
Space Complexity: O(1), because only a fixed number of variables are used and no dp array or recursion stack is required.
Interview follow-up Questions
Yes. House Robber adds a story around the same linear array rule. Both problems use an identical take-or-skip dynamic programming transition.
Be the first to add a comment.