题目描述(中等难度)

给定一个列表,将有重叠部分的合并。例如[ [ 1 3 ] [ 2 6 ] ] 合并成 [ 1 6 ] 。

解法一

常规的思想,将大问题化解成小问题去解决。

假设给了一个大小为 n 的列表,然后我们假设 n - 1 个元素的列表已经完成了全部合并,我们现在要解决的就是剩下的 1 个,怎么加到已经合并完的 n -1 个元素中。

这样的话分下边几种情况, 我们把每个范围叫做一个节点,节点包括左端点和右端点。

  1. 如下图,新加入的节点左端点和右端点,分别在两个节点之间。这样,我们只要删除这两个节点,并且使用左边节点的左端点,右边的节点的右端点作为一个新节点插入即可。也就是删除 [ 1 6 ] 和 [ 8 12 ] ,加入 [ 1 12 ] 到合并好的列表中。

  2. 如下图,新加入的节点只有左端点在之前的一个节点之内,这样的话将这个节点删除,使用删除的节点的左端点,新加入的节点的右端点,作为新的节点插入即可。也就是删除 [ 1 6 ],加入 [ 1 7 ] 到合并好的列表中。

  3. 如下图,新加入的节点只有右端点在之前的一个节点之内,这样的话将这个节点删除,使用删除的节点的右端点,新加入的节点的左端点,作为新的节点插入即可。也就是删除 [ 8 12 ],加入 [ 7 12 ] 到合并好的列表中。

  4. 如下图,新加入的节点的左端点和右端点在之前的一个节点之内,这样的话新加入的节点舍弃就可以了。

  5. 如下图,新加入的节点没有在任何一个节点之内,那么将它直接作为新的节点加入到合并好的节点之内就可以了。

  6. 如下图,还有一种情况,就是新加入的节点两个端点,将之前的节点囊括其中,这种的话,我们只需要将囊括的节点删除,把新节点加入即可。把 [ 8 12 ] 删除,将 7 13 加入即可。并且,新加入的节点可能会囊括多个旧节点,比如新加入的节点是 [ 1 100 ],那么下边的三个节点就都包括了,就需要都删除掉。

以上就是所有的情况了,可以开始写代码了。

public class Interval {int start;int end;Interval() {start = 0;end = 0;}Interval(int s, int e) {start = s;end = e;}
}public List<Interval> merge(List<Interval> intervals) {List<Interval> ans = new ArrayList<>();if (intervals.size() == 0)return ans; //将第一个节点加入,作为合并好的节点列表ans.add(new Interval(intervals.get(0).start, intervals.get(0).end));//遍历其他的每一个节点for (int i = 1; i < intervals.size(); i  ) {Interval start = null;Interval end = null;//新加入节点的左端点int i_start = intervals.get(i).start;//新加入节点的右端点int i_end = intervals.get(i).end;int size = ans.size();//情况 6,保存囊括的节点,便于删除List<Interval> in = new ArrayList<>(); //遍历合并好的每一个节点for (int j = 0; j < size; j  ) {//找到左端点在哪个节点内if (i_start >= ans.get(j).start && i_start <= ans.get(j).end) {start = ans.get(j);}//找到右端点在哪个节点内if (i_end >= ans.get(j).start && i_end <= ans.get(j).end) {end = ans.get(j);}//判断新加入的节点是否囊括当前旧节点,对应情况 6if (i_start < ans.get(j).start && i_end >ans.get(j).end) {in.add(ans.get(j));} }//删除囊括的节点if (in.size() != 0) { for (int index = 0; index < in.size(); index  ) {ans.remove(in.get(index));} }//equals 函数作用是在 start 和 end 有且只有一个 null,或者 start 和 end 是同一个节点返回 true,相当于情况 2 3 4 中的一种if (equals(start, end)) {//如果 start 和 end 都不等于 null 就代表情况 4// start 等于 null 的话相当于情况 3int s = start == null ? i_start : start.start;// end 等于 null 的话相当于情况 2int e = end == null ? i_end : end.end;ans.add(new Interval(s, e));// start 和 end 不是同一个节点,相当于情况 1} else if (start!= null && end!=null) {ans.add(new Interval(start.start, end.end));// start 和 end 都为 null,相当于情况 5 和 情况 6 ,加入新节点}else if (start == null) {ans.add(new Interval(i_start, i_end));}//将旧节点删除if (start != null) {ans.remove(start);}if (end != null) {ans.remove(end);}}return ans;
}private boolean equals(Interval start, Interval end) { if (start == null && end == null) {return false;}if (start == null || end == null) {return true;}if (start.start == end.start && start.end == end.end) {return true;}return false;
}

时间复杂度:O(n²)。

