ST算法求解RMQ问题的模板

ST算法主要用来快速求解某个区间的最大/小值问题(RMQ问题),需要借助于一个二维数组dp进行预处理,预处理的时间复杂度为O(nlogn),预处理的过程主要使用了倍增思想,查询的时间复杂度为O(1),所以整个算法时间复杂度为O(nlogn)。我们需要声明一个二维数组,其中第一维的长度为n,第二维的长度为k + 1(在具体实现的时候可以多声明几个长度),其中n为数组的长度,k = log2n,dp[i][j]表示区间[i,i + 2 ^ j - 1]的最值,递推的时候以2 ^ (j - 1)作为分隔点分为左右两部分求解最值,所以区间最大值的状态转移方程为dp[i][j]  =  max{dp[i][j - 1],dp[i + 2 ^ (j - 1)][j - 1]},我们可以使用两层循环进行状态计算,第一层循环表示区间的某个起点i开始的长度为j,第二层循环表示区间的起点i,为什么第一层循环先是某个起点开始的长度呢?这是由dp数组的状态转移方程决定的,我们在状态转移的时候当前状态的值依赖于上一个状态的值,所以需要先计算出j - 1的状态那么才可以计算出当前状态j,所以最外层的循环为某个区间起点对应的长度j,预处理的时间复杂度为O(nlogn),查询区间最值的时间复杂度为O(1),所以整个算法的时间复杂度为O(nlogn)。在查询区间[l, r]最值的时候那么我们直接使用公式查询即可: a = dp[l][k],b = dp[r - (1 << k) + 1][k],其实也是以k = logn作为分隔点求解以分隔点k的左右两部分的最值即可,为什么使用分为两个部分来求解最值呢?因为只有两个部分同时求解最值才可以覆盖当前区间中的所有数字这样求解的最值才是正确的。我们可以理解其中的思路记住代码,考试的时候直接写出来即可。

from typing import List
import math
import random


class Solution:
    def process(self):
        # 列表的长度为10
        n = 10
        nums = list()
        for i in range(n):
            # 随机生成10个[-n * 10, n * 10]范围内的随机整数
            nums.append(random.randint(-n * 10, n * 10))
        # 输出生成的原列表的值
        print(nums)
        dp = self.get(n, nums)
        # 从控制台输入l和r, 查询区间[l, r]的最值, l,r的下标从0开始
        l, r = map(int, input().split())
        return self.query(l, r, dp)

    def get(self, n: int, nums: List[int]):
        k = int(math.log(n, 2))
        INF = 10 ** 15
        dp = [[-INF] * (k + 10) for i in range(n + 10)]
        for j in range(n + 1):
            i = 0
            while i + (1 << j) - 1 < n:
                if j == 0:
                    # j = 0的时候说明最大值是自己
                    dp[i][j] = nums[i]
                else:
                    # 以2 ^ (j - 1)作为分割点, dp[i][j - 1]计算的是区间nums[i][i + 2 ^ (j - 1) - 1]的最大值
                    # dp[i + (1 << j - 1)][j - 1]计算的是nums[i + 2 ^ (j - 1)][i + 2 ^ j - 1]的最大值
                    dp[i][j] = max(dp[i][j - 1], dp[i + (1 << j - 1)][j - 1])
                i += 1
        return dp

    def query(self, l: int, r: int, dp: List[List[int]]):
        k = int(math.log(r - l + 1, 2))
        # 以log2n作为分割点, dp[l][k]计算区间[l, l + 2 ^ k - 1], dp[r - (1 << k) + 1][k]计算[r - 2 ^ k + 1, r - 2 ^ k + 1 + 2 ^ k - 1]的最大值
        # 以log2n作为分隔点可以覆盖区间[1, r]中的所有数字
        return max(dp[l][k], dp[r - (1 << k) + 1][k])


if __name__ == "__main__":
    # 一直循环计算[l, r]的最大值
    while True:
        print(Solution().process())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值