题目描述
每个数是一条竖线的高度。选两条线和 x 轴围成容器,求最大盛水面积。
思路解析
枚举所有线对,n² 个组合,n 一大就慢,而且大量组合明显没希望还白算。换个思路:从「最宽」的一对开始,每步只放弃一条注定没希望的线。
l、r 在最左最右,此时宽度最大。面积由矮的那条决定,所以移走矮的——因为留着它、宽度还只会变小,面积只会更差;只有移走它,才可能换到更高的线、把面积翻盘上去。这就是从暴力到双指针的关键转折。
准备 · 两端出发:左指针 l=0、右指针 r=5,从最宽的一对开始。best 先记 0,准备一路刷新。
第 1 次 · 算面积:当前面积 = min(1,7)×5 = 5,刷新 best=5。两条里左边的 1 是矮的,它就是瓶颈。
第 1 次 · 移矮的左:左边 1 比右边 7 矮,移走它(l 右移)。注意不是看面积大小,而是看谁矮就移谁。
第 2 次 · 算面积:l 来到 8:面积 = min(8,7)×4 = 28,刷新 best=28!这一步就是最终答案。被丢掉的 1(灰)以后不再看。
第 2 次 · 移矮的右:这回左边 8 高、右边 7 矮,所以移走右边(r 左移)。移矮边的判据每步重新看。
第 3 次 · 面积变小:负例分支:面积 = min(8,5)×3 = 15,比 best 还小,best 不更新。但右边 5 仍是矮的,必须照样移走它——不能因为面积变小就停或回头,宽度只会越来越小,留着矮边没有未来。
第 4 次 · 继续移矮:min(8,2)×2 = 4,更小,best 仍是 28。右边 2 矮,继续 r 左移。
第 5 次 · 相遇收束:最后 min(8,6)×1 = 6。再移一次 l、r 就相遇、循环结束。全程 best 最大是 28,对应一开始那对 8 和 7。
面积受限于矮的一边,留着它宽度还在缩、注定更差;放弃矮的才有翻盘机会——这是对撞双指针里的贪心选择。
参考代码(Python)
1l, r, best = 0, len(height) - 1, 0
2while l < r:
3 area = min(height[l], height[r]) * (r - l) # 矮边×宽
4 best = max(best, area) # 刷新最大
5 if height[l] < height[r]: l += 1 # 移走较矮的左
6 else: r -= 1 # 否则移右
7return best复杂度分析
- 时间复杂度:O(n) —— l、r 一头一尾相向,加起来只走一遍 n,不回头
- 空间复杂度:O(1) —— 只用 l、r、best 几个变量
套路模板
记住骨架:两端收缩、每步移走限制结果的那一端。和「有序找一对」的双指针同形,区别只是「移谁」的判据。
1# 两端向中间收、每步放弃"拖后腿"的一边
2l, r, best = 0, n - 1, 初值
3while l < r:
4 best = 更新(best, 用 l, r 算的值)
5 if 谁是瓶颈 == 左: l += 1
6 else: r -= 1易错点
- 错误写法:哪边面积小就移哪边 / 移走较高的一边 → 正确写法:永远移走较矮的一边(判据是「谁矮移谁」,不是「面积大小」。面积由矮边决定,移高边宽度还变小、必更差,会直接错过 28 这种解)
- 错误写法:面积用较高的高度 → 正确写法:用 min(左, 右) 高度(水从矮的一边溢出,取高边会把面积算大)
- 错误写法:while l <= r → 正确写法:while l < r(l == r 时宽度为 0、装不了水,应停下)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。