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
数据范围:0≤n≤105,0≤val≤104
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天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
-
你可以多次买卖该只股票,但是再次购买前必须卖出之前的股票
-
如果不能获取收益,请返回0
-
假设买入卖出均无手续费
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天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
- 你最多可以对该股票有两笔交易操作,一笔交易代表着一次买入与一次卖出,但是再次购买前必须卖出之前的股票
- 如果不能获取收益,请返回0
- 假设买入卖出均无手续费
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天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
-
你最多可以对该股票有k笔交易操作,一笔交易代表着一次买入与一次卖出,但是再次购买前必须卖出之前的股票
-
如果不能获取收益,请返回0
-
假设买入卖出均无手续费
$$
1≤prices.length≤1000
$$
0 ≤ p r i c e s [ i ] ≤ 1000 0 \le prices[i] \le 1000 0≤prices[i]≤1000
1 ≤ k ≤ 100 1 \le k \le 100 1≤k≤100
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的题目全部做完。