Algorithm routine seventeen - buying and selling stocks problem: state machine DP

Algorithm routine seventeen - buying and selling stocks problem: state machine DP

State machine DP is a problem solving method that applies dynamic programming method to finite state machine (Finite State Machine).
State Machine DP (State Machine DP) is a dynamic programming idea, which is usually used to solve some problems with state transition. In the state machine DP, we abstract the problem into a state machine, in which each state represents a sub-problem of the problem, and there is a state transition between each state, representing the process of transferring from one sub-problem to another. The state machine DP method is also suitable for problems involving dependencies among multiple sub-problems, such as string matching, sequence comparison, etc.
The problem of buying and selling stocks is a typical state machine DP problem. There are two states of "not holding stocks" and "holding stocks". Each node represents the maximum income in a certain state, and the edges between adjacent nodes represent The income obtained by performing a transaction in the current state.

insert image description here

Algorithm example 1: LeetCode122. The best time to buy and sell stocks II

You are given an integer array prices, where prices[i] represents the price of a certain stock on day i.
On each day, you can decide whether to buy and/or sell stocks. You can hold no more than one share of stock at any one time. You can also buy first and then sell on the same day.
Return the maximum profit you can make.
insert image description here

Method 1: Recursive + memory search

  1. Recursive function: dfs(i, hold)Indicates the maximum return obtained by holding stocks on the i-th day. Hold is true to indicate that there is a stock, and false to indicate that there is no stock
  2. Transfer equation:
    • if iiIf you hold stocks for i days, then the current maximum return comes from two possible transfers:
      • present i − 1 i - 1iIf you hold the stock for 1 day and choose not to sell: the income at this time isdfs ( i − 1 , T rue ) dfs(i - 1, True)dfs(i1,True)
      • present i − 1 i - 1iIf you do not hold stocks for 1 day and choose to buy: the return at this time isdfs ( i − 1 , F alse ) − prices [ i ] dfs(i - 1, False) - prices[i]dfs(i1,False)p r i ces [ i ] , current profit minus today's price.
      • Take the maximum value of the two as the transfer result.
    • if iiIf the stock is not held for i days, then the current maximum return is transferred from two possibilities:
      • present i − 1 i - 1iIf you do not hold the stock for 1 day and choose not to buy: the return at this time isdfs ( i − 1 , F alse ) dfs(i - 1, False)dfs(i1,False)
      • present i − 1 i - 1iIf you hold the stock for 1 day and choose to sell: the return at this time isdfs ( i − 1 , True ) + prices [ i ] dfs(i - 1, True) + prices[i]dfs(i1,True)+p r i ces [ i ] , funds are available because they do not currently have stock.
      • Take the maximum value of the two as the transfer result.
        insert image description here
  3. Boundary value: when everything is traversed, that is, i<0, if you hold stocks, return negative infinity (representing an illegal state); otherwise return 0
  4. Return value: dfs(n - 1, False)
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices) 
        @cache  # 使用缓存装饰器,加速递归函数计算
        # i 表示当前考虑到的股票价格下标,hold 表示当前是否持有股票
        def dfs(i: int, hold: bool) -> int:
            if i < 0:  # 如果已经遍历完所有股票价格
                return -inf if hold else 0  # 如果持有股票,返回负无穷(代表不合法状态);否则返回零利润。
            # 如果当前已经持有一支股票 
            if hold:      
                #卖或不卖    
                return max(dfs(i - 1, True), dfs(i - 1, False) - prices[i]) 
            #买或不买    
            return max(dfs(i - 1, False), dfs(i - 1, True) + prices[i])
        return dfs(n - 1, False) 

Method 2: Dynamic programming of two-dimensional dp array

Directly use the above recursive + memorized search code to convert to dynamic programming
dp[i][0] means that there is no stock in hand after the i-th day. dp[i][1] means that after the i-th day There is the maximum return of a stock, and finally returns dp[n][0], indicating the maximum return after selling all the stocks in hand on the last day

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)  
        dp = [[0] * 2 for _ in range(n + 1)]
        dp[0][1]=-inf
        for i, price in enumerate(prices):
            #当前没有股票,只能不买或卖
            dp[i+1][0]=max(dp[i][0],dp[i][1]+price)
             #当前有股票,只能买或不卖
            dp[i+1][1]=max(dp[i][0]-price,dp[i][1])
        return dp[n][0]

