贪心算法总结

贪心算法:局部最优达到全局最优

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

贪心思想:
因为本题用到了贪心算法所以先来了解一下「贪心算法」的问题需要满足的条件:

最优子结构:规模较大的问题的解由规模较小的子问题的解组成,规模较大的问题的解只由其中一个规模较小的子问题的解决定;
无后效性:后面阶段的求解不会修改前面阶段已经计算好的结果;
贪心选择性质:从局部最优解可以得到全局最优解。
综上可得:贪心算法就是做出当前状态下的最优选择认为就可以解决问题。

第一题:

942. 增减字符串匹配

由范围 [0,n] 内所有整数组成的 n + 1 个整数的排列序列可以表示为长度为 n 的字符串 s ,其中:

如果 perm[i] < perm[i + 1] ,那么 s[i] == 'I' 
如果 perm[i] > perm[i + 1] ,那么 s[i] == 'D' 
给定一个字符串 s ,重构排列 perm 并返回它。如果有多个有效排列perm,则返回其中 任何一个 。

示例 1:

输入:s = "IDID"
输出:[0,4,1,3,2]

示例 2:

输入:s = "III"
输出:[0,1,2,3]

示例 3:

输入:s = "DDI"
输出:[3,2,0,1]
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
    public int[] diStringMatch(String s) {
       int length=s.length();
        int low=0;
        int high=s.length();
        int[]perm=new int[length+1];
        for(int i=0;i<length;i++){
            perm[i]=s.charAt(i)=='I'?low++:high--;
        }
        perm[length]=low;
        return perm;
    }
}

  如果字母是‘I’,趋势是上升的,perm[i+1]>perm[i]。取perm[0]=0,下一个任意元素都大于perm[0],到达局部最优。然后比较剩下的。剩下的最大元素未high-1

  如果字母是‘D’,原理类似。

 

第二题:

455. 分发饼干

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

对每个孩子 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。

 

示例 2:

输入: g = [1,2], s = [1,2,3]
输出: 2
解释: 
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
    public int findContentChildren(int[] g, int[] s) {
        int cookie=0,child=0;
        Arrays.sort(g);
        Arrays.sort(s);
        while(child<g.length&&cookie<s.length){
            //如果孩子的食量小于饼干,换孩子,换饼干
            if(g[child]<=s[cookie])
                child++;
            //如果孩子的食量大于饼干,只换饼干
                cookie++;
        }
        return child;
    }
}

  第三题:

135. 分发糖果

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

你需要按照以下要求,给这些孩子分发糖果:

每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数。

示例 1:

输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 212 颗糖果。

 

实例2

输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 121 颗糖果。
     第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
    public int candy(int[] ratings) {
        int ans=0;
        int length=ratings.length;
        int[]res=new int[length];
        //没人至少一个糖,没人先发一个
        for(int i=0;i<length;i++){
            res[i]=1;
        }
        for(int i=1;i<length;i++){
            if(ratings[i]>ratings[i-1])
            res[i]=res[i-1]+1;
        }
        for(int i=length-2;i>=0;i--){
            if(ratings[i]>ratings[i+1])
            //取较大的值,有一种情况本身就是评分高的糖果多,所以就不用+1了
            res[i]=Math.max(res[i],res[i+1]+1);
        }
        for(int i=0;i<length;i++){
            ans+=res[i];
        }
        return ans;
    }
}

  第四题:

435. 无重叠区间

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。

示例 1:

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

示例 2:

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

实例3:

输入: intervals = [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        //重点是根据第二列元素对数组进行排序
        Arrays.sort(intervals, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1]-o2[1];
            }
        });
        int count=0;
        int end=intervals[0][1];
        for(int i=1;i<intervals.length;i++){
            if(intervals[i][0]<end){
                count++;
            }else{
                 end=intervals[i][1];
            }
            
        }
        return count;
    }
}

 第五题

605. 种花问题

假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

给你一个整数数组  flowerbed 表示花坛,由若干 0 和 1 组成,其中 0 表示没种植花,1 表示种植了花。另有一个数 n ,能否在不打破种植规则的情况下种入 n 朵花?能则返回 true ,不能则返回 false。

 示例 1:

输入:flowerbed = [1,0,0,0,1], n = 1
输出:true

实例2:

输入:flowerbed = [1,0,0,0,1], n = 2
输出:false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
    public boolean canPlaceFlowers(int[] flowerbed, int n) {
        //贪心的思想,每个位置我都想种花
        int length=flowerbed.length;
        for(int i=0;i<length;i++){
            if(flowerbed[i]==0&&(i==0||flowerbed[i-1]==0)&&(i==length-1||
            flowerbed[i+1]==0)){
                flowerbed[i]=1;
                n--;
          }
          if(n<=0) return true;
        }
        return false;
    }
}

第六题:

763. 划分字母区间

字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。

示例:

输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
    public List<Integer> partitionLabels(String s) {
        List<Integer>res=new ArrayList<>();
        int[]words=new int[26];
        int start=0;
        int end=0;
        for(int i=0;i<s.length();i++){
            //统计字母最后一次出现的次数
            words[s.charAt(i)-'a']=i;
        }
        for(int i=0;i<s.length();i++){
            end=Math.max(end,words[s.charAt(i)-'a']);
            //说明后面不会再出现该字母
            if(end==i){
                res.add(end-start+1);
                start=end+1;
            }
        }
        return res;
    }
}

第七题

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

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
     总利润为 4 + 3 = 7

示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
     总利润为 4

示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
    public int maxProfit(int[] prices) {
        //将数组中的上升段进行累加,就得到最大利润
        int length=prices.length;
        int max=0;
        int res=0;
        for(int i=1;i<length;i++){
            max=Math.max(0,prices[i]-prices[i-1]);
            res+=max;
        }
        return res;
    }
}

第八题:

406. 根据身高重建队列

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

示例 1:

复制代码
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 01 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0123 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列
复制代码

示例 2:

输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people, new Comparator<int[]>() {
            @Override
            public int compare(int[] p1, int[] p2) {
                //首先根据身高排序
                if (p1[0] != p2[0]) return p2[0] - p1[0];
                //升高相同的情况下根据前面有几个人排序(升序)
                else return p1[1] - p2[1];
            }
        });
        List<int[]> res = new LinkedList<>();
        for (int[] person : people) {
            //根据前面有几个人升序插入应该所在的位置
            res.add(person[1], person);
        }
        return res.toArray(new int[res.size()][]);
    }
}

第九题:

665. 非递减数列 

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

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

示例一:

输入: nums = [4,2,3]
输出: true
解释: 你可以通过把第一个 4 变成 1 来使得它成为一个非递减数列。

示例 2:

输入: nums = [4,2,1]
输出: false
解释: 你不能在只改变一个元素的情况下将其变为非递减数列。
1
2
3
4
5
6
7
8
9
10
11
12
这道题给了我们一个数组,说我们最多有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

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
    public boolean checkPossibility(int[] nums) {
        int k=0;
        if(nums==null||nums.length<=0) return true;
        for(int i=1;i<nums.length;i++){
            if(nums[i]>=nums[i-1]) continue;
             
             k++;
            if(i-2>=0&&nums[i-2]>nums[i]){
            //需要对nums[i]值进行修改,会影响到下一个元素的判断
                nums[i]=nums[i-1];
            }
            //一下可以不做操作,因为不会影响后面的操作
        //     else {
        //  nums[i-1] = nums[i];
        // }
        }
        return k<2;
    }
}

 

posted @   luorx  阅读(223)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示