😀大家好,我是白晨,一个不是很能熬夜😫,但是也想日更的人✈。如果喜欢这篇文章,点个赞👍,关注一下👀白晨吧!你的支持就是我最大的动力!💪💪💪

文章目录
观前提示:此文章需要一定贪心算法的基础。
大家好呀,我是白晨🧐。
贪心算法算是一种比较耳熟能详的算法,只要求出局部最优解就可以得到整体的最优解,而且面试很喜欢出这种问题。但是,贪心算法其实并不好想,特别是有些问题比较绕的时候,你可能根本就想不到贪心算法。动态规划这个算法是从整体出发求整体最优,而贪心算法是从局部出发求整体最优,所以很多时候贪心算法并不好用,而且动态规划说是难,但是掌握方法后使用起来非常爽,而贪心算法规律比较难找,每一种题都有不同的具体贪心思想。
所以,白晨选出了一些贪心算法的几道经典题目,以帮助大家理解贪心算法。题目难度由低到高,都是比较经典的题型。可能不是很难,但是有些题目很绕,看了答案后,会觉得自己被侮辱了,这就是贪心算法🤪。

原题链接:分割平衡字符串
解析:
这道题要求尽可能多的切割平衡字符串
R后 L 或者先 L后 R 。绝对不能出现 L R 相互交错出现。cnt ,在遇到不同的字符时,向不同的方向变化,设 cnt 为 0 时达到平衡,分割数更新。L , cnt + 1,遇到 R ,cnt - 1cnt 为0时即,更新记录平衡字符串子串的变量 ++numclass Solution {
public:
int balancedStringSplit(string s) {
int len = s.size();
int cnt = 0, num = 0;
for (int i = 0; i < len; ++i)
{
// 遇到R,--cnt
if (s[i] == 'R')
--cnt;
// 遇到L,++cnt
else
++cnt;
// 判断:如果cnt等于0,说明要切割
if (cnt == 0)
++num;
}
return num;
}
};

原题链接:买卖股票的最佳时机
解析:
这道题非常具有迷惑性,也特别容易想复杂,当你看到答案的时候可能会想找个豆腐撞死。
法1:贪心算法
其实你完全没有必要去想怎么买卖才能利益最大化,因为这个题目是给出了后面几天的股价的,所以我们可以到第二天再做决策。
所以,第二天股价涨时,把我们就买入前一天的股票,再前一天的股票卖出,也就是第二天得到了利润
如果第二天跌了,我们第一天就不买入
总结一下就是:
- 连续上涨交易日: 则第一天买最后一天卖收益最大,等价于每天都买卖
- 连续下降交易日: 则不买卖收益最大,即不会亏钱。
故可以遍历整个股票交易日价格列表,在所有上涨交易日都买卖(赚到所有利润),所有下降交易日都不买卖(永不亏钱)。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int day = prices.size();
int profit = 0;
for (int i = 1; i < day; ++i)
{
// 股价比前一天高就卖出
if (prices[i] > prices[i - 1])
profit += prices[i] - prices[i - 1];
}
return profit;
}
};
法二:动态规划
这道题也能用动态规划做,就是很麻烦,而且效果没有贪心好。如果没有学过动态规划的同学可以不用看这种方法。

状态:
定义状态 F ( i , 0 ) F(i,0) F(i,0) 表示第
i天交易完后手里没有股票的最大利润, F ( i , 1 ) F(i,1) F(i,1)表示第i天交易完后手里持有一支股票的最大利润。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int day = prices.size();
vector<vector<int> > ret(day, vector<int>(2, 0));
// 第一天的状态
ret[0][0] = 0;
ret[0][1] = -prices[0];
for (int i = 1; i < day; ++i)
{
// 每天选一次
ret[i][0] = max(ret[i - 1][0], ret[i - 1][1] + prices[i]);
ret[i][1] = max(ret[i - 1][1], ret[i - 1][0] - prices[i]);
}
return ret[day - 1][0];
}
};
优化算法:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int day = prices.size();
int ret[2];
ret[0] = 0;
ret[1] = -prices[0];
// 存放昨天的状态
int tmp0 = ret[0];
int tmp1 = ret[1];
for (int i = 1; i < day; ++i)
{
ret[0] = max(tmp0, tmp1 + prices[i]);
ret[1] = max(tmp1, tmp0 - prices[i]);
tmp0 = ret[0];
tmp1 = ret[1];
}
return ret[0];
}
};

