@[LeedCode-合并区间]
LeedCode-合并区间
网上很多代码,对于我这种才开始学算法的来说,不太懂他们的逻辑,所以自己写了一个JAVA数组适用的代码,性能速度也不是很好,请谅解,毕竟纯自己想的,放上来主打一个自己开心。
题意如下
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
作者:LeetCode
链接:
https://leetcode.cn/leetbook/read/array-and-string/c5tv3/
比如:
int[][] intervals = {{2,3},{4,5},{6,7},{8,9},{1,10}}; ------------------->[1,10]
int[][] intervals = {{0,3},{2,6},{8,10},{15,18}}; ------------------------>[0,6],[8,10],[15,18]
int[][] intervals ={{1,4},{0,4}}; ------------------------------------------>[0,4]
int[][] intervals = {{1,4},{0,0}}; ------------------------------------------>[1,4],[0,0]
int[][] intervals = {{0,3},{4,6},{8,10}}; --------------------------------->[0,3],[4,6],[8,10]
int[][] intervals = {{0,3},{3,6},{8,10}}; --------------------------------->[0,6],[8,10]
个人思路
1. 错误方法
我们可以用一个一维的数组 arr,大小为最后 intervals 【intervals.length】【1】 的那个数字的大小,再加上一(比如[15,18],我就直接创建一个19的空间,这样我就可以直接数字和下标对应上,方便些)
之后我们再把 intervals 的每一个区间取出来,然后进行遍历,把这个区间的所有值(包括左右边界),在数组 arr上标出来,我还考虑到 0 ,所以开始初始化的时候就把数组 arr的每个值都变成-1,这样标记的时候,就能和没在区间里面的数字有区别,大概就像下面的图这样。
示例:
int[][] intervals = {{0,3},{3,6},{8,10}};------------------------>[0,6],[8,10]
然后这个思路就有两个问题出现了,首先:
- 你能保证你定义的这个数组 arr的大小正确吗,题目没有告诉你这是有序的(可恶,因为这个问题我吃了大亏)
- 如果是这样的 intervals呢,你这边界都标识上了,图看起来没差别,但结果可是千差万别。
int[][] intervals = {{0,3},{4,6},{8,10}}; --------------------------------->[0,3],[4,6],[8,10]
然后我就放弃了这个思路,这个跑出来的成功率为 7/170 (可能主打一个瞎猫碰上死耗子)
2. 正确方法
上面的错误方法折腾了我一个小时(太菜了太菜了呜呜呜)。
痛定思痛,开始想新的办法,我想了下,或许应该从它们小边界(intervals 的每一行,我简称为一个小边界哈,前面的就是头,后面的就是尾)之间的关系出发,比如我们把第一个边界作为标准,看第二个边界和它的关系,来考虑如何扩大这个区间。
具体情况:
接下来我们就要开始讨论情况了,首先,这个题最重要的是看每一个小边界的开头,你想想,它直接可以决定我们是要新开一个答案区间(我把区间整理后的结果称之为答案,里面的也是小边界【毕竟是答案,就成为小区间吧】),还是在上一个答案区间的基础上改动。
这里我们就默认把 intervals的第一行作为最开始的基准哈,然后先讨论开头【我代码也是这样写的】
- 情况一:当前小边界的头在基准区间的里面
1.1. 当前小边界的尾在基准区间的右边(更大)
这个时候就要把基准区间的尾替换成更大的当前小边界的尾。
可以看下面的图,我们就把3变成6(注意,没有创建新的答案区间,就是当前的边界上改动)
1.2. 当前小边界的尾在基准区间的左边(更小)
这个时候啥也不动,直接判断下一个小边界
- 情况二:当前小边界的头在基准区间的左边
2.1. 当前小边界的尾在基准区间的左边(意思这个整个小边界都在基准区间的左边)
那就要新创建一个答案区间咯
2.2. 当前小边界的尾在基准区间的里面
那就不需要动基准区间的右边,但是左边要更改成当前小边界的左边。
2.3. 当前小边界的尾在基准区间的右边(左右都比基准覆盖的范围大)
那不得两边都改?大爷来咯
- 情况三:当前小边界的头在基准区间的右边
这种就不需要判断了,必须新开一个答案区间,我们可以直接把当前的小边界赋值进去,后面再迭代更新这个区间。
然后你发现,讨论小区间开的情况都讨论完了,这个时候我们来想下是不是该考虑下小区间的尾呢?
它只有两个情况:
如果尾在基准区间的范围里,啥也不干。
如果它在目前基准区间的右边,那我们就把基准右边的区间改了。【这里我们为什么不靠考虑小区间的头,因为我们前面已经把情况分析完啦,甚至我怀疑这里根本都不用考虑尾,只是我代码这样写的反正也AC了,我就没去再仔细的分析了】
3.最终一步
当时我的正确率是
问题出在哪?????我们考虑一下它给我的错误示例:
int[][] intervals = {{2,3},{4,5},{6,7},{8,9},{1,10}}; ------------------->[1,10]
如果按照我的代码,这个结果是 【2,3】,【4,5】,【6,7】,【1,10】
老天爷,我想了半天的情况,自认为无懈可击,你就给我这!!!
所以它为什么会这样呢?
(自问自答)因为它迭代答案区间的时候,每一次都只把前一个答案区间当成是基准,基准在变!
(怎么办)咱们直接反手一个对二维数组按照第一列升序排序【看的解析,就copy了这一句代码,其他逻辑都没看,直接试试就AC,俺觉得还是可以了,成功鼓励到自己,激情写下第一篇CSDN】
附上代码
在这里插入代码片--好哦 --- 这是我在IDEA上的代码哈
public class LC3new {
public static void main(String[] args) {
//int[][] intervals = {{0,3},{2,6},{8,10},{15,18}};
//int[][] intervals = {{0,3},{3,6},{8,10}};
int[][] intervals = {{2,3},{4,5},{6,7},{8,9},{1,10}};
//int[][] intervals = {{0,3},{4,6},{8,10}};
//int[][] intervals = {{1,4},{0,2},{3,5}};
//int[][] intervals = {{1,4},{0,0}};
//int[][] intervals ={{1,4},{0,4}};
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);//最重要的代码!画龙点睛!!排序!
int hang = intervals.length;
int lie = 2;
int[][] res = new int[hang][2];//这是存答案区间的,开始不知道大小,只有搞一个和题目一样大
res[0][0] = intervals[0][0];
res[0][1] = intervals[0][1];
int h = 0; //控制答案区间的行下标
int l = 0; //控制答案区间的列下表
int start = intervals[0][0];//基准头!(最开始的)
int end = intervals[0][1];//基准尾!(最开始的)
//直接从 intervals的第二个小边界开始处理,因为我们把一个当成基准咯
for (int i = 1; i < hang; i++) {
//后面迭代产生的新基准(新创建一个答案区间后,基准区间也跟着变)
start = res[h][0];
end = res[h][1];
for (int j = 0; j < 2; j++) {
int now = intervals[i][j];
boolean flag = isInside(start,end,now);
if(j%2 == 0){
//是小边界开头,并且头在基准区间里面。
if(flag){
//这种情况是头在范围内,也要考虑,尾部在不在,要不要换
if(isInside(start,end,intervals[i][j+1])){
//头尾都在,就都不动
}else{
//头在尾不在(尾巴更大)
j++;
res[h][j] = intervals[i][j];
}
}else{
//是小边界开头,并且头不在基准区间里面
if(now < start && isInside(start,end,intervals[i][j+1])){
//小边界的数字比基准区间的数字小,并且小边界的尾(在基准区间范围里面)
//那就只换前面
res[h][j] = now;
}else if(now < start && intervals[i][j+1]>end){
//小边界的数字比基准区间的数字小,并且小边界的尾(在基准区间范围的右边,更大)
//那就换前面和后面
res[h][j] = now;
j++;
res[h][j] = intervals[i][j];
}else if(now < start && intervals[i][j+1]>start){
//如果它整段都在前面,也要新开一个,存起来
h++;
res[h][j] = intervals[i][j];
j++;
res[h][j] = intervals[i][j];
}
else{
//如果头数字是在第一个范围的后面
//直接新开一个,并且先把值都弄进去
h++;
res[h][j] = intervals[i][j];
j++;
res[h][j] = intervals[i][j];
}
}
}
else{
//当天考虑的点是小区间的尾
if(isInside(start,end,intervals[i][j])){
//尾在基准区间里面,就不动
}else{
//尾不在(尾巴更大),直接换基准的右区间
res[h][j] = intervals[i][j];
}
}
}
}
int[][] result = new int[h+1][2]; // 最后重新给一个答案区间那么大的二维数组(因为要给LeetCode提交代码)
for (int i = 0; i < result.length; i++) {
for (int j = 0; j < result[0].length; j++) {
result[i][j] = res[i][j];
System.out.println(result[i][j]); //看看自己结果对不对
}
}
}
//判断target在不在【start,end】这个区间里面,包括边界
public static boolean isInside(int start,int end,int target){
if(target >= start && target <= end){
return true;
}else{
return false;
}
}
}