A family of problems on a price array , distinguished by how many transactions are allowed.
One transaction —
Buy once, sell once, later.
long long maxProfit1(const vector<int>& p) {
long long best = 0; int minSoFar = p[0];
for (int x : p) {
best = max(best, (long long)x - minSoFar);
minSoFar = min(minSoFar, x);
}
return best;
}This is exactly Kadane on the difference array — the maximum subarray of daily changes.
Unlimited transactions —
Sum every upward move:
long long maxProfitInf(const vector<int>& p) {
long long total = 0;
for (int i = 1; i < (int)p.size(); i++)
total += max(0, p[i] - p[i-1]);
return total;
}Any sequence of buys and sells telescopes into a sum of consecutive differences, so taking every positive one is optimal — a one-line exchange argument.
At most transactions —
long long maxProfitK(const vector<int>& p, int k) {
int n = p.size();
if (k >= n / 2) return maxProfitInf(p); // unlimited case
vector<long long> buy(k + 1, LLONG_MIN / 2), sell(k + 1, 0);
for (int x : p)
for (int j = 1; j <= k; j++) {
buy[j] = max(buy[j], sell[j-1] - x); // hold after j-th buy
sell[j] = max(sell[j], buy[j] + x); // done after j-th sell
}
return sell[k];
}The k >= n/2 shortcut matters: with enough transactions allowed, the constraint is vacuous, and it prevents an blowup when .
The state-machine view
Every variant is a small DP over “how many transactions used” × “holding or not”:
buy sell
free ------> holding ------------> free (one more transaction used)
Adding a rule adds a state or an edge:
| Rule | Change |
|---|---|
| Cooldown of 1 day after selling | add a cooldown state |
| Transaction fee | subtract on each sell |
| At most transactions | index the states by |
| Must hold days | index by days held |
| Short selling allowed | add a symmetric short state |
Once you draw the state machine, the code is mechanical. This is the clearest small example of DP as a state machine.
Huge — the Aliens trick
For up to with large, the DP is too slow. But the profit as a function of is concave (each extra transaction adds less), so Lagrangian relaxation applies:
- Charge a penalty per transaction.
- Solve the unlimited version with that penalty in .
- Binary search until exactly transactions are used.
- The answer is .
— independent of . This is the canonical worked example for the Aliens trick, and worth studying for that reason alone.
The variant table
| Variant | Method | Cost |
|---|---|---|
| One transaction | running minimum | |
| Unlimited | sum of positive differences | |
| At most 2 | the DP, or forward/backward passes | |
| At most | state DP | |
| At most , huge | Aliens trick | |
| With cooldown | 3-state DP | |
| With a fee | 2-state DP | |
| Best time to buy and sell with short selling | 4-state DP |
At most 2 transactions in
Two passes: left[i] = best profit using one transaction in ; right[i] = best in . Then . The same prefix/suffix decomposition solves many “split the array into two independent parts” problems.
Why it is worth knowing
Three techniques appear in one small problem family:
- Reduction to a known problem — one transaction is Kadane on differences.
- State-machine DP — the general framework, extended by adding states.
- Lagrangian relaxation — removing a “exactly ” constraint when the answer is concave in .
See also: Kadane’s Algorithm · Aliens Trick · Introduction to DP