空间复杂度:O (n),用来存储结果。

解法二

参考这里的解法二。

在解法一中,我们每次对于新加入的节点,都用一个 for 循环去遍历已经合并好的列表。如果我们把之前的列表,按照左端点进行从小到大排序了。这样的话,每次添加新节点的话,我们只需要和合并好的列表最后一个节点对比就可以了。

排好序后我们只需要把新加入的节点和最后一个节点比较就够了。

情况 1,如果新加入的节点的左端点大于合并好的节点列表的最后一个节点的右端点,那么我们只需要把新节点直接加入就可以了。

情况 2 ,如果新加入的节点的左端点不大于合并好的节点列表的最后一个节点的右端点,那么只需要判断新加入的节点的右端点和最后一个节点的右端点哪个大,然后更新最后一个节点的右端点就可以了。

private class IntervalComparator implements Comparator<Interval> {@Overridepublic int compare(Interval a, Interval b) {return a.start < b.start ? -1 : a.start == b.start ? 0 : 1;}
}public List<Interval> merge(List<Interval> intervals) {Collections.sort(intervals, new IntervalComparator());LinkedList<Interval> merged = new LinkedList<Interval>();for (Interval interval : intervals) {//最开始是空的,直接加入//然后对应情况 1,新加入的节点的左端点大于最后一个节点的右端点if (merged.isEmpty() || merged.getLast().end < interval.start) {merged.add(interval);}//对于情况 2 ,更新最后一个节点的右端点else {merged.getLast().end = Math.max(merged.getLast().end, interval.end);}}return merged;
}

时间复杂度:O(n log(n)),排序算法。

空间复杂度:O(n),存储结果。另外排序算法也可能需要。

解法三

参考这里的解法 1。

刷这么多题,第一次利用图去解决问题,这里分享下作者的思路。

如果每个节点如果有重叠部分,就用一条边相连。

我们用一个 HashMap,用邻接表的结构来实现图,类似于下边的样子。

图存起来以后,可以发现,最后有几个连通图,最后合并后的列表就有几个。我们需要把每个连通图保存起来,然后在每个连通图中找最小和最大的端点作为一个节点加入到合并后的列表中就可以了。最后,我们把每个连通图就转换成下边的图了。

class Solution {private Map<Interval, List<Interval> > graph; //存储图private Map<Integer, List<Interval> > nodesInComp; ///存储每个有向图private Set<Interval> visited; //主函数public List<Interval> merge(List<Interval> intervals) {buildGraph(intervals); //建立图buildComponents(intervals); //单独保存每个有向图List<Interval> merged = new LinkedList<>();//遍历每个有向图,将有向图中最小最大的节点加入到列表中for (int comp = 0; comp < nodesInComp.size(); comp  ) { merged.add(mergeNodes(nodesInComp.get(comp)));}return merged;}// 判断两个节点是否有重叠部分private boolean overlap(Interval a, Interval b) {return a.start <= b.end && b.start <= a.end;}//利用邻接表建立图private void buildGraph(List<Interval> intervals) {graph = new HashMap<>();for (Interval interval : intervals) {graph.put(interval, new LinkedList<>());}for (Interval interval1 : intervals) {for (Interval interval2 : intervals) {if (overlap(interval1, interval2)) {graph.get(interval1).add(interval2);graph.get(interval2).add(interval1);}}}}// 将每个连接图单独存起来private void buildComponents(List<Interval> intervals) {nodesInComp = new HashMap();visited = new HashSet();int compNumber = 0;for (Interval interval : intervals) {if (!visited.contains(interval)) {markComponentDFS(interval, compNumber);compNumber  ;}}}//利用深度优先遍历去找到所有互相相连的边private void markComponentDFS(Interval start, int compNumber) {Stack<Interval> stack = new Stack<>();stack.add(start);while (!stack.isEmpty()) {Interval node = stack.pop();if (!visited.contains(node)) {visited.add(node);if (nodesInComp.get(compNumber) == null) {nodesInComp.put(compNumber, new LinkedList<>());}nodesInComp.get(compNumber).add(node);for (Interval child : graph.get(node)) {stack.add(child);}}}}// 找出每个有向图中最小和最大的端点private Interval mergeNodes(List<Interval> nodes) {int minStart = nodes.get(0).start;for (Interval node : nodes) {minStart = Math.min(minStart, node.start);}int maxEnd = nodes.get(0).end;for (Interval node : nodes) {maxEnd= Math.max(maxEnd, node.end);}return new Interval(minStart, maxEnd);}
}

时间复杂度:

空间复杂度:O(n²),最坏的情况,每个节点都互相重合,这样每个都与其他节点相连,就会是 n² 的空间存储图。

可惜的是,这种解法在 leetcode 会遇到超时错误。

开始的时候,使用最常用的思路,将大问题化解为小问题,然后用递归或者直接用迭代实现。解法二中,先对列表进行排序,从而优化了时间复杂度,也不是第一次看到了。解法三中,利用图解决问题很新颖,是我刷题第一次遇到的,又多了一种解题思路。

更多详细通俗题解详见 leetcode.wang 。

LeetCode 力扣 56. 合并区间相关推荐

  1. 【Leetcode | 13】56. 合并区间

    给出一个区间的集合,请合并所有重叠的区间. 示例 1: 输入: [[1,3],[2,6],[8,10],[15,18]] 输出: [[1,6],[8,10],[15,18]] 解释: 区间 [1,3] ...

  2. 【LeetCode笔记】56. 合并区间(Java、排序)

    文章目录 题目描述 代码 & 思路 更新版 2.0 题目描述 重叠区间:需要有重叠判断 注意:题目并没有说集合间有序,因此要先做一个排序,以左下标为排序值(否则会出错 代码 & 思路 ...

  3. leetcode力扣617. 合并二叉树

    给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠. 你需要将他们合并为一个新的二叉树.合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 ...

  4. 《LeetCode力扣练习》第56题 合并区间 Java

    <LeetCode力扣练习>第56题 合并区间 Java 一.资源 题目: 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, ...

  5. 力扣记录:贪心算法3较难(1)区间问题——55 跳跃游戏,45 跳跃游戏II,452 用最少数量的箭引爆气球,435 无重叠区间,763 划分字母区间,56 合并区间

    本次题目 55 跳跃游戏 45 跳跃游戏II 452 用最少数量的箭引爆气球 435 无重叠区间 763 划分字母区间 56 合并区间 55 跳跃游戏 局部最优:不管每次跳多少步,取最大跳跃步数,若覆 ...

  6. 《LeetCode力扣练习》剑指 Offer 25. 合并两个排序的链表 Java

    <LeetCode力扣练习>剑指 Offer 25. 合并两个排序的链表 Java 一.资源 题目: 输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的. 示例1: ...

  7. 《LeetCode力扣练习》第617题 合并二叉树 Java

    <LeetCode力扣练习>第617题 合并二叉树 Java 一.资源 题目: 给你两棵二叉树: root1 和 root2 . 想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些 ...

  8. 《LeetCode力扣练习》第21题 合并两个有序链表 Java

    <LeetCode力扣练习>第21题 合并两个有序链表 Java 一.资源 题目: 将两个升序链表合并为一个新的 升序 链表并返回.新链表是通过拼接给定的两个链表的所有节点组成的. 示例 ...

  9. leetcode 56. 合并区间

    leetcode 56. 合并区间 题目描述: 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] .请你合并所有重叠的区间, ...

最新文章

  1. 什么是CommonJS?
  2. CCNA认证指南note 01
  3. Windows Phone UI控件
  4. 数据流中的中位数,我轻敌了
  5. Python自动检测视频画面的旋转角度
  6. python语言type board_Micropython TPYBoard开发板控制无线加速度小车
  7. P102、面试题14:调整数组顺序使奇数位于偶数前面
  8. tkinter事件机制
  9. Intellij IDEA 14.x 菜单项中Compile、Make和Build的区别
  10. layui多文件上传讲解_layui文件上传的实际应用实例
  11. Java---遍历Map集合的三种方式
  12. x240无线网卡驱动 linux,Linux2.6移植:DM9000驱动
  13. android ct扫描模拟,基于Android平台的CT图像可视化显示方法及实现
  14. python 操作excel 表格
  15. Linux 查看CPU温度
  16. 假如举行一场世界功夫大赛,这33位动作片明星谁可跻身前十?
  17. [转载]系统提示:“您可能是微软盗版的受害者”的解决方法
  18. JavaSE----基础语法(数组)
  19. 红孩儿编辑器的详细设计
  20. Combo用户板中XG-PON资源利用率的提升方案

热门文章

  1. ubuntu18.04 设置字体样式, 调整字体大小
  2. 如何用C++开发STM32?
  3. 推荐系统和搜索引擎的比较
  4. android怎么美化ui,安卓教程第一期最终篇(转)systemui.apk最全修改美化
  5. Multisim高频电子线路2.7章LC谐振电路的仿真
  6. 2004-6-6 0:03:43 死得其所
  7. 黑科技之资源搜索网站
  8. python虚拟环境创建失败_python 创建虚拟环境时报错OSError, setuptools下载失败
  9. JPA——API介绍、完成JPA的CRUD操作、JPQL完成复杂查询操作
  10. 微信JSAPI支付对接流程及支付接口设计