一、买卖股票的最佳时机
虽然这题标记的是简单,个人感觉还是挺难的。。。也许是我太菜了吧,找了题解才会做,看了大佬的题解后,发现这是一种题型,真是醍醐灌顶。觉得应该总结一下,作为自己的刷题笔记。下面进入正文。
老司机一眼可以看出,这种求差问题可以转换成区间和的问题(emmmmm,我肯定看不出来)。为什么呢?因为我们有牛顿莱布尼茨公式:
∫ a b f ( x ) d x = F ( x ) ∣ a b = F ( b ) − F ( a ) \qquad\qquad\qquad\int_a^bf(x)dx=F(x)|_a ^b=F(b)-F(a) ∫abf(x)dx=F(x)∣ab=F(b)−F(a)
在上面的公式中, F ( ) F() F()表示的数组称为前缀和,其实也就是题目所给的当天股票价格,最大的区间和可用动态规划求解,公式如下:
d p [ i ] = m a x ( 0 , d p [ i − 1 ] + d i f f [ i ] ) \qquad\qquad\qquad dp[i]=max(0, dp[i-1]+diff[i]) dp[i]=max(0,dp[i−1]+diff[i])
其中, d p [ i ] dp[i] dp[i]表示在不亏钱的前提下,第 i i i天卖出股票所赚的钱,若 d p [ i ] dp[i] dp[i]等于0了,则说明第 i i i天卖股票亏了,将 d p [ i ] dp[i] dp[i]重新置0。 d i f f diff diff数组表示相邻两天股票价格的差
int maxProfit(vector<int>& prices) {
if(prices.size() < 2) return 0;//股票只有一天,不赚钱
vector<int> diff;
for(int i=0; i<prices.size()-1; i++){
diff.push_back(prices[i+1] - prices[i]);
}
vector<int> dp(diff.size());
dp[0] = max(0, diff[0]);
int profit = dp[0];
for(int i=1; i<dp.size(); i++){
dp[i] = max(0, dp[i-1] + diff[i]);
profit = max(profit, dp[i]);
}
return profit;
}
提交结果:
其实 d i f f diff diff数组是可以优化掉的
int maxProfit(vector<int>& prices) {
if(prices.size() < 2) return 0;
vector<int> dp(prices.size()-1);
dp[0] = max(0, prices[1] - prices[0]);
int profit = dp[0];
for(int i=1; i<dp.size(); i++){
dp[i] = max(0, dp[i-1] + prices[i+1] - prices[i]);
profit = max(profit, dp[i]);
}
return profit;
}
提交结果:
其实,dp数组也是可以优化掉的
int maxProfit(vector<int>& prices) {
if(prices.size() < 2) return 0;
int cur_profit = max(0, prices[1] - prices[0]);
int profit = cur_profit;
for(int i=1; i<prices.size()-1; i++){
cur_profit = max(0, cur_profit + prices[i+1] - prices[i]);
profit = max(profit, cur_profit);
}
return profit;
}
提交结果:
总结:求区间和问题和数组元素求差问题是可以相互转化的
二、求最大子序和
代码如下:
int maxSubArray(vector<int>& nums) {
int cur = nums[0];
int res = cur;
for(int i=1; i<nums.size(); i++){
cur = cur > 0 ? cur + nums[i] : nums[i];
if(cur > res) res = cur;
}
return res;
}
其中 cur = cur > 0 ? cur + nums[i] : nums[i] 可理解为:
- 若当前累加和cur大于0,则可以利用累加和,将当前元素与cur相加,得到更大的累加和
- 否则,舍弃累加和,从当前元素重新累加,以免使得累加和变小
注:本题应采用逆向思维理解,不是遍历到一个新元素时考虑是否将新元素加入到累加和。而是每次都从此新的元素开始累加,但是要考虑是否利用上前面的累加和。
按照此理解,买卖股票的最佳时机 代码可写为
int maxProfit(vector<int>& prices) {
if(prices.size() < 2) return 0;
int cur_profit = max(0, prices[1] - prices[0]);
int profit = cur_profit;
for(int i=1; i<prices.size()-1; i++){
int diff = prices[i+1] - prices[i];
cur_profit = cur_profit > 0 ? cur_profit + diff : diff;
if(cur_profit > profit) profit = cur_profit;
}
return profit;
}
三、最长上升子序列
首先初始化一个全1的dp数组(表示每个元素至少能独立成为一个上升子序列),
d
p
[
i
]
dp[i]
dp[i]表示包含0~i号元素中,包含
n
u
m
s
[
i
]
nums[i]
nums[i]在内的上升序列中最长上升序列的长度
递推公式如下:
d
p
[
i
]
=
m
a
x
(
d
p
[
i
]
,
d
p
[
j
]
+
1
)
\qquad\qquad\qquad dp[i] = max(dp[i], dp[j]+1)
dp[i]=max(dp[i],dp[j]+1)
递推公式说明
j ∈ [ 0 , i ) j\in[0,i) j∈[0,i),确定 d p [ i ] dp[i] dp[i]时,重新遍历区间 [ 0 , i ) [0,i) [0,i),做如下判断:
- 当 n u m s [ i ] > n u m s [ j ] nums[i] > nums[j] nums[i]>nums[j]时,此时 n u m s [ i ] nums[i] nums[i]可以和 [ 0 , j ] [0,j] [0,j]中的部分元素一起组成上升子序列,此时做更新: d p [ i ] = m a x ( d p [ i ] , d p [ j ] + 1 ) dp[i] = max(dp[i], dp[j]+1) dp[i]=max(dp[i],dp[j]+1)
- 当 n u m s [ i ] < = n u m s [ j ] nums[i] <= nums[j] nums[i]<=nums[j]时,此时 n u m s [ i ] nums[i] nums[i]不可以和 [ 0 , j ] [0,j] [0,j]中的部分元素一起组成上升子序列,不用更新,直接下一次循环
举例:
注意:dp数组可不是递增的,仔细理解上面提到的
d
p
[
i
]
dp[i]
dp[i]的含义
int lengthOfLIS(vector<int>& nums) {
int N = nums.size();
if(N <= 1) return N;
vector<int> dp(N, 1);
int res = 1;
for(int i=1; i<N; i++){
for(int j=0; j<i; j++){
if(nums[i] > nums[j]){
dp[i] = max(dp[i], dp[j]+1);
}
}
res = max(res, dp[i]);
}
return res;
}