dp 30 - 33 买卖股票的最好时机1-4

dp 30 - 33 买卖股票的最好时机

此题在牛客和leetcode均存在。以下以牛客版本为主。

如下是leetcode链接:
买卖股票的最好时机
买卖股票的最好时机2
买卖股票的最好时机3
买卖股票的最好时机4

1
问题描述

假设你有一个数组prices,长度为n,其中prices[i]是股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益

1.你可以买入一次股票和卖出一次股票,并非每天都可以买入或卖出一次,总共只能买入和卖出一次,且买入必须在卖出的前面的某一天

2.如果不能获取到任何利润,请返回0

3.假设买入卖出均无手续费
数据范围: 0 ≤ n ≤ 1 0 5 , 0 ≤ v a l ≤ 1 0 4 数据范围: 0 \le n \le 10^5 , 0 \le val \le 10^4 数据范围:0n105,0val104

7
8 9 2 5 4 7 1
5

3
2 4 1
2

3
3 2 1
0
思路

要最大利润,只能买一次,那么就是在最低价的时候买,最高价的时候卖就行。

注意买入在卖出前,每次先更新最低价,再去更新最高价和最大利润即可。

代码如下:

#include <cstdio>

int main () {
    int n;
    scanf("%d", &n);
    int data, min = 0x7f7f7f7f, max = 0;
    for(int i = 0; i < n; ++i) {
        scanf("%d", &data);
        min = min > data ? data : min;
        max = data - min > max ? data - min : max;
    }
    printf("%d", max);
    return 0;
}
2
问题描述

假设你有一个数组prices,长度为n,其中prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益

  1. 你可以多次买卖该只股票,但是再次购买前必须卖出之前的股票

  2. 如果不能获取收益,请返回0

  3. 假设买入卖出均无手续费

7
8 9 2 5 4 7 1
7

5
5 4 3 2 1
0

5
1 2 3 4 5
4
思路

由于可以买卖无限次。所以只要股票涨了,我们就昨天买,今天卖。

完事了。

#include <stdio.h>

int main() {
    int n;
    scanf("%d", &n);
    if(n == 0) {
        printf("0");
        return 0;
    }
    int pre, now, sum = 0;
    scanf("%d", &pre);
    for(int i = 1; i < n; ++i) {
        scanf("%d", &now);
        if(now > pre) {
            sum += now - pre;
        }
        pre = now;
    }
    printf("%d", sum);
    return 0;
}
3
问题描述

假设你有一个数组prices,长度为n,其中prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益

  1. 你最多可以对该股票有两笔交易操作,一笔交易代表着一次买入与一次卖出,但是再次购买前必须卖出之前的股票
  2. 如果不能获取收益,请返回0
  3. 假设买入卖出均无手续费
6
8 9 3 5 1 3
4

4
9 8 4 1
0

5
1 2 8 3 8
12
思路

先确定状态。

由于最多只能买两次。

因此一共有如下5种状态。

0:一次都没有买卖

1:第一次买入

2:第一次卖出

3:第二次买入

4:第二次卖出

上述状态一一递进,0->1->2->3->4。

答案必然不在状态1和3。因为状态2 和 4 必然优于1 和3。

状态2 和 4相较于状态1和3,有卖出的收入。

同时答案也不在状态0和2。

因为,状态4必然包含上述状态。

对于状态0,状态2只需要在同一天买入和卖出就可达到。但是只要股票价格上涨过,状态2的获利就比状态0的获利多。

同理,对于状态2,状态4只需要在同一天买入和卖出就可达到。只要在达到状态2后,股票又出现过上涨,那么状态4优于状态2。

因此结果必然在状态4。

设定dp (i,j) 到第i天,第j种状态的最大获利。

对于第 i 天,的第 j 种状态,我们有两种决策,第一种是什么都不做,另外一种是进行相关操作,买股票的买入当前股票,卖的卖出。然后在这两种决策中选择获利更好的一种。

按照上述思路可得到如下代码:

#include <iostream>
#include <algorithm>

using namespace std;

int price[100000];
int dp[200005][5];

int main () {
    ios::sysnc_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    int n;
    cin >> n;
    for(int i = 0; i < n; ++i) {
        cin >> price[i];
    }
    
    dp[0][1] = -price[0];//买入股票的价格
    dp[0][3] = -price[0];
    
    for(int i = 1; i < n; ++i) {
        dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - price[i]);//买入 不操作
        dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + price[i]);//卖出 操作
        dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - price[i]);//同上
        dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + price[i]);//同上
    }
    cout << dp[n - 1][4];
    return 0;
}

