【算法训练营】贪心算法专题(一)

简介: 【算法训练营】贪心算法专题(一)

前言

这篇都是关于贪心算法的OJ题,但是并不只有这一种方法,但在本篇中只写出贪心的解法,标题下面都有超链接,可以同时过去写题哦~

之后还会更新相关专题的,敬请期待!!

算法解释

顾名思义,贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的。

举一个最简单的例子:小明和小王喜欢吃苹果,小明可以吃五个,小王可以吃三个。已知苹果园里有吃不完的苹果,求小明和小王一共最多吃多少个苹果。在这个例子中,我们可以选用的贪心策略为,每个人吃自己能吃的最多数量的苹果,这在每个人身上都是局部最优的。又因为全局结果是局部结果的简单求和,且局部结果互不相干,因此局部最优的策略也同样是全局最优的策略。

📍455. 分发饼干

链接:455. 分发饼干

解题思路

因为饥饿度最小的孩子最容易吃饱,所以我们先考虑这个孩子。为了尽量使得剩下的饼干可以满足饥饿度更大的孩子,所以我们应该把大于等于这个孩子饥饿度的、且大小最小的饼干给这个孩子。满足了这个孩子之后,我们采取同样的策略,考虑剩下孩子里饥饿度最小的孩子,直到没有满足条件的饼干存在。

简而言之,这里的贪心策略是,给剩余孩子里最小饥饿度的孩子分配最小的能饱腹的饼干。

至于具体实现,因为我们需要获得大小关系,一个便捷的方法就是把孩子和饼干分别排序。这样我们就可以从饥饿度最小的孩子和大小最小的饼干出发,计算有多少个对子可以满足条件。

注意:对数组或字符串排序是常见的操作,方便之后的大小比较。

注意:在之后的讲解中,若我们谈论的是对连续空间的变量进行操作,我们并不会明确区分数组和字符串,因为它们本质上都是在连续空间上的有序变量集合。一个字符串“abc”可以被看作一个数组 [‘a’,‘b’,‘c’]。

AC代码

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        int child=0,cookie=0;
        while(child<g.size()&&cookie<s.size())
        {
            if(g[child] <= s[cookie]) ++child;
            ++cookie;
        }
        return child;
    }
};

📍435. 无重叠区间

链接:435. 无重叠区间

解题思路

在选择要保留区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间就越大,就越能保留更多的区间。因此,我们采取的贪心策略为,优先保留结尾小且不相交的区间。

具体实现方法为,先把区间按照结尾的大小进行增序排序,每次选择结尾最小且和前一个选择的区间不重叠的区间。我们这里使用 C++ 的 Lambda,结合 std::sort() 函数进行自定义排序。

在样例中,排序后的数组为 [[1,2], [1,3], [2,4]]。按照我们的贪心策略,首先初始化为区间[1,2];由于 [1,3] 与 [1,2] 相交,我们跳过该区间;由于 [2,4] 与 [1,2] 不相交,我们将其保留。因此最终保留的区间为 [[1,2], [2,4]]。

AC代码

class Solution {
public:
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end(), [](vector<int>& a, vector<int>& b) {
            return a[1] < b[1];
        });
        int total = 0, prev = intervals[0][1];
        for (int i = 1; i < intervals.size(); ++i) {
            if (intervals[i][0] < prev) {
                ++total;
            } else {
                prev = intervals[i][1];
            }
        }
        return total;
    }
};

📍605. 种花问题

链接:605. 种花问题

解题思路

从左向右遍历花坛,在可以种花的地方就种一朵,能种就种(因为在任一种花时候,不种都不会得到更优解),就是一种贪心的思想 这里可以种花的条件是:

自己为空

左边为空 或者 自己是最左

右边为空 或者 自己是最右

最后判断n朵花是否有剩余,为了效率起见,可以在种花的过程中做判断,一旦花被种完就返回true

AC代码

class Solution {
public:
    bool canPlaceFlowers(vector<int>& flowerbed, int n) {
        for(int i=0;i<flowerbed.size();++i)
        {
            if(flowerbed[i]==0&&(i==0||flowerbed[i-1]==0)&&(i==flowerbed.size()-1||flowerbed[i+1]==0))
            {
                n--;
                if(n<=0)return true;
                flowerbed[i]=1;
            }
        }
        return n<=0;
    }
};

