LeetCode学习(一)贪心算法

1.分发饼干(leetcode455)(Easy)

题目:
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值(注意:每个小孩最多只能给一块饼干)

样例:

输入: g = [1,2], s = [1,2,3]
输出: 2
解释: 
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.

思路:
运用贪心策略,首先用"小饼干"满足胃口小的,然后满足其他孩子;
将饼干与胃口值进行升序排序,然后用饼干依次满足孩子,若能满足,则继续使用后面的饼干满足下一位的孩子,直到饼干或者孩子被遍历完即可;(饼干与孩子只能是一对一的关系

代码:

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());  //升序排序
        int i=0,j=0;
        while(i<g.size()&&j<s.size()){  //饼干或者孩子结束则结束
            if(g[i]<=s[j])i++;  //当前饼干满足则进行下一位孩子的任务
            j++;  //不满足或者满足都需要使用下一个饼干
        }
        return i;
    }
};

2.分发糖果(leetcode135)(Hard)

题目:
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 评分更高的孩子必须比他两侧的邻位孩子获得更多的糖果。

那么这样下来,老师至少需要准备多少颗糖果呢?

样例:

输入:[1,0,2]
输出:5
解释:你可以分别给这三个孩子分发 2、1、2 颗糖果。
输入:[1,2,2]
输出:4
解释:你可以分别给这三个孩子分发 1、2、1 颗糖果。
     第三个孩子只得到 1 颗糖果,这已满足上述两个条件。

思路:

对于贪心策略,这道题还是从局部看起,只需满足当前孩子的糖果数比两侧的孩子多即可,首先从左到右进行扫描,如果右边孩子的评分高于左侧,则使得右边孩子的糖果数为左边孩子的糖果数加1(结果要使得总计糖果数最少),因为要满足两侧的情况,所以再从右到左进行扫描,如果左边孩子评分比右边高,并且左边孩子糖果数少于右边,则使左边孩子的糖果数为右边糖果数加1;(从左到右扫描并更新右边孩子糖果数也实现了局部影响全局的策略,试想如果从左到右扫描而更新左边孩子的糖果数会有什么影响?)

代码:

class Solution {
public:
    int candy(vector<int>& ratings) {
        if(ratings.size()<=1){ //0||1个孩子直接返回0,1
            return ratings.size();
        }
        int ans[100005];  //记录每个孩子的糖果数
        for(int i=0;i<ratings.size();i++){
            ans[i]=1;   //初始每个孩子糖果数为1
        }
        int cnt=0;
        for(int i=1;i<ratings.size();i++){  //从左到右扫描
            if(ratings[i]>ratings[i-1]){  //右边比左边大
                ans[i]=ans[i-1]+1;  //更新右边孩子的糖果数为左边+1
            }
        }
        for(int i=ratings.size()-1;i>0;i--){ //再从右到左扫描
            if(ratings[i-1]>ratings[i]){  //左边孩子比右边大
                if(ans[i-1]<=ans[i]){	//并且左边糖果数小于右边
                    ans[i-1]=ans[i]+1;  //则更新左边为右边+1
                }
            }      
        }
        for(int i=0;i<ratings.size();i++){
            cnt+=ans[i];  //统计所有的糖果数
        }
       return cnt;
    }
};

3.无重叠区间(leetcode435)(Medium)

题目:

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:

  • 可以认为区间的终点总是大于它的起点。
  • 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。

样例:

输入: [ [1,2], [2,3], [3,4], [1,3] ]

输出: 1

解释: 移除 [1,3] 后,剩下的区间没有重叠
输入: [ [1,2], [1,2], [1,2] ]

输出: 2

解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

思路:

对于选择要留下的区间,其范围要尽可能的小,尤其是区间的结尾更为重要,根据贪心的策略,选择的区间范围越小,则移除的区间数量就更少;
这道题首先根据每个区间的结尾将区间进行升序排序,对于区间结尾相同的区间,这里可以不用考虑区间的开始坐标(区间的结尾已经决定是否有重叠区域);然后从左到右遍历所有区间,用后一个区间的开始去比较前一个区间的结尾即可知是否重叠,并将有重叠的区间去除即可;

