490, the best time to buy and sell stocks with dynamic programming and two-pointer solution

Insert picture description here

If you want to see more algorithm questions, you can scan the QR code above to follow my WeChat official account " Data Structure and Algorithms ". Up to now, I have updated more than 500 algorithm questions in the official account , some of which have been sorted into pdf documents. , As of now, there are more than 800 pages in total (and will continue to increase), you can reply to the keyword "pdf" in the official account to download.


Problem Description

Given an array, its i-th element is the price of a given stock on the i-th day .

If you are only allowed to complete one transaction at most (that is, buy and sell a stock at once), design an algorithm to calculate the maximum profit you can make.

Note: You cannot sell stocks before buying them.


Example 1:

Input: [7,1,5,3,6,4]

Output: 5

Explanation: Buy on the 2nd day (stock price = 1) and sell on the 5th day (stock price = 6). Maximum profit = 6-1 = 5.

Note that the profit cannot be 7-1 = 6, because the selling price needs to be greater than the buying price; at the same time, you cannot sell the stock before buying.

Example 2:

Input: [7,6,4,3,1]

Output: 0

Explanation: In this case, no transaction is completed, so the maximum profit is 0.


1. Dynamic planning solution

This question is to achieve the maximum profit obtained from an exchange. First of all, let’s take a look at how to solve it using dynamic programming. Dynamic programming is still the common steps.


  1. Confirm status
  2. Find the transfer formula
  3. Determine initial conditions and boundary conditions
  4. Calculation results

Let's define a two-dimensional array dp[length][2], where dp[i][0] represents the maximum profit of not holding stocks at the end of day i+1 (i starts from 0) , dp[ i][1] represents the maximum profit of holding stocks at the end of the i+1 day.


If we require the maximum profit dp[i][0] without holding stocks at the end of the i+1 day , there will be two situations.

The first situation is that on the i+1 day we neither bought nor sold, then the maximum profit is the maximum profit dp[i-1][0] of the i-th day without holding the stock.

The second situation is that we sold a stock on day i+1, then the maximum profit is the maximum profit of the stock held on day i ( this is negative, and it may not be held on day i, there is It may have been held before the i day ) plus the maximum profit of selling the stock on the i+1 day, dp[i-1][1]+prices[i].


Obviously we can get

dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);


In the same way, we can get the maximum profit of the stock we hold at the end of the i+1 day

dp[i][1]=max(dp[i-1][1],-prices[i]);


The boundary condition is that on day 1, if we don’t hold stocks, then

dp[0][0]=0;

If you hold stocks, then

dp[0][1]=-prices[0];


With boundary conditions and recurrence formulas, the code is easy to write, take a look at the code

public int maxProfit(int[] prices) {
    
    
    if (prices == null || prices.length == 0)
        return 0;
    int length = prices.length;
    int[][] dp = new int[length][2];
    //边界条件
    dp[0][0]= 0;
    dp[0][1] = -prices[0];
    for (int i = 1; i < length; i++) {
    
    
        //递推公式
        dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
        dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
    }
    //毋庸置疑,最后肯定是手里没持有股票利润才会最大,也就是卖出去了
    return dp[length - 1][0];
}

2. Code optimization

We see that the calculation of the maximum profit of the day in the above two-dimensional array is only related to the profit of the previous day , so there is no need to use a two-dimensional array. You only need to use two variables. The maximum profit without holding stocks on the day, the code is as follows.

public int maxProfit(int[] prices) {
    
    
    if (prices == null || prices.length == 0)
        return 0;
    int length = prices.length;
    int hold = -prices[0];//持有股票
    int noHold = 0;//不持有股票
    for (int i = 1; i < length; i++) {
    
    
        //递推公式
        noHold = Math.max(noHold, hold + prices[i]);
        hold = Math.max(hold, -prices[i]);
    }
    //毋庸置疑,最后肯定是手里没持有股票利润才会最大,
    //也就是卖出去了
    return noHold;
}

3. Double pointer solution

We can also use two pointers. One pointer records the minimum value visited (note that this is the minimum value visited), and the other pointer keeps going backwards, and then calculate their difference and save the largest value. Example 1 is an example to draw a picture to see

Insert picture description here
Insert picture description here

