贪心算法--常见问题总结

什么是贪心算法?

贪婪算法(贪心算法)是指在对问题进行求解时,在每一步选择中都采取最好或者最优(即最有利)的选择,从而希望能够导致结果是最好或者最优的算法。
贪婪算法所得到的结果往往不是最优的结果(有时候会是最优解),但是都是相对近似(接近)最优解的结果。

贪心解决问题的基本思路?

1.建立数学模型来描述问题
2.把求解的问题分成若干个子问题
3.对每一子问题求解,得到子问题的局部最优解
4.把子问题对应的局部最优解合成原来整个问题的一个近似最优解

例题

1. 分配饼干
题目描述:每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。
给一个孩子的饼干应当尽量小又能满足该孩子,这样大饼干就能拿来给满足度比较大的孩子。因为最小的孩子最容易得到满足,所以先满足最小的孩子。

/*
    *每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。
    * */
    public int findContentChildren(int[] g, int[] s) {
        /*
        * 思路:将所有 饼干,孩子的愿望 分别从小到大排序。
        * 从小到大遍历每一个孩子的愿望, 按照饼干从小到大的顺序来满足从小到大的孩子的愿望。
        * 判断当前块的饼干是否可以满足孩子的愿望。如果不能(那么后面有更大愿望的孩子就更不能满足了),所以刨除此块,继续用下一块来判断;如果能,则累计count。
        * 直到饼干遍历结束或者孩子的愿望遍历结束。
        * */
        Arrays.sort(g);
        Arrays.sort(s);
        int gi = 0;
        int si = 0;
        int count = 0;
        while (gi < s.length && si < s.length) {
            if (s[si] >= g[gi]) {
                count++;
                gi++;
            }
            si++;
        }
        return count;
    }

2. 不重叠的区间个数
题目描述:计算让一组区间不重叠所需要移除的区间个数

    /*
    * 计算让一组区间不重叠所需要移除的区间个数
    * */
    public int eraseOverlapIntervals(int[][] intervals) {
        /*
        * 思路:让一组区间不重叠,优先选择区间末尾值小的,这样才能让后面的区间最可能不重叠。
        * 换句话说 优先选择区间末尾值小的区间,才能拼接更多的区间,移除更少的区间
        * 做法:
        * a.首先根据区间末尾值由小到大的顺序 将区间排序
        * b.维持一个区间末尾值end
        * c.按照排序后的顺序遍历所有区间,如果当前区间不会与上一个区间重叠(判断方式:当前区间的首值是否大于等于end),就更新end
        * */
        Arrays.sort(intervals, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1] - o2[1];
            }
        });
        int end = Integer.MIN_VALUE;
        int removeCount = 0;
        for (int i = 0; i < intervals.length; i++) {
            if (intervals[i][0] >= end) {
                end = intervals[i][1];
            } else {
                removeCount++;
            }
        }
        return removeCount;
    }

相同思路的题目:
a.假设有如下课程,希望尽可能多的将课程安排在一间教室里:
在这里插入图片描述
解题思路:选择结束最早的课

b.气球在一个水平数轴上摆放,可以重叠,飞镖垂直投向坐标轴,使得路径上的气球都被刺破。求解最小的投飞镖次数使所有气球都被刺破。也是计算不重叠的区间个数,不过和Non-overlapping Intervals 的区别在于,[1, 2] 和 [2, 3] 在本题中算是重叠区间。

3.根据身高和序号重组队列
题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。

 /*
    * 根据身高和序号重组队列
    * 题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。
    * */
    public int[][] recombination(int[][] people) {
        /*
        * 解题思路:优先向队伍中插入最高的。原因是,确保了最高的学生的组队条件,后面再向队伍中插入比他矮的学生时,都不会影响他的条件。
        * 因为他的条件是:排在他前面的k个学生 身高比他高或者和他一样高。即使比他矮的学生插入到了他的前面也不会影响他,当然插到他后面更不会影响他了
        * 做法:
        * a.将所有的学生按照身高由大到小的顺序排序,身高相同的情况下,将k小的排在前面。
        * b.按照排序后的顺序 不断将学生插入到队列中
        * */
        Arrays.sort(people, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o2[0] == o1[0] ? o2[1] - o2[1] : o2[0] - o1[0];
            }
        });
        LinkedList<int[]> list = new LinkedList<>();
        list.add(people[0]);
        for (int i = 1; i < people.length; i++) {
            list.add(people[i][1], people[i]);
        }
        return list.toArray(new int[list.size()][]);
    }

