贪心算法第五篇: 区间问题(右边界总是大于左边界-隐含条件)

Posted Java架构师(公众号:毛奇志)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了贪心算法第五篇: 区间问题(右边界总是大于左边界-隐含条件)相关的知识,希望对你有一定的参考价值。

1、贪心问题比较复杂,leetcode都是中等起步;
2、贪心问题都是 贪心要求+硬性要求 构成题目总要求。

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

在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。

由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。

一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。

可以射出的弓箭的数量没有限制。弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。

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

示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]]

就是找范围的交集

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

示例 2:
输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4

示例 3:
输入:points = [[1,2],[2,3],[3,4],[4,5]]
输出:2

示例 4:
输入:points = [[1,2]]
输出:1

示例 5:
输入:points = [[2,3],[2,3]]
输出:1

局部最优:当气球出现重叠,一起射,所用弓箭最少。
全局最优:把所有气球射爆所用弓箭最少。
核心:就是找范围的交集

解法一:左边界升序排序,从左到右覆盖

class Solution {
    public int findMinArrowShots(int[][] points) {
      if (points.length==0) return 0;
        Arrays.sort(points, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0] > o2[0] ? 1: -1;  // 升序
            }
        });
      int arrow=1;
      for (int i=1;i<points.length;i++){  // 从【1,length-1】   下面只能出现i-1和i,不能出现i+1
          if (points[i-1][1] < points[i][0])
              arrow++;
          else{  // 小于等于  取出  points[i-1][1] >=  points[i][0]
              // 更改了元素  i++之后,point[i][1]就是上面比较的points[i-1][1]
              points[i][1] = Math.min(points[i-1][1],points[i][1]);
          }
      }
      return arrow;
    }
}

在java里面,int a = -2147483646 - 2147483646 = 4,不会为负数,所以不能用 减号return o1[0]-o2[0];
只能用 > < == return o1[0] > o2[0] ? 1: -1;

else块中的核心: points[i][1] = Math.min(points[i-1][1],points[i][1]);
1、因为已经按左边界升序排列,所以i++,每次左边界都会增加,右边界取当前的比较的两个区段中的min,就得到了交集;
2、i++ 之后,points[i-1][1] 就是指当前else块中更新的 points[i][1]

解法二:左边界升序排序,从右到左覆盖

class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points.length==0) return 0;
        Arrays.sort(points, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0] > o2[0] ? 1: -1;  // 升序
            }
        });
        int arrow=1;
        for (int i=points.length-2;i>=0;i--){  // 从【1,length-1】   下面只能出现i-1和i,不能出现i+1
            if (points[i][1] < points[i+1][0])
                arrow++;
            else{ 
                points[i][0] = Math.max(points[i][0],points[i+1][0]);
            }
        }
        return arrow;
    }
}

左边界已经升序排列,要取max,直接取后面那个就好了

class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points.length==0) return 0;
        Arrays.sort(points, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0] > o2[0] ? 1: -1;  // 升序
            }
        });
        int arrow=1;
        for (int i=points.length-2;i>=0;i--){  // 从【1,length-1】   下面只能出现i-1和i,不能出现i+1
            if (points[i][1] < points[i+1][0])
                arrow++;
            else{ 
                points[i][0] = points[i+1][0];  // 左边界已经排序,所以直接赋值就好了
            }
        }
        return arrow;
    }
}

else块中的核心: points[i][0] = Math.max(points[i][0],points[i+1][0]);
1、因为已经按左边界升序排列,所以i–,每次左边界都会减少,左边界取当前的比较的两个区段中的max,就得到了交集(其实,左边界已经排序,所以直接赋值就好了);
2、i-- 之后,points[i+1][0] 就是指当前else块中更新的 points[i][0]

解法三:右边界降序排序,从右往左覆盖

class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points.length==0) return 0;
        Arrays.sort(points, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1] < o2[1] ? 1: -1;  // 右边界降序排序
            }
        });
        int arrow=1;
        for (int i=1;i<points.length;i++){  // 从【1,length-1】   下面只能出现i-1和i,不能出现i+1
            if (points[i-1][0] > points[i][1])  // i-1左边界更大
                arrow++;
            else{  // 小于等于  取出  points[i-1][1] >=  points[i][0]
                // 更改了元素  i++之后,point[i][1]就是上面比较的points[i-1][1]
                points[i][0] = Math.max(points[i-1][0],points[i][0]);
            }
        }
        return arrow;
    }
}

