Topics › Dynamic Programming › DP on Stocks
Best time to buy and sell stock with transaction fees
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
prices[i] is the price of a stock on day i, and fee is a fixed charge paid for every completed transaction (one buy plus one sell). You may make as many transactions as you like, holding at most one share at a time. Return the maximum profit after fees.
Constraints: 1 ≤ n ≤ 5 × 10^4, 1 ≤ prices[i] < 5 × 10^4, 0 ≤ fee < 5 × 10^4. The Java file also accepts an empty array.
Example 1
prices = [1, 3, 2, 8, 4, 9], fee = 2
answer = 8 // buy 1, sell 8: 7 - 2 = 5; buy 4, sell 9: 5 - 2 = 3
Example 2
prices = [1, 3, 7, 5, 10, 3], fee = 3
answer = 6 // one long trade 1 -> 10 pays the fee once: 9 - 3 = 6
In Example 2, two trades 1 → 7 and 5 → 10 earn 6 + 5 = 11 before fees, but pay 6 in fees, leaving 5. One long trade pays the fee only once and earns 6. The fee makes the number of trades matter, which is why the “sum of all rises” greedy breaks and the DP is needed.
Approach 1: Recursion
Intuition. The state is the Stock II state: f(i, canBuy) is the best profit from day i onward, where canBuy = 1 means no share is held. The fee is charged once per transaction, so attach it to exactly one of the two moves. Attaching it to the sell is natural: a sale brings in prices[i] - fee.
canBuy = 1:max(-prices[i] + f(i + 1, 0), f(i + 1, 1)).canBuy = 0:max(prices[i] - fee + f(i + 1, 1), f(i + 1, 0)).- Base:
f(n, *) = 0.
A share bought but never sold pays no fee, but it also never returns its price, so such plans are never optimal; the base case needs no special handling.
Algorithm.
- If
i == n, return0. - Apply the buy or sell recurrence; subtract
feeon the sell.
static int recursive(int[] prices, int fee) {
return solve(0, 1, prices, fee);
}
static int solve(int i, int canBuy, int[] prices, int fee) {
if (i == prices.length) return 0;
if (canBuy == 1) {
return Math.max(-prices[i] + solve(i + 1, 0, prices, fee), // buy today
solve(i + 1, 1, prices, fee)); // skip
}
return Math.max(prices[i] - fee + solve(i + 1, 1, prices, fee), // sell today and pay the fee
solve(i + 1, 0, prices, fee)); // keep holding
}
Complexity. Time O(2^n), space O(n) stack.
Approach 2: Memoization
Intuition. There are 2n states. Cache each answer; -1 is a safe “not computed” marker because doing nothing always earns 0, so no state is negative.
static int memoization(int[] prices, int fee) {
int[][] dp = new int[prices.length][2];
for (int[] row : dp) Arrays.fill(row, -1);
return memo(0, 1, prices, fee, dp);
}
static int memo(int i, int canBuy, int[] prices, int fee, int[][] dp) {
if (i == prices.length) return 0;
if (dp[i][canBuy] != -1) return dp[i][canBuy];
int best;
if (canBuy == 1) best = Math.max(-prices[i] + memo(i + 1, 0, prices, fee, dp), memo(i + 1, 1, prices, fee, dp));
else best = Math.max(prices[i] - fee + memo(i + 1, 1, prices, fee, dp), memo(i + 1, 0, prices, fee, dp));
return dp[i][canBuy] = best;
}
Complexity. Time O(n), space O(n) plus O(n) stack. With n = 5 × 10^4, recursion this deep may overflow Java’s default stack, so the next two versions are the practical ones.
Approach 3: Tabulation
Intuition. Row i reads only row i + 1. Fill from i = n - 1 down to 0; row n is the zero base case.
Algorithm.
dp[n + 1][2], all zero.- For
ifromn - 1down to0:dp[i][1] = max(-prices[i] + dp[i + 1][0], dp[i + 1][1])dp[i][0] = max(prices[i] - fee + dp[i + 1][1], dp[i + 1][0])
- Return
dp[0][1].
static int tabulation(int[] prices, int fee) {
int n = prices.length;
int[][] dp = new int[n + 1][2]; // dp[n][*] = 0
for (int i = n - 1; i >= 0; i--) {
dp[i][1] = Math.max(-prices[i] + dp[i + 1][0], dp[i + 1][1]);
dp[i][0] = Math.max(prices[i] - fee + dp[i + 1][1], dp[i + 1][0]);
}
return dp[0][1];
}
Dry run on prices = [1, 3, 2, 8, 4, 9], fee = 2 (dp[i][1] = free to buy, dp[i][0] = holding):
| i | price | dp[i][1] | dp[i][0] |
|---|---|---|---|
| 6 | - | 0 | 0 |
| 5 | 9 | max(-9 + 0, 0) = 0 | max(9 - 2 + 0, 0) = 7 |
| 4 | 4 | max(-4 + 7, 0) = 3 | max(4 - 2 + 0, 7) = 7 |
| 3 | 8 | max(-8 + 7, 3) = 3 | max(8 - 2 + 3, 7) = 9 |
| 2 | 2 | max(-2 + 9, 3) = 7 | max(2 - 2 + 3, 9) = 9 |
| 1 | 3 | max(-3 + 9, 7) = 7 | max(3 - 2 + 7, 9) = 9 |
| 0 | 1 | max(-1 + 9, 7) = 8 | max(1 - 2 + 7, 9) = 9 |
Row 3 shows the key decision: holding a share on day 3, selling at 8 (and paying the fee) then buying again at 4 gives 6 + 3 = 9, which beats holding on to sell at 9 (7). The answer is dp[0][1] = 8.
Complexity. Time O(n), space O(n).
Approach 4: Space optimised
Intuition. Only the next row is read, so two variables hold dp[i + 1][1] and dp[i + 1][0]. Compute day i into temporaries, then overwrite.
static int spaceOptimized(int[] prices, int fee) {
int aheadBuy = 0, aheadHold = 0; // dp[i + 1][1], dp[i + 1][0]
for (int i = prices.length - 1; i >= 0; i--) {
int buy = Math.max(-prices[i] + aheadHold, aheadBuy);
int hold = Math.max(prices[i] - fee + aheadBuy, aheadHold);
aheadBuy = buy;
aheadHold = hold;
}
return aheadBuy;
}
Complexity. Time O(n), space O(1).
Forward view. The same recurrence is often written forwards as two running values, free and hold: hold = max(hold, free - p) and free = max(free, hold + p - fee). It is the identical DP read from the left; the backward version above is kept because it follows directly from the recursion.
Edge cases and pitfalls
- Charge the fee once per transaction. Subtracting it on both the buy and the sell doubles the fee. Pick one side; the sell side is the usual choice.
- A trade that only breaks even. If
prices[sell] - prices[buy] == fee, the trade earns0, the same as not trading.[4, 1, 7]withfee = 6returns0. - Fee of zero. The problem becomes Stock II exactly; the test with
fee = 0checks that the answer equals the sum of all rises (13for[1, 3, 2, 8, 4, 9]). - Huge fee. If the fee exceeds every possible price gain, the answer is
0. - Overflow. Every profitable rise needs its own low day and high day, so the total profit is at most about
(n / 2) × max(price) ≈ 1.25 × 10^9, which fits in anint. Intermediate values such as-prices[i] + dp[i + 1][0]are bounded the same way. With larger constraints, switch the DP values tolong. - One day or no days. The answer is
0.
Related problems
- Best time to buy and sell stock II is this problem with
fee = 0. - Best Time to Buy and Sell Stock with Cooldown restricts trades with a waiting day instead of a fee.
- Best time to buy and sell stock IV restricts the number of trades.
- Best time to buy and sell stock is the single-trade version.