4.买卖股票最大的收益

/*
    * 题目描述:一次股票交易包含买入和卖出,只进行一次交易,求最大收益。
    * */
    public int maxProfit(int[] prices) {
        int minBuy = Integer.MAX_VALUE;
        int maxProfit = 0;
        for (int i = 0; i < prices.length; i++) {
            if (prices[i] < minBuy) {
                minBuy = prices[i];
                continue;
            }
            maxProfit = Math.max(maxProfit, prices[i] - minBuy);
        }
        return maxProfit;
    }

    /*
    * 题目描述:可以进行多次交易,多次交易之间不能交叉进行,可以进行多次交易。
    * 做法:当遇到a[i]-a[i-1]>0时,就把这个差值累计算的利润中
    * 思路:
    * 1.会不会出现a[i]前面有比a[i-1]还小的情况,不应该累计最多的利润吗?
    * 例如:2~5~9。 (5-2)+(9-5)=9-5  这种情况时,已经将5-2的利润累计了,所以不必担心累计不到9-2这种更大利润的情况。宽泛的说:当a<b<c<d时,(b-a)+(c-b)+(d-c)=d-a
    * 2. 万一出现2~5~10~9这种情况呢?
    * 9之前出现比它更大的元素,这种情况出现时,这段区间的最大利润已经被10截断了,跟9没关系了。 最大利润就是(5-2)+(10-5)=10-2.
    * 3.那么9还有可能被累计吗?
    * 当2~5~10~9~11时会被累计(5-2)+(10-5)+(11-9)。当2~5~10~9~8时,就直接舍弃9了。
    * */
    public int maxProfit2(int[] prices) {
        int maxProfit = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] - prices[i - 1] > 0) {
                maxProfit += prices[i] - prices[i - 1];
            }
        }
        return maxProfit;
    }

5.种植花朵
题目描述:flowerbed 数组中 1 表示已经种下了花朵。花朵之间至少需要一个单位的间隔,求解是否能种下 n 朵花。

    /*
    *种植花朵
    * 题目描述:flowerbed 数组中 1 表示已经种下了花朵。花朵之间至少需要一个单位的间隔,求解是否能种下 n 朵花。
    * */
    public boolean canPlaceFlower(int[] flowerbed, int n) {
        /*
        * 解题思路:如果当前元素为0,判断当前元素左右是否也为0
         * 注意边界值就好
        * */
        if (flowerbed.length == 1) {
            if (flowerbed[0] == 0) n--;
        } else {
            for (int i = 0; i < flowerbed.length; i++) {
                if (flowerbed[i] == 0) {
                    if (i == 0) {
                        if (flowerbed[i + 1] == 0) {
                            n--;
                            flowerbed[i] = 1;
                        }
                    } else if (i == flowerbed.length - 1) {
                        if (flowerbed[i - 1] == 0) {
                            n--;
                            flowerbed[i] = 1;
                        }
                    } else if (flowerbed[i - 1] == 0 && flowerbed[i + 1] == 0) {
                        n--;
                        flowerbed[i] = 1;
                    }
                }
            }
        }
        if (n <= 0) return true;
        return false;
    }

6.判断是否为子序列

/*
    *判断是否为子序列
    * */
    public boolean isSubsequence(String s, String t) {
        /*做法一*/
        char []strT=t.toCharArray();
        int index=0;
        for(int i=0;i<strT.length;i++){
            if(index==s.length()) return true;
            if(s.charAt(index)==strT[i]){
                index++;
            }
        }
        if(index==s.length()){
            return true;
        }
        return false;
    }
    public boolean isSubsequence2(String s, String t) {
        /*做法二*/
        int index=-1;
        for(char c:s.toCharArray()){
            index=t.indexOf(c,index+1);
            if(index==-1) return false;
        }
        return true;
    }

