Topics › Dynamic Programming › 1D DP
Maximum sum of non adjacent elements
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
Given an array nums of non-negative integers, choose some of its elements so that no two chosen elements sit at adjacent indices, and maximise their sum. Choosing nothing is allowed and gives 0.
This is the same problem as LeetCode 198 “House Robber”: houses in a row hold nums[i] money, and robbing two neighbouring houses triggers the alarm.
Constraints: LeetCode allows n ≤ 100 and values up to 400, but other versions allow far larger inputs (for example 10^5 elements of up to 10^5 each). There the total can reach about 5 × 10^9, which overflows int, so the methods return long.
Example 1
nums = [2, 1, 4, 9]
answer = 11 // 2 + 9 (indices 0 and 3)
Example 2
nums = [1, 2, 4]
answer = 5 // 1 + 4; taking 2 would block both neighbours
Why the obvious shortcuts fail
Two shortcuts come to mind, and both are wrong:
- Take every other element (all even indices or all odd indices). On
[2, 1, 1, 2]that gives 3 either way, but indices 0 and 3 give 4. The best choice can skip two elements in a row. - Grab the largest element first. On
[5, 6, 5]that takes 6 and blocks both 5s, but5 + 5 = 10is better.
Every element is either taken or not, and each decision constrains its neighbours. That is the take / not-take pattern from subsequence recursion, with one extra rule, and DP makes it fast.
Approach 1: Recursion (pick / not pick)
Intuition. Let f(i) be the best sum using only nums[0..i]. Look at the last element, nums[i]:
- Pick it. Then
nums[i - 1]is forbidden, and the rest of the choice is any valid choice fromnums[0..i - 2]. The best such choice isf(i - 2), so this option is worthnums[i] + f(i - 2). - Skip it. Then the choice is any valid choice from
nums[0..i - 1], worthf(i - 1).
Every valid selection falls into exactly one of these two cases, so
f(i) = max( nums[i] + f(i - 2), f(i - 1) ), f(negative) = 0
The answer is f(n - 1). A single base case f(i < 0) = 0 covers everything: f(0) becomes max(nums[0] + 0, 0) = nums[0].
Algorithm.
- If
i < 0, return 0. pick = nums[i] + f(i - 2),skip = f(i - 1).- Return
max(pick, skip).
static long recursive(int[] nums) {
return solve(nums.length - 1, nums);
}
static long solve(int i, int[] nums) {
if (i < 0) return 0; // no elements left
long pick = nums[i] + solve(i - 2, nums); // take nums[i], so nums[i - 1] is off limits
long skip = solve(i - 1, nums); // leave nums[i]
return Math.max(pick, skip);
}
Complexity. Time O(2^n): two branches per call and heavy recomputation. Space O(n) for the recursion depth.
Approach 2: Memoization
Intuition. The recursion only ever asks about prefixes nums[0..i], so there are n states. Remember each answer.
Algorithm.
- Create
dpof sizenfilled with-1. Sums of non-negative numbers are never negative, so-1safely means “not computed”. - In the helper, return
dp[i]when it is filled; otherwise computepickandskip, store the maximum, and return it.
static long memoization(int[] nums) {
long[] dp = new long[nums.length];
Arrays.fill(dp, -1); // -1 = not computed (real sums are >= 0)
return memo(nums.length - 1, nums, dp);
}
static long memo(int i, int[] nums, long[] dp) {
if (i < 0) return 0;
if (dp[i] != -1) return dp[i];
long pick = nums[i] + memo(i - 2, nums, dp);
long skip = memo(i - 1, nums, dp);
return dp[i] = Math.max(pick, skip);
}
Complexity. Time O(n), space O(n) for the table plus O(n) stack. At n = 10^5 the stack is too deep for Java’s defaults.
Approach 3: Tabulation
Intuition. dp[i] needs dp[i - 1] and dp[i - 2], so fill left to right. Treat out-of-range indices as 0, exactly like the recursion’s base case.
Algorithm.
- For
ifrom 0 ton - 1:pick = nums[i] + (i ≥ 2 ? dp[i - 2] : 0),skip = (i ≥ 1 ? dp[i - 1] : 0),dp[i] = max(pick, skip). - Return
dp[n - 1], or 0 for an empty array.
static long tabulation(int[] nums) {
int n = nums.length;
if (n == 0) return 0;
long[] dp = new long[n];
for (int i = 0; i < n; i++) {
long pick = nums[i] + (i >= 2 ? dp[i - 2] : 0);
long skip = i >= 1 ? dp[i - 1] : 0;
dp[i] = Math.max(pick, skip);
}
return dp[n - 1];
}
Dry run on nums = [2, 1, 4, 9]:
| i | nums[i] | pick | skip | dp[i] |
|---|---|---|---|---|
| 0 | 2 | 2 + 0 = 2 | 0 | 2 |
| 1 | 1 | 1 + 0 = 1 | 2 | 2 |
| 2 | 4 | 4 + 2 = 6 | 2 | 6 |
| 3 | 9 | 9 + 2 = 11 | 6 | 11 |
At i = 3, picking 9 combines with dp[1] = 2 (the best of [2, 1]), not with the 6 that used index 2.
Complexity. Time O(n), space O(n).
Approach 4: Space optimized
Intuition. Only the last two table cells are ever read. Keep prev = dp[i - 1] and prev2 = dp[i - 2], both starting at 0 (the values for “before the array”).
Algorithm.
prev2 = prev = 0.- For each
xinnums:cur = max(x + prev2, prev), thenprev2 = prev,prev = cur. - Return
prev.
static long spaceOptimized(int[] nums) {
long prev2 = 0, prev = 0; // dp[i - 2] and dp[i - 1]
for (int x : nums) {
long cur = Math.max(x + prev2, prev);
prev2 = prev;
prev = cur;
}
return prev;
}
Complexity. Time O(n), space O(1). This four-line loop is the answer most interviewers expect, but derive it from the recursion so you can explain it.
Edge cases and pitfalls
- Empty array. The answer is 0. The tabulation needs an explicit check because it returns
dp[n - 1]; the other methods handle it naturally. - One element. The answer is that element.
- Overflow. Two values of
2 × 10^9that are not adjacent already sum pastint. The tests include[2000000000, 1, 2000000000]with expected4000000000. - Negative numbers. This formulation lets you choose nothing, so with negative values it never goes below 0. If a problem insists on at least one element, the base case for
i == 0must returnnums[0]instead. - Zeros.
[0, 0, 0]gives 0; ties between pick and skip do not matter for the sum. - Testing. Besides the hand-picked cases, the Java file runs 200 seeded random arrays through all four methods and requires identical results.
Related problems
- House robber puts the houses in a circle, so the first and last are neighbours too.
- Frog Jump is the previous 1D DP, with “which jump came last” instead of pick / not pick.
- Introduction to DP explains the move from recursion to memoization to tabulation.
- Learn All Patterns of Subsequences (Theory) covers the take / not-take recursion this problem builds on.
- Subset sum equal to target (DP- 14) uses pick / not pick with a second state, the remaining target.