题目描述
给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),
设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
输入格式
第一行包含整数 N,表示数组长度。
第二行包含 N 个不大于 109 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
数据范围
1≤N≤105,
输入样例1:
6
7 1 5 3 6 4
输出样例1:
5
输入样例2:
5
7 6 4 3 1
输出样例2:
0
样例解释
样例1:在第 2 天(股票价格 = 1)的时候买入,
在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为你不能在买入股票前卖出股票。
样例2:在这种情况下, 不进行任何交易, 所以最大利润为 0。
算法1
遍历每天的股票价格 并且记录一直到当前最低的股票价格。
将最低股票价格与当前价格差比较一下,记录差值最大的(当然是低买高卖的差值,不然亏大了)
C++ 代码
// Acwing1054.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
/*
数据范围
1≤N≤105,
输入样例1:
6
7 1 5 3 6 4
输出样例1:
5
输入样例2:
5
7 6 4 3 1
输出样例2:
0
*/
const int N = 100010;
int n;
int arr[N];
void Method1()
{
cin >> n;
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
int ans = 0;
int buyprice = arr[0];
for (int i = 0; i < n; i++) {
if (buyprice > arr[i]) {
buyprice = arr[i];
}
ans = max(ans, arr[i] - buyprice);
}
cout << ans << endl;
return;
}
//第N天 已经交易X次(以sell为1次) 当前是否持有股票(0否1真)
//表示这种情况下的最大收益
int dp[N][2][2];
void Method2()
{
cin >> n;
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
dp[0][0][1] = -arr[0]; //第一天买入 花费了第一天股票的价格
dp[0][0][0] = 0; //初始情况
dp[0][1][1] = -99999999;
dp[0][1][0] = -99999999;
for (int i = 1; i < n; i++) {
dp[i][0][0] = dp[i - 1][0][0];
dp[i][0][1] = max(dp[i - 1][0][1], dp[i - 1][0][0] -arr[i]);
dp[i][1][0] = max(dp[i - 1][1][0], dp[i - 1][0][1] + arr[i]);
dp[i][1][1] = -9999999;
}
cout << max(dp[n - 1][1][0], 0);
return ;
}
int main()
{
Method1();
}
算法2
由于该股票是一个系列,
大牛们总结出一个可以通用的动态规划转移方程来解决这一系列问题。
本题是第一题,所以使用通用的动态规划转移方程看起来比较臃肿,但是也是值得学习的
使用一个dp[x][y][z]数组来表示某种情况下的收益情况
x表示是第x天,y表示当前已经经过了第几次交易(交易以买进卖出两个操作为一次)
z使用0或者1来表示当前是否持有股票。
这样的状态有一个疑问,如果z为1,怎么知道是持有哪天的股票呢
可以这样解决 在dp[x][y][z]的第X天中如果z不为1 则可以进行购买股票
那么dp[x][y][z] = dp[x][y][1]-prices[i]. 从收益中减去买入的股票价格即可
状态转移方程如下
//当前第i天交易0次并持有股票的状态 是从前一天的交易0次持有股票转化而来
//或者是从前一天交易0次未持有股票买进了当前的股票转化而来。
dp[i][0][1] = max(dp[i - 1][0][1], dp[i - 1][0][0] -prices[i]);
//当前第i天交易1次并未持有股票的状态 是从前一天交易1次未持有股票转化而来
//或者是从前一天交易1次持有股票以当天价格卖掉状态转化
dp[i][1][0] = max(dp[i - 1][1][0], dp[i - 1][0][1] + prices[i]);
dp的边界和初始化
dp[0][0][1] = -arr[0]; //第一天买入 花费了第一天股票的价格
dp[0][0][0] = 0; //初始情况
dp[0][1][1] = -99999999;//理论上不存在第一天就交易了一次还持有股票的情况
dp[0][1][0] = -99999999;//理论上不存在第一天就交易了一次的情况
这个办法看似臃肿,但是轻松扩展到卖买两次和买卖K次的限制的情况,也就是可以解决另外两题
C++ 代码
// Acwing1054.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
/*
数据范围
1≤N≤105,
输入样例1:
6
7 1 5 3 6 4
输出样例1:
5
输入样例2:
5
7 6 4 3 1
输出样例2:
0
*/
const int N = 100010;
int n;
int arr[N];
void Method1()
{
cin >> n;
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
int ans = 0;
int buyprice = arr[0];
for (int i = 0; i < n; i++) {
if (buyprice > arr[i]) {
buyprice = arr[i];
}
ans = max(ans, arr[i] - buyprice);
}
cout << ans << endl;
return;
}
//第N天 已经交易X次(以sell为1次) 当前是否持有股票(0否1真)
//表示这种情况下的最大收益
int dp[N][2][2];
void Method2()
{
cin >> n;
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
dp[0][0][1] = -arr[0]; //第一天买入 花费了第一天股票的价格
dp[0][0][0] = 0; //初始情况
dp[0][1][1] = -99999999;
dp[0][1][0] = -99999999;
for (int i = 1; i < n; i++) {
dp[i][0][0] = dp[i - 1][0][0];
dp[i][0][1] = max(dp[i - 1][0][1], dp[i - 1][0][0] -arr[i]);
dp[i][1][0] = max(dp[i - 1][1][0], dp[i - 1][0][1] + arr[i]);
dp[i][1][1] = -9999999;
}
cout << max(dp[n - 1][1][0], 0);
return ;
}
int main()
{
Method2();
}