📍452. 用最少数量的箭引爆气球

链接:452. 用最少数量的箭引爆气球

解题思路

  • 这题和435题有异曲同工之妙
  • 注意它是垂直x轴射箭
  • 和435题思路相似,先按右边界升序排列
  • 排列好以后,拿出第一个区间的右边界
  • 循环遍历后面的区间的左边界
  • 如果左边界大于前一个区间的右边界
  • 就加1,然后将pos设置为这个区间的右边界

总之:

我想让当前区间尽可能重合更多区间,而且希望这些区间,内部互相都重合。

即我想追求遍历时重合的连续性。基于当前区间右端这个参照物,b 的重合带来一层限制,右端递增带来另一层限制,二者共同保证了 a、b 的重合。

当前区间  -----R
  区间a ---------R
      区间b  L-------

AC代码

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        if(points.empty())return 0;
        sort(points.begin(),points.end(),[](const vector<int>&u,const vector<int>&v){
            return u[1]<v[1]
        });
        int pos=points[0][1];
        int ans=1;
        for(const vector<int>&ball:points)
        {
            if(ball[0]>pos)
            {
                pos=ball[1];
                ++ans;
            }
        }
        return ans;
    }
};

📍763. 划分字母区间

链接:763. 划分字母区间

解题思路

  • 这题也是使用贪心算法
  • 在做一些贪心算法的题时预处理一下会让代码变得更优美
  • 这题要分成更多的片段,但是同样的字符只能在同一个片段中
  • 预处理:将每一个字符的最后位置记录下来
  • 设置start、end记录字段串的起始终止位置
  • 再遍历一遍字符串,如果某个字符的最后位置大于当前end
  • end=那个字符的最后位置
  • 直到 i 等于end说明字符找到了结尾

AC代码

class Solution {
public:
    vector<int> partitionLabels(string s) {
        int last[26];
        int length = s.size();
        for (int i = 0; i < length; i++) {
            last[s[i] - 'a'] = i;
        }
        vector<int> partition;
        int start = 0, end = 0;
        for (int i = 0; i < length; i++) {
            end = max(end, last[s[i] - 'a']);
            if (i == end) {
                partition.push_back(end - start + 1);
                start = end + 1;
            }
        }
        return partition;
    }
};

📍121. 买卖股票的最佳时机

链接:121. 买卖股票的最佳时机

解题思路

  • 只需要考虑最低的花费和最高的利润
  • 最低的花费是在当天的价格中选择最低的
  • 利润就需要考虑今天的价格 - 当前最低利润
  • 然后取最大利润

AC代码

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int cost=INT_MAX;
        int profit=0;
        for(auto price:prices)
        {
            cost=min(cost,price);
            profit=max(profit,price-cost);
        }
        return profit;
    }
};

📍122. 买卖股票的最佳时机 II

链接:122. 买卖股票的最佳时机 II

解题思路

  • 这题和上一题不同,它的要求是一只股票,不管你什么时候卖,求最大利润
  • 只需要在低点买,高点卖即可

AC代码

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int profit=0;
        for(int i=1;i<prices.size();++i)
        {
            if(prices[i]-prices[i-1]>0)profit+=prices[i]-prices[i-1];
        }
        return profit;
    }
};

📍406. 根据身高重建队列

链接:406. 根据身高重建队列

解题思路

  • [h,k],h是身高看得懂。问题是为毛k这个数这么奇怪。是不是!
  • 试着理解一下,k代表的是,现在这个队里,自己前面的人里面,h比自己大或与自己相等的人-的个数。
  • 好比[7,1],代表,自己高7,排在自己前面,且比自己高或相等的只有一个人。
  • 而[7,0]意思是,排在自己前面的人,没有比自己高的

AC代码:

class Solution {
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),[](const vector<int>&u,const vector<int>&v){
            return u[0]>v[0] || (u[0]==v[0]&&u[1]<v[1]);
        });
        vector<vector<int>> ans;
        for(auto& person : people)
        {
            ans.insert(ans.begin()+person[1],person);
        }
        return ans;
    }
};

📍665. 非递减数列

