Dynamic Programming · 48 of 60
Maximum profit by buying and selling a share at most twice
Make the most money from at most two buy and sell trips by carrying four running money totals through the days.
- Simple way
- time O(n^2)
room O(1) - Clever way
- time O(n)
room O(1)
The problem, simply
You get prices, the price of a share on each day. You may do at most two trades. A trade is one buy and then one later sell. You can hold only one share at a time, so the second buy must come after the first sell.
Give back the most money you can make. Doing nothing (0) is always allowed.
A story to remember it
Picture it
Prices over 8 days. One good plan: buy at 0 on day 3, sell at 3 on day 5, buy at 1 on day 6, sell at 4 on day 7.
- answer
- just added
Examples
Two separate rises
- In
prices = [3, 3, 5, 0, 0, 3, 1, 4]- Out
6
Buy at 0, sell at 3 (profit 3). Buy at 1, sell at 4 (profit 3). Total 6.
One long rise
- In
prices = [1, 2, 3, 4, 5]- Out
4
Buy at 1, sell at 5. Splitting it into two trades never beats one long trade.
Falling prices
- In
prices = [7, 6, 4, 3, 1]- Out
0
Every trade loses, so do not trade.
Small bumps
- In
prices = [2, 1, 2, 0, 1]- Out
2
Buy at 1, sell at 2. Buy at 0, sell at 1. One trade alone gets only 1.
Way 1: the simple way
Two trades never overlap, so there is a day that splits them: the first trade lives on the left, the second on the right. Try every split day.
- For each split day
i, find the best single trade inside days0..i: track the lowest price so far. - Find the best single trade inside days
i..n-1: walk from the right and track the highest price so far. - Add the two. Keep the biggest sum over all splits.
Split after day 5. Best single trade on the left (days 0 to 5) is 0 to 3, worth 3. Best on the right (days 5 to 7) is 1 to 4, worth 3. Together 6.
- answer
- just added
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int best = 0;
for (int i = 0; i < n; i++) {
int left = 0, low = Integer.MAX_VALUE;
for (int j = 0; j <= i; j++) { // best trade on the left
low = Math.min(low, prices[j]);
left = Math.max(left, prices[j] - low);
}
int right = 0, high = Integer.MIN_VALUE;
for (int j = n - 1; j >= i; j--) { // best trade on the right
high = Math.max(high, prices[j]);
right = Math.max(right, high - prices[j]);
}
best = Math.max(best, left + right);
}
return best;
}
}How long it takes: n splits, and each one walks the whole list, O(n^2). Extra room: a few numbers, O(1). You can make this O(n) with two helper arrays, one for the best left trade up to each day and one for the best right trade from each day.
Way 2: the clever way
Keep the four piggy banks as four numbers. Money spent counts as negative.
buy1: most money after the first buy. Start at minus infinity (not bought yet).sell1: most money after the first sell. Start at 0.buy2: most money after the second buy. Start at minus infinity.sell2: most money after the second sell. Start at 0.
Every day, with price p, update them in this order:
buy1 = max(buy1, -p)sell1 = max(sell1, buy1 + p)buy2 = max(buy2, sell1 - p)sell2 = max(sell2, buy2 + p)
The table below is the full DP table these four numbers stand for: one row per bank, one column per day. Each box comes from the box on its left (do nothing today) and the box above it (do this step today). The code only keeps the newest column.
Four money boxes move forward one day at a time
01 / 09
Four rows, one per step of the plan. Each box holds the most money we could have after that step, by that day. Nothing is filled yet.
- looking now
- answer
- just added
class Solution {
public int maxProfit(int[] prices) {
int buy1 = Integer.MIN_VALUE, sell1 = 0; // first trip
int buy2 = Integer.MIN_VALUE, sell2 = 0; // second trip
for (int p : prices) {
buy1 = Math.max(buy1, -p); // buy for the first time today
sell1 = Math.max(sell1, buy1 + p); // or sell the first share today
buy2 = Math.max(buy2, sell1 - p); // or buy again with the profit
sell2 = Math.max(sell2, buy2 + p); // or sell the second share today
}
return sell2;
}
}How long it takes: one walk over the days, O(n). Extra room: four numbers, O(1).
Watch out for
- Buying and selling on the same day is allowed by the updates, but it earns 0, so it never changes the answer.
sell2already covers "only one trade" and "no trade", becausebuy2can besell1 - pwith the samepthat was just sold at.- Do not pick the two biggest single rises. In [1, 2, 4, 2, 5, 7, 2, 4, 9, 0] the best is 13 (1 to 7, then 2 to 9). The two biggest separate rises, 2 to 7 and 2 to 9, make only 12.
What interviewers ask next
- At most
ktrades? Use arraysbuy[1..k]andsell[1..k]with the same rules,O(n * k). - As many trades as you like? Add up every rise from one day to the next.
- With a cooldown day after selling, or a fee per trade? Same piggy bank idea with one more bank or a fee subtracted on each sell.
- Return the actual buy and sell days. Remember the day each bank last improved, plus the day of the bank before it at that moment.
Remember this
Grown-up words: the piggy banks are states, the chain of updates is a state machine DP, minus infinity means an impossible state, and the split day idea is a prefix and suffix best trade.