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

RMQ问题

时间:2019-10-04 11:37:06      阅读:117      评论:0      收藏:0      [点我收藏+]

标签:连续   遍历   目录   无法   研究   任务   最小值   时间复杂度   不同   

1.RMQ问题

    RMQ (Range Minimum/Maximum Query):对于长度为n的数组A,回答若干询问RMQ(A,i,j)(i,j<=n-1),返回数组A中下标在i,j范围内的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。最简单的方法,就是遍历数组直接搜索,但是这种方式时间复杂度是O(n)。对于数组长度较大,性能要求高的场景不适用。

 

2.ST(Sparse Table)算法

ST算法是一种更加高效的算法,以O(nlogn)的预处理代价,换取O(1)的查询时性能。现在我们来看下ST算法的思路和求解过程。假设有一个长度n=10的数组a,各个数组元素a[0],a[1]……a[9]的值分别是3 ,2, 4, 5 ,6, 8 ,1,2, 9, 7。我们以求最大值为例,令0 <= i <= j <= n-1,那么RMQ(a,i,j)就是:找出a[i],a[i+1]....a[j]这些元素中的最大值。令f[i,j]代表从第i个数起连续2^j个数中的最大值。

 

显然f[i,0]的值是确定的,就是第i个数自己。即f[i,0] = a[i],这就是动态规划DP的初始条件值。

 

现在我们来看下动态规划的状态转移方程,有了初始值和动态转移方程,就能够用动态规划算法解决问题。

为了求f[i,j],我们把f[i,j]平均分成两段(因为f[i,j]一定是偶数个数字),从i到i+2^(j-1)-1为一段,i+2^(j-1)到i+2^j-1为一段(长度都为2^(j-1))。显然f[i , j] = max(f[i ,  j-1],f[ i + 2^(j-1),  j-1])。

 

目前为止,已经有了DP的初始化条件和状态转移方程。接下来我们来看如何利用f[i,j]来RMQ(a, i, j)。

取k=[log2(j-i+1)],则有:RMQ(a, i, j) = max{f[i,k], f[j-2^k+1,k]}。

举例说明,如果要求区间[2,8]的最大值,总共2到8是7个元素,所以k=2,那么就要把它分成[2,5]和[5,8]两个区间,因为这两个区间的最大值我们可以直接由f[2,2]和f[5,2]得到。

 

现在我们看下k的值是如何得出的。假设拆分成的2个子区间,每个子区间都有2^(k-1)个元素。则2个子区间分别为:

[i, 2^(k-1) + i -1]和[j - 2^(k-1) + 1,j]。显然必须要满足2个子区间能够完全覆盖[i,j],即(2^(k-1) + i -1) + 1 >= (j - 2^(k-1) +1)

进而可以推导出 2^k >= (j - i + 1)。这样的话,我们取满足条件的K最小值就可以了。为什么要取最小值呢?是为了保证2个子区间长度尽可能的短。

 

 

 

 

RMQ (Range Minimum/Maximum Query ):中文名为“区间最值查询”。RMQ 问题指的是给定一段区间,针对给定区间进行若干次查询,每次给出不同的待查询子区间范围,要求返回子区间内的最大值或者最小值。

 

一般此类问题会给出区间内总元素个数 n 、待查询的次数 q ,以及每次查询的始末位置。

 

根据常规的思路,要找出一段区间中的最大值或最小值,我们会采取遍历区间的做法,该方法的核心代码在此不作展示。分析可知,n 个数据的规模,进行 q 次查询,时间复杂度为 o(q*n ),当问题的规模逐渐增大或者查询的次数逐渐增多的时候,我们无法在有限的时间内完成任务。因此,寻找更方便更快捷的办法成为我们的目标。在学习算法的道路上,我们都知道,第一直觉想出来的解决思路往往是最直接也是最低效的,于是理所当然的,我们需要对原来的思路进行改进,甚至摒弃原来低效的方法另辟蹊径。常规的思路时间主要耗费在对最值的查询和求解工作上,我们可以想办法将查询时间缩短,有人提出将可能查询的区间列出来,针对每个区间找出其中的最值存储在数组里,每次查询最值数组即可,这个思路需要存储的量有三个,起始点、终止点、区间最值,可以考虑模仿动态规划的理念,对二维数组的两个下标和存储的值赋予特殊含义,即用二维数组的第一坐标表示起始点、第二坐标表示终止点,那么值就可以表示区间最值。这样一来,查询的时间缩短为 o(1),但是问题在于,该 n* n 的二维数组赋值的时候,如果采用遍历对应区间的方法求最值,整个二维数组赋值过程时间复杂度为 o(n^3),这就意味着,虽然做了多余的工作,也做了合理的设想,但是并未达到优化问题的目的。这是不是意味着当前的思路不可行呢?我们需要先考虑该二维数组赋值过程能不能优化,如果不能,那这个思路就可以宣布失败了。

