前言
区间极值问题在计算机编程领域中常见的问题,其描述为:
给一个数组 A[1…N]A[1 \dots N]A[1…N],然后有大量的询问,询问某个区间的 [L,R],1≤L≤R≤N[L,R], 1\le L\le R\le N[L,R],1≤L≤R≤N 的最大/最小值(或者下标)
例如,数组 A=[0,5,2,5,4,3,1,6,3]A=[0,5,2,5,4,3,1,6,3]A=[0,5,2,5,4,3,1,6,3],查询 A[3…8]=[2,5,4,3,1,6]A[3 \dots 8]=[2,5,4,3,1,6]A[3…8]=[2,5,4,3,1,6] 的最小值 为 A[7]=1A[7]=1A[7]=1 (下标从1开始)
变种 & 算法
在此基础上,又有不同的常见变种,根据不同的变种,我们有对应的复杂度/写法简易度的算法来解决:
用 O(A)∼O(B)O(A) \sim O(B)O(A)∼O(B) 来表示预处理需要的复杂度AAA和每次查询需要的复杂度BBB
比如最朴素的做法:不需要预处理,每次都从 L 遍历 到 R,它的时间复杂度就是 O(1)∼O(n)O(1) \sim O(n)O(1)∼O(n)。这个做法就不介绍了,有手就能写,但显然是最差的方案。
下面介绍一些适用于不同场景的算法
静态数组
如果 AAA 是静态数组,既 A 的元素不会动态变化,有如下一些算法
单调栈/队列
适用于: 计算每个元素左边/右边(k 个元素)的最大最小值,既询问的区间只会是[1,i][1, i][1,i]、[i,n][i,n][i,n] 或者 [i−k,i][i-k,i][i−k,i]、[i,i+k][i,i+k][i,i+k]
时间复杂度:O(n)∼O(1)O(n) \sim O(1)O(n)∼O(1)
优势:优秀的复杂度,实现简单
劣势:适用场景有限
稀疏表 Sparse Table
适用于: 无限制
时间复杂度:O(nlogn)∼O(1)O(n \log n) \sim O(1)O(nlogn)∼O(1)
优势:优秀的查询速度,实现简单
劣势:需要 O(nlogn)O(n \log n)O(nlogn) 的空间,这个还好,其实不算缺点
备注:静态 RMQ 问题的最适合方案
也可以用于区间求和,不过询问的复杂度就不是 O(1)O(1)O(1) ,而是 O(logn)O(\log n)O(logn)
并查集 Disjoint Set Union
适用于:所有询问都已经知道,离线查询
时间复杂度:O(n)∼O(1)O(n) \sim O(1)O(n)∼O(1)
优势:优秀的查询速度,实现简单
劣势:只适用与离线场景
备注:很神奇的技巧(由 AmirReza Poorakhavan 发明,所以也称之为 arpa’s trick)这里先简单说一下原理:将查询按照右端点桶排序,从左到右遍历,每个元素的父节点设置为右边已经遍历的最小元素,然后查询左端点的根节点即可
分块树 Sqrt Tree
适用于:无限制
时间复杂度:O(nloglogn)∼O(1)O(n\log \log n) \sim O(1)O(nloglogn)∼O(1)
优势:优秀的复杂度
劣势:超复杂的实现
最近公共祖先 LCA 的应用
适用于:无限制
时间复杂度:O(n)∼O(1)O(n) \sim O(1)O(n)∼O(1)
优势:优秀的复杂度
劣势:超复杂的实现
动态数组:
对于 A 是动态的情况,有如下几个算法:
线段树 Segment Tree
适用于:无限制
时间复杂度:O(n)∼O(logn)O(n) \sim O(\log n)O(n)∼O(logn)
空间复杂度:O(n)O(n)O(n)
优势:优秀的
劣势:实现略复杂
备注:静态 RMQ 问题的最适合方案
除了 RMQ,大部分区间操作、询问都可以用线段树解决
分块 Sqrt-decomposition
适用于:无限制
时间复杂度:O(n)∼O(n)O(n) \sim O(\sqrt n)O(n)∼O(n)
优势:实现简单
劣势:复杂度较差
树状数组 Fenwick Tree
适用于:查询是 [1,R][1,R][1,R]
时间复杂度:O(nlogn)∼O(logn)O(n \log n) \sim O(\log n)O(nlogn)∼O(logn)
优势:实现简单
劣势:适用场景有限
备注:除了 RMQ,更适用于区间求和,区间求异或,因为这两个操作顺序无关
文章讨论了在计算机编程中处理区间极值问题的不同算法,包括静态数组和动态数组的情况。对于静态数组,提到了单调栈、稀疏表、并查集等方法,而动态数组则涉及线段树、分块和树状数组等数据结构。每种算法都有其适用场景、时间复杂度和优缺点。
2730

被折叠的 条评论
为什么被折叠?



