title: Best Time to Buy and Sell Stock II slug: best-time-to-buy-and-sell-stock-ii difficulty: medium leetcode_id: 122 leetcode_url: https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/ categories: - arrays - dynamic-programming patterns: - slug: greedy is_optimal: true function_signature: "def max_profit(prices: list[int]) -> int:" test_cases: visible: - input: { prices: [7, 1, 5, 3, 6, 4] } expected: 7 - input: { prices: [1, 2, 3, 4, 5] } expected: 4 - input: { prices: [7, 6, 4, 3, 1] } expected: 0 hidden: - input: { prices: [1] } expected: 0 - input: { prices: [2, 1] } expected: 0 - input: { prices: [1, 2] } expected: 1 - input: { prices: [3, 3, 3, 3] } expected: 0 - input: { prices: [1, 2, 1, 2, 1, 2] } expected: 3 - input: { prices: [5, 1, 3, 2, 8, 4, 9] } expected: 14 description: | You are given an integer array `prices` where `prices[i]` is the price of a given stock on the ith day. On each day, you may decide to buy and/or sell the stock. You can only hold **at most one** share of the stock at any time. However, you can buy and sell on the **same day** (buy then immediately sell, or sell then immediately buy). Return *the **maximum** profit you can achieve*. constraints: | - `1 <= prices.length <= 3 * 10^4` - `0 <= prices[i] <= 10^4` examples: - input: "prices = [7,1,5,3,6,4]" output: "7" explanation: "Buy on day 2 (price = 1) and sell on day 3 (price = 5), profit = 5 - 1 = 4. Then buy on day 4 (price = 3) and sell on day 5 (price = 6), profit = 6 - 3 = 3. Total profit is 4 + 3 = 7." - input: "prices = [1,2,3,4,5]" output: "4" explanation: "Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5 - 1 = 4. Equivalently, you could buy and sell every consecutive day for the same total profit." - input: "prices = [7,6,4,3,1]" output: "0" explanation: "There is no way to make a positive profit, so we never buy the stock to achieve the maximum profit of 0." explanation: intuition: | Unlike the original "Best Time to Buy and Sell Stock" problem where you can only make one transaction, here you can make **unlimited transactions**. This changes the problem fundamentally. Imagine visualising the stock prices as a line graph. In the original problem, you needed to find the single largest "valley to peak" rise. But now, you can capture **every upward movement** in the graph. Think of it like this: if you could time-travel and knew all the prices in advance, you'd want to buy at every local minimum and sell at every local maximum. But here's the key insight — you don't actually need to identify peaks and valleys explicitly. Instead, consider this: if the price goes up from day `i` to day `i+1`, you could have bought on day `i` and sold on day `i+1` for a profit of `prices[i+1] - prices[i]`. By **summing up all the positive differences** between consecutive days, you capture every upward movement. This greedy approach works because every upward price movement represents profit you could have captured, and skipping any positive gain would leave money on the table. approach: | We solve this using a **Greedy Sum of Gains** approach: **Step 1: Initialise the profit tracker** - `total_profit`: Set to `0`, as we accumulate gains from each profitable day-to-day movement   **Step 2: Iterate through consecutive day pairs** - For each pair of consecutive days (from index `1` to `n-1`), compare today's price with yesterday's price - If `prices[i] > prices[i-1]`, we have a gain — add the difference to `total_profit` - If `prices[i] <= prices[i-1]`, the price dropped or stayed flat — skip it (we wouldn't have held the stock)   **Step 3: Return the accumulated profit** - After processing all consecutive pairs, `total_profit` contains the maximum achievable profit - This captures every upward movement in the price series   **Why this works:** The sum of all positive consecutive differences equals the maximum profit from any sequence of transactions. Whether you hold through a multi-day rise or buy/sell each day, the total gain is identical. common_pitfalls: - title: Trying to Find Peaks and Valleys description: | A natural instinct is to explicitly identify local minima (buy points) and local maxima (sell points), then calculate profits between them. While this works, it's unnecessarily complex. You need to handle edge cases like plateaus, the array start/end, and consecutive rises/falls. The simple "sum all positive differences" approach achieves the same result with much cleaner code. wrong_approach: "Explicitly finding local minima and maxima" correct_approach: "Sum all positive consecutive differences" - title: Using Dynamic Programming When Greedy Suffices description: | This problem can be solved with DP using states like `hold` and `not_hold` for each day. While correct, it's overkill. The greedy solution runs in O(n) time and O(1) space with simpler logic. DP is useful for variants with constraints (cooldown, transaction fees, limited transactions), but for unlimited transactions, greedy is optimal. wrong_approach: "DP with hold/not_hold states" correct_approach: "Single pass greedy sum" - title: Confusing with Single Transaction Problem description: | In the original problem (one transaction), you track `min_price` and find the maximum `price - min_price`. That logic doesn't apply here. With unlimited transactions, you're not looking for one optimal buy-sell pair — you want to capture **every** upward movement. The problems look similar but require different approaches. wrong_approach: "Tracking minimum price for single best transaction" correct_approach: "Summing all profitable day-to-day gains" key_takeaways: - "**Greedy insight**: When you can act unlimited times, capture every positive gain rather than searching for optimal single actions" - "**Consecutive differences**: The sum of all positive `prices[i] - prices[i-1]` equals the maximum profit from any transaction sequence" - "**Problem variant awareness**: This extends the single-transaction problem; further variants add cooldowns, fees, or transaction limits" - "**Simplicity over complexity**: Sometimes the elegant solution is simpler than the obvious one — summing gains beats finding peaks/valleys" time_complexity: "O(n). We traverse the prices array exactly once, comparing each element to its predecessor." space_complexity: "O(1). We only use a single variable (`total_profit`) regardless of the input size." solutions: - approach_name: Greedy Sum of Gains is_optimal: true code: | def max_profit(prices: list[int]) -> int: # Accumulate profit from every upward price movement total_profit = 0 # Compare each day with the previous day for i in range(1, len(prices)): # If price went up, we could have profited from this movement if prices[i] > prices[i - 1]: total_profit += prices[i] - prices[i - 1] return total_profit explanation: | **Time Complexity:** O(n) — Single pass through the array. **Space Complexity:** O(1) — Only one variable used. We iterate through the array once, adding every positive price difference to our total. This captures all upward movements, which is equivalent to buying at every local minimum and selling at every local maximum. - approach_name: Peak Valley Approach is_optimal: false code: | def max_profit(prices: list[int]) -> int: if not prices: return 0 total_profit = 0 i = 0 n = len(prices) while i < n - 1: # Find the valley (local minimum) while i < n - 1 and prices[i] >= prices[i + 1]: i += 1 valley = prices[i] # Find the peak (local maximum) while i < n - 1 and prices[i] <= prices[i + 1]: i += 1 peak = prices[i] # Add profit from this valley-peak pair total_profit += peak - valley return total_profit explanation: | **Time Complexity:** O(n) — Each element is visited at most twice. **Space Complexity:** O(1) — Only tracking indices and profit. This approach explicitly finds local minima (valleys) and maxima (peaks), calculating profit from each pair. While correct and equally efficient, it's more complex than the simple consecutive difference sum. Included to show an alternative perspective on the problem. - approach_name: Dynamic Programming is_optimal: false code: | def max_profit(prices: list[int]) -> int: if not prices: return 0 # hold = max profit if we currently hold a stock # cash = max profit if we don't hold any stock hold = -prices[0] # Buy on day 0 cash = 0 # Do nothing on day 0 for i in range(1, len(prices)): # Either keep holding, or buy today (from cash state) new_hold = max(hold, cash - prices[i]) # Either keep cash, or sell today (from hold state) new_cash = max(cash, hold + prices[i]) hold = new_hold cash = new_cash # Maximum profit is when we don't hold any stock return cash explanation: | **Time Complexity:** O(n) — Single pass through the array. **Space Complexity:** O(1) — Only two state variables. This DP approach tracks two states: `hold` (owning stock) and `cash` (not owning). At each day, we decide optimally whether to buy, sell, or do nothing. While overkill for this problem, this pattern extends naturally to variants with cooldowns or transaction fees.