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