在做力扣1483. 树节点的第 K 个祖先时用到了树上倍增。
了解ST表是利用倍增思想可以实现区间最值查询。ST表是利用倍增思想的一种数据结构,适合求区间最值/区间最小公倍数等问题。
建表时间复杂度:nlog(n), 查询时间复杂都O(1),适合一个建表多次查询的搽干净
学习bilibili种,ST表讲解:https://www.bilibili.com/video/BV1gq4y1C7Ls
项目 | 描述 |
---|---|
ST表说明 | 是一个2维表,st[i][j] 从第i个开始,长度为2^j的值为多少 |
时间复杂度 | 建表时间复杂度:nlog(n), 查询时间复杂都O(1) |
空间复杂度 | nlog(n) |
适用场景 | 必须满足两个条件的函数才可以使用ST表: 1.满足结合率,如乘法:(a * b) * c = a * (b * c)。 2.满足交叉计算: 如计算最大值函数max([a, b, c, d]) = max(max([a, b, c]), max(b, c, d)) 满足以上两个条件的有区间最大值/区间最小公倍数 适用于数组的值不变,并且需要经常查询。不适用数组的值需要修改,变化需要重新建表,数值会变化的适用线段树 |
以查询区间最大值为例
计算最大值一个长度数组arr = [4,8,6,3,25,96,82,100,85]
此时st[i][j]表示第i个开始,长度为2^j的值的区间最大值
如st[3][2]表示arr中,i = 3,长度为2^2 区间的最大值,即[3,3 + 2 ^ 2) = [3, 25, 96, 82], 最大值为96,即arr[3, 2] = 96
如果要计算区间[2, 7]的最大值,怎么进行查表呢?
- 首先查询区间2开始,长度为4的区间,即查询[2, 5]区间(st[2, 2])的最大值=96
- 然后查询从4开始,长度为4的区间,即查询[4, 7]区间(st[4, 2])的最大值=100
- max(96, 100) = 100, 100为区间[2, 7]的最大值
第1和第2都是通过查询ST表得到的
看了一遍视频,挺有意思的。自己推导写了一遍代码
/**
* 利用ST表求区间最值,建表时间复杂度:O(nlog(n)), 查询时间复杂都O(1)
* @author zhiqiu
* @date 2022-09-22
*/
public class SparseTable {
private int n;
private int[][] ST;
public SparseTable(int[] nums) {
n = nums.length;
// log2(n)。 根据java doc可以写成: int k = 31 - Integer.numberOfLeadingZeros(n);
int k = (int) (Math.log(n) / Math.log(2));
// ST[i][j] 表示 从[i, i + 2 ^ j - 1] 的最大值,即表示i开始,长度为2^j的区间
ST = new int[n][k + 1];
for (int i = 0; i < n; i++) {
// 从第i开始,长度为1 即表示nums[i]本身
ST[i][0] = nums[i];
}
for (int j = 1; j <= k; j++) {
// 当前长度,计算长度为2^j的区间
int len = Math.pow(2, j);
// 当前长度的一半,即2^(j-1)
int half = len / 2;
int last = n - len;
for (int i = 0; i <= last; i++) {
// 长度为2^j的最大值,由两段长度为2^(j - 1)的最大值求max得到
ST[i][j] = Math.max(ST[i][j - 1], ST[i + half][j - 1]);
}
}
}
public int query(int left, int right) {
if (left > right || left < 0 || left >= n || right > n) {
throw new IllegalArgumentException("区间错误");
}
int k = (int) (Math.log(right - left + 1) / Math.log(2));
return Math.max(ST[left][k], ST[right - (int)Math.pow(2, k) + 1][k]);
}
}
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class SparseTableTest {
@Test
void query() {
int[] nums = {3, 18, 14, 8, 7, 13, 27, 4, 20, 10, 5, 9, 26, 0, 24};
SparseTable st = new SparseTable(nums);
// [3, 18, 14, 3, 7] 的最大值
assertEquals(18, st.query(0, 4));
// [14, 3, 7, 13, 27, 7, 20, 10]
assertEquals(27, st.query(2, 9));
// [7]
assertEquals(7, st.query(4, 4));
// [3, 18, 14, 3, 7, 13, 27, 7, 20, 10, 5, 7, 27, 0, 24]
assertEquals(27, st.query(0, nums.length - 1));
// [20, 10, 5, 7, 23, 0, 24]
assertEquals(26, st.query(8, nums.length - 1));
// [10, 5, 9]
assertEquals(10, st.query(9, 11));
}
}