【问题标题】:Algorithm: Merge overlapping segments算法:合并重叠段
【发布时间】:2015-12-11 16:55:57
【问题描述】:

我有以下 ADT(未排序):List<Segment>

//direction is from 0 to 2pi
class Segment {
    int start;
    int end;
}

他们代表例如这种情况:

如何进行合并阶段(示例中的绿色箭头)?显然,我需要遍历列表并将每个段与所有其他段进行比较,并且如果可能的话,对每一对进行简单的合并(这很容易)。但是在第二次迭代中,我需要以某种方式返回列表的开头并重新开始等等......所以我很难找到这个算法将如何收敛。

编辑:这些段可以是圆形的——从 1.75pi 到 0.5pi 等...

【问题讨论】:

  • 你的列表是有序的还是随机的?
  • 是循环的吗?也就是说,你能有一个从 1.5pi 开始,然后从 2pi 到 0.5pi 的段吗?
  • @Petr 我没有注意这个,但实际上是的 - 它可以是循环的......

标签: java arrays algorithm list sorting


【解决方案1】:

我将在其他答案中添加循环的方法,即如果你有一些段循环超过 2pi,你应该怎么做。

有两种方法可以处理这个问题。一种是简单地将每个这样的段分成两部分:一个从任何地方到 2pi,另一个从零到任何地方。之后,解决问题,就好像它不是圆形一样,然后如果你有一个从零开始的线段和一个以 2pi 结束的线段,那么就合并它们。

第二种方法专门针对 Yves Daoust 的回答。你所需要的只是知道有多少段覆盖了零点(你可以很容易地计算出来);在此之后,您不是用零初始化“计数”,而是使用这个覆盖段数。

【讨论】:

    【解决方案2】:

    将所有端点放在一个数组中并为其分配一个极性(+ 然后-)。然后对列表进行排序。

    当您通过增加值来遍历列表时,更新重叠段的计数器就足够了。

    0+ 0.75- 0.5+ 1- 1.25+ 2-
    

    然后,排序,

    0+ 0.5+ 0.75- 1- 1.25+ 2-
    

    给出计数(在0初始化)

    1 2 1 0 1 0
    

    因此间隔界限(在转换0 to >0>0 to 0

    0 1 1.25 2
    

    这也可以完全就地完成没有额外的标志

    您将startend 值分别就地排序(不要将Segments 整体移动);这样,极性仍然是隐含的。

    然后将列表作为两个排序列表的合并遍历(使用两个独立的索引),并维护重叠计数器。您可以就地覆盖边界,因为合并的结果没有更多的间隔。

    [0 0.75][0.5 1][1.25 2]
    

    两个列表都意外地已经排序了。

    0 0.5 1.25 (+)
    0.75 1 2   (-)
    

    继续进行合并,它将按顺序选择元素

    + + - - + -
    

    通过移位得到最终结果

    [0 1][1.25 2][x x]
    

    如果在边界上出现平局,最好按顺序处理+-,以避免发出两个相等的边界。

    【讨论】:

      【解决方案3】:

      按开始时间对片段进行排序。

      创建一个将存储合并间隔的堆栈。

      将排序后的数组的第一个元素添加到栈中,然后对于数组中的每个元素,将其与栈顶的元素进行比较

      如果开始时间大于栈顶元素的结束时间,则将区间加到栈中。

      如果开始时间小于栈顶元素的结束时间,则更新栈顶元素的结束时间以匹配新元素的结束时间。

      当整个数组被处理时,结果堆栈应该包含合并的间隔。

      在java上的实现:

      /**
       * Definition for an interval.
       * public class Interval {
       *     int start;
       *     int end;
       *     Interval() { start = 0; end = 0; }
       *     Interval(int s, int e) { start = s; end = e; }
       * }
       */
      public class Solution {
          public List<Interval> merge(List<Interval> intervals) {
              Deque<Interval> stack = new ArrayDeque<Interval>();
      
              Collections.sort(intervals, new Comparator<Interval>() {
                      public int compare(Interval p1, Interval p2) {
                          return Integer.compare(p1.start, p2.start);
                      }
                  }
              );
      
              if (intervals.size() < 1) {
                  return intervals;
              }
              stack.push(intervals.get(0));
      
              for (int j = 1; j < intervals.size(); j++) {
                  Interval i = intervals.get(j);
                  Interval top  = stack.peek();
                  if (top.end < i. start) {
                      stack.push(i);
                  }
                  else if (top.end < i.end) {
                      top.end = i.end;
                  }
              }
              return new ArrayList<Interval>(stack);
      
          }
      }
      

      【讨论】:

        【解决方案4】:

        按起点对细分进行排序。

        然后,对于每个段,如果它的起点在前一段的起点和终点之间,并且它的终点大于前一段的终点,则将前一段的终点设置为本段的终点,并去掉/ 忽略当前段。

        如果当前段完全包含在前一个段中,则只需删除/忽略它。

        这是O(n log n),因为排序,您不需要将每个段与所有其他段进行比较。

        请注意删除或忽略的方式。它应该是一个O(1) 操作。例如,保留一个始终存储前一个未删除段的变量,也许还有一些已删除段的标志,以便您知道最后要收集哪些段。集合上的.remove() 操作可以O(n),您希望避免这种情况。

        【讨论】:

        • @IVIAd 感谢复杂性分析
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-05
        • 1970-01-01
        • 2015-05-10
        • 2020-09-20
        相关资源
        最近更新 更多