买卖股票的最佳时期
Best Time to Buy and Sell Stock I
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票
示例 1:
输入: [7,1,5,3,6,4] 输出: 5 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1] 输出: 0 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
分析:
贪心法,分别找到价格最低和最高的一天,低进高出,注意最低的一天要在最高的一天之前。
把原始价格序列变成差分序列,本题也可以做是最大 m 子段和, m = 1。
int maxProfit(vector<int>& prices) { //现在最低的时候买入,再在最高的时候卖掉 /*把原始价格序列变成差分序列,本题也可以做是最大 m 子段和, m = 1。*/ if(prices.size()<2) return 0; int profit=0; int cur_min=prices[0]; for(int i=1;i<prices.size();++i){ profit=max(profit, prices[i]-cur_min); cur_min=min(cur_min, prices[i]); } return profit;
}
买卖股票的最佳时机 II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
分析:
贪心法,低进高出,把所有正的价格差价相加起来。
把原始价格序列变成差分序列,本题也可以做是最大 m 子段和, m = 数组长度。
int maxpfrofit2(vector<int> prices) { int sum = 0; for (int i = 1; i < prices.size(); ++i){ if (prices[i] - prices[i - 1]>0) sum += prices[i] - prices[i - 1]; } return sum; }
买卖股票的最佳时机 III
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
分析:
设状态 f(i) ,表示区间[0, i](0 ≤ i ≤ n - 1)的最大利润,状态 g(i) ,表示区间[i, n - 1](0 ≤ i ≤ n - 1)的最大利润,则最终答案为
max {f(i) + g(i)} , 0 ≤ i ≤ n - 1。
允许在一天内买进又卖出,相当于不交易,因为题目的规定是最多两次,而不是一定要两次。将原数组变成差分数组,本题也可以看做是最大 m 子段和, m=2
以第i天为分界线,计算第i天之前进行一次交易的最大收益preProfit[i],和第i天之后进行一次交易的最大收益postProfit[i],最后遍历一遍,max{preProfit[i]
+ postProfit[i]} (0≤i≤n-1)就是最大收益。
方法一:
int maxProfit(vector<int>& prices) { //其实也就是找到两个最大子数组和,和I题差不多。 //先求出第一个最大子数组,避开第一个最大子数组的情况下,再求出第二大子数组,再相加即可。 if(prices.size()<2) return 0; int n=prices.size(); vector<int> maxprofit1(n,0), maxprofit2(n,0); int maxprofit=0; for(int i=1, cur_min1=prices[0]; i<n; ++i){ cur_min1=min(cur_min1, prices[i]); maxprofit1[i]=max(maxprofit1[i-1], prices[i]-cur_min1); } for(int i=n-2, cur_max2=prices[n-1]; i>=0; --i){ cur_max2=max(cur_max2, prices[i]); maxprofit2[i]=max(maxprofit2[i], cur_max2-prices[i]); } for(int i=0;i<n;++i) maxprofit=max(maxprofit, maxprofit1[i]+maxprofit2[i]); return maxprofit; }
方法二:
int maxProfit(vector<int>& prices) {
//buy1:买第一次手里的钱;sell1卖出去一次手里的钱 = max(buy1+卖出当前股票价格的钱,上一轮卖出第一笔股票后手里剩的钱);
//buy2在该价格买入第二次手里的钱 = max(上一轮买入第二笔股票后手里剩的钱,sell1-当前点股票价格)
//sell2在该价格卖出第二次后手里剩的钱 = max(上一轮卖出第二笔股票后手里剩的钱, buy2+当前股票价格)
int buy1=INT_MIN, buy2=INT_MIN; int sell1=0,sell2=0; for(int i=0;i<prices.size();i++){ sell2=max(sell2, buy2+prices[i]); buy2=max(buy2, sell1-prices[i]); sell1=max(sell1, buy1+prices[i]); buy1=max(buy1, -prices[i]); } return sell2; }
买卖股票的最佳时机 IV
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)
分析:
用一个局部最优解和全局最有解表示到第i天进行j次的收益,特殊的动态规划。local[i][j]为在到达第i天时最多可进行j次交易并且最后一次交易在最后一天卖出的最大利润,此为局部最优。定义global[i][j]为在到达第i天时最多可进行j次交易的最大利润,
此为全局最优。他们的递推公式为:diff = prices[i]-prices[i-1]
local[i][j] = max(global[i - 1][j - 1] + max(diff, 0), local[i - 1][j] + diff)
global[i][j] = max(local[i][j], global[i - 1][j])
关于 local 的状态转移方程,取下面二者中较大的一个:
- 全局前 i-1 天进行了 j-1 次交易,然后然后加上今天的交易产生的利润(如果赚钱就交易,不赚钱就不交易,什么也不发生,利润是0)
- 局部前 i-1 天进行了 j 次交易,然后加上今天的差价(local[i-1][j]是第 i-1 天卖出的交易,它加上diff后变成第 i 天卖出,并不会增加交易次数。无论 diff 是正还是负都要加上,否则就不满足 local[i][j] 必须在最后一天卖出的条件了)
local[i][j]意味着在第i天一定有交易(卖出)发生,当第i天的价格高于第i-1天(即diff > 0)时,那么可以把这次交易(第i-1天买入第i天卖出)跟第i-1天的交易(卖出)合并为一次交易,
即local[i][j]=local[i-1][j]+diff;当第i天的价格不高于第i-1天(即diff<=0)时,那么local[i][j]=global[i-1][j-1]+diff,而由于diff<=0,所以可写成local[i][j]=global[i-1][j-1]
global[i][j]就是我们所求的前i天最多进行k次交易的最大收益,可分为两种情况:如果第i天没有交易(卖出),那么global[i][j]=global[i-1][j];如果第i天有交易(卖出),那么global[i][j]=local[i][j]。
- 动规所用的二维辅助数组可以降为一维的,即只用大小为k的一维数组记录到达第i天时的局部最优解和全局最优解。需要注意的是,由于第i天时交易k次的最优解依赖于第i-1天时交易k-1次的最优解,
所以数组更新应当从后往前(即从k到1)更新。
买卖股票的最佳时期带有一天的冷却期
维护三个一维数组buy, sell,和rest。其中:
buy[i]表示在第i天之前最后一个操作是买,此时的最大收益。
sell[i]表示在第i天之前最后一个操作是卖,此时的最大收益。
rest[i]表示在第i天之前最后一个操作是冷冻期,此时的最大收益。
我们写出递推式为:
- buy[i] = max(rest[i-1] - price, buy[i-1])
- sell[i] = max(buy[i-1] + price, sell[i-1])
- rest[i] = max(sell[i-1], buy[i-1], rest[i-1])
上述递推式很好的表示了在买之前有冷冻期,买之前要卖掉之前的股票。一个小技巧是如何保证[buy, rest, buy]的情况不会出现,这是由于buy[i] <= rest[i], 即rest[i] = max(sell[i-1], rest[i-1]),这保证了[buy, rest, buy]不会出现。
另外,由于冷冻期的存在,我们可以得出rest[i] = sell[i-1],这样,我们可以将上面三个递推式精简到两个:
- buy[i] = max(sell[i-2] - price, buy[i-1])
- sell[i] = max(buy[i-1] + price, sell[i-1])
由于i只依赖于i-1和i-2,所以我们可以在O(1)的空间复杂度完成算法:
- pre_buy = buy;【因为buy是上一轮的,此句含义:pre_buy = buy[i-1]】
- buy = max(pre_buy, pre_sell - prices[i]);【此句含义:buy[i]即buy = max(buy[i-1]即pre_buy,sell[i-2]【尚未更新的即上一轮的pre_sell】 - prices[i])】
- pre_sell = sell;【因为sell是上一轮的,此句含义:sell[i-1] = sell】
- sell = max(pre_sell, pre_buy + prices[i]);【此句含义:sell[i]即sell = max(sell[i-1]即pre_sell,buy[i-1]【已更新为pre_buy了】 + prices[i])】
int maxProfit(vector<int>& prices) { int n=prices.size(); if(n<2) return 0; int buy=-prices[0], pre_buy=INT_MIN,sell=0,pre_sell=0; for(int i=1;i<n;i++){ pre_buy=buy; buy=max(pre_buy, pre_sell-prices[i]); pre_sell=sell; sell=max(pre_sell, pre_buy+prices[i]); } return sell; }
买卖股票的最佳时期带有K天的冷却期(ali的笔试题)