else块中的核心: points[i][0] = Math.max(points[i-1][0],points[i][0]);
1、因为已经按右边界降序排列,所以i++,每次右边界都会减少,左边界取当前的比较的两个区段中的max,就得到了交集;
2、i-- 之后,points[i-1][0] 就是指当前else块中更新的 points[i][0]

解法四:右边界降序排序,从左往右覆盖(两次反转,从“解法一”一样了)

class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points.length==0) return 0;
        Arrays.sort(points, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1] < o2[1] ? 1: -1;  // 右边界降序排列
            }
        });
        int arrow=1;
        for (int i=points.length-2;i>=0;i--){  // 从【1,length-1】   下面只能出现i-1和i,不能出现i+1
            if (points[i+1][1] < points[i][0])
                arrow++;
            else{  // 小于等于  取出  points[i-1][1] >=  points[i][0]
                // 更改了元素  i++之后,point[i][1]就是上面比较的points[i-1][1]
                points[i][1] = Math.min(points[i][1],points[i+1][1]);
            }
        }
        return arrow;
    }
}

右边界已经降序,取min就去后面那个就好了,直接赋值就行

class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points.length==0) return 0;
        Arrays.sort(points, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1] < o2[1] ? 1: -1;  // 右边界降序排列
            }
        });
        int arrow=1;
        for (int i=points.length-2;i>=0;i--){  // 从【1,length-1】   下面只能出现i-1和i,不能出现i+1
            if (points[i+1][1] < points[i][0])
                arrow++;
            else{  // 小于等于  取出  points[i-1][1] >=  points[i][0]
                // 更改了元素  i++之后,point[i][1]就是上面比较的points[i-1][1]
                points[i][1] = points[i+1][1];
            }
        }
        return arrow;
    }
}

else块中的核心: points[i][1] = Math.min(points[i][1],points[i+1][1]);
1、因为已经按右边界降序排列,所以i–,每次右边界都会增加,右边界取当前的比较的两个区段中的min,就得到了交集(其实,右边界已经排序,所以直接赋值就好了);
2、i-- 之后,points[i+1][1] 就是指当前else块中更新的 points[i][1]

435. 无重叠区间

给定一个区间的集合,找到需要移除区间的最小数量(贪心要求)使剩余区间互不重叠(目的)

注意1:右边界永远大于左边界;
注意2:区间 [1,2] 和 [2,3] 的边界相互“接触”,不算重叠。

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

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

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

按右边界排序之后,局部最优:优先选右边界小的区间,所以从左向右遍历,留给下一个区间的空间大一些,从而尽量避免交叉。全局最优:选取最多的非交叉区间(即最小的删除原有区间)。

解法一:右边界升序排列,从左向右遍历

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        if (intervals.length==0) return 0;
        Arrays.sort(intervals, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1] < o2[1] ? -1:1; // 有边界升序排列,不要用减号
            }
        });
        int count=1; // 非重叠区间初始化为1
        int end=intervals[0][1]; // 第一个元素是有边界
        for (int i=1;i<intervals.length;i++){  // 遍历整个
            if (intervals[i][0] >= end){ //大于不是重叠,等于也不是重叠
                end = intervals[i][1];
                count++;
            }
        }
        return intervals.length - count;
    }
}

解法二:左边界升序排列,从右向左遍历

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        if (intervals.length==0) return 0;
        Arrays.sort(intervals, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0] < o2[0] ? -1:1; // 左边界升序排列,不要用减号
            }
        });
        int count=1; // 非重叠区间初始化为1
        int start=intervals[intervals.length-1][0]; // 从最后一个元素(左边界最大的元素)开始
        for (int i=intervals.length-2;i>=0;i--){  // 遍历整个
            if (intervals[i][1] <= start){ //大于不是重叠,等于也不是重叠    某个的右边界小于这个start了,覆盖不了了,非重叠区域加一
                start = intervals[i][0];
                count++;
            }
        }
        return intervals.length - count;
    }
}

以上是关于贪心算法第五篇: 区间问题(右边界总是大于左边界-隐含条件)的主要内容,如果未能解决你的问题,请参考以下文章

贪心算法2无重叠区间(中等)

51Nod 1133 不重叠的线段 | 典型贪心

ACM道路之一:基础算法(快速排序)

排序算法专题:快排和归并排序

二分搜索(常规左边界右边界)

leetcode 每日一题 56. 合并区间