LeetCode算法之贪心

文章介绍了贪心算法的概念,强调其在保证局部最优解的基础上找到全局最优解的特点,并通过三个具体的LeetCode题目(分配饼干、无重叠区间、用最少数量的箭引爆气球)展示了贪心算法的应用,每个问题的关键在于排序和迭代以寻找最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

贪心算法

1. 思想

保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。

什么是贪心算法呢?贪心算法可以认为是动态规划算法的一个特例,相比动态规划,使用贪心算法需要满足更多的条件(贪心选择性质),但是效率比动态规划要高。

比如说一个算法问题使用暴力解法需要指数级时间,如果能使用动态规划消除重叠子问题,就可以降到多项式级别的时间,如果满足贪心选择性质,那么可以进一步降低时间复杂度,达到线性级别的。

什么是贪心选择性质呢,简单说就是:每一步都做出一个局部最优的选择,最终的结果就是全局最优。注意哦,这是一种特殊性质,其实只有一小部分问题拥有这个性质。

动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。

2. 经典问题

LeetCode题目

1. 分配饼干

455. 分发饼干

难度简单293

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

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

示例 1:

输入: g = [1,2,3], s = [1,1]
输出: 1
解释: 
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。

关键:

把胃口值和饼干升序排

贪心思想是: 尽量用小饼干来满足小胃口的人

使用双指针,看最后还是是不是孩子都满足了

代码

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int g1 = 0;
        int s1 = 0;
        while(g1<g.length && s1<s.length){
            if(g[g1] <= s[s1]){
                g1++;
                s1++;
            } else {
                s1++;
            }
        }
        return g1;
    }
}

2. 无重叠区间

435. 无重叠区间

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

注意:

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

示例 1:

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

输出: 1

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

关键:

这道题的本质就是 找出有几个区间是不重复的,得到不重叠的区间数量,在用总的数量减去不重叠的区间数量,就是多的区间的数量

重叠区间:按照end升序排

代码

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) { 
        if(intervals.length == 0) return 0;
        // 找出互不重叠的区间, 然后就是len - k即可
        Arrays.sort(intervals, new Comparator<int[]>(){
            public int compare(int[] a, int[] b){
                return a[1] - b[1]; // 以结束时间升序
            }
        });
        int end = intervals[0][1];
        int count = 1;
        for(int i = 1; i<intervals.length;i++){
            int[] interval = intervals[i];
            if(interval[0]>=end){
                count++;
                end = interval[1];
            }

        }
        return intervals.length-count;
    }
}

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

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

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

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

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

示例 1:

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

关键:

这个就是和上一题完全一样的,没什么大的区别

class Solution {
    public int findMinArrowShots(int[][] points) {

        if(points.length == 0) return 0;
        Arrays.sort(points, new Comparator<int[]>(){
            public int compare(int[] a, int[] b){
                return a[1] > b[1]? 1:-1; // 防止内存溢出!!!!!
            }
        });

        int end = points[0][1];
        int count = 1;
        for(int i = 0; i<points.length;i++){
            if(end<points[i][0]){
                count++;
                end = points[i][1];
            }
        }
        return count;
    }
}

贪心算法

1. 思想

保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。

什么是贪心算法呢?贪心算法可以认为是动态规划算法的一个特例,相比动态规划,使用贪心算法需要满足更多的条件(贪心选择性质),但是效率比动态规划要高。

比如说一个算法问题使用暴力解法需要指数级时间,如果能使用动态规划消除重叠子问题,就可以降到多项式级别的时间,如果满足贪心选择性质,那么可以进一步降低时间复杂度,达到线性级别的。

什么是贪心选择性质呢,简单说就是:每一步都做出一个局部最优的选择,最终的结果就是全局最优。注意哦,这是一种特殊性质,其实只有一小部分问题拥有这个性质。

动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。

2. 经典问题

LeetCode题目

1. 分配饼干

455. 分发饼干

难度简单293

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

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

示例 1:

输入: g = [1,2,3], s = [1,1]
输出: 1
解释: 
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。

关键:

把胃口值和饼干升序排

贪心思想是: 尽量用小饼干来满足小胃口的人

使用双指针,看最后还是是不是孩子都满足了

代码

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int g1 = 0;
        int s1 = 0;
        while(g1<g.length && s1<s.length){
            if(g[g1] <= s[s1]){
                g1++;
                s1++;
            } else {
                s1++;
            }
        }
        return g1;
    }
}

2. 无重叠区间

435. 无重叠区间

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

注意:

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

示例 1:

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

输出: 1

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

关键:

这道题的本质就是 找出有几个区间是不重复的,得到不重叠的区间数量,在用总的数量减去不重叠的区间数量,就是多的区间的数量

重叠区间:按照end升序排

代码

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) { 
        if(intervals.length == 0) return 0;
        // 找出互不重叠的区间, 然后就是len - k即可
        Arrays.sort(intervals, new Comparator<int[]>(){
            public int compare(int[] a, int[] b){
                return a[1] - b[1]; // 以结束时间升序
            }
        });
        int end = intervals[0][1];
        int count = 1;
        for(int i = 1; i<intervals.length;i++){
            int[] interval = intervals[i];
            if(interval[0]>=end){
                count++;
                end = interval[1];
            }

        }
        return intervals.length-count;
    }
}

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

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

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

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

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

示例 1:

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

关键:

这个就是和上一题完全一样的,没什么大的区别

class Solution {
    public int findMinArrowShots(int[][] points) {

        if(points.length == 0) return 0;
        Arrays.sort(points, new Comparator<int[]>(){
            public int compare(int[] a, int[] b){
                return a[1] > b[1]? 1:-1; // 防止内存溢出!!!!!
            }
        });

        int end = points[0][1];
        int count = 1;
        for(int i = 0; i<points.length;i++){
            if(end<points[i][0]){
                count++;
                end = points[i][1];
            }
        }
        return count;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值