原题链接:跳跃游戏
解析:
贪心策略
设想一下,对于数组中的任意一个位置y,我们如何判断它是否可以到达?根据题目的描述,只要存在一个位置
x,它本身可以到达,并且它跳跃的最大长度为 x + n u m s [ x ] x + nums[x] x+nums[x],这个值大于等于y,即 x + n u m s [ x ] ≥ y x+nums[x] ≥ y x+nums[x]≥y ,那么位置y也可以到达。
换句话说,对于每一个可以到达的位置x,它使得 x + 1 , x + 2 , ⋯ , x + n u m s [ x ] x+1, x+2,⋯,x+nums[x] x+1,x+2,⋯,x+nums[x] 这些连续的位置都可以到达。这样以来,我们依次遍历数组中的每一个位置,并实时维护最远可以到达的位置。对于当前遍历到的位置x,如果它在最远
可以到达的位置的范围内,那么我们就可以从起点通过若干次跳跃到达该位置,因此我们可以用 x + n u m s [ x ] x+nums[x] x+nums[x]更新 最远可以
到达的位置。
在遍历的过程中,如果最远可以到达的位置大于等于数组中的最后一个位置,那就说明最后一个位置可达,我们就可以直接
返回True作为答案。反之,如果在遍历结束后,最后一个位置仍然不可达,我们就返回 False 作为答案。、
以题目中的示例一
[2, 3, 1, 1, 4]
为例:
我们一开始在位置 0,可以跳跃的最大长度为 2,因此最远可以到达的位置被更新为 2;
我们遍历到位置 1,由于 1≤2,因此位置 1 可达。我们用 1加上它可以跳跃的最大长度3,将最远可以到达的位置更新为
4。由于4大于等于最后一个位置4,因此我们直接返回 True。
我们再来看看题目中的示例二
[3, 2, 1, 0, 4]
我们一开始在位置0,可以跳跃的最大长度为3,因此最远可以到达的位置被更新为3;
我们遍历到位置1,由于1≤3,因此位置1可达,加上它可以跳跃的最大长度2得到3,没有超过最远可以到达的位置;
位置 2、位置 3 同理,最远可以到达的位置不会被更新;
我们遍历到位置4,由于4>3,因此位置4不可达,我们也就不考虑它可以跳跃的最大长度了。
在遍历完成之后,位置4仍然不可达,因此我们返回 False
class Solution {
public:
bool canJump(vector<int>& nums) {
int maxLen = 0;
int len = nums.size();
for(int i = 0; i < len; ++i)
{
// 必须要判断前面最大能不能到达这个点
// 能到达才更新,不能到达就不更新
if(i <= maxLen)
maxLen = max(i + nums[i], maxLen);
// 遇到最大到达距离大于等于最后一个数的下标就返回true
if(maxLen >= len - 1)
return true;
}
return false;
}
};
// 改版
class Solution {
public:
bool canJump(vector<int>& nums) {
int maxLen = 0;
int len = nums.size();
for(int i = 0; i < len; ++i)
{
// 通过上面的代码,相信很多人已经发现,如果到最大到达距离达不了遍历中的某一个元素
// 那么,最后一定到不了最后一个元素
// 因为最大到达距离是覆盖式的,能到最后一个元素必然可以到达前面的元素
// 所以我们可以先判断是否能到这个元素,能到达就更新,不能到达就直接返回false
if(maxLen < i)
return false;
maxLen = max(i + nums[i], maxLen);
}
// 遍历完成说明必然可以到达最后一个元素,直接返回true
return true;
}
};
某工厂有
n个独立的作业,由m台相同的机器进行加工处理。作业i所需的加工时间为ti,任何作业在被处理时不能中
断,也不能进行拆分处理。现厂长请你给他写一个函数:算出 n 个作业由 m 台机器加工处理的最短时间
贪心思路:
- 如果 n < = m n <= m n<=m ,那么只需要返回任务中时间最长的一项任务即可
- 如果 n > m n > m n>m
- 首先,我们可以将任务按时间长短排序,按降序排列。
- 其次,我们针对每一项任务,都要选出当前所有机器中执行任务时间最短的机器来执行。
- 最后,选出执行任务总时间最长的一个机器,返回其执行任务所需要的时间。
bool cmp(const int& x, const int& y)
{
return x > y;
}
int greedStrategy(const vector<int>& works, vector<int>& machines)
{
// 按工作时间长短排序
sort(works.begin(), works.end(), cmp);
int workNum = works.size();
int machineNum = machines.size();
// 如果 n <= m ,返回任务中时间最长的一项任务
if (workNum <= machineNum)
return works[0];
// 选择机器执行每一个任务
for (int i = 0; i < workNum; ++i)
{
int flag = 0;
// 选择现在执行任务花费时间最少的机器
for (int j = 1; j < machineNum; ++j)
{
if (machines[j] < machines[flag])
flag = j;
}
// 执行本次任务
machines[flag] += works[i];
}
// 选出用时最长的机器
int max = 0;
for(int e : machines)
{
if (e > max)
max = e;
}
// 返回时间
return max;
}
有n个需要在同一天使用同一个教室的活动a1, a2, …, an,教室同一时刻只能由一个活动使用。每个活动a[i]都有一个开始时间s[i]和结束时间f[i]。一旦被选择后,活动a[i]就占据半开时间区间[s[i],f[i])。如果[s[i],f[i])和[s[j],f[j])互不重叠,a[i]和a[j]两个活动就可以被安排在这一天。求使得尽量多的活动能不冲突的举行的最大数量。