此题还可以优化空间复杂度。不过降低了可读性。

别人的代码如下

#include<bits/stdc++.h>
using namespace std;
int maxProfit(vector<int>&prices){
    int n=prices.size();
    /*
    五种状态
    1.未进行任何操作
    2.只进行了一次买操作buy1
    3.完成了一次买卖操作sell1
    4.第二次买入buy2
    5.完成第二次买卖操作sell2
    */
    int buy1=-prices[0],sell1=0;
    int buy2=-prices[0],sell2=0;
    for(int i=1;i<n;i++){
        buy1=max(buy1,-prices[i]);
        sell1=max(sell1,buy1+prices[i]);
        buy2=max(sell1-prices[i],buy2);
        sell2=max(sell2,buy2+prices[i]);
    }
    return sell2;
}
int main(){
    int n;cin>>n;
    vector<int>prices(n);
    for(int i=0;i<n;i++)cin>>prices[i];
    cout<<maxProfit(prices);
}
4
问题描述

假设你有一个数组prices,长度为n,其中prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益

  1. 你最多可以对该股票有k笔交易操作,一笔交易代表着一次买入与一次卖出,但是再次购买前必须卖出之前的股票

  2. 如果不能获取收益,请返回0

  3. 假设买入卖出均无手续费

$$
1≤prices.length≤1000

$$

0 ≤ p r i c e s [ i ] ≤ 1000 0 \le prices[i] \le 1000 0prices[i]1000

1 ≤ k ≤ 100 1 \le k \le 100 1k100

6 3
8 9 3 5 1 3
5

8 2
3 2 5 0 0 3 1 4
7

4 4
9 8 4 1
0
思路

本题稍微将第三题的思路做下拓展就好了。

3题中买卖2次,这里买卖k次。

于是状态变为

0:没有买入和卖出

1:买入1次

2:卖出1次

2*k-1:买入k次

2*k :卖出k次

参考上述分析,我们可以知道结果在dp[n - 1] [2 * k]处。

巴拉巴拉,后面都是一致的。

代码如下:

#include <bits/stdc++.h>
using namespace std;

int maxProfit(int profits[], int n, int k) {
    //总共有多少种状态
    //什么都没有买
    //买了第1次 0
    //卖了第一次 1
    //...
    //买了第k次 2*k-2
    //卖了第k次 2*k-1
    //由于不存在手续费 因此 卖完股票后的收益必然高于买完股票后的收益 什么都没买的话 利润永远是0 
    int dp[n][2 * k];// 直到第i天 各种状态下的收益 
    //这里为了节约一点空间 没有考虑是什么都没买的时候 因为这种时候都是0 带来的代价就是初始化的时候很麻烦

    // 状态初始化
    for(int i = 0; i < n; ++i) {
        for(int j = 1; j < 2 * k; j+=2) {//因为在第一天 无论怎么卖都是0
            dp[i][j] = 0;
        }
        if(i == 0) {
            for(int j = 0; j < 2 * k; j+=2) {
                dp[0][j] = -profits[0];
            }
            continue;
        }
        for(int j = 0; j < 2 * k; j+=2) {
            dp[i][j] = max(dp[i-1][j], -profits[i]);//在第i天第j/2+1次买入 需要付出 profits[i]
        }
    }

    for(int i = 1; i < n; ++i) {
        for(int j = 1; j < 2 * k; ++j) {
            if(j%2) { // 奇数状态  卖出去
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] + profits[i]);//不操作 和 进行操作
            } else { // 偶数状态 买入
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] - profits[i]);//不操作和昨天利益保持一致 否则付出买入的代价
            }
        }
    }

    // for(int i = 0; i < n; ++i) {
    //     for(int j = 0; j < 2 * k; ++j) {
    //         cout << dp[i][j] << ' ';
    //     }
    //     cout << endl;
    // }

    // 在最后一天之后 看看那种情况下可以获得最大利润
    return dp[n-1][2 * k - 1];
}


int main() {
    int n, k;
    scanf("%d %d", &n, &k);
    int a[n];
    for(int i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
    }
    printf("%d", maxProfit(a, n, k));
    return 0;
}
// 64 位输出请用 printf("%lld")

至此,牛客动态规划专项中的线性dp的题目全部做完。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值