代码:

class Solution {
public:
   
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        if(intervals.empty()){  //区间为空返回0
            return 0;
        }
        int ans=intervals.size();  //区间个数
        // cout<<ans;
        // int pre=0;   
        sort(intervals.begin(),intervals.end(),[](const auto&a,const auto&b){
            return a[1]<b[1];  //按区间结尾进行升序排序
        });
        int total=1,pre=intervals[0][1];  //pre记录上一个区间的结尾
        for(int i=1;i<ans;i++){
            if(intervals[i][0]>=pre){  //记录没有重叠的区间个数
               total++;    //无重叠区间个数
               pre=intervals[i][1];  //更新前驱
            }
        }
        return intervals.size()-total;   
    }
   
};

4.引爆气球(leetcode452)(Medium)

题目:
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。

一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。

给你一个数组 points ,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数。

样例:

输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:对于该样例,x = 6 可以射爆 [2,8],[1,6] 两个气球,以及 x = 11 射爆另外两个气球
输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4

思路:

这道题与上面的无重叠区间很相似,前面是计算要移除的最少区间,而且我们也可以算出重叠的区间数,这里我们只要计算出有多少个区间有公共区间即可;(注意这里的边界值也算是公共区间)根据前面的贪心策略,还是将区间结尾进行升序排序,完成后遍历所有区间,用后面区间的开始与前面区间的结尾进行比较,小于等于即有公共区间,继而判断后面的区间与前几个区间是否有公共区间(都是与当前比较的第一个区间进行比较)(弓箭一次可以射没有上限个气球),每当找到一个有公共子区间的气球,则弓箭数减1,遍历所有区间即可;

代码:

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        if(points.empty()){  //0个气球返回0
            return 0;
        }
        sort(points.begin(),points.end(),[](const auto&a,const auto&b){
            return a[1]<b[1];   //按区间右端升序排序
        });
        int ans=points.size();  //无重叠区间所需弓箭数
        int total=0,pre=points[0][1];
        for(int i=1;i<points.size();i++){
            if(points[i][0]<=pre){  //有一个相同区间则弓箭数少1,多个公共区间需一致,即要与第一个pre对比
                ans--;
            }else{
                pre=points[i][1];  //这里可能与前面的区间有重叠,但是与再前面的区间不重叠,则更新pre
            }
        }
        return ans;
    }
};

5.非递减序列(leetcode665)(Easy)

题目:
给你一个长度为 n 的整数数组,请你判断在最多改变 1个元素的情况下,该数组能否变成一个非递减数列。

我们是这样定义一个非递减数列的:对于数组中所有的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]。

样例:

输入: nums = [4,2,3]
输出: true
解释: 你可以通过把第一个4变成1来使得它成为一个非递减数列。
输入: nums = [4,2,1]
输出: false
解释: 你不能在只改变一个元素的情况下将其变为非递减数列。

思路:

总体来说,该数列中至多一个低谷,即最多一个元素,存在nums[i]<nums[i-1];当然也可以在计算过程中修改一个值,使其满足非递减序列;要修改的值存在以下几种情况:

  • 4,2,3 要修改的数位于第二位,则修改第一个数
  • 1,5,2,4 对于数字2,比nums[i-1]小,但是比nums[i-2]大,则修改5=2
  • 3,3,2,4 对于2,比nums[i-1]小,且比nums[i-2]小,则修改2=3,也只有这一种情况会影响到落差的判断,即nums[i]与nums[i-1]的大小关系,此处修改了nums[i]

代码:

class Solution {
public:
    bool checkPossibility(vector<int>& nums) {
        if(nums.size()<=1){ //0||1个数据一定满足
            return true;
        }
        int cnt=0;
        for(int i=1;i<nums.size();i++){
           if(nums[i]>=nums[i-1]){   //如果为非递减序列,则继续
               continue;
           }  //否则为低谷
           cnt++;  
           if(cnt>=2){ //两个低谷,则退出
                return false;
           }
           if(i>=2&&nums[i]<nums[i-2]){  //小于前面两个值,则必须修改当前值
               nums[i]=nums[i-1];
           }//另一种情况不必考虑
        }
        return true;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值