Dynamic Programming · 19 of 60
Maximum Sum Increasing Subsequence
Pick numbers that keep getting bigger from left to right so that their total is as large as possible.
- Simple way
- time O(2^n)
room O(n) - Clever way
- time O(n^2)
room O(n)
The problem, simply
You get a list of numbers. Choose some of them, keeping their order, so that each chosen number is strictly bigger than the one chosen before it. This is an increasing subsequence.
Give back the biggest possible sum of such a choice. The longest climb is not always the heaviest one.
A story to remember it
Picture it
Pick numbers left to right, each bigger than the last. 1, 2, 3, 100 adds up to 106. The longer climb 1, 2, 3, 4, 5 only adds up to 15.
- answer
Examples
Heavy beats long
- In
[1, 101, 2, 3, 100, 4, 5]- Out
106
1 + 2 + 3 + 100 = 106. The climb 1, 2, 3, 4, 5 is longer but only 15. 1 + 101 = 102 is close but smaller.
Already climbing
- In
[3, 4, 5, 10]- Out
22
Every number is bigger than the last, so take them all.
Only falling
- In
[10, 5, 4, 3]- Out
10
No two numbers can be climbed in order. The best single number is 10.
Equal numbers
- In
[1, 1, 1]- Out
1
Strictly bigger is required, so equal numbers cannot follow each other.
Way 1: the simple way
For each position i, ask: "what is the best climb that ends here?" Try every smaller number before it as the step before.
end(i)starts asa[i], the climb with only this number.- For every
j < iwitha[j] < a[i], tryend(j) + a[i]. - The answer is the biggest
end(i).
Using the first five numbers [1, 101, 2, 3, 100]. end(i) is the best climb that finishes on number i. end(4) asks every smaller number before it. end(2) is worked out twice and end(0) four times.
- seen
class Solution {
public int maxSumIS(int[] a) {
int best = 0;
for (int i = 0; i < a.length; i++) {
best = Math.max(best, end(a, i));
}
return best;
}
private int end(int[] a, int i) { // best climb that finishes on a[i]
int best = a[i];
for (int j = 0; j < i; j++) {
if (a[j] < a[i]) {
best = Math.max(best, end(a, j) + a[i]);
}
}
return best;
}
}How long it takes: the same end(j) is recomputed many times, which grows like O(2^n) on an increasing list. Extra room: the waiting calls, O(n).
Way 2: the clever way
Fill the notes from left to right in an array dp, so each one is computed once.
dp[i] = a[i]to start.- For each
j < iwitha[j] < a[i]:dp[i] = max(dp[i], dp[j] + a[i]). - The answer is the biggest value in
dp.
Arrows show which earlier boxes each box can build on. Gray boxes are not smaller, so they cannot be the step before.
Each number looks back at smaller numbers
01 / 08
The first number has nothing before it, so the best climb ending on it is 1.
- checking
- answer
- just added
- ignored
class Solution {
public int maxSumIS(int[] a) {
int n = a.length;
int[] dp = new int[n];
int best = 0;
for (int i = 0; i < n; i++) {
dp[i] = a[i]; // climb with only a[i]
for (int j = 0; j < i; j++) {
if (a[j] < a[i]) { // a[j] can be the step before
dp[i] = Math.max(dp[i], dp[j] + a[i]);
}
}
best = Math.max(best, dp[i]);
}
return best;
}
}How long it takes: each number looks back at all earlier numbers, O(n^2). Extra room: the dp array, O(n).
Watch out for
- Use
<for strictly increasing. Some versions allow equal numbers, then use<=. - Start each box at
a[i], not 0, so a number alone still counts. - If numbers can be negative, start
bestat the smallest value, not 0, and decide whether an empty choice is allowed.
What interviewers ask next
- Print the chosen numbers. Keep
from[i], thejthat gave the best, and walk back from the biggest box. - Can it be faster than
O(n^2)? Yes. Squash the values to ranks and use a Fenwick tree (or segment tree) that answers "bestdpamong values smaller than mine" inO(log n). TotalO(n log n). - Longest increasing subsequence instead of heaviest? Same table, add 1 instead of
a[i]. There is also a patience sorting trick forO(n log n). - Biggest sum of a bitonic sequence (up then down)? Run this left to right and right to left, then combine at each peak.
Remember this
Grown-up words: the climb is an increasing subsequence, the note per hold is dp[i], looking back at smaller holds is the longest increasing subsequence recurrence, and recomputing end(j) is an overlapping subproblem.