目录
①力扣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 <= 10^5
0 <= prices[i] <= 10^4
class Solution {
public:
int maxProfit(vector<int>& prices) {
}
};
解析代码
可以两层for循环暴力枚举,会超时。
贪心策略:由于只能交易一次,所以对于某一个位置 i ,要想获得最大利润,仅需知道前面所有元素的最小值。然后在最小值的位置买入股票,在当前位置卖出股票即可。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int Min = INT_MAX, ret = 0;
for(int i = 0; i < prices.size() - 1; ++i)
{
Min = min(Min, prices[i]);
ret = max(ret, prices[i + 1] - Min);
}
return ret;
}
};
②力扣122. 买卖股票的最佳时机 II
难度 中等
给你一个整数数组 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 * 10^4
0 <= prices[i] <= 10^4
class Solution {
public:
int maxProfit(vector<int>& prices) {
}
};
解析代码1_拆分交易
贪心策略: 由于可以进行无限次交易,所以只要是一个上升区域,我们就把利润拿到手就好了(可以拆分一天一天交易,也可以用双指针得到这个上升区域的起点和终点)。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ret = 0, sz = prices.size();
for (int i = 1; i < sz; i++)
{
if(prices[i] > prices[i - 1])
ret += prices[i] - prices[i - 1];
}
return ret;
}
};
解析代码2_双指针
虽然两个循环,但时间也是O(N)的。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ret = 0, left = 0, sz = prices.size();
while(left < sz)
{
int right = left + 1;
while(right < sz && prices[right] > prices[right - 1])
{
++right;
}
ret += prices[right - 1] - prices[left]; // right已经是极大值下一个了
left = right;
}
return ret;
}
};
③力扣1005. K 次取反后最大化的数组和
难度 简单
给你一个整数数组 nums
和一个整数 k
,按以下方法修改该数组:
- 选择某个下标
i
并将nums[i]
替换为-nums[i]
。
重复这个过程恰好 k
次。可以多次选择同一个下标 i
。
以这种方式修改数组后,返回数组 可能的最大和 。
示例 1:
输入:nums = [4,2,3], k = 1 输出:5 解释:选择下标 1 ,nums 变为 [4,-2,3] 。
示例 2:
输入:nums = [3,-1,0,2], k = 3 输出:6 解释:选择下标 (1, 2, 2) ,nums 变为 [3,1,0,2] 。
示例 3:
输入:nums = [2,-3,-1,5,-4], k = 2 输出:13 解释:选择下标 (1, 4) ,nums 变为 [2,3,-1,5,4] 。
提示:
1 <= nums.length <= 10^4
-100 <= nums[i] <= 100
1 <= k <= 10^4
class Solution {
public:
int largestSumAfterKNegations(vector<int>& nums, int k) {
}
};
解析代码
贪心策略:可以先排序,然后分情况讨论,设整个数组中负数的个数为 m 个:
- m > k :把前 k 小负数,全部变成正数;
- m == k :把所有的负数全部转化成正数;
- m < k :
- 先把所有的负数变成正数。
- 然后根据 k - m 的奇偶分情况讨论:如果是偶数,直接忽略,如果是奇数,挑选当前数组中最小的数,变成负
class Solution {
public:
int largestSumAfterKNegations(vector<int>& nums, int k) {
int sz = nums.size(), Min = INT_MAX, sum = 0, m = 0; // m统计0和负数
sort(nums.begin(), nums.end());
for(int i = 0; i < sz; ++i)
{
sum += nums[i];
Min = min(Min, abs(nums[i])); // 绝对值最小的数
if(nums[i] <= 0)
++m;
}
if(m >= k)
{
for(int i = 0; i < k; ++i)
sum -= 2 * nums[i]; // 最小的负数变正数,两倍
}
else // m < k
{
for(int i = 0; i < m; ++i) // 先把所有的负数变成正数
sum -= 2 * nums[i];
if((k - m) % 2 == 1) // 绝对值最小的数取反
sum += 2 * -Min;
}
return sum;
}
};
④力扣2418. 按身高排序
难度 简单
给你一个字符串数组 names
,和一个由 互不相同 的正整数组成的数组 heights
。两个数组的长度均为 n
。
对于每个下标 i
,names[i]
和 heights[i]
表示第 i
个人的名字和身高。
请按身高 降序 顺序返回对应的名字数组 names
。
示例 1:
输入:names = ["Mary","John","Emma"], heights = [180,165,170] 输出:["Mary","Emma","John"] 解释:Mary 最高,接着是 Emma 和 John 。
示例 2:
输入:names = ["Alice","Bob","Bob"], heights = [155,185,150] 输出:["Bob","Alice","Bob"] 解释:第一个 Bob 最高,然后是 Alice 和第二个 Bob 。
提示:
n == names.length == heights.length
1 <= n <= 10^3
1 <= names[i].length <= 20
1 <= heights[i] <= 10^5
names[i]
由大小写英文字母组成heights
中的所有值互不相同
class Solution {
public:
vector<string> sortPeople(vector<string>& names, vector<int>& heights) {
}
};
解析代码
此题并不是贪心的题,只是一个简单的排序,为下一道贪心的题做准备。
可以直接创建一个二元组vector<pair<int, string>>,或者用哈希的解法。下面看另一种解法:
对下标排序解法(通过排序 ''索引'' 的方式):我们不能直接按照 i 位置对应的 heights 来排序,因为排序过程是会移动元素的,但是names 内的元素是不会移动的。
由题意可知, names数组和 heights数组的下标是壹壹对应的,因此可以重新创建出来一个下标数组,将这个下标数组按照 heights[i] 的大小排序。那么,当下标数组排完序之后,里面的顺序就相当于heights这个数组排完序之后的下标。之后通过排序后的下标,依次找到原来的name ,完成对名字的排序。
class Solution {
public:
vector<string> sortPeople(vector<string>& names, vector<int>& heights) {
int sz = heights.size();
vector<int> index(sz);
for(int i = 0; i < sz; ++i)
{
index[i] = i;
}
sort(index.begin(), index.end(),[&](int index1, int index2)
{
return heights[index1] > heights[index2];
});
vector<string> ret;
for(auto& e : index)
{
ret.push_back(names[e]);
}
return ret;
}
};
⑤力扣870. 优势洗牌(田忌赛马)
难度 中等
给定两个长度相等的数组 nums1
和 nums2
,nums1
相对于 nums2
的优势可以用满足 nums1[i] > nums2[i]
的索引 i
的数目来描述。
返回 nums1 的任意排列,使其相对于 nums2
的优势最大化。
示例 1:
输入:nums1 = [2,7,11,15], nums2 = [1,10,4,11] 输出:[2,11,7,15]
示例 2:
输入:nums1 = [12,24,8,32], nums2 = [13,25,32,11] 输出:[24,32,8,12]
提示:
1 <= nums1.length <= 10^5
nums2.length == nums1.length
0 <= nums1[i], nums2[i] <= 10^9
class Solution {
public:
vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
}
};
解析代码
这里讲一下田忌赛马背后的博弈决策,从三匹马拓展到 n 匹马之间博弈的最优策略。
- 田忌:下等马 中等马 上等马
- 齐王:下等马 中等马 上等马
- 田忌的下等马 pk 不过齐王的下等马,因此把这匹马丢去消耗一个齐王的最强的马。
- 接下来选择中等马 pk 齐王的下等马,勉强获胜。
- 最后用上等马 pk 齐王的中等马,勉强获胜。
由此可以得出一个最优的决策方式:
- 当我方此时最差的比不过对面最差的时候,让我方最差的去处理掉对面最好的(反正要输,不如去拖掉对面一个最强的)。
- 当我方此时最差的能比得上对面最差的时候,就让两者比对下去(最差的都能获胜,就不需要更强的比了)。
class Solution {
public:
vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
int sz = nums1.size();
sort(nums1.begin(), nums1.end());
vector<int> index(sz), ret(sz); // index和nums2下标绑定
for(int i = 0; i < sz; ++i)
{
index[i] = i;
}
sort(index.begin(), index.end(),[&](int index1, int index2)
{
return nums2[index1] < nums2[index2];
});
int left = 0, right = sz - 1;
for(auto& e : nums1)
{
if(e <= nums2[index[left]]) // 最弱的和最弱的判断
ret[index[right--]] = e; // 比不过就去和最强的比,相等也比不过
else // 比过了,放到最弱的位置
ret[index[left++]] = e;
}
return ret;
}
};
⑥力扣409. 最长回文串
难度 简单
给定一个包含大写字母和小写字母的字符串 s
,返回 通过这些字母构造成的 最长的回文串 。
在构造过程中,请注意 区分大小写 。比如 "Aa"
不能当做一个回文字符串。
示例 1:
输入:s = "abccccdd" 输出:7 解释: 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
示例 2:
输入:s = "a" 输出:1
示例 3:
输入:s = "aaaaaccc" 输出:7
提示:
1 <= s.length <= 2000
s
只由小写 和/或 大写英文字母组成
class Solution {
public:
int longestPalindrome(string s) {
}
};
解析代码
贪心策略:用尽可能多的字符去构造回文串:
- 如果字符出现偶数个,那么全部都可以用来构造回文串。
- 如果字符出现奇数个,减去一个之后,剩下的字符能够全部用来构造回文串。
- 最后再判断⼀下,如果有字符出现奇数个,就把它单独拿出来放在中间。
class Solution {
public:
int longestPalindrome(string s) {
int ret = 0;
int hash[123] = { 0 }; //⽤数组模拟哈希表计数,ASCII码最大127,z是122
for(auto& e : s)
{
hash[e]++;
}
for(auto& e : hash)
{
ret += (e / 2 * 2); // 奇数就去掉1再乘2
}
return ret == s.size() ? ret : ret + 1; // 不足原长度就有奇数
}
};
⑦力扣942. 增减字符串匹配
难度 简单
由范围 [0,n]
内所有整数组成的 n + 1
个整数的排列序列可以表示为长度为 n
的字符串 s
,其中:
- 如果
perm[i] < perm[i + 1]
,那么s[i] == 'I'
- 如果
perm[i] > perm[i + 1]
,那么s[i] == 'D'
给定一个字符串 s
,重构排列 perm
并返回它。如果有多个有效排列perm,则返回其中 任何一个 。
示例 1:
输入:s = "IDID" 输出:[0,4,1,3,2]
示例 2:
输入:s = "III" 输出:[0,1,2,3]
示例 3:
输入:s = "DDI" 输出:[3,2,0,1]
提示:
1 <= s.length <= 10^5
s
只包含字符"I"
或"D"
class Solution {
public:
vector<int> diStringMatch(string s) {
}
};
解析代码
贪心策略:
- 当遇到 'I' 的时候,为了让下一个上升的数可选择的范围更多,当前选择最小的那个数。
- 当遇到 'D' 的时候,为了让下一个下降的数可选择的范围更多,选择当前最大的那个数。
class Solution {
public:
vector<int> diStringMatch(string s) {
int left = 0, right = s.size();
vector<int> ret;
for(auto& e : s)
{
if(e == 'I') // 递增,先放最小
ret.push_back(left++);
else
ret.push_back(right--);
}
ret.push_back(right); // 重合
return ret;
}
};
本篇完。
下一篇是DFS解决FloodFill。
下下篇是贪心算法的第三部分。