Taro Logo

Best Time to Buy and Sell Stock with Transaction Fee

Medium
Apple logo
Apple
Topics:
ArraysDynamic ProgrammingGreedy Algorithms

You are given an array prices where prices[i] is the price of a given stock on the i<sup>th</sup> day, and an integer fee representing a transaction fee.

Find the maximum profit you can achieve. You may complete as many transactions as you like, but you need to pay the transaction fee for each transaction.

Note:

  • You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again).
  • The transaction fee is only charged once for each stock purchase and sale.

Example 1:

Input: prices = [1,3,2,8,4,9], fee = 2
Output: 8
Explanation: The maximum profit can be achieved by:
- Buying at prices[0] = 1
- Selling at prices[3] = 8
- Buying at prices[4] = 4
- Selling at prices[5] = 9
The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8.

Example 2:

Input: prices = [1,3,7,5,10,3], fee = 3
Output: 6

How would you go about implementing a function to solve this problem? Consider various edge cases and optimize for both time and space complexity.

Solution


Naive Approach (Brute Force)

The most straightforward approach is to explore all possible transaction combinations to find the maximum profit. This would involve recursively trying every buy and sell point, which quickly becomes computationally expensive.

Code (Illustrative - Not Efficient):

def max_profit_recursive(prices, fee, index, holding):
    if index == len(prices):
        return 0

    # Option 1: Do nothing
    profit = max_profit_recursive(prices, fee, index + 1, holding)

    if holding:
        # Option 2: Sell if holding stock
        profit = max(profit, prices[index] - fee + max_profit_recursive(prices, fee, index + 1, False))
    else:
        # Option 3: Buy if not holding stock
        profit = max(profit, -prices[index] + max_profit_recursive(prices, fee, index + 1, True))

    return profit


prices = [1, 3, 2, 8, 4, 9]
fee = 2
print(max_profit_recursive(prices, fee, 0, False))

Time Complexity: O(2^n), where n is the number of days (prices). Space Complexity: O(n) due to the recursion depth.

Optimal Approach (Dynamic Programming / Greedy)

A more efficient solution uses dynamic programming or a greedy approach to track the maximum profit at each day, considering whether we are holding a stock or not.

Explanation:

  1. Initialize cash to 0 (maximum profit if we don't hold stock) and hold to -prices[0] (maximum profit if we hold stock on the first day).
  2. Iterate through the prices array.
  3. Update cash and hold at each step:
    • cash = max(cash, hold + prices[i] - fee) (either keep the previous cash, or sell the stock today).
    • hold = max(hold, cash - prices[i]) (either keep holding the stock, or buy the stock today).
  4. Return cash as the maximum profit.

Code (Python):

def max_profit_optimal(prices, fee):
    cash = 0  # Max profit if we don't hold stock
    hold = -prices[0]  # Max profit if we hold stock

    for i in range(1, len(prices)):
        cash = max(cash, hold + prices[i] - fee)
        hold = max(hold, cash - prices[i])

    return cash


prices = [1, 3, 2, 8, 4, 9]
fee = 2
print(max_profit_optimal(prices, fee))

Time Complexity: O(n), where n is the number of days (prices). Space Complexity: O(1), constant space.

Edge Cases

  • Empty Prices Array: If the prices array is empty, the maximum profit is 0.
  • Zero Fee: If the fee is 0, the problem reduces to finding maximum profit with unlimited transactions.
  • Decreasing Prices: If prices are continuously decreasing, it might not be profitable to make any transactions.

Summary

The optimal solution leverages dynamic programming principles in a greedy manner to efficiently calculate the maximum profit. This approach provides a significant improvement over the brute-force method in terms of time complexity.