Algorithm Exercise 1: LeetCode714. The best time to buy and sell stocks includes handling fees

Given an integer array prices, where prices[i] represents the stock price on the i-th day; the integer fee represents the handling fee for trading stocks.
You can complete transactions an unlimited number of times, but you need to pay a transaction fee for each transaction. If you have already bought a stock, you cannot continue to buy another stock until you sell it.
Returns the maximum value of profit.
Note: A transaction here refers to the entire process of buying, holding and selling stocks, and you only need to pay a handling fee for each transaction.
insert image description here

The idea is the same as the example, except that not only the stock price but also the handling fee need to be subtracted when purchasing

func maxProfit(prices []int, fee int) int {
    
    
    n := len(prices)
    dp := make([][]int, n+1)
    for i := range dp {
    
    
        dp[i] = make([]int, 2)
    }
    dp[0][1] = -math.MaxInt32
    for i, price := range prices {
    
    
    	//不买或卖
        dp[i+1][0] = max(dp[i][0], dp[i][1]+price)
        //买并支付小费
        dp[i+1][1] = max(dp[i][0]-price-fee, dp[i][1])
        
    }
    return dp[n][0]
}
func max(x, y int) int {
    
    if x > y {
    
    return x};return y}

Algorithm exercise 2: LeetCode309. The best time to buy and sell stocks includes a freezing period

Given an integer array prices, where prices[i] represents the stock price on the i-th day. ​Design an algorithm to calculate the maximum profit. Under the following constraints, you can complete as many transactions as possible (buy and sell a stock multiple times):
After selling the stock, you cannot buy the stock the next day (that is, the freezing period is 1 day).
Note: You cannot participate in multiple transactions at the same time (you must sell the previous stock before buying again).
insert image description here

The only difference between this question and the example is that there is an additional freezing period, and this will only affect the situation of buying stocks. Due to the freezing period, when calculating the situation of buying stocks, only 2 days ago and no stocks in hand can be used , and there is no purchase on the previous day at the end of the first day, so we need to deal with the first day separately.

func maxProfit(prices []int) int {
    
    
    n := len(prices)
    dp := make([][]int, n+1)
    for i := range dp {
    
    
        dp[i] = make([]int, 2)
    }
    //-math.MaxInt32表示不符合现实
    dp[0][1] = -math.MaxInt32
    for i, price := range prices {
    
    
        //当前没有股票,只能不买或卖
        dp[i+1][0] = max(dp[i][0], dp[i][1]+price)
        //当前有股票,只能买或不卖
        if i>0{
    
    
        //i>0时,对于买的情况,由于冷冻期所以只能用dp[i-1][0]即2天前且手上没有股票的最大利润
        dp[i+1][1] = max(dp[i-1][0]-price, dp[i][1])
        }else {
    
    
            //i=0即第一天想要有股票只能买
            dp[i+1][1] =-price
        }
    }
    return dp[n][0]
}
func max(x, y int) int {
    
    if x > y {
    
    return x};return y}

Algorithm exercise 3: LeetCode123. The best time to buy and sell stocks III

Given an array whose ith element is the price of a given stock on day i.
Design an algorithm to calculate the maximum profit you can make. You can complete up to two transactions.
Note: You cannot participate in multiple transactions at the same time (you must sell the previous stock before buying again).
insert image description here

Recursive + memoized search

First think about recursion + memory search to develop ideas. It is easy to know that a parameter j can be added to the recursion to record the current number of traversals. The code is as follows

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        # 使用cache装饰器来实现记忆化搜索
        @cache
        def dfs(i: int, j: int, hold: bool) -> int:
            # 如果j小于0,表示交易次数已经用完,返回负无穷
            if j < 0:
                return -inf
            # 如果i小于0,表示已经到达第0天,如果持有股票,返回负无穷,否则返回0
            if i < 0:
                return -inf if hold else 0
            if hold:
                return max(dfs(i - 1, j, True), dfs(i - 1, j - 1, False) - prices[i])
            else:
                return max(dfs(i - 1, j, False), dfs(i - 1, j, True) + prices[i])
        return dfs(n - 1, 2, False)

dynamic programming

