码迷,mamicode.com
首页 > 其他好文 > 详细

《编程之美》区间重合判断的一些思考

时间:2020-07-12 22:06:54      阅读:52      评论:0      收藏:0      [点我收藏+]

标签:遍历   重复   序列   说明   初始化   线性   处理过程   连续   初始   

问题:给定一个源区间[x, y]和N个无序的目标区间[x1, y1] [x2, y2] ... [xn, yn],判断源区间是不是在目标区间内(即源区间与[目标区间的并集]是否相交)。

这道题我是少有的不看答案就能把所有解法思考出来的,给了我的笨脑袋一点信心。

简单叙述下两个解法:

解法一:从源区间依次减去目标区间,到最后如果源区间为空,则说明在目标区间内。这种方法在处理过程中,计算过程中源区间变成了一个数组,因为每次减法可能会将其一分为二。每次减法计算哪些源区间被覆盖需要O(logN)的时间复杂度(即使有多个被覆盖也成立,因为我们要找的是一个连续的源区间,因此分别对新区间的left和right进行二分就能达到目的),但是更新这个数组需要O(N),毕竟是数组,牵一发而动全身。因此整体时间是O(N^2)。

解法二:将目标区间预处理,按left从小到大排序(O(N*logN)),然后将连续的有重叠的合并(O(N)),然后查询源区间是否属于这些处理后的任一个区间(二分,O(logN))。整体时间O(N*logN)。

解法一有改进空间吗?

解法一在中间过程需要存储源区间序列,一般来说无非数组或者链表的形式。如果采用链表,更新只需要O(1),但是不能随机访问,意味着不能用二分法进行覆盖查询了,因此每一步还是得O(N).

有没有办法结合数组和链表的优势呢?我想到了N年前学过的一个东西:线段树。

源区间初始为这个二叉树的根节点,表示一条线段。每次减去一个目标空间,可能将这个线段直接左截短,右截短,或者分成两个子线段,也就是两棵子树。在有子树的情况下,减法需要递归地向下进行,可能产生新的结点,也可能删除结点。最后判断这棵树是否为空即可。

比如源区间[0,10],目标区间[4, 6], [2, 8], [1,10]。

初始化:

.
└── 0,10

减去[4,6]:

.
└── 0,10
    ├── 0,4
    └── 6,10

减去[2,8]:

.
└── 0,10
    ├── 0,2
    └── 8,10

减去[1,10]:

.
└── 0,10
    └── 0,1

直觉认为每一次减法的时间复杂度是O(logN),但是如何证明呢?

首先,N个结点的平均树深度是logN,如果减去一个区间始终都是线性地向下遍历这棵树,那么结论是成立的。对于左截短和右截短的操作,就满足这样的遍历方式。但是还有一种,例如上面的减去[2,8]这一步,会分成左右两棵子树遍历,就不是线性的了。其实这样的分化至多一次,因为分化之后就简化为左截短和右截短了。因此整体上每一步减法的时间复杂度还是O(logN)。

这么一来,时间复杂度优化为解法二的O(N*logN)了。

解法二始终比解法一更优吗?

即使解法一做了上述优化,解法二的空间上也更省。另外一点就是,实际应用中可能不止查询一个源区间,可能是多个。对于这样的应用,解法二无疑更合适,因为每次查询只需要logN的时间。

解法二之所以好,是因为对静态数据进行了预处理,建立了一个静态模型,这样对于动态的查询,能够避免很多重复计算。这让我想到了大数据中的流式处理。

不过如果问题反过来,源区间是静态的,而目标区间是动态的呢?比如目标区间会不断的给出,每次更新目标区间都要进行查询。这样每次都要更新模型,不管怎么优化,时间复杂度都会提高到O(N)。

此时如果用解法一就会更快,甚至可以支持删除目标区间这样的操作,相当于是在线段树上做加法,和减法的时间复杂度一样,只是可能会在树的中间而不是叶子处添加结点。

《编程之美》区间重合判断的一些思考

标签:遍历   重复   序列   说明   初始化   线性   处理过程   连续   初始   

原文地址:https://www.cnblogs.com/xrst/p/13289638.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!