The principle is relatively simple, look at the code

public static int maxProfit(int[] prices) {
    
    
    if (prices == null || prices.length == 0)
        return 0;
    int maxPro = 0;//记录最大利润
    int min = prices[0];//记录数组中访问过的最小值
    for (int i = 1; i < prices.length; i++) {
    
    
        min = Math.min(min, prices[i]);
        maxPro = Math.max(prices[i] - min, maxPro);
    }
    return maxPro;
}

4. Monotonic stack solution

The principle of monotonic stack solution is very simple. We must always keep the top element of the stack as the smallest element that has been visited . If the current element is smaller than the top element of the stack, the top element will be popped from the stack and the current element will be pushed onto the stack. If the accessed element is greater than the top element of the stack, the difference between it and the top element of the stack must be calculated, and we can record the largest one. The code is as follows.

public int maxProfit(int[] prices) {
    
    
    if (prices == null || prices.length == 0)
        return 0;
    Stack<Integer> stack = new Stack<>();
    stack.push(prices[0]);
    int max = 0;
    for (int i = 1; i < prices.length; i++) {
    
    
        //如果栈顶元素大于prices[i],那么栈顶元素出栈,
        //把prices[i]压栈,要始终保证栈顶元素是最小的
        if (stack.peek() > prices[i]) {
    
    
            stack.pop();
            stack.push(prices[i]);
        } else {
    
    
            //否则如果栈顶元素不大于prices[i],就要计算
            //prices[i]和栈顶元素的差值
            max = Math.max(max, prices[i] - stack.peek());
        }
    }
    return max;
}

A closer look will understand that this solution is actually another implementation of double pointers, but the double pointer uses a variable to record the minimum value visited, and here is the stack record.


5. Refer to the largest subsequence sum

I just talked about the problem of maximum suborder sum. If you don’t understand, you can look at 486. Dynamic programming solves the maximum suborder sum . For today’s problem, you can refer to the 486th problem solving idea.


Assuming that the value of the array is [a,b,c,d,e,f], we subtract the last value from the previous value of the array, and the new array is as follows

[b-a,c-b,d-c,e-d,f-e]

We can find a rule by adding up several consecutive numbers in the new array , that is, the numbers in the middle can be reduced. For example, the sum of the first to fourth numbers in the new array is

b-a+c-b+d-c+e-d=e-a。


Let's take a look at the new array obtained in Example 1. The continuous maximum value is

4+(-2)+3=5。

Insert picture description here

The code is much simpler if you understand the above principle. The code for the largest subsequence sum we just talked about is as follows

public int maxSubArray(int[] num) {
    
    
    int length = num.length;
    int cur = num[0];
    int max = cur;
    for (int i = 1; i < length; i++) {
    
    
        cur = Math.max(cur, 0) + num[i];
        //记录最大值
        max = Math.max(max, cur);
    }
    return max;
}

Then let's revise him a bit, which is the answer to today's question.

public int maxProfit(int[] prices) {
    
    
    if (prices == null || prices.length == 0)
        return 0;
    int length = prices.length;
    int cur = 0;
    int max = cur;
    for (int i = 1; i < length; i++) {
    
    
        //这地方把prices[i]改为 prices[i] - prices[i - 1]即可
        cur = Math.max(cur, 0) + prices[i] - prices[i - 1];
        //记录最大值
        max = Math.max(max, cur);
    }
    return max;
}

6. Violent solution

This is a pairwise comparison, just save the calculated maximum value, there is nothing to say, although simple, but the efficiency is very poor, look at the code

public int maxProfit(int[] prices) {
    
    
    if (prices == null || prices.length == 0)
        return 0;
    int maxPro = 0;
    for (int i = 0; i < prices.length; i++) {
    
    
        for (int j = i + 1; j < prices.length; j++) {
    
    
            maxPro = Math.max(maxPro, prices[j] - prices[i]);
        }
    }
    return maxPro;
}

7, summary

There are many solutions to this problem, so we won't talk about violent solutions. In addition to violent solutions, double pointers should be the easiest to think of. Among them, referring to the largest sub-order and solving is also a more ingenious way of solving problems.

Guess you like

Origin blog.csdn.net/abcdef314159/article/details/112298894