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

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

前言

这篇都是关于贪心算法的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;
    }
};


相关文章
|
7月前
|
算法
算法:贪心算法的原理和基本思路
算法:贪心算法的原理和基本思路
|
7月前
|
算法
带你读《图解算法小抄》二十二、贪心算法(3)
带你读《图解算法小抄》二十二、贪心算法(3)
|
7月前
|
算法
带你读《图解算法小抄》二十二、贪心算法(5)
带你读《图解算法小抄》二十二、贪心算法(5)
|
12天前
|
算法 程序员
贪心算法(被黑心公司强迫写的降薪算法)
贪心算法(被黑心公司强迫写的降薪算法)
|
2月前
|
存储 算法 Java
【算法设计与分析】— —实现活动安排问题的贪心算法。
【算法设计与分析】— —实现活动安排问题的贪心算法。
47 0
|
5月前
|
算法 测试技术
[算法训练营] 贪心算法专题(二)
[算法训练营] 贪心算法专题(二)
32 0
|
5月前
|
算法
[算法训练营] 回溯算法专题(三)
[算法训练营] 回溯算法专题(三)
26 0
|
5月前
|
存储 算法
[算法训练营] 回溯算法专题(二)
[算法训练营] 回溯算法专题(二)
32 0
|
5月前
|
存储 算法
[算法训练营] 回溯算法专题(一)
[算法训练营] 回溯算法专题(一)
35 0
|
6月前
|
监控 算法 程序员
代码随想录算法训练营第三十六天 | LeetCode 738. 单调递增的数字、贪心算法总结
代码随想录算法训练营第三十六天 | LeetCode 738. 单调递增的数字、贪心算法总结
27 0