Problem Address:http://poj.org/problem?id=3264
【前言】
RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。
特别的,在这道题中,要求某个区间内最大值与最小值之差。
本来是在学习线段树的,拿到这个题目,很快就写好了。
但是发现这是一个赤裸裸的RMQ问题。而事实上RMQ问题还有一种ST算法的应用。
最后掀开了收藏夹中一篇封尘的用树状数组解决RMQ问题的网页。
最后总结出了四种解决RMQ问题的方法,并在空间、时间以及编程复杂度方面作出了比较。
【方法一:朴素算法】
最朴素的方法莫过于朴素模拟。
用一个数组记录所有数据。
对于每次询问,简单地遍历一次该区间,找出最大最小值,求差。
这种朴素方法的编程复杂度极低,空间复杂度为O(n),初始化时间复杂度O(n),查询时间复杂度为O(n)。
这个时间复杂度在这个题目中是无法接受的,因为n=50000,m=200000。
如果对于小型的数据,这样的算法还是可以接受的。
在这里也就不贴出实现的代码了。
【方法二:线段树】
相信很多人对线段树都是不陌生的。
线段树在区间和点的操作上都是很有优势的。
如果把线段树用于朴素的RMQ问题,那么想当于是用了二分的思想:
如果刚好吻合,则返回最值;如果区间位于左边,则向左搜索;如果位于右边,则向右搜索。如果跨越了左右,则分两边搜索并取最值者。
线段树的思想还是比较简单的。
但是,线段树的编程复杂度在四者中是最大的,代码最长。空间复杂度为O(n),初始化时间复杂度为O(nlgn),查询时间复杂度为O(lgn)。
这个查询复杂度O(lgn)跟上面朴素算法O(n)相比可谓是天壤之别。由于有m个查询,这个差距是显而易见的。
实现代码见文章末尾。
【方法三:ST算法】
ST算法可谓是RMQ问题的经典算法。
它的编程复杂度算是比较低的。空间复杂度为O(nlgn),初始化时间复杂度为O(nlgn),查询时间复杂度为O(1)。
O(1)是什么概念,O(1)就是,初始化之后,你一输入一个查询,马上就可以得到一个结果。
事实上,ST算法就是个动态规划。用一个二维数组dp[i][j]存储从i开始的2^j长度中的最值。
如果你想查询一个区间(l, r)的最值,只需要找到一个2^k的长度使(l, l+2^k)和(r-2^k+1, r)覆盖到整个区间,然后取最值就得出结果。
但是查询时间上巨大的优化也为其空间上带来了更多的劣势。
一般情况下ST算法会占用更多的内存。
如此算来,在RMQ问题上,相比于线段树,ST算法毫无优势可言。
实现代码见文章末尾。
【方法四:树状数组】
这种方法其实一旦理解了就会发现它是很简单的。
跟用树状数组求解区间和问题类似,这里也是用同样的方法构造了一个新的数组存储最值。
查询的时候从后向前查。
如果待查区域包含了管辖区域,则读取该管辖区域的最值;
如果是被包含的关系,则只读取当前位置的数据,使索引后减一并重复上述步骤直到全部查询完。
这种思路是很简洁的,实现起来也很简洁。
树状数组相比于线段树是有很多优势的,包括它的编程复杂、空间复杂度和时间复杂度。
虽说树状数组的功能线段树都能实现,而线段树的功能树状数组不一定能实现,但是如果可以的话用树状数组是一个非常不错的选择。
在这里,树状数组的空间复杂度为O(n),初始化时间复杂度为O(n),查询时间复杂度为O(lgn)。
虽然线段树和树状数组都是O(n)的空间复杂度,但是前者的系数要比后者的大得多。
【比较总结】
在这个问题中,综合多方面的考虑,树状数组可以说是最好的选择。
以下为三者的比较:
Memory Time Code Length
线段树 2724K 2172MS 1730B
ST算法 8232K 1985MS 1210B
树状数组 760K 1500MS 1277B
虽然可能会因为个人的编程方式不同而在各个数据之间有所差异,但是就整体来说,这组数据还是多少能反映点问题的。
树状数组因为其简洁与方便而著称,如果能够巧妙地转化问题,则可以达到一个不错的效果。
但是,线段树的应用是很广泛也很灵活的,不能因为它的编程复杂度高而放弃了对它的选择。
而关于ST算法,与其说是一种算法,还不如说是一种思想,动态规划的思想,而ST只是它的一个应用而已。
【代码】
(代码一:线段树)
(代码二:ST算法)
(代码三:树状数组)
【P.S】
我承认很多代码都是抄过来的。包括一些牛人的博客,当然还有很多很多介绍类似东西的网页。
其实我也是处于一个学习的阶段,还有很多需要学习的东西,模仿也在所难免。
我也希望能够在“抄袭”的过程中领悟到学习的魅力,继续坚持着自己的道路走下去。
【完】