1、121. 买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
两种方法:动规和差分
1.1 动规
思路:状态转移方程
d
p
[
i
]
=
m
a
x
(
d
p
[
i
−
1
]
,
p
r
i
c
e
s
[
i
]
−
m
i
n
p
r
i
c
e
[
i
]
)
dp[i]=max(dp[i-1],prices[i]-minprice[i])
dp[i]=max(dp[i−1],prices[i]−minprice[i])
dp[i]表示前i天卖出的最大利润,=max(前i-1天的最大利润,第i天的价格-前i-1天的最低价格)
动态规划就是选和不选的问题,选第i天卖出的话就是max的第二种情况,不选就是max的第一种情况
所以还需要一个数组记录前i天的最低价格。
v
e
c
t
o
r
<
i
n
t
>
m
i
n
p
r
i
c
e
(
n
,
0
)
vector<int> minprice(n,0)
vector<int>minprice(n,0)
m
i
n
p
r
i
c
e
[
i
]
=
m
i
n
(
m
i
n
[
i
−
1
]
,
p
r
i
c
e
s
[
i
]
)
minprice[i]=min(min[i-1],prices[i])
minprice[i]=min(min[i−1],prices[i])
前i天的最低价格=min(前i-1天的最低价格,第i天的价格)
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
if(n<1)
return 0;
vector<int> dp(n,0);
vector<int> minprice(n,0);
minprice[0]=prices[0];
for(int i=1; i<n; i++)
{
minprice[i]=min(minprice[i-1],prices[i]);
dp[i]=max(dp[i-1],prices[i]-minprice[i]);
}
return dp[n-1];
}
};
当然,我们也可以优化时间复杂度
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
if(n<1)
return 0;
int dp=0;
int minprice=prices[0];//寻找到当前天的最低价格
for(int i=1; i<n; i++)
{
minprice=min(minprice,prices[i]);
dp=max(dp,prices[i]-minprice);
}
return dp;
}
};
1.2 差分
思路:对于一组数据
[
7
,
1
,
5
,
3
,
6
,
4
]
[7 , 1 , 5 ,3 , 6 , 4 ]
[7,1,5,3,6,4],我们求出差分数组为d=
[
−
6
,
4
,
−
2
,
3
,
−
2
]
[-6 , 4, -2 , 3, -2 ]
[−6,4,−2,3,−2],
d
[
i
]
=
p
r
i
c
e
s
[
i
+
1
]
−
p
r
i
c
e
s
[
i
]
d[i]=prices[i+1]-prices[i]
d[i]=prices[i+1]−prices[i]
直观的,在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润
=
p
r
i
c
e
s
[
4
]
−
p
r
i
c
e
s
[
1
]
=
6
−
1
=
5
=prices[4]-prices[1]= 6-1 = 5
=prices[4]−prices[1]=6−1=5 。
其实我们发现就是求差分数组的最大子段和,
d
[
3
]
+
d
[
2
]
+
d
[
1
]
=
4
+
(
−
2
)
+
3
=
5
d[3]+d[2]+d[1]=4+(-2)+3=5
d[3]+d[2]+d[1]=4+(−2)+3=5。
这是因为
p
r
i
c
e
s
[
4
]
−
p
r
i
c
e
s
[
1
]
=
p
r
i
c
e
s
[
4
]
−
p
r
i
c
e
s
[
3
]
+
p
r
i
c
e
s
[
3
]
−
p
r
i
c
e
s
[
2
]
+
p
r
i
c
e
s
[
2
]
−
p
r
i
c
e
s
[
1
]
=
d
[
3
]
+
d
[
2
]
+
d
[
1
]
prices[4]-prices[1]\\ =prices[4]-prices[3]+prices[3]-prices[2]+prices[2]-prices[1]\\ =d[3]+d[2]+d[1]
prices[4]−prices[1]=prices[4]−prices[3]+prices[3]−prices[2]+prices[2]−prices[1]=d[3]+d[2]+d[1]
所以,我们将求问题转换为求最大字段和的问题,而求数组的最大字段和在我们之前的博客已经提过。状态转移方程为
d
p
[
i
]
=
m
a
x
(
d
p
[
i
−
1
]
+
n
u
m
[
i
]
,
n
u
m
[
i
]
)
dp[i]=max(dp[i-1]+num[i],num[i])
dp[i]=max(dp[i−1]+num[i],num[i])
d
p
[
i
]
dp[i]
dp[i]代表着以i结尾的数组的最大子段和,最后我们再进行一次for 循环,找出dp数组中的最大值。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
//传入差分数组,求其最大子段和
int dp,predp=nums[0];
int Maxprice=nums[0];
for(int i=1;i<nums.size();i++)
{
dp=max(nums[i],predp+nums[i]);
Maxprice=max(Maxprice,dp);
predp=dp;
}
return Maxprice;
}
int maxProfit(vector<int>& prices) {
/*
买卖股票也可以采用差分
将其转换为差分数组求其最大子段和就是最大利润
*/
if(prices.size()==0||prices.size()==1)
return 0;
vector<int>b(prices.size()-1,0);
for(int i=0;i<prices.size()-1;i++)
{
b[i]=prices[i+1]-prices[i];
}
int n=maxSubArray(b);
return n<0? 0: n;
}
};
当然,和我们之前讲过的求最大子段和一样,也可以进行优化。我们注意到在计算最大字段和的函数中只用到了当前的dp[i-1],所以还可以进行优化,边计算差分遍计算最大字段和
int maxProfit(vector<int>& prices) {
int dp,predp=nums[0];
int Maxprice=nums[0];
for(int i=1;i<nums.size();i++)
{
int b=prices[i]-prices[i-1];
dp=max(b,predp+b);
Maxprice=max(Maxprice,dp);
predp=dp;
}
return Maxprice;
}
2、122. 买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票。
思路:股票问题2与股票问题1的主要区别在于用户可以多次买卖,这里我们还是借助差分数组来做,计算完差分数组,将差分数组中为正数的元素加起来,就是答案。
直观的,我们知道,当
第2天买(价格为1),第4天卖(价格为8) 8-1=7
第5天买(价格为3),第6天卖(价格为6) 6-3=3
最大利润为7+3=10
其实就是我们的差分数组为正数的元素的和 4+3+3=10
股票买卖策略:
1、单独交易日: 设今天价格
p
1
p_1
p1,明天价格
p
2
p_2
p2,则今天买入、明天卖出可赚取金额
p
2
−
p
1
p_2 - p_1
p2−p1。
2、 连续上涨交易日: 设此上涨交易日股票价格分别为
p
1
,
p
2
,
.
.
.
,
p
n
p_1,p_2,...,p_n
p1,p2,...,pn,则最大价值为
p
n
−
p
1
p_n-p_1
pn−p1。
p
n
−
p
1
=
p
n
−
p
n
−
1
+
p
n
−
1
−
p
n
−
2
+
.
.
.
+
p
2
−
p
1
=
d
[
n
−
1
]
+
.
.
.
d
[
n
]
p_n-p_1=p_n-p_{n-1}+p_{n-1}-p_{n-2}+...+p_2-p_1\\ =d[n-1]+...d[n]
pn−p1=pn−pn−1+pn−1−pn−2+...+p2−p1=d[n−1]+...d[n].
3、 连续下降交易日: 则不买卖收益最大,即不会亏钱。
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<int> d(prices.size()-1,0);
int ans=0;
for(int i=0;i<prices.size()-1;i++)
{
d[i]=prices[i+1]-prices[i];
}
for(auto e:d)
{
if(e>0)
{
ans+=e;
}
}
return ans;
}
};
常数优化,边计算差分数组边判断
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ans=0;
for(int i=0;i<prices.size()-1;i++)
{
if((prices[i+1]-prices[i])>0)
ans+=(prices[i+1]-prices[i]);
}
return ans;
}
};