稀疏表(ST表)O(1)时间复杂度查询区间最值

在做力扣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]的最大值,怎么进行查表呢?

  1. 首先查询区间2开始,长度为4的区间,即查询[2, 5]区间(st[2, 2])的最大值=96
  2. 然后查询从4开始,长度为4的区间,即查询[4, 7]区间(st[4, 2])的最大值=100
  3. 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));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值