Best Time to Buy and Sell Stock V

MEDIUM

Description

You are given an integer array prices where prices[i] is the price of a stock in dollars on the ith day, and an integer k.

You are allowed to make at most k transactions, where each transaction can be either of the following:

  • Normal transaction: Buy on day i, then sell on a later day j where i < j. You profit prices[j] - prices[i].

  • Short selling transaction: Sell on day i, then buy back on a later day j where i < j. You profit prices[i] - prices[j].

Note that you must complete each transaction before starting another. Additionally, you can't buy or sell on the same day you are selling or buying back as part of a previous transaction.

Return the maximum total profit you can earn by making at most k transactions.

 

Example 1:

Input: prices = [1,7,9,8,2], k = 2

Output: 14

Explanation:

We can make $14 of profit through 2 transactions:
  • A normal transaction: buy the stock on day 0 for $1 then sell it on day 2 for $9.
  • A short selling transaction: sell the stock on day 3 for $8 then buy back on day 4 for $2.

Example 2:

Input: prices = [12,16,19,19,8,1,19,13,9], k = 3

Output: 36

Explanation:

We can make $36 of profit through 3 transactions:
  • A normal transaction: buy the stock on day 0 for $12 then sell it on day 2 for $19.
  • A short selling transaction: sell the stock on day 3 for $19 then buy back on day 4 for $8.
  • A normal transaction: buy the stock on day 5 for $1 then sell it on day 6 for $19.

 

Constraints:

  • 2 <= prices.length <= 103
  • 1 <= prices[i] <= 109
  • 1 <= k <= prices.length / 2

Approaches

Checkout 2 different approaches to solve Best Time to Buy and Sell Stock V. Click on different approaches to view the approach and algorithm in detail.

Recursive Approach with Memoization

This approach uses recursion with memoization, which is a form of top-down dynamic programming. We define a function that calculates the maximum profit from a given day with a certain number of transactions remaining. The state is defined by (current_day, transactions_left). For each state, we explore all possible future transactions, leading to a high time complexity.

Algorithm

  • Define a recursive function solve(i, k) that computes the maximum profit from day i to n-1 with at most k transactions.
  • The base cases for the recursion are:
    • If k is 0, no more transactions can be made, so the profit is 0.
    • If i is greater than or equal to n-1, there are not enough days to make a transaction, so the profit is 0.
  • In the recursive step for solve(i, k), we consider two main possibilities:
    1. Skip day i: We don't start a transaction on day i. The maximum profit is then found by solving the subproblem for the remaining days, which is solve(i + 1, k).
    2. Start a transaction on day i: We can pair day i with any subsequent day j (where j > i) to form a transaction. The profit for this single transaction is |prices[j] - prices[i]|. After this transaction, which ends on day j, we can start the next transaction from day j+1. The profit from the remaining k-1 transactions is solve(j + 1, k - 1). We iterate through all possible j from i+1 to n-1 and take the one that maximizes the total profit.
  • The result for solve(i, k) is the maximum of the outcomes from these two possibilities.
  • To avoid recomputing the same subproblems, we use a 2D array memo[i][k] for memoization.

We can define a DP state dp[i][k] as the maximum profit achievable using the subarray of prices from day i to the end, with at most k transactions allowed.

The goal is to compute dp[0][k].

The recurrence relation can be formulated as follows: dp[i][k] = max(option1, option2)

  • Option 1: Don't transact starting at day i We skip day i and the problem reduces to finding the max profit from day i+1 onwards with k transactions. The profit is dp[i+1][k].

  • Option 2: Start a transaction at day i We start a transaction at day i and end it at some future day j > i. The profit from this transaction is |prices[j] - prices[i]|. After this transaction concludes on day j, we are left with k-1 transactions and can continue from day j+1. We must try every possible j from i+1 to n-1 and choose the one that yields the maximum total profit. This is expressed as: max_{j=i+1}^{n-1} (|prices[j] - prices[i]| + dp[j+1][k-1])

Combining these, the full recurrence is: dp[i][k] = max(dp[i+1][k], max_{j=i+1}^{n-1} (|prices[j] - prices[i]| + dp[j+1][k-1]))

This can be implemented using a recursive function with a 2D memoization table to store the results of dp[i][k] and avoid redundant calculations.

import java.util.Arrays;

class Solution {
    private long[][] memo;
    private int[] prices;
    private int n;

    public long maxProfit(int[] prices, int k) {
        this.prices = prices;
        this.n = prices.length;
        this.memo = new long[n][k + 1];
        for (long[] row : memo) {
            Arrays.fill(row, -1);
        }
        return solve(0, k);
    }

    private long solve(int i, int k) {
        if (k == 0 || i >= n - 1) {
            return 0;
        }
        if (memo[i][k] != -1) {
            return memo[i][k];
        }

        // Option 1: Skip day i
        long res = solve(i + 1, k);

        // Option 2: Start a transaction on day i
        for (int j = i + 1; j < n; j++) {
            long currentProfit = Math.abs((long)prices[j] - prices[i]);
            long futureProfit = (j + 1 < n) ? solve(j + 1, k - 1) : 0;
            res = Math.max(res, currentProfit + futureProfit);
        }

        return memo[i][k] = res;
    }
}

Complexity Analysis

Time Complexity: O(k * n^2) because for each state `(i, k)`, we iterate from `j = i+1` to `n-1`, which takes O(n) time. There are `k*n` states.Space Complexity: O(k * n) for the memoization table.

Pros and Cons

Pros:
  • The logic is a direct translation of the problem statement, making it relatively straightforward to understand.
  • It correctly models the sequential, non-overlapping nature of transactions.
Cons:
  • The time complexity of O(k * n^2) is too slow for the given constraints and will likely result in a 'Time Limit Exceeded' error.

Video Solution

Watch the video walkthrough for Best Time to Buy and Sell Stock V



Patterns:

Dynamic Programming

Data Structures:

Array

Subscribe to Scale Engineer newsletter

Learn about System Design, Software Engineering, and interview experiences every week.

No spam, unsubscribe at any time.