动态规划二三事<一>
这是对动态规划的一些学习。
这篇主要是最简单的DP和股票问题。
走起~
LeetCode70 爬楼梯:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
这道题有个小问题,我一开始想用递归解决(如下),但是递归这种方法效率并不高,时间复杂度高达O(2^n),多半要超过时间限制。
class Solution {
public:
int climbStairs(int n) {
if(n<1) return 0;
if(n==1) return 1;
else if(n==2) return 2;
return climbStairs(n-1)+climbStairs(n-2);
}
};
用DP循环解决:
class Solution {
public:
int climbStairs(int n) {
if(n<=1) return n;
int *dp=new int [n+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
(偏一下题)再仔细观察发现是斐波那契模样的数列,利用其性质空间复杂度下降到O(1):
public class Solution {
public int climbStairs(int n) {
if (n == 1) {
return 1;
}
int first = 1;
int second = 2;
for (int i = 3; i <= n; i++) {
int third = first + second;
first = second;
second = third;
}
return second;
}
}
LeetCode53 最大子序和:
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
一般DP:时间复杂度O(N),空间复杂度O(N)
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int size=nums.size(),res=nums[0];
vector<int> dp(size);
dp[0]=nums[0];//dp[i]用于记录nums[i]结尾的最大子串和
for(int i=1;i<size;i++)
{
dp[i]=max(dp[i-1]+nums[i],nums[i]);
res=max(dp[i],res);
}
return res;
}
};
股票问题(方法来自labuladong):
定义dp:dp记录状态:dp[天数i][交易数k][是否持有股票0或1]
dp[i][k][0]=max(dp[i-1][k][0],dp[i-1][k][1]+prices[i])
即:前一天没持有股票,这一天不进行操作,故还是没持有;
前一天持有股票,这一天进行操作,故今天就不持有股票,这个dp是前一天现金加上今天卖出股票获得的现金;
dp[i][k][1]=max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i])
即:前一天持有股票,这一天不进行操作,故还是持有;
前一天不持有股票,这一天进行操作,故今天就持有股票,这个dp是前一天现金减去今天买入股票支付的现金;
总结方程:
Base Case:
不持有股票现金为0:dp[i][0][0]=0
一开始持有股票为不可能情况,用无穷小值表示:dp[i][0][1]=INT_MIN
状态转移:
dp[i][k][0]=max(dp[i-1][k][0],dp[i-1][k][1]+prices[i])
dp[i][k][1]=max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i])
LeetCode 121 买卖股票的最佳时机:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
由于只进行一次买卖,所以k可去掉。虽然这种方法在这道题目里面效率并不高,但是简单粗暴是不错的。
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.empty()) return 0;
int size=prices.size();
vector<vector<int> > dp(size,vector<int>(2));
dp[0][1]=-prices[0];
for(int i=1;i<size;i++)
{
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1]=max(dp[i-1][1],-prices[i]);
}
return dp[size-1][0];
}
};
把数组优化掉。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
int dp_i_0=0,dp_i_1=INT_MIN;
for(int i=0;i<n;i++)
{
dp_i_0=max(dp_i_0,dp_i_1+prices[i]);
dp_i_1=max(dp_i_1,-prices[i]);
}
return dp_i_0;
}
};
LeetCode 309 最佳买卖时机含冷冻期:
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
与上一题一样,再增加一个变量保存上一次操作后剩的现金。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
int dp_i_0=0,dp_i_1=INT_MIN;
int dp_pre_0=0;
for(int i=0;i<n;i++)
{
int t=dp_i_0;
dp_i_0=max(dp_i_0,dp_i_1+prices[i]);
dp_i_1=max(dp_i_1,dp_pre_0-prices[i]);
dp_pre_0=t;
}
return dp_i_0;
}
};
LeetCode 714 最佳买卖时机含手续费:
给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每次交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
万变不离其宗,干就是了:
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int dp_i_0=0,dp_i_1=-50005,n=prices.size();
for(int i=0;i<n;i++)
{
dp_i_0=max(dp_i_0,dp_i_1+prices[i]-fee);
dp_i_1=max(dp_i_1,dp_i_0-prices[i]);
}
return dp_i_0;
}
};
LeetCode 714 最佳买卖最佳时机(k=2):
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
加入k状态(就是多一个维度)
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.empty()) return 0;
int n=prices.size();
vector<vector<vector<int> > > dp(n, vector<vector<int> >(3, vector<int>(2)));
for(int i=0;i<n;i++)
{
for(int k=1;k<=2;k++)
{
if(i-1==-1)
{
dp[i][k][0]=0;
dp[i][k][1]=-prices[i];
continue;
}
dp[i][k][0]=max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);
dp[i][k][1]=max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);
}
}
return dp[n-1][2][0];
}
};
LeetCode 188 最佳买卖最佳时机(k任意值):
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
做的时候遇到好多bug,最要命的是有一个k=1000000的数据,刚开始的报错都没看懂。最后最主要的问题在于:如果k>n/2,那么k就会有多余的。做了很多不必要的计算。
class Solution {
public:
int maxProfit_lnf(vector<int>& prices) {
int n=prices.size();
int dp_i_0=0,dp_i_1=INT_MIN;
for(int i=0;i<n;i++)
{
int t=dp_i_0;
dp_i_0=max(dp_i_0,dp_i_1+prices[i]);
dp_i_1=max(dp_i_1,t-prices[i]);
}
return dp_i_0;
}
int maxProfit(int k, vector<int>& prices) {
if(prices.empty()) return 0;
int n=prices.size();
if(k>n/2)
{
return maxProfit_lnf(prices);
}
k=min(k,n/2);
vector<vector<vector<int> > > dp(n, vector<vector<int> >(k+1, vector<int>(2)));
for(int i=0;i<n;i++)
{
for(int t=1;t<=k;t++)
{
if(i-1==-1)
{
dp[i][t][0]=0;
dp[i][t][1]=-prices[i];
continue;
}
dp[i][t][0]=max(dp[i-1][t][0],dp[i-1][t][1]+prices[i]);
dp[i][t][1]=max(dp[i-1][t][1],dp[i-1][t-1][0]-prices[i]);
}
}
return dp[n-1][k][0];
}
};
这次暂时策到这里,虽然是学习心得。。。但写下来还是好累。。。
图片授权:www.unsplash.com
长按二维码关注,共同精进!