According to the above recursive process converted to dynamic programming, the recursive function adds a parameter, so we add the number of one-dimensional transactions in the dynamic array dp, where dp[i][j][0] represents the i-th day, and at most j transactions , and currently does not hold the maximum return on stocks, dp[i][j][1] represents the i-th day, at most j transactions, and currently holds the maximum return on stocks.

However, it should be noted that only when buying is counted as a new transaction, and selling is not counted as a new transaction

func maxProfit(prices []int) int {
    
    
    n := len(prices)
    dp := make([][][2]int, n+1)
    for i:=range dp{
    
    
        dp[i]=make([][2]int,3)
        for j:=0;j<3;j++{
    
    
            dp[i][j][1] = math.MinInt32/2
        }
    }
    for i:=0;i<n;i++{
    
    
        for j:=0;j<2;j++{
    
    
            //不买或卖
            dp[i+1][j+1][0]=max(dp[i][j+1][0],dp[i][j+1][1]+prices[i])
            //不卖或买,买即增加一次交易
            dp[i+1][j+1][1]=max(dp[i][j+1][1],dp[i][j][0]-prices[i])
        }
    }
    return dp[n][2][0]
}
func max(x, y int) int {
    
    if x > y {
    
    return x};return y}

Algorithm exercise 4: LeetCode188. The best time to buy and sell stocks IV

Given an integer array prices, its i-th element prices[i] is the price of a given stock on day i, and an integer k.
Design an algorithm to calculate the maximum profit you can make. You can complete at most k transactions. In other words, you can buy k times at most and sell k times at most.
Note: You cannot participate in multiple transactions at the same time (you must sell the previous stock before buying again).
insert image description here

Directly adopt the idea of ​​the above question, modify the range of j from 0-2 to 0-k

func maxProfit(k int, prices []int) int {
    
    
    n := len(prices)
    dp := make([][][2]int, n+1)
    for i:=range dp{
    
    
        dp[i]=make([][2]int,k+1)
        for j:=0;j<k+1;j++{
    
    
            dp[i][j][1] = math.MinInt32/2
        }
    }
    for i:=0;i<n;i++{
    
    
        for j:=0;j<k;j++{
    
    
            //不买或卖
            dp[i+1][j+1][0]=max(dp[i][j+1][0],dp[i][j+1][1]+prices[i])
            //不卖或买,买即增加一次交易
            dp[i+1][j+1][1]=max(dp[i][j+1][1],dp[i][j][0]-prices[i])
        }
    }
    return dp[n][k][0]
}
func max(x, y int) int {
    
    if x > y {
    
    return x};return y}

Algorithm Exercise 5: LeetCode1911. Maximum Subsequence Alternating Sum

The alternating sum of an array with indices starting at 0 is defined as the sum of elements at even indices minus the sum of elements at odd indices.
For example, the alternating sum of the array [4,2,5,3] is (4 + 5) - (2 + 3) = 4.
Given an array nums, please return the maximum alternating sum of any subsequence in nums (subscripts of subsequences start numbering from 0).
A subsequence of an array is an array in which the order of the remaining elements remains unchanged after some elements are deleted from the original array (or none of them are deleted). For example, [2,7,4] is a subsequence (bold elements) of [4,2,3,7,2,1,4], but [2,4,2] is not.
insert image description here

Analogous to buying and selling stocks, the difference is that the stock has been bought at 0 yuan at the beginning of this question, so the initialization array dp[0][1] = 0, dp[0][0] = math.MinInt64 / 2, and the rest is the same as the example idea Same

func maxAlternatingSum(nums []int) int64 {
    
    
    //类比买卖股票,即第一次0元买进,之后卖出
    n := len(nums)
    dp := make([][]int, n+1)
    for i := range dp {
    
    
        dp[i] = make([]int, 2)
    }
    //负无穷表示不符合题意,/2防止越界
    dp[0][0] =math.MinInt64 / 2
    //开始0元买进了股票
    dp[0][1] =0
    for i, num := range nums {
    
    
        dp[i+1][0] = max(dp[i][0], dp[i][1]+num)
        dp[i+1][1] = max(dp[i][0]-num, dp[i][1])
    }
    return int64(dp[n][0])
}
func max(x, y int) int {
    
    if x > y {
    
    return x};return y}

Guess you like

Origin blog.csdn.net/qq_45808700/article/details/130490058