当前位置: 代码网 > it编程>软件设计>算法 > 【算法训练营】贪心算法专题(一)

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

2024年08月02日 算法 我要评论
算法解释顾名思义,贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的。举一个最简单的例子:小明和小王喜欢吃苹果,小明可以吃五个,小王可以吃三个。已知苹果园里有吃不完的苹果,求小明和小王一共最多吃多少个苹果。在这个例子中,我们可以选用的贪心策略为,每个人吃自己能吃的最多数量的苹果,这在每个人身上都是局部最优的。又因为全局结果是局部结果的简单求和,且局部结果互不相干,因此局部最优的策略也同样是全局最优的策略。

前言

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

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

算法解释

📍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. 无重叠区间

在这里插入图片描述

解题思路

在选择要保留区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间就越大,就越能保留更多的区间。因此,我们采取的贪心策略为,优先保留结尾小且不相交的区间。
具体实现方法为,先把区间按照结尾的大小进行增序排序,每次选择结尾最小且和前一个选择的区间不重叠的区间。我们这里使用 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. 种花问题

在这里插入图片描述

解题思路

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

自己为空
左边为空 或者 自己是最左
右边为空 或者 自己是最右
最后判断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. 用最少数量的箭引爆气球

在这里插入图片描述

解题思路

  • 这题和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. 划分字母区间

在这里插入图片描述

解题思路

  • 这题也是使用贪心算法
  • 在做一些贪心算法的题时预处理一下会让代码变得更优美
  • 这题要分成更多的片段,但是同样的字符只能在同一个片段中
  • 预处理:将每一个字符的最后位置记录下来
  • 设置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. 买卖股票的最佳时机

在这里插入图片描述

解题思路

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

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

在这里插入图片描述

解题思路

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

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. 根据身高重建队列

在这里插入图片描述

解题思路

  • [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. 非递减数列

在这里插入图片描述

解题思路

思路来自:@码不停题

大佬思路出生入化

这道题给了我们一个数组,说我们最多有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;
    }
};
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com