[路飞]_子数组和排序后的区间和

788 阅读2分钟

1508. 子数组和排序后的区间和

题目

给你一个数组 nums ,它包含 n 个正整数。你需要计算所有非空连续子数组的和,并将它们按升序排序,得到一个新的包含 n * (n + 1) / 2 个数字的数组。

请你返回在新数组中下标为 left 到 right (下标从 1 开始)的所有数字和(包括左右端点)。由于答案可能很大,请你将它对 10^9 + 7 取模后返回。

示例1

输入:nums = [1,2,3,4], n = 4, left = 1, right = 5
输出:13 
解释:所有的子数组和为 1, 3, 6, 10, 2, 5, 9, 3, 7, 4 。将它们升序排序后,我们得到新的数组 [1, 2, 3, 3, 4, 5, 6, 7, 9, 10] 。下标从 le = 1 到 ri = 5 的和为 1 + 2 + 3 + 3 + 4 = 13

题解

暴力循环

题目数据量10310^3,暴力循环时间复杂度为10610^6,可以AC的;代码如下

var rangeSum = function (nums, n, left, right) {
  let array = []
  for (let i = 0; i < n; i++) {
    let t = 0
    for (let j = i; j < n; j++) {
      t += nums[j]
      array.push(t)
    }
  }
  array.sort((a, b) => a - b)
  const total = array.slice(left - 1, right)
  let result = 0
  for (let i = left - 1; i < right; i++) {
    result += array[i]
    result = result % 1000000007
  }
  return result
}

小顶堆+贪心

暴力循环中将所有数据计算出来,然后排序,时间复杂度为O(n2)O(n^2),但是在本题中只需要知道排序后[left,right]之间的数据,没必要将所有数据计算出来。现在一个思考方向,计算数组并且计算得到的结果是在数组中递增的。

依nums = [1,2,3,4]为例;

  • 构建数组list = [[1,0],[2,1],[3,2],[4,3]];中二维数组第一位为值,第二位表示当前值所在原数组的下标;将list放入小顶堆;
  • 小顶堆弹出小顶堆堆顶,放入数组result,result得到[1,0];
  • 将弹出的值,处理,再次压入小顶堆;如何处理?判断弹出的数据下标是否到达数组尾部,如果没有到达,将数组当前值+数组下标+1值压入堆中,比如第一次得到[1,0]处理 后得到[1+2,1];
  • 这时堆中数据为[[2,1],[1+2,1],[3,2],[4,3]]
  • 继续弹出小顶堆堆顶[2,1],放入result,result得到[[1,0],[2,1]];
  • 处理弹出值[2,1] ==> [2+3,1+1];压入堆中;
  • 得到堆[[1+2,1],[3,2],[4,3],[2+3,2]]
  • 继续弹出小顶堆堆顶[1+2,1],放入result,result得到[[1,0],[2,1],[1+2,1]];
  • 处理弹出值[1+2,1] ==> [1+2+3,1+1];压入堆中;
  • 得到栈[[3,2],[4,3],[2+3,2],[1+2+3,2]]
  • ...
  • 知道result.length === right;
  • 枚举result所有数据,累加[left,right]和且对1000000007求余即可

代码

class Heap {
  constructor(fn) {
    this.list = [0]
    this.compose = typeof fn === 'function' ? fn : this.defaultFn
  }
  defaultFn(a, b) {
    return a < b
  }

  top() {
    return this.list[1]
  }
  left(x) {
    return 2 * x
  }
  right(x) {
    return 2 * x + 1
  }
  parent(x) {
    return x >> 1
  }
  isEmpty() {
    return this.list.length === 1
  }
  getSize() {
    return this.list.length - 1
  }
  push(value) {
    this.list.push(value)
    this.up(this.list.length - 1)
  }
  up(k) {
    const { compose, list, parent } = this
    const size = this.getSize()
    while (size > 1 && compose(list[k], list[parent(k)])) {
      this.wrap(k, parent(k))
      k = parent(k)
    }
  }
  pop() {
    const size = this.getSize()
    if (size === 0) return null
    this.wrap(1, this.list.length - 1)
    const top = this.list.pop()
    this.down(1)
    return top
  }
  down(k) {
    const size = this.getSize()
    const { left, right, compose, list } = this
    while (left(k) <= size) {
      let next = left(k)
      if (right(k) <= size && compose(list[right(k)], list[next])) {
        next = right(k)
      }
      if (compose(list[k], list[next])) return
      this.wrap(k, next)
      k = next
    }
  }
  wrap(x, y) {
    const t = this.list[x]
    this.list[x] = this.list[y]
    this.list[y] = t
  }
}

var rangeSum = function (nums, n, left, right) {
  const len = nums.length
  const list = nums.map((v, i) => [v, i])
  const minHead = new Heap((a, b) => a[0] < b[0])

  const result = []
  for (let i = 0; i < n; i++) {
    minHead.push(list[i])
  }
  while (!minHead.isEmpty()) {
    const [t, index] = minHead.pop()
    if (index < len - 1) {
      minHead.push([t + nums[index + 1], index + 1])
    }
    result.push(t)
    if (result.length === right) {
      const t = result.slice(left - 1, right).reduce((a, b) => a + b)
      return t % 1000000007
    }
  }
}