Searching & Sorting · 12 of 36
Maximum sum such that no 2 elements are adjacent
Grab the most candy from a row of jars when you are never allowed to take from two jars that stand side by side.
- Simple way
- time O(2^n)
room O(n) - Clever way
- time O(n)
room O(1)
The problem, simply
You get a row of positive numbers. Pick some of them so that no two picked numbers stand next to each other. Make the total as big as you can, and give back that total.
You do not need to say which numbers you picked, only the best total. You may also pick nothing, which gives 0.
A story to remember it
Picture it
Six jars. The best plan opens jars 0, 3 and 5 for 5 + 100 + 5 = 110. No two opened jars touch.
- answer
- ignored
Examples
A normal case
- In
[5, 5, 10, 100, 10, 5]- Out
110
Take 5, 100 and 5 (positions 0, 3 and 5).
Greedy fails
- In
[2, 9, 10, 9]- Out
18
Grabbing the biggest jar, 10, first leaves only 2 to go with it, for 12. Taking both 9s gives 18.
Three jars
- In
[1, 20, 3]- Out
20
1 + 3 = 4 is allowed, but the 20 alone is better.
One jar
- In
[7]- Out
7
Take it. There is nobody next to it.
No jars
- In
[]- Out
0
Nothing to take.
Way 1: the simple way
At each jar you have two choices: take it and jump over the next jar, or skip it and move to the next jar. Try both choices everywhere and keep the best.
best(i)is the most candy you can get from jarito the end.- If
iis past the end, the answer is 0. - Otherwise
best(i) = max(arr[i] + best(i + 2), best(i + 1)).
The choice tree for four jars. f(i) is short for best(i), the most candy from jar i onward. The marked nodes are asked again and again: f(3) three times, f(2) twice. With more jars, the tree doubles in size.
- rejected
class Solution {
public int findMaxSum(int[] arr) {
return best(arr, 0);
}
private int best(int[] arr, int i) {
if (i >= arr.length) return 0; // no jars left
int take = arr[i] + best(arr, i + 2); // open this jar, skip the next
int skip = best(arr, i + 1); // leave this jar closed
return Math.max(take, skip);
}
}How long it takes: each call makes two more calls, so the work roughly doubles with every jar, O(2^n). Extra room: the calls pile up as deep as the row is long, O(n).
Way 2: the clever way
The tree asks the same questions over and over. So answer each question once, from left to right, and remember the answers. Even better: each answer only needs the two answers just before it, so two notes are enough.
prev1is the best total using the jars up to the one just before this jar.prev2is the best total using the jars up to two jars back.- At jar
i:cur = max(prev1, arr[i] + prev2). Skip the jar and keepprev1, or take it and add it toprev2. - Slide the notes:
prev2 = prev1,prev1 = cur. - At the end,
prev1is the answer.
Two notes walk along the jars
01 / 08
Before any jar, both notes are 0. The bottom row will show the best total after each jar.
- prev2
- 0
- prev1
- 0
- looking now
- checking
- done
- answer
- just added
class Solution {
public int findMaxSum(int[] arr) {
int prev2 = 0; // best total up to two jars back
int prev1 = 0; // best total up to the jar just before
for (int x : arr) {
int cur = Math.max(prev1, x + prev2); // skip it, or take it
prev2 = prev1;
prev1 = cur;
}
return prev1;
}
}How long it takes: one walk along the row, O(n). Extra room: two notes, O(1).
Watch out for
- Greedy by size fails too, as
[2, 9, 10, 9]shows. - Updating the notes in the wrong order. Compute
curfirst, then moveprev1intoprev2, then putcurintoprev1. - If the numbers can be negative, starting both notes at 0 means "pick nothing" is allowed. If the problem demands at least one pick, return the biggest number when every number is negative.
- Large sums can overflow
int. Uselongwhen numbers are big.
What interviewers ask next
- The jars stand in a circle, so the first and last jars touch. Solve twice: once without the last jar, once without the first jar, and take the bigger answer.
- Say which jars to take. Keep the full
dparray, then walk back from the end: ifdp[i] == dp[i - 1], jariwas skipped, otherwise it was taken and you jump toi - 2. - The jars are houses on a tree, and a parent and child cannot both be robbed. For each node return two numbers: the best if you take it and the best if you skip it.
- Delete and earn: picking a number
xremoves allx - 1andx + 1. Add up points per value, then run this same loop over the values.
Remember this
Grown-up words: the jar row is an array, "take or skip" is a recurrence, remembering answers is dynamic programming, and keeping only the last two answers is the space optimization. Interviewers also call this problem House Robber.