链接:665. 非递减数列

解题思路

思路来自:@码不停题

大佬思路出生入化

这道题给了我们一个数组,说我们最多有1次修改某个数字的机会,
 问能不能将数组变为非递减数组。题目中给的例子太少,不能覆盖所有情况,我们再来看下面三个例子:
  4,2,3
    -1,4,2,3
  2,3,3,2,4
我们通过分析上面三个例子可以发现,当我们发现后面的数字小于前面的数字产生冲突后,
[1]有时候需要修改前面较大的数字(比如前两个例子需要修改4),
[2]有时候却要修改后面较小的那个数字(比如前第三个例子需要修改2),
那么有什么内在规律吗?是有的,判断修改那个数字其实跟再前面一个数的大小有关系,
首先如果在前面的数不存在,比如例子1,4前面没有数字了,我们直接修改前面的数字为当前的数字2即可。
而当再前面的数字存在,并且小于当前数时,比如例子2,-1小于2,我们还是需要修改前面的数字4为当前数字2;
如果再前面的数大于当前数,比如例子3,3大于2,我们需要修改当前数2为前面的数3。

AC代码

class Solution {
public:
    bool checkPossibility(vector<int>& nums) {
        int count=0;
        for(int i=1;i<nums.size();++i)
        {
            if(nums[i-1]>nums[i])
            {
                count++;
                if(i-1==0)nums[i-1]=nums[i];
                else if(i>=2&&nums[i-2]<nums[i])nums[i-1]=nums[i];
                else if(nums[i-2]>nums[i])nums[i]=nums[i-1];
            }
            if(count>1)return false;
        }
        return true;
    }
};


相关文章
|
13天前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
30 2
|
1月前
|
存储 机器学习/深度学习 算法
蓝桥杯练习题(三):Python组之算法训练提高综合五十题
蓝桥杯Python编程练习题的集合,涵盖了从基础到提高的多个算法题目及其解答。
60 3
蓝桥杯练习题(三):Python组之算法训练提高综合五十题
|
21天前
|
分布式计算 Java 开发工具
阿里云MaxCompute-XGBoost on Spark 极限梯度提升算法的分布式训练与模型持久化oss的实现与代码浅析
本文介绍了XGBoost在MaxCompute+OSS架构下模型持久化遇到的问题及其解决方案。首先简要介绍了XGBoost的特点和应用场景,随后详细描述了客户在将XGBoost on Spark任务从HDFS迁移到OSS时遇到的异常情况。通过分析异常堆栈和源代码,发现使用的`nativeBooster.saveModel`方法不支持OSS路径,而使用`write.overwrite().save`方法则能成功保存模型。最后提供了完整的Scala代码示例、Maven配置和提交命令,帮助用户顺利迁移模型存储路径。
|
1月前
|
机器学习/深度学习 算法 决策智能
【机器学习】揭秘深度学习优化算法:加速训练与提升性能
【机器学习】揭秘深度学习优化算法:加速训练与提升性能
|
1月前
|
算法 Java C++
【贪心算法】算法训练 ALGO-1003 礼物(C/C++)
【贪心算法】算法训练 ALGO-1003 礼物(C/C++)
【贪心算法】算法训练 ALGO-1003 礼物(C/C++)
|
1月前
|
算法 C++
蓝桥 算法训练 共线(C++)
蓝桥 算法训练 共线(C++)
|
2月前
|
算法 前端开发
一文了解贪心算法和回溯算法在前端中的应用
该文章深入讲解了贪心算法与回溯算法的原理及其在前端开发中的具体应用,并通过分析LeetCode题目来展示这两种算法的解题思路与实现方法。
|
4月前
knn增强数据训练
【7月更文挑战第28天】
41 2
|
3月前
|
算法 搜索推荐
支付宝商业化广告算法问题之基于pretrain—>finetune范式的知识迁移中,finetune阶段全参数训练与部分参数训练的效果如何比较
支付宝商业化广告算法问题之基于pretrain—>finetune范式的知识迁移中,finetune阶段全参数训练与部分参数训练的效果如何比较
|
3月前
|
算法
【算法】贪心算法——柠檬水找零
【算法】贪心算法——柠檬水找零
下一篇
无影云桌面