> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。> 目标:了解什么是贪心算法,并且掌握贪心算法。
> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!
> 专栏选自:贪心算法_დ旧言~的博客-CSDN博客
> 望小伙伴们点赞👍收藏✨加关注哟💕💕
一、算法讲解
贪心算法的定义:
贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
解题的一般步骤是:
- 建立数学模型来描述问题;
- 把求解的问题分成若干个子问题;
- 对每一子问题求解,得到子问题的局部最优解;
- 把子问题的局部最优解合成原来问题的一个解。
如果大家比较了解动态规划,就会发现它们之间的相似之处。最优解问题大部分都可以拆分成一个个的子问题,把解空间的遍历视作对子问题树的遍历,则以某种形式对树整个的遍历一遍就可以求出最优解,大部分情况下这是不可行的。贪心算法和动态规划本质上是对子问题树的一种修剪,两种算法要求问题都具有的一个性质就是子问题最优性(组成最优解的每一个子问题的解,对于这个子问题本身肯定也是最优的)。
动态规划方法代表了这一类问题的一般解法,我们自底向上构造子问题的解,对每一个子树的根,求出下面每一个叶子的值,并且以其中的最优值作为自身的值,其它的值舍弃。而贪心算法是动态规划方法的一个特例,可以证明每一个子树的根的值不取决于下面叶子的值,而只取决于当前问题的状况。换句话说,不需要知道一个节点所有子树的情况,就可以求出这个节点的值。由于贪心算法的这个特性,它对解空间树的遍历不需要自底向上,而只需要自根开始,选择最优的路,一直走到底就可以了。
二、算法习题
2.1、第一题
题目描述:
算法思路:
- 我们发现,当从 i 位置出发,⾛了 step 步之后,如果失败了。那么 [i, i + step] 这个区间内任意⼀个位置作为起点,都不可能环绕⼀圈。
- 因此我们枚举的下⼀个起点,应该是 i + step + 1 。
代码呈现:
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost)
{
int n = gas.size();
for (int i = 0; i < n; i++) // 依次枚举所有的起点
{
int rest = 0; // 标记⼀下净收益
int step = 0;
for (; step < n; step++) // 枚举向后⾛的步数
{
int index = (i + step) % n; // 求出⾛ step 步之后的下标
rest = rest + gas[index] - cost[index];
if (rest < 0)
break;
}
if (rest >= 0)
return i;
i = i + step; // 优化
}
return -1;
}
};
2.2、第二题
题目描述:
算法思路:
- a. 为了⽅便处理数中的每⼀位数字,可以先讲整数转换成字符串;
- b. 从左往右扫描,找到第⼀个递减的位置;
- c. 从这个位置向前推,推到相同区域的最左端;
- d. 该点的值 -1 ,后⾯的所有数统⼀变成 9 。
代码呈现:
class Solution {
public:
int monotoneIncreasingDigits(int n)
{
string s = to_string(n); // 把数字转化成字符串
int i = 0, m = s.size();
// 找第⼀个递减的位置
while (i + 1 < m && s[i] <= s[i + 1])
i++;
if (i + 1 == m)
return n; // 判断⼀下特殊情况
// 回推
while (i - 1 >= 0 && s[i] == s[i - 1])
i--;
s[i]--;
for (int j = i + 1; j < m; j++)
s[j] = '9';
return stoi(s);
}
};
2.3、第三题
题目描述:
算法思路:
- 当 end <= begin 的时候,只能执⾏「加法」操作;
- 当 end > begin 的时候,对于「奇数」来说,只能执⾏「加法」操作;对于「偶数」来说,最好的⽅式就是执⾏「除法」操作
这样的话,每次的操作都是「固定唯⼀」的。
代码呈现:
class Solution {
public:
int brokenCalc(int startValue, int target)
{
// 正难则反 + 贪⼼
int ret = 0;
while (target > startValue) {
if (target % 2 == 0)
target /= 2;
else
target += 1;
ret++;
}
return ret + startValue - target;
}
};
2.4、第四题
题目描述:
算法思路:
贪⼼策略:
- 先按照区间的「左端点」排序:此时我们会发现,能够合并的区间都是连续的;
- 然后从左往后,按照求「并集」的⽅式,合并区间。
如何求并集:由于区间已经按照「左端点」排过序了,因此当两个区间「合并」的时候,合并后的区间
- 左端点就是「前⼀个区间」的左端点;
- 右端点就是两者「右端点的最⼤值」。
代码呈现:
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals)
{
// 1. 先按照左端点排序
sort(intervals.begin(), intervals.end());
// 2. 合并区间
int left = intervals[0][0], right = intervals[0][1];
vector<vector<int>> ret;
for (int i = 1; i < intervals.size(); i++) {
int a = intervals[i][0], b = intervals[i][1];
if (a <= right) // 有重叠部分
{
// 合并 - 求并集
right = max(right, b);
} else // 没有重叠部分
{
ret.push_back({left, right}); // 加⼊到结果中
left = a;
right = b;
}
}
// 别忘了最后⼀个区间
ret.push_back({left, right});
return ret;
}
};
2.5、第五题
题目描述:
算法思路:
贪⼼策略:
- 按照「左端点」排序;
- 当两个区间「重叠」的时候,为了能够「在移除某个区间后,保留更多的区间」,我们应该把「区间范围较⼤」的区间移除。
如何移除区间范围较⼤的区间:
由于已经按照「左端点」排序了,因此两个区间重叠的时候,我们应该移除「右端点较⼤」的区间
代码呈现:
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals)
{
// 1. 按照左端点排序
sort(intervals.begin(), intervals.end());
// 2. 移除区间
int ret = 0;
int left = intervals[0][0], right = intervals[0][1];
for (int i = 1; i < intervals.size(); i++) {
int a = intervals[i][0], b = intervals[i][1];
if (a < right) // 有重叠部分
{
ret++; // 删掉⼀个区间
right = min(right, b);
} else // 没有重叠部分
{
right = b;
}
}
return ret;
}
};
2.6、第六题
题目描述:
算法思路:
贪⼼策略:
- 按照左端点排序,我们发现,排序后有这样⼀个性质:「互相重叠的区间都是连续的」;
- 这样,我们在射箭的时候,要发挥每⼀⽀箭「最⼤的作⽤」,应该把「互相重叠的区间」统⼀引爆。
如何求互相重叠区间?
- 由于我们是按照「左端点」排序的,因此对于两个区间,我们求的是它们的「交集」
- 左端点为两个区间左端点的「最⼤值」(但是左端点不会影响我们的合并结果,所以可以忽略);
- 右端点为两个区间右端点的「最⼩值」。
代码呈现:
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points)
{
// 1. 按照左端点排序
sort(points.begin(), points.end());
// 2. 求互相重叠区间的数量
int right = points[0][1];
int ret = 1;
for (int i = 1; i < points.size(); i++) {
int a = points[i][0], b = points[i][1];
if (a <= right) // 有重叠部分
{
right = min(right, b);
} else // ⽆重叠部分
{
ret++;
right = b;
}
}
return ret;
}
};
三、结束语
今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。