|
| 1 | +# [Problem 3573: Best Time to Buy and Sell Stock V](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-v/description/?envType=daily-question) |
| 2 | + |
| 3 | +## Initial thoughts (stream-of-consciousness) |
| 4 | +We can do at most k transactions. A transaction can be a normal buy-then-sell or a short-sell (sell then buy back). For any pair of days i < j the profit from a transaction between i and j is |prices[j] - prices[i]| (we would only take the positive direction). Transactions must be non-overlapping and cannot start on the same day the previous transaction ended (so if previous ended on day j, next must start at day > j). |
| 5 | + |
| 6 | +This looks like a dynamic programming problem similar to the classic "at most k transactions" stock problems (LeetCode 188). The naive DP dp[t][i] = max(dp[t][i-1], max_{s < i} dp[t-1][s] + |p[i]-p[s]|) is O(k * n^2) which is too slow for n up to 1000 and k up to ~500. For the standard problem without short-selling we optimize by tracking a running "best" value of dp[t-1][s-1] - price[s]. Here, the absolute value splits into two linear terms depending on sign: p[i]-p[s] or p[s]-p[i], so we can similarly maintain two running maxima to get O(k*n). |
| 7 | + |
| 8 | +Need to be careful about the "cannot start on same day previous ended" constraint: when adding a transaction that starts at s and ends at i, we must use dp[t-1][s-1] (profit up to day s-1) — not dp[t-1][s] — to avoid allowing immediate reuse of day s. |
| 9 | + |
| 10 | +So split the absolute into two cases and maintain: |
| 11 | +- best1 = max over s in [0..i-1] of dp_prev[s-1] - p[s] (handles p[i] + best1) |
| 12 | +- best2 = max over s in [0..i-1] of dp_prev[s-1] + p[s] (handles -p[i] + best2) |
| 13 | + |
| 14 | +Then dp_cur[i] = max(dp_cur[i-1], p[i] + best1, -p[i] + best2). Update best1/best2 after computing dp_cur[i] using dp_prev[i-1] (dp_prev[-1] = 0 for s = 0). |
| 15 | + |
| 16 | +## Refining the problem, round 2 thoughts |
| 17 | +- We must ensure indices are handled so that transactions don't start on the same day a previous transaction ended: use dp_prev[s-1] in the expressions (with dp_prev[-1] = 0). |
| 18 | +- Base cases: dp[0][*] = 0; dp[*][0] = 0 (no profit with zero days or zero transactions). |
| 19 | +- Time complexity O(k * n) and space O(n) if we keep only previous and current dp rows. |
| 20 | +- Edge cases: small n (2), k up to n//2 per constraints; prices large but only used in additions/subtractions so Python int fine. |
| 21 | + |
| 22 | +This yields a correct and efficient algorithm. |
| 23 | + |
| 24 | +## Attempted solution(s) |
| 25 | +```python |
| 26 | +from typing import List |
| 27 | + |
| 28 | +class Solution: |
| 29 | + def maxProfit(self, k: int, prices: List[int]) -> int: |
| 30 | + n = len(prices) |
| 31 | + if n < 2 or k == 0: |
| 32 | + return 0 |
| 33 | + |
| 34 | + # If k is large enough we could capture every adjacent absolute diff by using |
| 35 | + # transactions on every adjacent pair, but constraints guarantee k <= n//2. |
| 36 | + # We'll use O(k * n) DP. |
| 37 | + |
| 38 | + # dp_prev[i] = max profit using at most (t-1) transactions up to and including day i |
| 39 | + dp_prev = [0] * n |
| 40 | + |
| 41 | + for _ in range(1, k + 1): |
| 42 | + dp_cur = [0] * n |
| 43 | + # best1 = max(dp_prev[s-1] - prices[s]) for s in [0..i-1] |
| 44 | + # best2 = max(dp_prev[s-1] + prices[s]) for s in [0..i-1] |
| 45 | + # initialize as -inf so that if no s exists (i=0) we don't use them |
| 46 | + best1 = float("-inf") |
| 47 | + best2 = float("-inf") |
| 48 | + |
| 49 | + for i in range(n): |
| 50 | + # carry forward the best without using a transaction ending at i |
| 51 | + if i > 0: |
| 52 | + dp_cur[i] = dp_cur[i - 1] |
| 53 | + else: |
| 54 | + dp_cur[i] = 0 |
| 55 | + |
| 56 | + # use a transaction that ends at i and starts at some s in [0..i-1] |
| 57 | + if best1 != float("-inf"): |
| 58 | + dp_cur[i] = max(dp_cur[i], prices[i] + best1) |
| 59 | + if best2 != float("-inf"): |
| 60 | + dp_cur[i] = max(dp_cur[i], -prices[i] + best2) |
| 61 | + |
| 62 | + # now allow future i' > i to consider starting at s = i: |
| 63 | + prev_val = dp_prev[i - 1] if i - 1 >= 0 else 0 |
| 64 | + best1 = max(best1, prev_val - prices[i]) |
| 65 | + best2 = max(best2, prev_val + prices[i]) |
| 66 | + |
| 67 | + dp_prev = dp_cur |
| 68 | + |
| 69 | + return dp_prev[-1] |
| 70 | +``` |
| 71 | +- Approach notes: |
| 72 | + - We use dp over number of transactions. For iteration t we build dp_cur from dp_prev = dp for t-1 transactions. |
| 73 | + - For a transaction that starts at s and ends at i (s < i), the gain is |prices[i] - prices[s]| plus dp_prev[s-1] (profit before s). Splitting abs into two linear forms yields two running maxima that let us compute the inner max in O(1) per i. |
| 74 | +- Time complexity: O(k * n) where n = len(prices). |
| 75 | +- Space complexity: O(n) (we keep two arrays of length n, dp_prev and dp_cur). |
0 commit comments