LeetCode刷题day17——贪心
周日休息,昨天有个课程作业说是晚上开会,等了很久一直没开,也没跑步,卡在摆动序列,题也没打完…今天补上!今天晚上跑!热血沸腾,活力满满的一天!
376. 摆动序列
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 **摆动序列 。**第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
- 例如,
[1, 7, 4, 9, 2, 5]
是一个 摆动序列 ,因为差值(6, -3, 5, -7, 3)
是正负交替出现的。 - 相反,
[1, 4, 7, 2, 5]
和[1, 7, 4, 5, 5]
不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums
,返回 nums
中作为 摆动序列 的 最长子序列的长度 。
示例 1:
输入:nums = [1,7,4,9,2,5]
输出:6
解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。
示例 2:
输入:nums = [1,17,5,10,13,15,10,5,16,8]
输出:7
解释:这个序列包含几个长度为 7 摆动序列。
其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8) 。
示例 3:
输入:nums = [1,2,3,4,5,6,7,8,9]
输出:2
提示:
1 <= nums.length <= 1000
0 <= nums[i] <= 1000
**进阶:**你能否用 O(n)
时间复杂度完成此题?
//思路写在注释,这题没做过,还真蛮难想的,题解里有一个是动态规划,但是暂时让我逃避一下吧,等到了动态规划再说,继续贪心先,局部最优推出全局最优
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
//此题的思路很巧妙,不是删除元素,而是转化为去找极值问题
if (nums.size() <= 1)//先选出特殊的
return nums.size();
int cur = 0;
int pre = 0;//假设添加了一个元素在头部,形成开头平坡
int res = 1;//默认尾部直接算一个峰值,现在从头开始找峰值
for (int i = 0; i < nums.size() - 1; i++) {
cur = nums[i + 1] - nums[i];
if ((pre >= 0 && cur < 0) || (pre <= 0 && cur > 0)) {//只允许左边平坡
res++;
pre = cur;//可以理解为,把cur加入我们的结果数组
}
}
return res;
}
};
738. 单调递增的数字
当且仅当每个相邻位数上的数字 x
和 y
满足 x <= y
时,我们称这个整数是单调递增的。
给定一个整数 n
,返回 小于或等于 n
的最大数字,且数字呈 单调递增 。
示例 1:
输入: n = 10
输出: 9
示例 2:
输入: n = 1234
输出: 1234
示例 3:
输入: n = 332
输出: 299
提示:
0 <= n <= 109
分析:
暴力法:from n to 0,从n不断减小遍历,每次都检查是否符合单调递增的条件,优点是:方法简单;缺点是:当n太大时,会超出时间限制。
class Solution {
public:
int monotoneIncreasingDigits(int n) {
for (int i = n; i >=0; i--) {
int k=i;
vector<int> ans;
int sum=0;
int flag=1;
while(i/10>0) {
ans.push_back(i%10);
i/=10;
}
ans.push_back(i%10);
/*out<<"***"<<endl;
for(int j=0;j<ans.size();j++)
cout<<ans[j]<<" ";
cout<<endl;*/
i=k;
reverse(ans.begin(), ans.end());
for(int j=0;j<ans.size()-1;j++) {
if(ans[j]<=ans[j+1]) {
sum=sum*10+ans[j];
continue;
}
else {
flag=0;
break;
}
}
sum=sum*10+ans[ans.size()-1];
if(flag==1) {
cout<<sum<<endl;
return sum;
}
}
return 0;
}
};
贪心算法可以高效地解决这个问题,避免逐个数字递减。具体思路是:
- 从左到右遍历数字:将数字转为字符串逐位检查。
- 遇到下降点:当发现某一位数字大于其后继位数字时(即下降点),说明从该位置开始需要调整。
- 调整数字:将下降点前一位的数字减一,并将后面的所有位数都改为
9
,这样确保数字仍为最大单调递增数。 - 处理进位:如果减小某一位后,可能会导致更早的位也需要调整,因此从右向左继续修正,直到没有下降为止。
class Solution {
public:
int monotoneIncreasingDigits(int n) {
string s = to_string(n);
for (int i = 0; i < s.length()-1; ) {
if(s[i]>s[i+1]) {
s[i]-=1;
for(int j = i+1; j < s.length(); j++) {
s[j]='9';
}
i=0;
}
else
i++;
}
return stoi(s);
}
};
122. 买卖股票的最佳时机Ⅱ
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
示例 1:
输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3。
最大总利润为 4 + 3 = 7 。
示例 2:
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
最大总利润为 4 。
示例 3:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0。
提示:
1 <= prices.length <= 3 * 104
0 <= prices[i] <= 104
分析:
算法的核心思想是通过计算相邻价格之间的差值,并累加所有正差值来获得最大利润。为什么可以这样做?
拿到这道题的第一想法是:利润就是某个大区间的差,比如:示例 2:
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
最大总利润为 4 。
利润是day5-day1
。这样想就复杂了,是的!或许可以想成:(day5-day4)+(day4-day3)+(day3-day2)+(day2-day1) = day5-day1
。这样只需要求每天的差值,找利润是正数时,买入卖出。贪心策略:只收集正利润。
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<int> bonus;
int sum = 0;
for (int i = 0; i < prices.size() - 1; i++) {
bonus.push_back(prices[i + 1] - prices[i]);
if (bonus.back() > 0)
sum += bonus.back();
}
return sum;
}
};
121. 买卖股票的最佳时机
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 105
0 <= prices[i] <= 104
分析:
该算法基于贪心策略,目标是通过每次选择当前最优的买入和卖出时机来获得最大利润。核心思想是:
- 遍历股票价格,维护当前的最低买入价格
minCost
和最大利润maxP
。 - 对于每一天的价格,计算当前卖出的利润并更新最大利润
maxP
。 - 最终返回最大利润,若无利润则返回 0。
通过一次遍历完成,时间复杂度为 O(n)。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int maxP = 0;
int minCost = INT_MAX;
for (int i = 0; i < prices.size(); i++) {
minCost = min(minCost, prices[i]);
maxP = max(maxP, prices[i] - minCost);
}
return maxP >= 0 ? maxP : 0;
}
};
另外,暴力法能通过大部分样例,但是某些样例时间超限:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int max=INT_MIN;
for (int i = 0; i < prices.size()-1; i++) {
if(prices[i+1]<=prices[i])//后一天价格更高,前一天就不适合买入了
continue;
for (int j = i + 1; j < prices.size(); j++) {
int profit = prices[j] - prices[i];
if(profit > max) max = profit;
}
}
return max>=0?max:0;
}
};