RMQ+动态规划 求数组某一个范围的最大值

130 阅读3分钟

题目

  • 给定一个数组,求数组某一个范围的最大值
  • 通过下面的RQM结构,生成dp表的代价为 O(N*logN),查询的代价为 O(1)
  • dp[i][j] 表示,从 i 位置开始(包括i位置)的 2^j 个数的最大值
    • i从下标1开始,求下标1开始的 2^0、2^1、2^3、...不超过数组长度范围的最大值
    • 然后i从下标2开始,求下标2开始的 2^0、2^1、2^3、...不超过数组长度范围的最大值
    • 当 j 为 0时,每个位置开始的1个数的最大值也就是元素自身
    • 当 j >0 时,假设j=3,i=10,表示10位置开始的 2^3 个数的最大值dp[10][3],就等价于dp[10][2]10位置开始的4个数的最大值,dp[14][2],14位置开始的4个数的最大值 相比,最大的那一个
    • 因为 dp 表从上往下填 dp[i][j]=Math.max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]),所以下面的都能从上面已填写的信息得出答案
  • 当查找[left,right]范围内的最大值时,为了避免长度奇偶的影响,将范围拆分成两个范围分别求解答案再比较,范围长度为 len ,小于等于len的2的最大指数为k,则先求前2^k个数的最大值,然后求right往前推2^k这个范围的最大值,再比较得出答案

image.png

class RMQ {
  constructor(arr) {
    this.len = arr.length;
    const k = this.getPower2(this.len);
    // 建立二维dp表, dp[i][j] 表示从数组下标 i 开始(包括i),往后的 2^j 个数的最大值 , i 从 1 开始 对应原始数组下标为 i-1
    const dp = Array(len + 1).fill(0);
    for (let i = 0; i < dp.length; i++) {
      dp[i] - Array(k + 1).fill(0);
    }
    this.dp = dp;

    this.setRMQ(arr);
  }

  setRMQ(arr) {
    // 2^0也就1,区间长度为1时,i 位置开始的 1个数的最大值也就是元素本身
    for (let i = 1; i <= this.len; i++) {
      dp[i][0] = arr[i - 1];
    }
    // j 表示 i 位置开始的 2^j 个数、2^1、2^2、...
    for (let j = 1; 1 << j <= this.len; j++) {
      // 遍历数组下标,包括 i 位置之后的 2^j 个数(所以要减1),没有超过数组长度的话
      for (let i = 1; i + (1 << j) - 1 <= this.len; i++) {
        // 比如 10 位置后面的 2^3 个数的最大值等价于: 10 位置开始的 2^2 4个数的最大值 和 10位置往后移动4个数的位置之后(14)开始的四个数的最大值 相比最大的那一个
        // 10 11 12 13 这个区间最大值和 14、15、16、17、18 这个区间的最大值相比,最大的那个一个
        dp[i][j] = Math.max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
      }
    }
  }

  // 2^n 次方,求小于等于 len 的最大指数 n
  getPower2(len) {
    let res = 0;
    while (1 << res <= len) {
      res++;
    }
    return res - 1;
  }

  // left 索引也是从 1 开始
  getMax(left, right) {
    const k = this.getPower2(right - left + 1);
    // 求 [left,right]范围,假设范围长度为9,不超过 9 的最大为 2^3 ,可以拆分成先求 前8个数的最大值,然后求后八个数的最大值,任意一个范围都能这样拆
    return Math.max(this.dp[left][k], this.dp[right - (1 << k) + 1][k]);
  }
}