稀疏表
适用场景
稀疏表解决的是区间最值问题(RMQ),对于多次查找区间最值的场景,稀疏表的作用是能够优化时间复杂度,常规的单次查找区间最值的时间复杂度为O(n),使用稀疏表能够将单次区间最值的查找时间均摊到O(logn)。当然对于区间最值查找的优化还存在诸如线段树等数据结构。
基本思路
稀疏表的构成主要分两个部分,一、建表,二、查找。
建表
建表:采用动态规划的方式建表。以查找区间内的最小值为例,采用倍增法的思想。待查找的数组为nums,定义二维数组f[i][k]。含义:起始位置为i,区间长度为2^k的区间范围内的最小值。样例数组[2,5,3,78,2,0],f[2][1] = 0,解释为起始位置为2,长度为2^2 = 4的区间内([3,78,2,0])的最小值为0。动态规划问题的解决步骤,一、定义数组状态(f[i][k]已定义),二、找到basecase,填充状态,三、根据递推关系式,填充数组。
这里的basecase很显然,f[i][0] = nums[i],区间[i,i]的最小值就是nums[i]。
递推关系式为:f[i][k] = min(f[i][k - 1],f[i + 2^(k - 1)][k - 1])。解释为将区间[i,i + 2^k]分成两个子区间[i,i + 2^(k - 1) - 1],[i + 2^(k - 1),i + 2^k],两个区间的最小值取最小就是大区间的最小值。
建表复杂度分析
建表的时间复杂度:O(nlogn)、空间复杂度:O(nlogn)
查找
查找函数:对于任意长的区间[left,right],首先确定该区间能分为多大的两个子区间(确定k的过程),然后将区间分成f[left][k - 1],f[j][k - 1];这里的j应该通过计算得出。这里值得注意的是分成的两个区间可能会存在重叠,区间一是当前[left,right]以left为起点,长度为2^(k - 1)的区间,区间二是当前[left,right]以right为终点,长度为2^(k - 1)的区间。区间重叠并不会影响最值的选取。
查找复杂度分析
查找的时间复杂度:O(1)、空间复杂度:O(1)
代码
#include <bits/stdc++.h>
using namespace std;
// 解决区间最值问题(RMQ)
// 初始化时间复杂度为 O(nlogn),空间复杂度 O(nlogn)
// 查找函数时间复杂度 O(1)
class ST{
vector<int> nums;
vector<vector<int> > f;
void init(vector<int>& nums){
int n = nums.size();
int d = (int)log2(n);
for (int i = 0;i < n;++i) f[i][0] = i;
for (int j = 1;j <= d;++j){
for (int i = 0;i < n;++i){
f[i][j] = f[i][j - 1];
int r = i + (1 << (j - 1));
if (r < n && nums[f[r][j - 1]] < nums[f[i][j - 1]]) {
f[i][j] = f[r][j - 1];
}
}
}
}
public:
ST(vector<int>& nums){
this->nums = nums;
int n = nums.size();
int d = (int)log2(n);
f = vector<vector<int> >(n,vector<int>(d + 1));
init(nums);
}
// 查找当前区间的最小值,返回其索引
// 当存在多个最小值返回索引最小的
int query(int left,int right){
int len = right - left + 1;
int d = (int)log2(len);
int L = f[left][d],R = f[right - (1 << d) + 1][d];
if (nums[L] <= nums[R]) return L;
return R;
}
};
int main(void){
vector<int> A{9,1,0,67,2,43,12,55,1,0,8};
ST st(A);
int n = A.size();
for (int i = 0;i < n;++i){
for (int j = i;j < n;++j){
cout << A[st.query(i,j)] << " ";
}
cout << endl;
}
return 0;
}
样例代码结果分析
关键词
关键词:区间最值问题(RMQ)、动态规划、倍增法
注意事项
值得注意的问题,查找区间,将区间分为两个子区间时可能会出现重叠,但是最值问题不会受重叠的影响。如果将该方法运用到其他场景的其他问题中,必须保证区间重叠时,结果不受影响。
小结
稀疏表这种数据结构的作用体现在对于区间最值查找的时间优化上,有很好的通用性,值得了解。