【问题标题】:Find total number of intersections between a given set of ranges in python在python中查找给定范围集之间的交集总数
【发布时间】:2020-08-08 19:48:16
【问题描述】:

计算给定范围集的交集数量的最佳方法是什么。

例如: 考虑一个范围对列表[start,stop]

[[1,5], [3,7], [9,11], [6,8]]

这里一共有2个路口,

[1,5] 与 [3,7] 相交

并且 [3,7] 与 [6,8] 相交

【问题讨论】:

  • 定义“最佳”...
  • 这真的可以说是十字路口吗?
  • [3:6] 表示 3、4、5 实际上与 [6:7] 不相交,只有 6。它们之间有什么共同点吗?还是我对交叉点和范围的概念有误?
  • @CoolCloud 是的,你是对的,将停止值增加 1,我将在问题中对其进行编辑
  • 也许给我们一些你自己试过的代码???

标签: python


【解决方案1】:

这个问题可以在 nlogn 时间内完成,当然你可以在 n^2 内完成,但听起来你希望它的时间最优。

我将这些称为间隔重叠,这是一个经典,您会在许多访谈书籍中找到它的变体。操作方法如下:

  1. 按开始对项目进行排序。
  2. 现在逐步浏览这些项目。为每个结束的项目存储一个 minheap,成本为 nlogn 但没关系,我们已经支付了。在每个新的起始编号处,您都知道有多少先前的交叉点重叠。删除您已经超过结束的间隔。

由于搜索而最终成为 nlogn,无论您是否在 n 时间内逐步完成。

例子:

  1. 排序为 [1, 5], [3, 7], [6, 8], [9, 11]
  2. 存储一个以 5 结尾的堆项目。
  3. 进入第二个项目。堆大小为 1,因此将重叠计数加 1。将 7 添加到堆中。
  4. 转到第三项。从堆中删除 5。留下 7,所以再加 1。将 8 添加到堆中。
  5. 到第 4 项,丢弃 7 和 8,留下一个空堆。结果是 2。
import heapq
import operator

def mysol(v):

    overlaps = 0
    minheap = []

    vsorted = sorted(v, key=operator.itemgetter(0))

    for i in range(len(vsorted)):
        while len(minheap) > 0 and minheap[0] < vsorted[i][0]:
            heapq.heappop(minheap)

        overlaps += len(minheap)
        heapq.heappush(minheap, vsorted[i][1])

    return overlaps

【讨论】:

  • 对于最佳时间复杂度没有任何要求,我不明白为什么需要包含“不接受其他答案”。
  • 它是标记算法,如果人们认为比较所有配对是解决方案,他们的面试测验就会不及格。我不反对几行简单的解决方案,但这个问题听起来像是他们想要一个最佳解决方案。
  • @TadhgMcDonald-Jensen 这个问题确实要求“最好的方法”,并且在 cmets 中,OP 将其定义为“最好的快速或最小步骤”......
  • 这个问题从来没有用算法标签标记,只是 python,最小步骤对于计算机几个步骤或程序员几个步骤仍然是模棱两可的。
  • @carlos--更新了你的答案,因为我们得到了相同的结果。
【解决方案2】:

试试这个使用列表理解和itertools.combinations 的单行简单方法 -

  1. 迭代列表组合
  2. 将它们转换为范围,然后将这些范围集合
  3. 走一个路口
  4. 只返回交集 = True 的那些
import itertools
r = [[1,5], [3,7], [9,11], [6,8]]

overlapping_ranges = [i for i in list(itertools.combinations(r, 2))\
                      if set(range(*i[0])).intersection(set(range(*i[1])))]

print('Count of overlapping ranges:',len(overlapping_ranges))
print(overlapping_ranges)
Count of overlapping ranges: 2
[([1, 5], [3, 7]), ([3, 7], [6, 8])]

