贪心算法 | 每周8题(二)
目录
0.引言
1.例题详解(题目来源力扣)
1.1 买卖股票的最佳时机Ⅰ(只能买卖一次)
1.2买卖股票的最佳时机 II(可以多次买卖)
1.3按身高排序
1.4优势洗牌
1.5最长回文串
1.6增减字符串匹配
1.7分发饼干
1.8最优除法
2.小结
重要的事情说三遍:
(●'◡'●)喜欢小邓儿,一键三连哦(❤ ω ❤)
(●'◡'●)喜欢小邓儿,一键三连哦(❤ ω ❤)
(●'◡'●)喜欢小邓儿,一键三连哦(❤ ω ❤)
0.引言
首先,小编祝大家国庆快乐🎉🎉🎉经过了一周的学习与练习,相信各位读者对贪心算法有了一定的了解与认知,让咱们本周继续加油(ง •_•)ง
1.例题详解(题目来源力扣)
1.1 买卖股票的最佳时机Ⅰ(只能买卖一次)
题目:
给定一个数组
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。
🚩思路:在最低时买入,最高时卖出。遍历prices数组,从第一个开始,用每个值将去当前最小值(从第一个开始)得到此时利润,每次保留当前最大利润,并且更新最小值,直到遍历结束。
代码:
class Solution {
public:int maxProfit(vector<int>& prices) {int ret=0;int premin=INT_MAX;for(int i=0;i<prices.size();i++){ret=max(ret,prices[i]-premin);//先计算最大利润premin=min(premin,prices[i]);//在更新最小值}return ret;}
};
1.2买卖股票的最佳时机 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。
🚩思路:上涨前买,下跌前卖。这里提供两种方法(●'◡'●)
法一:双指针:遍历prices数组,用下一个元素j的值减去当前元素i的值,若为正数,加上此时利润;为负数,将此时的i移动到j的位置(贪心),继续上面操作,直至遍历结束。
法二:只加盈利部分:遍历prices数组,用下一个元素的值减去当前元素i的值,若为正数,加上此时利润;否则看下一个值,直至遍历结束。
代码:
//法一:双指针
class Solution {
public:int maxProfit(vector<int>& prices) {int ret=0; for(int i=0;i<prices.size();i++){ int j=i; //每次循环开始之前j从i位置开始while((j+1<prices.size())&&(prices[j+1]>prices[j]))j++;ret+=prices[j]-prices[i];i=j;}return ret;}
};/*
法二:每一天
class Solution {
public:int maxProfit(vector<int>& prices) {int ret=0;for(int i=0;i+1<prices.size();i++){if(prices[i+1]-prices[i]>0)ret+=prices[i+1]-prices[i];}return ret;}
};
*/
1.3按身高排序
题目:
(tip:本题严格来说不算是贪心题,但是为下面一题做铺垫,咱可以看一下,掠过也行哈😄)
给你一个字符串数组
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 <= 103
1 <= names[i].length <= 20
1 <= heights[i] <= 105
names[i]
由大小写英文字母组成heights
中的所有值互不相同
🚩思路:用一个索引数组(因为身高与姓名相对应,若身高顺序变了,不能返回原来的姓名)表示身高数组中的元素。将索引中的元素按照身高由高到低排序,再让姓名数组按照索引中顺序输出。
代码:
class Solution {
public:vector<string> sortPeople(vector<string>& names, vector<int>& heights) {//建立一个下标数组int n=names.size();vector<int>index(n);for(int i=0;i<n;i++){index[i]=i;}//按身高排序sort(index.begin(),index.end(),[&](int i,int j){return heights[i]>heights[j];});//取出下标对应的姓名vector<string>strs;for(auto e:index)strs.push_back(names[e]);return strs;}
};
1.4优势洗牌
题目:
给定两个长度相等的数组
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]
🔈🔈🔈tip:本题的“优势”其实和“田忌赛马”差不多,就是体现 “扬长避短、优化资源配置” 的智慧。
咱们在这儿补充一下“田忌赛马”的故事👇👇👇:
田忌与齐王赛马,设上、中、下三等级马对决,每次均以同等级马比拼,田忌因每一级马匹均稍逊齐王,屡赛屡输。孙膑献策:以田忌下等马对齐王上等马(先输一场),再用上等马对其中等马、中等马对其下等马。最终三局两胜,田忌获胜。
🚩思路:让nums1中最小的数去消耗nums2中最大的数。先用一个索引数组存放nums2(因为nums2中的元素顺序不能变),再将索引数组按照nums2中元素大小排升序,将nums1中的元素也排升序。然后遍历nums1和索引所对应的nums2元素大小,若满足 nums1[i] > nums2[index]
,比较下一个元素;否则,将 nums1[i]
对应到索引数组index的最后面。
代码:
class Solution {
public:vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {//将nums1排序(升序)sort(nums1.begin(),nums1.end());//构造num2的下标数组index2int n=nums2.size();vector<int>index2(n);for(int i=0;i<n;i++){index2[i]=i;}//对nums2的下标升序排序,不改变num2原来数据sort(index2.begin(),index2.end(),[&](int i,int j){return nums2[i]<nums2[j];});//田忌赛马vector<int>ret(n);int left=0,right=n-1;for(auto e:nums1){if(e>nums2[index2[left]])ret[index2[left++]]=e;else ret[index2[right--]]=e;}return ret;}
};
1.5最长回文串
题目:
给定一个包含大写字母和小写字母的字符串
s
,返回 通过这些字母构造成的 最长的 回文串 的长度。在构造过程中,请注意 区分大小写 。比如
"Aa"
不能当做一个回文字符串。示例 1:
输入:s = "abccccdd" 输出:7 解释: 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。示例 2:
输入:s = "a" 输出:1 解释:可以构造的最长回文串是"a",它的长度是 1。提示:
1 <= s.length <= 2000
s
只由小写 和/或 大写英文字母组成
🚩思路:将所有偶数个数的字母均加入到回文字符串当中,每加一次,长度加一。若还有奇数的字母,再最终长度上再+1。
代码:
class Solution {
public:int longestPalindrome(string s) {//计数,用数组模拟哈希表nt hash[127]={0};for(auto e:s)hash[e]++;int ret=0;for(auto e:hash)ret+=e/2*2; //偶数加,奇数舍return ret<s.size()?ret+1:ret; //若有奇数,最后加一}
};
1.6增减字符串匹配
题目:
由范围
[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]
🚩思路:若输入'I',将其和目前最小的匹配;输入'D'和最大的匹配。
代码:
class Solution {
public:vector<int> diStringMatch(string s) {int left=0,right=s.size();//取[0,n]包含n;vector<int>ret;for(int i=0;i<s.size();i++){if(s[i]=='I')ret.push_back(left++);else ret.push_back(right--);}ret.push_back(left);return ret;}
};
1.7分发饼干
题目:
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子
i
,都有一个胃口值g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干j
,都有一个尺寸s[j]
。如果s[j] >= g[i]
,我们可以将这个饼干j
分配给孩子i
,这个孩子会得到满足。你的目标是满足尽可能多的孩子,并输出这个最大数值。示例 1:
输入: g = [1,2,3], s = [1,1] 输出: 1 解释: 你有三个孩子和两块小饼干,3 个孩子的胃口值分别是:1,2,3。 虽然你有两块小饼干,由于他们的尺寸都是 1,你只能让胃口值是 1 的孩子满足。 所以你应该输出 1。示例 2:
输入: g = [1,2], s = [1,2,3] 输出: 2 解释: 你有两个孩子和三块小饼干,2 个孩子的胃口值分别是 1,2。 你拥有的饼干数量和尺寸都足以让所有孩子满足。 所以你应该输出 2。
🚩思路:给每个孩子都分配,目前可以满足其胃口的最小饼干尺寸。先将孩子胃口、饼干尺寸从小到大排序。先设置一个结果值。然后,遍历孩子胃口,饼干大小,若符合胃口,结果值+1;反之,舍去那个饼干,接着遍历,直至饼干或孩子遍历结束。
代码:
class Solution {
public:int findContentChildren(vector<int>& g, vector<int>& s) {int n=g.size(),m=s.size();int ret=0;sort(g.begin(),g.end());sort(s.begin(),s.end());for(int i=0,j=0;i<n;i++,j++)//若j++没有,当满足s[j] >= g[i]时,j会停滞{while(j<m&&s[j]<g[i])j++; //贪心--双指针if(j<m)ret++;}return ret; }
};
1.8最优除法
题目:
给定一正整数数组
nums
,nums
中的相邻整数将进行浮点除法。
- 例如,
nums = [2,3,4]
,我们将求表达式的值"2/3/4"
。但是,你可以在任意位置添加任意数目的括号,来改变算数的优先级。你需要找出怎么添加括号,以便计算后的表达式的值为最大值。
以字符串格式返回具有最大值的对应表达式。
注意:你的表达式不应该包含多余的括号。
示例 1:
输入: [1000,100,10,2] 输出: "1000/(100/10/2)" 解释: 1000/(100/10/2) = 1000/((100/10)/2) = 200 但是,以下加粗的括号 "1000/((100/10)/2)" 是冗余的, 因为他们并不影响操作的优先级,所以你需要返回 "1000/(100/10/2)"。其他用例: 1000/(100/10)/2 = 50 1000/(100/(10/2)) = 50 1000/100/10/2 = 0.5 1000/100/(10/2) = 2示例 2:
输入: nums = [2,3,4] 输出: "2/(3/4)" 解释: (2/(3/4)) = 8/3 = 2.667 可以看出,在尝试了所有的可能性之后,我们无法得到一个结果大于 2.667 的表达式。说明:
1 <= nums.length <= 10
2 <= nums[i] <= 1000
- 对于给定的输入只有一种最优除法。
🚩思路:若数字数组中元素大于2个,在第二个数字前加一个'(',并再最后加一个')'。本题这样添加括号是因为(“除除得乘”)👇
代码:
class Solution {
public:string optimalDivision(vector<int>& nums) {int n=nums.size();if(n==1)return to_string(nums[0]);else if(n==2) return to_string(nums[0])+'/'+to_string(nums[1]);else{string ret =to_string(nums[0])+"/("+to_string(nums[1]); //注意这里右边是一个字符串,左边不能定义成数组for(int i=2;i<n;i++){ret+='/'+to_string(nums[i]); //贪心}ret+=')';return ret;}}
};
2.小结
本文通过力扣例题详解贪心算法的应用场景与解题思路。主要内容包括:1. 股票买卖问题(单次/多次交易策略);2. 身高排序与田忌赛马式的优势洗牌;3. 构造最长回文串;4. 增减字符串匹配;5. 分发饼干的最优分配;6. 数学表达式的最优括号添加。解题核心在于抓住局部最优解,通过双指针、排序等技巧实现全局优化。
本周的就讲解到这里O(∩_∩)O
如果想了解更多算法题与思路,欢迎点赞收藏,咱们下周见🤭🤭🤭