面对活动选择会遇到以下三种情况:

这里我们选择结束时间早的。

这里我们选择结束时间早的

bool cmp(const pair<int, int>& a, const pair<int, int>& b)
{
return a.second < b.second;
}
int greedyActivitySelector(const vector<pair<int, int>>& act)
{
// 按照结束时间从早到晚排序
sort(act.begin(), act.end(), cmp);
// 记录一个活动的结束时间
int end = 1;
int num = 0;
// 遍历每一个活动
for (auto e : act)
{
// 当一个活动的开始时间大于等于前一个被举行活动的结束时间时,执行这个活动
if (e.first >= end)
{
end = e.second;
++num;
}
}
return num;
}

原题链接:最多可以参加的会议数目
这个题与上题类似,我们可以使用贪心算法,按照结束时间先后去参加,先结束的先参加。
但是这道题上道题不同的地方在于,会议有持续时间,而且可以只选择一天去,所以多个会议持续时间重叠并可能不影响参加会议,这道题的难度更大。

法一:贪心算法硬解
思路详解:
直接莽夫,记录下已经参加会议过的天。每次选择结束最早的会议,遍历这个会议的开始日期到结束日期,判断是否有未参加会议的天,如果有,就参加,如果没有,就不参加。
class Solution {
public:
int maxEvents(vector<vector<int>>& events) {
// 按照结束顺序排升序,如果结束时间相同,就将开始时间早的排在前
sort(events.begin(), events.end(), [](vector<int>& a, vector<int>& b) {
if (a[1] != b[1]) return a[1] < b[1];
return a[0] < b[0];
});
// 记录参会次数
int num = 0;
// 创建一个数组,判断这天有没有使用
vector<bool> used(100001, false);
for (auto e : events)
{
// 遍历一个会议的开始到结束,判断是否有空闲的天
for (int i = e[0]; i <= e[1]; ++i)
{
if (!used[i])
{
used[i] = true;
++num;
break;
}
}
}
return num;
}
};
这种方法可以说简单粗暴,但是相应的,时间复杂度取决于题目给的数据,如果给出一组例如
[
1
,
1
]
,
[
1
,
2
]
,
[
1
,
3
]
,
[
1
,
4
]
.
.
.
.
.
.
.
[
1
,
10000
]
[1,1],[1,2],[1,3],[1,4].......[1,10000]
[1,1],[1,2],[1,3],[1,4].......[1,10000]
开始时间相同,但是结束时间不同的数据,每一次都要从开始到结束遍历,时间复杂度非常之高,会导致时间太长而无法通过。
所以,直接莽行不太通。
法二:队列+贪心
思路解析:
class Solution {
public:
int maxEvents(vector<vector<int>>& events) {
priority_queue<int, vector<int>, greater<int>> pq;
sort(events.begin(), events.end());
int curDay = 1;
int i = 0;
int n = events.size();
int ans = 0;
while (i < n || !pq.empty()) {
// 队列存储已经开始的会议, 按结束时间小根堆排序
while (i < n && events[i][0] == curDay) {
pq.push(events[i][1]);
i++;
}
// 已经结束的会议出堆
while (!pq.empty() && pq.top() < curDay) {
pq.pop();
}
// 贪心思想, 选择最早结束的会议参加
if (!pq.empty()) {
pq.pop();
ans++;
}
curDay++;
}
return ans;
}
};

