16. 用最少数量的箭引爆气球
例题452:
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。
需要先将气球位置排序,让气球尽可能的重叠。
重叠之后怎么模拟射箭的过程呢?
算法确定下来了,那么如何模拟气球射爆的过程呢?是在数组中移除元素还是做标记呢?
如果真实的模拟射气球的过程,应该射一个,气球数组就remove一个元素,这样最直观,毕竟气球被射了。
但仔细思考一下就发现:如果把气球排序之后,从前到后遍历气球,被射过的气球仅仅跳过就行了,没有必要让气球数组remove气球,只要记录一下箭的数量就可以了。
如果气球重叠了,那么右边界中的最小值之前的区间一定需要一根箭。
遍历气球,如果右气球的左边界>坐气球的右边界,说明两个气球不相交,需要count++,如果<=的话说明两个气球相交,需要更新右气球的右边界,即两个气球较小的右边界,这表明下一个右气球是与前面两个相交的气球的最远发射横坐标位置对比,看是否能一箭多雕,或新增箭。
class Solution {
//时间复杂度:快排的O(nlgn)
//空间复杂度:O(logn),Java所使用的快排需要logn的空间
public int findMinArrowShots(int[][] points) {
//Arrays.sort(points,(a,b)->{
// if(a[0]==b[0])
// return a[1]-b[1];//a-b是升序
// return a[0]-b[0];
// });最后一个测试用例会溢出
Arrays.sort(points, (a, b) -> Integer.compare(a[0], b[0]));//使用Integer内置的compare才不会溢出
int count=1;//points不为空至少也需要一支箭
for(int i=1;i<points.length;i++){
if(points[i][0]>points[i-1][1]){
count++;
}else{
points[i][1]=Math.min(points[i][1],points[i-1][1]);
}
}
return count;
}
}
除了要想到怎么比较相交气球、以及怎么处理已经射过的气球,这里直接跳过,不用remove。
还需要用Integer自带的compare对比,这样才不会造成溢出
17. 无重叠空间
例题435:
给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
上个题的反面?算出不重叠个数,需要移除的个数=总数-不重叠数。重叠稍不同,挨在一起不算重叠。
求重叠区间个数,当两个区间相交时,这个整体的右边界保留的是两者右边界较小的一个,以便看后一个区间是否与这个整体相交。
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
int count=1;//重叠射箭个数
Arrays.sort(intervals,(a,b)-> Integer.compare(a[0],b[0]));
for(int i=1;i<intervals.length;i++){
if(intervals[i][0]>=intervals[i-1][1]){
count++;
}else{
intervals[i][1]=Math.min(intervals[i][1],intervals[i-1][1]);
}
}
return intervals.length-count;
}
}
直接求移除个数,也就是重叠的个数。
需要注意的是,如果两区间相交,更新的这个相交整体的右边界是两者较小的一个,以便比较后一个区间与这个整体相交不。因为较大的右边界那个区间已经加过移除个数了,也就是说大的区间已经被移除了,如果再和大右边界比又会加一次。
例如:[1,2][1,3][2,3],第一次比较[1,2][1,3]时,[1,3]已经被移除了,剩下[1,2]与后一个区间比较,才能避免大区间重复比较。
//
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, (a,b)-> {
return Integer.compare(a[0],b[0]);
});
int remove = 0;
int pre = intervals[0][1];
for(int i = 1; i < intervals.length; i++) {
if(pre > intervals[i][0]) {
remove++;
pre = Math.min(pre, intervals[i][1]);
}
else pre = intervals[i][1];
}
return remove;
}
}
18. 划分字母区间
例题763:
给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。
返回一个表示每个字符串片段的长度的列表。
用二维数组维护每个字母的[开始位置,结束位置]的区间,找到整个字符串中的不重叠区间个数——>简单做法:用一维数组统计每个字母的结束位置,找下一次不重叠的结束位置,就是上一个区间的长度。
与之前两个题不同的是,不是找到不重叠区间个数,而是不重叠区间的长度。
class Solution {
public List<Integer> partitionLabels(String s) {
List<Integer> res=new ArrayList<>();
int[] pos=new int[26];
char[] ch=s.toCharArray();
for(int i=0;i<ch.length;i++){
pos[ch[i]-97]=i;//统计每个字母的结束位置
}
int idx = 0;
int last = -1;
for(int i=0;i<ch.length;i++){
idx = Math.max(idx,pos[ch[i] - 97]);
if(i==idx){
res.add(i-last);
last=i;
}
}
return res;
}
}
1.将String转换为char[],函数是
char[] ch=字符串.toCharArray()
2.怎么求不重叠区间结束位置的最大值?维护两个值,分别表示区间最长位置和开始位置。如果遍历到了当前最远位置,就存入这个区间的长度,并更新下个区间的起始位置。、
18. 合并区间
例题56:
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
和上一题类似,找到不重叠区间的开始和结束
class Solution {
public int[][] merge(int[][] intervals) {
if(intervals.length==1){
return intervals;
}
Arrays.sort(intervals,(a,b)-> Integer.compare(a[0],b[0]));
int[][] res=new int[10000][2];//先初始化数组由于不知道有多少不重叠区间:耗费内存
int count=0;
res[0][0]=intervals[0][0];
for(int i=1;i<intervals.length;i++){
if(intervals[i][0]<=intervals[i-1][1]){
intervals[i][1]=Math.max(intervals[i][1],intervals[i-1][1]);
res[count][1]=intervals[i][1];
}else{
res[count][1]=intervals[i-1][1];
count++;
res[count][0]=intervals[i][0];
if(i==intervals.length-1){
res[count][1]=intervals[i][1];
}
}
}
int[][] result=new int[count+1][2];
for(int i=0;i<count+1;i++){
result[i]=res[i];
}
return result;
}
}
方式2:由int[][]——>(List<int[]>->in[t][])
先定义List<int[]> res=new LinkedList<>()
,每次碰到不重叠的区间添加一对左右区间值
res.add(new int[]{start,end})
,收集完所有不重叠区间后,将整个res转换res.toArray(new int[res.size()][])
代码如下:
class Solution {
public int[][] merge(int[][] intervals) {
List<int[]> res = new LinkedList<>();
//按照左边界排序
Arrays.sort(intervals, (x, y) -> Integer.compare(x[0], y[0]));
//initial start 是最小左边界
int start = intervals[0][0];
int rightmostRightBound = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
//如果左边界大于最大右边界
if (intervals[i][0] > rightmostRightBound) {
//加入区间 并且更新start
res.add(new int[]{start, rightmostRightBound});
start = intervals[i][0];
rightmostRightBound = intervals[i][1];
} else {
//更新最大右边界
rightmostRightBound = Math.max(rightmostRightBound, intervals[i][1]);
}
}
res.add(new int[]{start, rightmostRightBound});
return res.toArray(new int[res.size()][]);
}
}
20. 贪心周总结
用最少数量的箭引爆气球:求重叠区间的最大个数
判断两气球是否重叠,如果重叠需要用一支箭,并更新这个重叠区间的右边界为两气球中的较小值。
否则,直接跳过。
无重叠区间:去掉的最少区间数,让所有的区间不重叠
和上一题类似,找到重叠的区间,一旦找到重叠区间,就更新这个重叠区间的右边界为两气球中的较小值,因为较大值可能与下一个重叠,如果不去掉又会重复计算。
如果直接找不重叠区间,则一旦出现重叠区间保留右边界为较小值。
划分字母区间:将字母划分为尽可能多的片段,同一个字母最多出现在一个片段中。
用一维数组保存每个字母的结束位置,遍历字符串找到不重叠片段的最远结束位置。
合并区间:合并所有重叠区间
找到重叠区间,更新重叠区间的右边界为两区间的右边界的较大值。