● 122.买卖股票的最佳时机II
自己想的:从第一个元素开始,在自己后面连续的序列里面选中最接近自己的极大值,然后在这个极大值这卖掉,然后从极大值的下一个再开始,这么直到size()-1.
class Solution {
public:
int maxProfit(vector<int>& prices) {
int maxPro=0;
for(int i=0;i<prices.size();){
int j=i;
while(j<prices.size()-1 && prices[j+1]>=prices[j])j++;
if(prices[j]<prices[i]){ //没有极大值,从i的下一个开始
i++;
continue;
}
maxPro=maxPro+(prices[j]-prices[i]);
i=j+1; //有极大值,从j的下一个开始
}
return maxPro;
}
};
没有注意到这个条件,很重要:“你也可以先购买,然后在 同一天 出售”,同一天可以同时买卖,所以prices[i]可以加了之后再减!
我们就是为了寻找prices[j]-prices[i]的j和i,不用往后找最大值j跳过中间那些值,而是每天都买一个新的(-prices[i]),并且看看第二天要不要卖出去(+price[i+1]):
prices[j]-prices[i]=(prices[j]-prices[j-1])+(prices[j-1]-prices[j-2])+……+(prices[i+1]-prices[i])
如果今天买明天卖赚了(prices[i+1]-price[i]>0),那就第二天卖掉(maxPro+=prices[i+1]-price[i]);如果会陪,今天不买明天不卖(maxPro+=0)。maxPro是前i-1轮的最大利润。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int maxPro=0;
for(int i=0;i<prices.size()-1;i++){
maxPro=maxPro+max(0,prices[i+1]-prices[i]);
}
return maxPro;
}
};
这里1买5卖,5卖10买==1买10卖。最终利润是可以分解的,这是解法核心思想。
● 55. 跳跃游戏
思想:判断这个序列可跳的覆盖范围要得是:从0连续的到达最后那个元素。
所以知道思想了后就是要统计连续覆盖范围右区间能到哪,在循环里判断了一下现在这个i和right是不是连接着的,然后如果到最后那个元素了就返回true。
class Solution {
public:
bool canJump(vector<int>& nums) {
int right=nums[0];
for(int i=0;i<nums.size();++i){
if(i<=right)right=max(i+nums[i],right);//和之前统计的范围是连接的,更新
if(right>=nums.size()-1)return true;
}
return false;
}
};
整理一下得到下面更简短的代码,把i≤right作为条件,如果i大于right了肯定是不连续,就会返回false。
class Solution {
public:
bool canJump(vector<int>& nums) {
if(nums.size()==1)return true;
int right=nums[0];
for(int i=0;i<=right;++i){
right=max(i+nums[i],right);//和之前统计的范围是由重合的,更新
if(right>=nums.size()-1)return true;
}
return false;
}
};
下面是另一种办法,从右到左明确这个覆盖范围。从nums.size()-1开始,不断向前:nums.size()-1,nums.size()-2,……找覆盖范围,如果从前面的某个地方i可以跳过来,代表这段路ok,然后再更新last,下一轮再去找前面有哪个地方能跳到last……直到last等于0,因为last=能跳过来的一个i,所以都是连续的。这样我们只需讨论能不能跳到last。
class Solution {
public:
bool canJump(vector<int>& nums) {
int last=nums.size()-1;
for(int i=nums.size()-1;i>=0;--i){
if(i+nums[i]>=last)last=i;
if(last==0)return true;
}
return false;
}
};
● 45.跳跃游戏II
感觉这三道题里最difficult的,得再琢磨琢磨。
要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最少步数!
这里需要统计两个覆盖范围,当前这一步的最大覆盖(cur)和下一步最大覆盖(right)。
如果移动下标达到了当前这一步的最大覆盖最远距离了,还没有到终点的话,那么就必须再走一步来增加覆盖范围,直到覆盖范围覆盖了终点。
对每个元素,都统计第一个元素到这个元素的可覆盖下标(right),然后在这个区间里[这个元素的下一个元素,这个元素覆盖最大下标(cur)],都更新一下right。直到到了最大下标cur的时候,说明当前这个i和之前元素的right到不了最后,所以还要跳跃,count++,下一次的覆盖最大下标,应该更新为right,然后在这个区间内有right达到最大值之后就不会改变了,这个时候会跳出。也就是在下标是[0,2]的时候,right在下标1的时候更新为最大值了,是在第一步可覆盖范围(下标2)的时候才break,然后返回的。
class Solution {
public:
int jump(vector<int>& nums) {
if(nums.size()==1)return 0;
int right=0;
int cur=0;
int count=0;
for(int i=0;i<nums.size();++i){
right=max(right,nums[i]+i);
if(i==cur)//到达当前的最大范围
{
cur=right;
count++;
if(right>=nums.size()-1)break; //在[3,1]之间,到3的时候已经把right更新到n-1
}
}
return count;
}
};