原题链接:无重叠区间
法一:
要求最小移除的区间数,也就是求最大不重叠的区间,所以我们可以按照活动选择的方法,先求出最大不重叠区间数,再用总区间数减去最大不重叠区间数,就可以得到最小移除的区间数。
class Solution {
public:
static bool cmp(const vector<int>& a, const vector<int>& b)
{
return a[1] < b[1];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if (intervals.size() == 0)
return 0;
// 按结束时间从早到晚排序
sort(intervals.begin(), intervals.end(), cmp);
// 由于区间可能为负,并且第一个结束的区间一定是我们选择的不重叠的,所以我们这里先记录第一个区间
// 记录不重叠区间的数量
int num = 1;
// 记录上一个不重叠区间的结束
int end = intervals[0][1];
for (int i = 1; i < intervals.size(); ++i)
{
// 如果上一个不重叠区间的结束小于等于这个区间的开始,这个区间为不重叠区间
if (end <= intervals[i][0])
{
end = intervals[i][1];
++num;
}
}
return intervals.size() - num;
}
};
当然,这个题也可以用范围for
class Solution {
public:
static bool cmp(const vector<int>& a, const vector<int>& b)
{
return a[1] < b[1];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if (intervals.size() == 0)
return 0;
sort(intervals.begin(), intervals.end(), cmp);
int num = 0;
// 把end设置为int类型的最小值
int end = INT_MIN;
for (auto e : intervals)
{
if (end <= e[0])
{
end = e[1];
++num;
}
}
return intervals.size() - num;
}
};
这种方法时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) ,可以说已经是这个题最简单的算法。
法二:
根据题目求解:
如果我们按照起点对区间进行排序,贪心算法就可以起到很好的效果。
贪心策略:
当按照起点先后顺序考虑区间的时候。我们利用一个 prev 指针追踪刚刚添加到最终列表中的区间。遍历的时候,可能遇到图中的三种情况:
- 情况一:
当前考虑的两个区间不重叠:
在这种情况下,不移除任何区间,将 prev 赋值为后面的区间,移除区间数量不变。
- 情况二:
两个区间重叠,后一个区间的终点在前一个区间的终点之前:
这种情况下,我们可以简单地只用后一个区间。这是显然的,因为后一个区间的长度更小,可以留下更多的空间(AA 和BB),容纳更多的区间。因此, prev 更新为当前区间,移除区间的数量 + 1。
情况三:
两个区间重叠,后一个区间的终点在前一个区间的终点之后:
这种情况下,我们用贪心策略处理问题,直接移除后一个区间。这样也可以留下更多的空间,容纳更多的区间。因此,prev不变,移除区间的数量 + 1。
class Solution {
public:
static bool cmp(const vector<int>& a, const vector<int>& b)
{
//按起点递增排序
if (a[0] != b[0])
return a[0] < b[0];
return a[1] < b[1];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if (intervals.empty())
return 0;
// 排序
sort(intervals.begin(), intervals.end(), cmp);
// 删除的个数
int Dnum = 0;
// 保存前一个不重叠区间的下标
int prev = 0;
for (int i = 1; i < intervals.size(); ++i)
{
// 当区间重叠时
if (intervals[prev][1] > intervals[i][0])
{
// 判断是否为情况2
if (intervals[prev][1] > intervals[i][1])
{
prev = i;
}
++Dnum;
}
//情况3
else
{
prev = i;
}
}
return Dnum;
}
};
我们简单总结一下贪心算法的解题过程:
这是一个新的系列 ——【刷题日记】,白晨开这个系列的初衷是为了分享一些不同算法经典题型,以便于大家更好的学习编程。如果大家喜欢这个专栏的话——点击查看【刷题日记】所有文章。
如果解析有不对之处还请指正,我会尽快修改,多谢大家的包容。
如果大家喜欢这个系列,还请大家多多支持啦😋!
如果这篇文章有帮到你,还请给我一个大拇指 👍和小星星 ⭐️支持一下白晨吧!喜欢白晨【刷题日记】系列的话,不如关注👀白晨,以便看到最新更新哟!!!
我是不太能熬夜的白晨,我们下篇文章见。
目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非
嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来
目录第1题连续问题分析:解法:第2题分组问题分析:解法:第3题间隔连续问题分析:解法:第4题打折日期交叉问题分析:解法:第5题同时在线问题分析:解法:第1题连续问题如下数据为蚂蚁森林中用户领取的减少碳排放量iddtlowcarbon10012021-12-1212310022021-12-124510012021-12-134310012021-12-134510012021-12-132310022021-12-144510012021-12-1423010022021-12-154510012021-12-1523.......找出连续3天及以上减少碳排放量在100以上的用户分析:遇到这类
深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG
1.问题描述使用Python的turtle(海龟绘图)模块提供的函数绘制直线。2.问题分析一幅复杂的图形通常都可以由点、直线、三角形、矩形、平行四边形、圆、椭圆和圆弧等基本图形组成。其中的三角形、矩形、平行四边形又可以由直线组成,而直线又是由两个点确定的。我们使用Python的turtle模块所提供的函数来绘制直线。在使用之前我们先介绍一下turtle模块的相关知识点。turtle模块提供面向对象和面向过程两种形式的海龟绘图基本组件。面向对象的接口类如下:1)TurtleScreen类:定义图形窗口作为绘图海龟的运动场。它的构造器需要一个tkinter.Canvas或ScrolledCanva
我一直在尝试用Ruby实现Luhn算法。我一直在执行以下步骤:该公式根据其包含的校验位验证数字,该校验位通常附加到部分帐号以生成完整帐号。此帐号必须通过以下测试:从最右边的校验位开始向左移动,每第二个数字的值加倍。将乘积的数字(例如,10=1+0=1、14=1+4=5)与原始数字的未加倍数字相加。如果总模10等于0(如果总和以零结尾),则根据Luhn公式该数字有效;否则无效。http://en.wikipedia.org/wiki/Luhn_algorithm这是我想出的:defvalidCreditCard(cardNumber)sum=0nums=cardNumber.to_s.s
下面是我写的一个计算斐波那契数列中的值的方法:deffib(n)ifn==0return0endifn==1return1endifn>=2returnfib(n-1)+(fib(n-2))endend它工作到n=14,但在那之后我收到一条消息说程序响应时间太长(我正在使用repl.it)。有人知道为什么会这样吗? 最佳答案 Naivefibonacci进行了大量的重复计算-在fib(14)fib(4)中计算了很多次。您可以将内存添加到您的算法中以使其更快:deffib(n,memo={})ifn==0||n==1returnnen
为了防止在迁移到生产站点期间出现数据库事务错误,我们遵循了https://github.com/LendingHome/zero_downtime_migrations中列出的建议。(具体由https://robots.thoughtbot.com/how-to-create-postgres-indexes-concurrently-in概述),但在特别大的表上创建索引期间,即使是索引创建的“并发”方法也会锁定表并导致该表上的任何ActiveRecord创建或更新导致各自的事务失败有PG::InFailedSqlTransaction异常。下面是我们运行Rails4.2(使用Acti
我正在开发一个类似微论坛的项目,其中一个特殊用户发布一条快速(接近推文大小)的主题消息,订阅者可以用他们自己的类似大小的消息来响应。直截了当,没有任何形式的“挖掘”或投票,只是每个主题消息的响应按时间顺序排列。但预计会有很高的流量。我们想根据它们引起的响应嗡嗡声来标记主题消息,使用0到10的等级。在谷歌上搜索了一段时间的趋势算法和开源社区应用示例,到目前为止已经收集到两个有趣的引用资料,但我还没有完全理解它们:Understandingalgorithmsformeasuringtrends,关于使用基线趋势算法比较维基百科页面浏览量的讨论,在SO上。TheBritneySpearsP
我收到错误:unsupportedcipheralgorithm(AES-256-GCM)(RuntimeError)但我似乎具备所有要求:ruby版本:$ruby--versionruby2.1.2p95OpenSSL会列出gcm:$opensslenc-help2>&1|grepgcm-aes-128-ecb-aes-128-gcm-aes-128-ofb-aes-192-ecb-aes-192-gcm-aes-192-ofb-aes-256-ecb-aes-256-gcm-aes-256-ofbRuby解释器:$irb2.1.2:001>require'openssl';puts