天无绝人之路,机会总是偏爱大胆实践的人。我们可以在此作出大胆预测,如果采用以上对二维数组的定义:第一坐标表示起始点,第二坐标表示终止点,值表示最值,即使求解最值的方法时间复杂度是 o(1),那求解该二维数组的整个过程时间复杂度也在 o(n^2),我们知道并没有 o(1)求解乱序区间最值的方法,因此,可以得出结论,以上对二维数组的定义无法达到优化的目的。

这就意味着问题不仅出现在求解区间最值的方法上,同时基本的二维数组定义也是不合理的。不合理之处在于规模过大,我们需要想办法缩小二维数组的规模。表示某一区间和其最值除了首末端点和最值三个参数这种方法,还可以用区间起始点、区间长度、区间最值三个参数。区间长度范围是 1 ~ n ,换个思路想一下,表示区间长度时候定义可以是多样的。

 

由此衍生出第二种方法-- ST 方法。

 

ST(Sparse Table)方法,sparse 中文译为:稀疏的,稀少的,零星的;table 中文译为:桌子,表,目录,手术台,工作台,游戏台;Sparse Table 具体中文名到底如何翻译,没有一个固定说法,我们暂且称之为 ST方法,该方法的本质是用动态规划方法求出用于查询的最值数组,以待查询子区间的起始位置和区间长度标记一个状态,以该子区间内的最大值或最小值作为状态值。

以求最大值为例: dp[ i ][ j ] = max ,其中,i 表示待查询子区间起始位置 ;j 表示待查询子区间的长度为 2 ^ j , max 表示在该子区间中最大值为 max 。 分析可知:1 <= i <= n , 0 <= j <= log n (以 2 为底), 此时,二维数组的规模为 n *log n (以 2 为底) 。动态规划的表达式有了,现在我们来研究状态转换方程,由 dp 数组的定义可以看出来,dp[ i ][ j ] 表示的区间长度 2 ^ j 一定是偶数,因此,区间可以等分为两个长度为 2 ^( j - 1 ) 的子区间,依照分治的思想,求该区间的最值,可以通过选择两个子区间最值中的最值来实现。此时,状态转换方程为:

 dp[ i ] [ j ] = max ( dp[ i ][ j - 1 ] , dp[ i + 2^( j - 1 ) - 1][ j - 1 ] )

 

求最值数组具体的核心代码实现过程如下:

 

 

注意,第 13 行的注释“ j 必须为外层循环控制量”,原因在于求解过程中,先求出以各元素为初始元素区间长度为 1 的最值,再求出以各元素为初始元素区间长度为 2 的最值,依次求解。因此 i 为内层循环控制量, j 为外层循环控制量,而不能以 i 为外层循环控制量,因为此时假设 求 dp[ i ] [ j ] ,如果 i 为外层循环控制量,dp[i][j] = max( dp[i][j-1] , dp[i + 2 ^(j-1) - 1][j-1]) ,其中 dp[ i ][ j - 1] 已知 ,但是 dp[i + 2 ^(j-1) - 1][j-1] 未知的,准确说, 第一坐标值 大于 i 的值都是未知的。

 

查询的时候,已知的信息是始末元素下标 start 和 end ,我们的 dp 数组中存储的是长度为 2 的幂的区间最值,对于长度非 2 的幂的区间,我们需要找到能够覆盖这个区间的两个区间,选取两者的最值即可。

如下,求下标 1 ~12 的元素中的最值,log 12 (以 2 为底)= 3 ,因此选取 F(1,3)和 F(5,3)两个区间,组合起来可以覆盖待求区间的所有数据。 

 

查询部分核心代码如下:

 

 

 

 

RMQ问题

标签:连续   遍历   目录   无法   研究   任务   最小值   时间复杂度   不同   

原文地址:https://www.cnblogs.com/aprincess/p/11621465.html

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