vlambda博客
学习文章列表

动态规划二三事<一>


这是对动态规划的一些学习。


这篇主要是最简单的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



长按二维码关注,共同精进!