🌈【LeetCode 56. 装最多水的容器】- JavaScript => 线段树、排序+后缀

303 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。


说明:文章部分内容及图片出自网络,如有侵权请与我本人联系(主页有公众号:小攻城狮学前端)

作者:小只前端攻城狮、 主页:小只前端攻城狮的主页、 来源:掘金

GitHub:P-J27、 CSDN:PJ想做前端攻城狮

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


题目描述

给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器。

分析:题意求max((i-j)*min(a[i],a[j])),i > j。

我们发现a[j]>=a[i]的时候,min值取a[i],所以j越小越好,满足a[j]>=a[i]的那些j可以构成一个答案值ans1,ans1不一定等于最终答案。那么a[j]<a[i]的情况怎么办?把数组倒序,再求一遍!把这个答案记为ans2,则max(ans1,ans2)为所求。

所以我们只需要求最小的j,满足a[j]>=a[i]。

解法1:线段树。

线段树维护区间max,如果左侧区间max>=a[i],就递归左侧,否则递归右侧。


let ls = x => (x << 1), rs = x => (x << 1 | 1)
let nd = new Array(200010).fill(0)
​
function build(x, l, r, a) {
  if (l === r) {
    nd[x] = a[l - 1]
    return
  }
  let mid = (l + r) >> 1
  build(ls(x), l, mid, a)
  build(rs(x), mid + 1, r, a)
  nd[x] = Math.max(nd[ls(x)], nd[rs(x)])
}
​
function query(x, l, r, v) {
  if (l === r) return l
  let mid = (l + r) >> 1
  if (nd[ls(x)] >= v) return query(ls(x), l, mid, v)
  return query(rs(x), mid + 1, r, v)
}
​
var maxArea = function(a) {
  const n = a.length
  let solve = (a) => {
    build(1, 1, n, a)
    let ans = 0
    for (let i = 0; i < n; ++i) {
      let idx = query(1, 1, n, a[i]) - 1
      if (idx > i) continue
      ans = Math.max(ans, a[i] * (i - idx))
    }
    return ans
  }
  let ans = solve(a)
  a.reverse()
  ans = Math.max(ans, solve(a))
  return ans
};

解法2:排序+后缀min

把数值和下标绑在一起,按照数值升序排序,如果数值相同则随意排。记为b数组。

找到第一个大于等于a[i]的下标idx,则min(b[idx~n-1][1])(0-indexed)为所求。所以用到了后缀min。

var maxArea = function(a) {
  const n = a.length
  let lower_bound = (a, x) => {
    let l = 0, r = a.length
    while (l < r) {
      let mid = (l + r) >> 1
      if (a[mid] >= x) r = mid
      else l = mid + 1
    }
    return l
  }
  let solve = (a) => {
    let b = a.map((v, i) => [v, i])
    b.sort((x, y) => x[0] ^ y[0] ? x[0] - y[0] : x[1] - y[1])
    let val = b.map(v => v[0]), mn = b.map(v => v[1])
    for (let i = n - 2; ~i; --i) mn[i] = Math.min(mn[i], mn[i + 1])
    let ans = 0
    for (let i = 0; i < n; ++i) {
      let idx = lower_bound(val, a[i])
      ans = Math.max(ans, a[i] * (i - mn[idx]))
    }
    return ans
  }
  let ans = solve(a)
  a.reverse()
  ans = Math.max(ans, solve(a))
  return ans
};

感谢阅读,希望能对你有所帮助,文章若有错误或者侵权,可以在评论区留言或在我的主页添加公众号联系我。

写作不易,如果觉得不错,可以「点赞」+「评论」 谢谢支持❤