zoukankan      html  css  js  c++  java
  • 贪心算法

    贪心算法

    2.1 算法解释

    ​ 顾名思义,贪心算法贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的。

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

    2.2 分配问题

    1. Assign Cookies(Easy)

      题目描述

      ​ 有一群孩子和一堆饼干,每个孩子有一个饥饿度,每个饼干都有一个大小。每个孩子只能吃最多一个饼干,且只有饼干的大小大于孩子的饥饿度时,这个孩子才能吃饱。求解最多有多少孩子可以吃饱。

      输入输出样例

      ​ 输入两个数组,分别代表孩子的饥饿度和饼干的大小。输出最多有多少孩子可以吃饱的数量。

      input: [1, 2], [1, 2, 3]
      
      output: 2
      

      ​ 在这个样例中,我们可以给两个孩子喂[1,2]、[1,3]、[2,3]这三种组合中的任意一种。

      题解

      ​ 因为饥饿度最小的孩子最容易吃饱,所以我们先考虑这个孩子。为了尽量使得剩下的饼干可以满足饥饿度更大的孩子,所以我们应该把大于等于这个孩子饥饿度的、且大小最小的饼干给这个孩子。满足了这个孩子之后,我们采取同样的策略,也考虑剩下孩子里饥饿度最小的孩子,直到没有满足条件的饼干存在。

      ​ 简而言之,这里的贪心策略是,给剩余孩子里最小饥饿度的孩子分配最小的能饱腹的饼干。

      ​ 至于具体实现,因为我们需要获得大小关系,一个便捷的方法就是把孩子和饼干分别排序。这样我们就可以从饥饿度最小的孩子和大小最小的饼干出发,计算有多少个孩子可以满足条件。

      注意 对数组或字符串排序是常见的操作,方便之后的大小比较。

      private static int findContentChildren(List<Integer> children, List<Integer> cookies){
              children.sort(Comparator.naturalOrder());
              cookies.sort(Comparator.naturalOrder());
              int child=0, cookie=0;
              while(child<children.size() && cookie<cookies.size()){
                  if(children.get(child) <= cookies.get(cookie)){
                      child++;
                  }
                  cookie++;
              }
              return child;
          }
      
      1. Candy(Hard)

        题目描述

        ​ 一群孩子站成一排,每一个孩子有自己的评分。现在需要给这些孩子发糖果,规则是如果一个孩子的评分比身旁的孩子要高,那么这个孩子就必须得到比身旁孩子更多的糖果;所有孩子至少要有一个糖果。求解最少需要多少糖果。

        输入输出样例

        ​ 输入是一个数组,表示孩子的评分。输出是最少糖果的数量。

        input: [1, 0, 2]
        output: 5
        

        题解

        ​ 把所有孩子的糖果初始化为1;先从左往右遍历一遍,如果右边孩子的评分比左边的高,则右边孩子的糖果数更新为左边孩子的糖果数加1;再从右往左遍历一遍,如果左边孩子的评分比右边的高,且左边孩子当前的糖果数不大于右边孩子的糖果数,则左边孩子的糖果数更新为右边孩子的糖果数加1.通过这两次遍历,分配的糖果就可以满足题目要求了。这里的贪心策略为,在每次遍历中,只考虑并更新相邻一侧的大小关系。

        ​ 在样例中,我们初始化糖果的分配为[1, 1, 1],第一次遍历更新后的结果为[1, 1, 2],第二次遍历更新后的结果为[2, 1, 2]。

        private static int candy(List<Integer> ratings){
                int size = ratings.size();
                if(size<2){
                    return size;
                }
                List<Integer> num = new ArrayList<>();
                for(int i=0; i<size; i++) num.add(1);
                for(int i=1; i<size; i++){
                    if(ratings.get(i) > ratings.get(i-1)){
                        num.set(i, num.get(i-1)+1);
                    }
                }
                for(int i=size-1; i>0; i--){
                    if(ratings.get(i)<ratings.get(i-1) && num.get(i-1)<=num.get(i)){
                        num.set(i-1, num.get(i)+1);
                    }
                }
                return num.stream().mapToInt(Integer::intValue).sum();
            }
        

    2.3 区间问题

    1. Non-overlapping Intervals(Medium)

    题目描述

    ​ 给定多个区间,计算这些区间互不重叠所需要移除区间的最少个数。起止相连不算重叠。

    输入输出样例

    ​ 输入是一个数组,数组由多个长度固定为2的数组组成,表示区间的开始和结尾。输出一个整数,表示需要移除的区间数量。

    input: [[1, 2], [2, 4], [1, 3]]
    output: 1
    

    ​ 在这个样例中,我们可以移除区间[1, 3],使得剩余的区间[[1, 2], [2, 4]]互不重叠。

    题解

    ​ 在选择要保留的区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间就越大,就越能保留更多的区间。因此,我们采取的贪心策略为:优先保留结尾小且互不相交的区间。
    ​ 具体实现方法为:先把区间按照结尾的大小进行升序排列,每次选择结尾最小且和前一个选择区间互不重叠的区间。
    ​ 在样例中,排序后的数组为[[1, 2], [1, 3], [2, 4]]。按照我们的贪心策略,首先初始化为区间[1, 2];由于[1, 3]和[1, 2]相交,我们跳过该区间;由于[2, 4]与[1, 2]不相交,我们将其保留。因此最终保留的区间为[[1, 2], [2, 4]]。
    注意 需要根据实际情况判断按区间开头排序还是区间结尾排序。

    private static int eraseOverlapIntervals(List<ArrayList<Integer>> intervals){
            if(intervals.isEmpty()){
                return 0;
            }
            int n=intervals.size();
            Collections.sort(intervals, new Compare());
            int total = 0, prev = intervals.get(0).get(1);
            for(int i=1; i<n; i++){
                if(intervals.get(i).get(0) < prev){
                    total++;
                }else{
                    prev = intervals.get(i).get(1);
                }
            }
            return total;
        }
    
        private static class Compare implements Comparator<ArrayList<Integer>>{
    
            @Override
            public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
                if(o1.get(1).equals(o2.get(1))) return 0;
                return o1.get(1) > o2.get(1)? 1: -1;
            }
        }
    
    

    2.4 练习

    基础难度

    1. Can Place Flowers(Easy)

      采取什么样的贪心策略,可以种植最多的花朵呢?

      题目描述

      ​ 有一个长长的花坛,其中某些地块种植了一些花,有些没有。不能在相邻的地块上种花。

      ​ 给定一个包含整数0和1的花坛和一个整数n,其中0表示可以种花,1表示已经种了花。

      ​ 如果可以在不违反相邻地块不能种花的规则下种植n个花,可以则返回true,否则false。flowerbed.size() >=1, n>=0

      输入输出样例

      样例1:

      input: flowerbed = [1, 0, 0, 0, 1], n = 1
      output: true
      

      样例2:

      input: flowerbed = [1, 0, 0, 0, 1], n = 2
      output: false
      

      题解

      贪心策略:只有连续的三个0(即000)的中间位置可以种花。

      private static boolean canPlaceFlowers(List<Integer> bed, int n){
              int flowers=0, head=0, tail=bed.size()-1;
              if(bed.size()==1 && bed.get(0)==0){
                  return true;
              }
              for(int i=head; i<=tail; i++){
                  // 首
                  if(i==head && bed.get(i)==0 && bed.get(i+1)==0) {
                      bed.set(1, 0);
                      flowers++;
                  // 尾
                  } else if(i==tail && bed.get(i)==0 && bed.get(tail-1)==0){
                      bed.set(tail, 1);
                      flowers++;
                  // 中间
                  } else if(i!=head&&i!=tail && bed.get(i-1)==0 && bed.get(i+1)==0){
                      bed.set(i, 1);
                      flowers++;
                  }
              }
              return flowers>=n;
          }
      
      1. Minimum Number of Arrows to Burst Balloons(Medium)

      这道题和题目435非常类似,但是稍有不同。

      题目描述

      ​ 有一些气球散布在二维空间中。对于每个气球,给出它的水平方向的开始和结束坐标。由于它是水平的,因此y坐标无关紧要,只需要水平方向的起点和终点坐标。

      ​ 可以沿x轴垂直地射出箭头,如果xstart<=x<=send,则带有xstart和xend的气球会被x处射出的箭头射到而爆炸。射出的箭头会无限向上飞行。

      ​ 给定一个数组,其中points[i] = [xstart, xend],求解爆破所有气球需要的最小箭头数。

      样例输入输出

      input: points=[[10, 16], [2, 8], [1, 6], [7, 12]]
      output: 2
      Explanation: One way is to shoot one arrow for example at x = 6 (bursting the balloons [2,8] and [1,6]) and another arrow at x = 11 (bursting the other two balloons).
      

      题解

      贪心策略:尽量使气球重叠,按xend升序排列,一开始需要一只箭(射在x=points[0]的xend处);看这支箭能够穿透最右侧的一只气球points[i],这只气球的右侧下一个气球points[i+1]无法被穿透,需要额外的一只箭来穿透,这只额外的箭射在x=points[i]的xend处。

      private static int findMinArrowShots(List<ArrayList<Integer>> points){
              int arrows=1, size=points.size();
              if(size<2) return size;
              points.sort(new Compare());
              for(int i=0; i<size; i++){
                  for(int j=i+1; j<size; j++){
                      if(points.get(i).get(1) <= points.get(j).get(0)){
                          arrows++;
                          break;
                      }
                  }
              }
              return arrows;
          }
      
          // point = [xstart, xend], 按 xend升序排列
          private static class Compare implements Comparator<ArrayList<Integer>> {
      
              @Override
              public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
                  if(o1.get(1).equals(o2.get(1))) return 0;
                  return o1.get(1) > o2.get(1)? 1: -1;
              }
          }
      
      1. Partition Labels(Medium)

        为了满足此贪心策略,需要一些预处理。

        给出小写的英文字母字符串S,希望将此字符串划分为尽可能多的部分,以便每个字母只能出现在其中的一个部分,并返回代表这些部分大小的整数列表。

        样例输入输出

        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.
        

        题解

        start表示某一部分的开始索引,end表示这一部分的结束索引。

        每次for循环在这个部分中,根据每个字母的最后出现位置,决定是否更新end的值。下一部分的start=上一部分的end+1,end=这一部分中最后出现字母的索引。

        private static List<Integer> partitionLabel(String s){
            HashMap<Character, Integer> map = new HashMap<>();
            for(int i=0; i<s.length(); i++){
                map.put(s.charAt(i), s.lastIndexOf(s.charAt(i)));
            }
        
            int start = 0, end;
            List<Integer> results = new ArrayList<>();
            while (start<s.length()){
                end = map.get(s.charAt(start));
                for(int j=start; j<end; j++){
                    char c = s.charAt(j);
                    end = Math.max(map.get(c), end);
                }
                results.add(end - start + 1);
                start = end + 1;
            }
            return results;
        }
        
        1. Best Time to buy and Sell Stock II (Easy)

          假设有一个数组,里面第i个元素尾第i天股票的价格。

          设法找到最大的利润,必须全部卖出才能买下一次。不限制交易次数。

          样例输入输出

          Input: [7,1,5,3,6,4]
          Output: 7
          Explanation: Buy on day 2 (price = 1) and sell on day 3 (price = 5), profit = 5-1 = 4.
                       Then buy on day 4 (price = 3) and sell on day 5 (price = 6), profit = 6-3 = 3.
          
          Input: [1,2,3,4,5]
          Output: 4
          Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4.
                       Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are
                       engaging multiple transactions at the same time. You must sell before buying again.
          
          Input: [7,6,4,3,1]
          Output: 0
          Explanation: In this case, no transaction is done, i.e. max profit = 0.
          

          题解

          这是一个最大连续增长,再求和的问题。

          贪心策略:明天的价格高于今天,今天就买,明天卖。

          private static int maxProfit(List<Integer> prices){
                  int profit=0;
                  for(int i=0; i<prices.size()-1; i++){
                      int today = prices.get(i);
                      int tomorrow = prices.get(i+1);
                      if(today<tomorrow){
                          profit += tomorrow - today;
                      }
                  }
                  return profit;
              }
          

    进阶难度

    1. Queue Reconstruction By Height(Medium)

      需要同时插入和排序操作。

      给定一个people[[height, n], [height, n], ...]数组,height表示这个人的身高,n表示前面有n个人的身高大于等于他自身的身高。

      求解返回的排好序的数组。

      样例输入输出

      Input: people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
      Output: [[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]
      

      题解

      首先按照身高降序排列,如果身高相同就按n升序排列。

      然后遍历数组,按n的值插入对应位置。

      ArrayList中这个插入操作容易报下标越界异常,所以我先add再remove。

      private static List<ArrayList<Integer>> reconstructQueue(List<ArrayList<Integer>> people){
              people.sort(new Compare());
              int size = people.size();
              for(int i=0; i<size; i++){
                  ArrayList<Integer> person = people.get(i);
                  int currentIndex = people.indexOf(person);
                  int targetIndex = people.get(i).get(1);
                  people.add(targetIndex, person);
                  if(targetIndex<currentIndex){
                      people.remove(people.lastIndexOf(person));
                  }else{
                      people.remove(person);
                  }
              }
              return people;
          }
      
          // person[height, n], 默认按 height降序,相同则按 n升序
          private static class Compare implements Comparator<ArrayList<Integer>> {
      
              @Override
              public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
                  if(o1.get(0).equals(o2.get(0))) return o1.get(1) - o2.get(1);
                  return o2.get(0) - o1.get(0);
              }
          }
      
  • 相关阅读:
    Java程序:从命令行接收多个数字,求和并输出结果
    大道至简读后感
    大道至简第一章读后感Java伪代码
    Creating a SharePoint BCS .NET Connectivity Assembly to Crawl RSS Data in Visual Studio 2010
    声明式验证超时问题
    Error message when you try to modify or to delete an alternate access mapping in Windows SharePoint Services 3.0: "An update conflict has occurred, and you must re-try this action"
    Upgrading or Redeploying SharePoint 2010 Workflows
    Upgrade custom workflow in SharePoint
    SharePoint 2013中Office Web Apps的一次排错
    How to upgrade workflow assembly in MOSS 2007
  • 原文地址:https://www.cnblogs.com/pangqianjin/p/14248578.html
Copyright © 2011-2022 走看看