【LeedCode-合并区间】

@[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]
在这里插入图片描述
然后这个思路就有两个问题出现了,首先:

  1. 你能保证你定义的这个数组 arr的大小正确吗,题目没有告诉你这是有序的(可恶,因为这个问题我吃了大亏)
  2. 如果是这样的 intervals呢,你这边界都标识上了,图看起来没差别,但结果可是千差万别。
    int[][] intervals = {{0,3},{4,6},{8,10}}; --------------------------------->[0,3],[4,6],[8,10]
    然后我就放弃了这个思路,这个跑出来的成功率为 7/170 (可能主打一个瞎猫碰上死耗子)

2. 正确方法

上面的错误方法折腾了我一个小时(太菜了太菜了呜呜呜)。
痛定思痛,开始想新的办法,我想了下,或许应该从它们小边界(intervals 的每一行,我简称为一个小边界哈,前面的就是头,后面的就是尾)之间的关系出发,比如我们把第一个边界作为标准,看第二个边界和它的关系,来考虑如何扩大这个区间。

具体情况:
接下来我们就要开始讨论情况了,首先,这个题最重要的是看每一个小边界的开头,你想想,它直接可以决定我们是要新开一个答案区间(我把区间整理后的结果称之为答案,里面的也是小边界【毕竟是答案,就成为小区间吧】),还是在上一个答案区间的基础上改动。
这里我们就默认把 intervals的第一行作为最开始的基准哈,然后先讨论开头【我代码也是这样写的】

  1. 情况一:当前小边界的头在基准区间的里面
    1.1. 当前小边界的尾在基准区间的右边(更大)
    这个时候就要把基准区间的尾替换成更大的当前小边界的尾。
    可以看下面的图,我们就把3变成6(注意,没有创建新的答案区间,就是当前的边界上改动)

    1.2. 当前小边界的尾在基准区间的左边(更小)
    这个时候啥也不动,直接判断下一个小边界
    在这里插入图片描述
  2. 情况二:当前小边界的头在基准区间的左边
    2.1. 当前小边界的尾在基准区间的左边(意思这个整个小边界都在基准区间的左边)
    那就要新创建一个答案区间咯
    在这里插入图片描述
    2.2. 当前小边界的尾在基准区间的里面
    那就不需要动基准区间的右边,但是左边要更改成当前小边界的左边。
    在这里插入图片描述2.3. 当前小边界的尾在基准区间的右边(左右都比基准覆盖的范围大)
    那不得两边都改?大爷来咯
    在这里插入图片描述
  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;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值