7.修改一个数成为非递减数组
题目描述: 修改一个数,能否成为非递减数组

    /*
    * 修改一个数成为非递减数组
    * 思路:当nums[i]>nums[i+1]时,有两种修改方案,修改nums[i]或nums[i+1]。但要判断修改后,非递减数组这个条件能否成立,成立才可
    * 想要修改nums[i],要确保nums[i-1]<=nums[i+1]
    * 想要修改nums[i+1],要确保nums[i]<=nums[i+2]
    * */
    public boolean checkPossibility(int[] nums) {
        boolean isModify=false;
        for(int i=0;i<nums.length-1;i++){
            if(nums[i]>nums[i+1]){
                if(isModify) return false;
                if(i+1==nums.length-1) return true;
                if(nums[i]<=nums[i+2]) {
                    nums[i+1]=nums[i];
                    isModify=true;
                }else if(i==0||nums[i-1]<=nums[i+1]){
                    nums[i]=nums[i+1];
                    isModify=true;
                }else {
                    return false;
                }
            }
        }
        return true;
    }

8.子数组最大的和

/*
    * 子数组最大的和
    * 思路:
    * a.max作为最大的和,随时更新
    * b.用curSum累计,考虑某元素nums[i]时,如果curSum<0,一定会curSum+nums[i]<nums[i]。所以如果curSum<0,就将curSum置为0
    * */
    public int maxSubArray(int[] nums) {
        int max=Integer.MIN_VALUE;
        int curSum=0;
        for (int i=0;i<nums.length;i++){
            curSum+=nums[i];
            max=Math.max(max,curSum);
            if(curSum<0) curSum=0;
        }
        return max;
    }

9. 分隔字符串使同种字符出现在一起

    /*
     分隔字符串使同种字符出现在一起
    Input: S = "ababcbacadefegdehijhklij"
    Output: [9,7,8]
    Explanation:
    The partition is "ababcbaca", "defegde", "hijhklij".
    This is a partition so that each letter appears in at most one part.
    A partition like "ababcbacadefegde", "hijhklij" is incorrect, because it splits S into less parts.
    * */
    public List<Integer> partitionLabels(String S) {
        /*
        * 思路:每次遍历到一个元素时,就要考虑后面还有没有相同的元素,如果有,就要分到一组。
        * 做法:遍历整个字符串,遍历到元素curElem时,就从后向前查找,找到最后一个与之相同的元素,此时,这两个区间为一组,还要考虑在这个区间的元素
        * */
        char str[]=S.toCharArray();
        char label[]=new char[30];
        for(int i=0;i<str.length;i++){
            label[str[i]-'a']++;
        }
        List<Integer> list=new LinkedList<>();
        int firstIndex=0;//区间首元素索引
        int lastIndex=0;//区间尾元素索引
        int curIndex=0;//当前元素
        while (firstIndex<str.length){
            lastIndex=Math.max(lastIndex,S.lastIndexOf(S.charAt(curIndex)));//每次遍历一个元素,就要找到字符串中最后一个与之相同的元素。并更新区间尾元素索引
            if(curIndex==lastIndex){//直到当前元素是区间尾元素(与当前元素相同的最后一个元素是自己,说明同种字符都在一个区间内了),此时首尾元素之间的所有可以分为一组。
                list.add(lastIndex-firstIndex+1);
                firstIndex=lastIndex+1;//更新下一个区间的首元素索引
                curIndex=lastIndex+1;//更新下一个区间的尾元素索引
            }else {
                curIndex++;
            }
        }
        return list;
    }

10.集合覆盖问题:

假设存在如下表的需要付费的广播台,以及广播台信号可以覆盖的地区。 如何选择最少的广播台,让所有的地区都可以接收到信号。
在这里插入图片描述
解题思路:
1.将每个地区可以接收到信号的广播台 的数量 放入到map里。
ID–>2
NV–>3

2.遍历每个广播,如果该广播台能覆盖的地区,都可以由其他 广播台覆盖到(map中的value都大于1),那么抛弃该广播台,同时将map中 该广播台能覆盖的地区的value值减1
每次都要选择一个 能覆盖到其他广播台覆盖不到的地区的 广播台。

发布了184 篇原创文章 · 获赞 60 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/StubbornAccepted/article/details/101905474