【讨论】:

    【解决方案3】:

    修改算法以查找 Maximum Number of Overlaps 以计算重叠数。

    接近

    • 这个想法是将坐标存储在一个新的向量对中,该向量映射有字符“x”和“y”,以识别坐标。
    • 对向量进行排序。
    • 遍历向量,如果遇到x坐标就意味着增加了一个新的范围,所以增加重叠计数
    •  If count > 1, we have a new overlap
           so increment number of overlaps
      
    • 如果遇到 y 坐标表示范围结束,则减少重叠计数
    • 结果是重叠数

    算法复杂度为O(n*log(n))(来自排序)

    代码

    def overlap(v): 
        # variable to store the maximum 
        # count 
        ans = 0
        count = 0
        data = [] 
      
        # storing the x and y 
        # coordinates in data vector 
        for i in range(len(v)): 
      
            # pushing the x coordinate 
            data.append([v[i][0], 'x']) 
      
            # pushing the y coordinate 
            data.append([v[i][1], 'y']) 
      
        # sorting of ranges 
        data = sorted(data) 
      
        # Traverse the data vector to 
        # count number of overlaps 
        for i in range(len(data)): 
      
            # if x occur it means a new range 
            # is added so we increase count 
            if (data[i][1] == 'x'): 
                count += 1
      
                if count > 1:
                  ans += (count - 1) # new range intersets
                                     # count - 1 existing
                                     # ranges 
    
    
            # if y occur it means a range 
            # is ended so we decrease count 
            if (data[i][1] == 'y'): 
                count -= 1
      
      
        # Return number of overlaps 
        return ans
      
    

    测试

    v = [ [ 1, 2 ], [ 2, 4 ], [ 3, 6 ] ] 
    print(overlap(v)) # Output 2
    
    v = [[1,5], [3,7], [9,11], [6,8]]
    print(overlap(v)) # Output 2
    
    v = [[1,5], [3,7], [9,11], [6,8], [1, 11]]
    print(overlap(v))  # Output 6
    
    v = [ [ 1, 3 ], [ 2, 7 ], [3, 5], [4, 6] ] 
    print(overlap(v))  # Output 5
    

    【讨论】:

    • 我觉得是对的,还是接近。 v = [ [ 1, 3 ], [ 2, 7 ], [3, 5], [4, 6] ] 的答案应该是什么?我从运行它得到 3,但我认为第二个元素与其他三个元素重叠,第 1 和第 3 次触摸,以及第 3 次和第 4 次触摸。
    • @Carlos--调整了我的答案。当一个新范围开始时,它会与 (count - 1) 现有范围重叠,这意味着我们将 (count-1) 与更新相加,而不是 1。所以现在v = [ [ 1, 3 ], [ 2, 7 ], [3, 5], [4, 6] ] 产生 5 的答案。
    • 是的,我有我们的两个解决方案,它们似乎给出了相同的数字。我会支持你。
    【解决方案4】:

    使用intspan 模块,解决方案可能是:

    >>> from itertools import combinations
    >>> from intspan import intspan
    
    >>> L = [[1,5], [3,7], [9,11], [6,8]]
    >>> for s1, s2 in combinations(L, 2):
        if intspan.from_range(*s1) & intspan.from_range(*s2):
            print(s1, 'intersects', s2)
    

    打印:

    [1, 5] intersects [3, 7]
    [3, 7] intersects [6, 8]
    

    【讨论】:

      【解决方案5】:

      懒人的方法是只生成范围并检查集合的交集,这对内存使用很糟糕,并且不能扩展到非整数,但它很短:

      import itertools
      def range_intersection(a,b):
          return len(set(range(*a)) & set(range(*b))) > 0
      
      data = [[1,5], [3,7], [9,11], [6,8]]
      
      for a,b in itertools.combinations(data, 2):
          if range_intersection(a,b):
              print(a,b)
      

      这里set(range(*a)) 使用范围的开始和结束参数然后创建一个集合以便它可以与另一个范围相交,然后我们只检查相交的长度是否大于0(有公共元素)。 itertools.combinations 是为了简化对所有数据组合的检查。

      【讨论】:

      • 您对 range 的使用不包括“停止”(右)数字,因为在一个范围内,最上面的数字不包括在内。
      • 是的,这与所有 python 切片和范围机制一致......当我在 python 中看到整数范围时,我认为这是所需的行为,除非另有明确提及,我认为 comments on the question 确实如此解决这个问题并修改了 OP,以便在排除端点时范围确实相交。
      • 好吧,不是批评。在原始海报的 cmets 中,他似乎希望它是 inclusive 的最高数字。 (他在原帖中对CoolCloud的回复)
      【解决方案6】:

      “部分”模块在您处理细分时可能会有所帮助。

      import portion as P
      from itertools import combinations
      li = [[1,5], [3,7], [9,11], [6,8]]
      
      pairs=[el for el in combinations(li,2) if P.open(*el[0]) & P.open(*el[1]) != P.empty()]
      

      打印(对)

      输出是相交对的列表:

      [([1, 5], [3, 7]), ([3, 7], [6, 8])]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-03-07
        • 2016-04-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多