在数组类经典问题中,“接雨水”与“盛最多水的容器”因场景高度相似(均围绕柱子与水的交互)常被混淆,但二者的核心目标、储水逻辑和解法路径差异显著。
本文将系统拆解两类问题的主流解法,剖析其设计思路与创新点,通过表格直观对比核心差异,并提炼“快速联想解题思路”的触发点(trigger),帮助彻底吃透这类“柱子+水”的问题。
一、接雨水问题:3种解法深度解析
问题定义
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算下雨后所有柱子间凹槽能接住的总水量。
核心逻辑:单个位置的储水量由“左右两侧最高柱子的最小值”和“当前柱子高度”决定,公式为:
water[i] = max(0, min(left_max[i], right_max[i]) - height[i])
(left_max[i] 是 i 左侧所有柱子的最高值,right_max[i] 是 i 右侧所有柱子的最高值)
解法1:暴力解法(本质还原版)
思路
直接还原“单个位置储水量公式”的计算逻辑:遍历每个可储水的位置,分别找到其左右侧的最高柱子,累加所有位置的储水量。
创新点
无额外逻辑包装,完全贴合问题本质,是所有优化解法的“原型基础”,适合新手入门理解核心逻辑。
过程描述
- 边界判断:若数组长度
n < 3(少于3根柱子无法形成凹槽),直接返回 0; - 初始化总储水量
total = 0; - 遍历每个位置
i(首尾位置无左右边界,跳过i=0和i=n-1); - 对每个
i,向左遍历数组,找到左侧最高柱子高度left_max; - 对每个
i,向右遍历数组,找到右侧最高柱子高度right_max; - 计算当前位置储水量:
current_water = max(0, min(left_max, right_max) - height[i])(负数时取0,代表无法储水); - 累加
current_water到total,遍历结束后返回total。
代码片段
def trap(height):n = len(height)if n < 3:return 0total = 0for i in range(1, n-1):left_max = max(height[:i]) # 左侧最高(不包含i)right_max = max(height[i+1:]) # 右侧最高(不包含i)total += max(0, min(left_max, right_max) - height[i])return total
复杂度
- 时间复杂度:O(n²)(每个位置需左右遍历一次,共 n 个位置);
- 空间复杂度:O(1)(仅用临时变量存储最高值和总水量)。
解法2:双指针解法(最优效率版)
思路
通过双指针收缩,用两个全局变量实时记录“左侧最高值”和“右侧最高值”,避免重复遍历计算 left_max 和 right_max,将时间复杂度优化至 O(n)。
创新点
- 利用“储水量由较短的最高侧决定”的特性:水会从较矮的一侧溢出,因此只需关注较短侧的最高值,无需同时计算两侧全局最高;
- 双指针+常数空间:一次遍历完成所有计算,空间复杂度降至 O(1),是该问题的最优效率解法。
过程描述
- 边界判断:
n < 3时返回 0; - 初始化双指针
left=0(左端点)、right=n-1(右端点),左右最高值left_max=0、right_max=0,总储水量total=0; - 当
left < right时,根据两侧柱子高度判断:- 若
height[left] < height[right]:当前left位置的储水量由left_max决定(右侧最高足够高,不会成为瓶颈);更新left_max为max(left_max, height[left]),累加left_max - height[left]到total,移动左指针left += 1; - 否则:当前
right位置的储水量由right_max决定(左侧最高足够高);更新right_max为max(right_max, height[right]),累加right_max - height[right]到total,移动右指针right -= 1;
- 若
- 遍历结束后返回
total。
代码片段
def trap(height):n = len(height)if n < 3:return 0left, right = 0, n-1left_max = right_max = 0total = 0while left < right:if height[left] < height[right]:left_max = max(left_max, height[left])total += left_max - height[left]left += 1else:right_max = max(right_max, height[right])total += right_max - height[right]right -= 1return total
复杂度
- 时间复杂度:O(n)(双指针仅遍历数组一次);
- 空间复杂度:O(1)(仅用 4 个临时变量)。
解法3:单调栈解法(凹槽匹配版)
思路
用单调栈维护“柱子索引的递减序列”(栈内索引对应的柱子高度单调递减),当遇到比栈顶高的柱子时,栈顶元素即为“凹槽底部”,弹出后新栈顶为“凹槽左边界”,当前柱子为“凹槽右边界”,计算该凹槽的储水量并累加。
创新点
- 将“找凹槽左右边界”转化为“单调栈的入栈出栈操作”,一次遍历完成所有凹槽匹配;
- 思路可迁移至“找最近更大/更小元素”的问题(如柱状图中最大矩形),通用性强。
过程描述
- 边界判断:
n < 3时返回 0; - 初始化单调栈
stack(存储柱子索引,维持栈内索引对应的高度递减),总储水量total=0; - 遍历数组每个索引
i:- 当栈不为空且
height[i] > height[stack[-1]](遇到右边界,可形成凹槽):- 弹出栈顶
bottom_idx(凹槽底部的索引); - 若栈为空(无左边界,无法形成凹槽),则 break;
- 新栈顶
left_idx为凹槽左边界(左侧最近的更高柱子); - 计算凹槽高度:
h = min(height[left_idx], height[i]) - height[bottom_idx](扣除凹槽底部高度); - 计算凹槽宽度:
w = i - left_idx - 1(左右边界之间的间隔数); - 累加储水量:
total += h * w;
- 弹出栈顶
- 否则,将当前索引
i入栈(维持栈的递减顺序);
- 当栈不为空且
- 遍历结束后返回
total。
代码片段
def trap(height):n = len(height)if n < 3:return 0stack = []total = 0for i in range(n):# 遇到右边界,弹出栈顶找凹槽while stack and height[i] > height[stack[-1]]:bottom_idx = stack.pop()if not stack:breakleft_idx = stack[-1]# 计算凹槽的高度和宽度h = min(height[left_idx], height[i]) - height[bottom_idx]w = i - left_idx - 1total += h * wstack.append(i)return total
复杂度
- 时间复杂度:O(n)(每个元素入栈、出栈各一次);
- 空间复杂度:O(n)(最坏情况栈存储所有索引,如柱子高度单调递减时)。
二、盛最多水的容器问题:2种解法深度解析
问题定义
给定 n 个非负整数 height,每个元素对应一条垂直柱子,找出两条柱子,使其与 x 轴构成的容器能容纳最多的水(单个容器的最大面积)。
核心公式:容器面积 = 底边长 × 最短柱子高度,即:
area = (right - left) × min(height[left], height[right])
(left、right 为两条柱子的索引,底边长为索引差)
解法1:暴力解法(枚举所有组合)
思路
枚举所有可能的“两条柱子组合”,计算每个组合的容器面积,记录并返回最大值。
创新点
完全基于问题定义推导,无额外逻辑门槛,直观易懂,适合快速理解问题核心。
过程描述
- 边界判断:
n < 2(少于2根柱子无法构成容器)时返回 0; - 初始化最大面积
max_area=0; - 遍历所有左索引
left(从 0 到n-2); - 遍历所有右索引
right(从left+1到n-1); - 计算当前组合的面积:
width = right - left,h = min(height[left], height[right]),current_area = width × h; - 更新
max_area为max(max_area, current_area); - 遍历结束后返回
max_area。
代码片段
def maxArea(height):n = len(height)if n < 2:return 0max_area = 0for left in range(n):for right in range(left+1, n):width = right - lefth = min(height[left], height[right])max_area = max(max_area, width * h)return max_area
复杂度
- 时间复杂度:O(n²)(需枚举
n×(n-1)/2种组合); - 空间复杂度:O(1)(仅用临时变量存储面积和最大值)。
解法2:双指针解法(贪心优化版)
思路
利用“底边长越大,面积潜力越大”的特性,初始取首尾柱子(底边长最大),通过贪心策略移动“较短的柱子”,尝试提升柱子高度,从而最大化面积。
创新点
- 兼顾“底边长”和“高度”:初始底边长最大,后续仅通过移动短边提升高度,避免无效枚举(移动长边会导致底边长减小且高度不提升,面积必然变小);
- 一次遍历实现 O(n) 效率,是该问题的唯一最优解法。
过程描述
- 边界判断:
n < 2时返回 0; - 初始化双指针
left=0(左端点)、right=n-1(右端点),最大面积max_area=0; - 当
left < right时:- 计算当前组合的面积:
width = right - left,h = min(height[left], height[right]),current_area = width × h; - 更新
max_area为max(max_area, current_area); - 贪心移动短边:
- 若
height[left] < height[right]:移动左指针left += 1(短边在左,尝试找更高的左柱); - 否则:移动右指针
right -= 1(短边在右,尝试找更高的右柱);
- 若
- 计算当前组合的面积:
- 遍历结束后返回
max_area。
代码片段
def maxArea(height):n = len(height)if n < 2:return 0left, right = 0, n-1max_area = 0while left < right:width = right - lefth = min(height[left], height[right])max_area = max(max_area, width * h)# 移动短边,提升高度潜力if height[left] < height[right]:left += 1else:right -= 1return max_area
复杂度
- 时间复杂度:O(n)(双指针仅遍历数组一次);
- 空间复杂度:O(1)(仅用 3 个临时变量)。
三、两类问题解法对比表格
| 对比维度 | 接雨水问题 | 盛最多水的容器问题 |
|---|---|---|
| 核心目标 | 计算所有凹槽的总储水量(多个小容器累加和) | 找到两条柱子构成的最大单容器面积(单个组合的最大值) |
| 储水/面积公式 | 单个位置水量 = max(0, min(左最高, 右最高) - 当前柱高)(底边长=1) | 容器面积 = (右-左) × min(左柱高, 右柱高)(底边长=索引差) |
| 暴力解法 | 时间 O(n²),空间 O(1);遍历每个位置,分别找左右最高 | 时间 O(n²),空间 O(1);枚举所有双柱子组合,计算面积 |
| 最优解法 | 双指针(时间 O(n),空间 O(1)) | 双指针(时间 O(n),空间 O(1)) |
| 备选高效解法 | 单调栈(时间 O(n),空间 O(n));适合迁移“最近更大元素”问题 | 无(单调栈不适用,无需找凹槽,仅需最优双元素组合) |
| 解法核心创新点 | 双指针:用全局变量记录左右最高,避免重复计算;单调栈:匹配凹槽边界 | 双指针:贪心移动短边,平衡底边长和高度,避免无效枚举 |
| 边界依赖 | 每个位置依赖“左右全局最高柱子” | 容器依赖“两条目标柱子”(局部边界) |
| 中间柱子影响 | 中间柱子遮挡水,需扣除当前柱高 | 中间柱子不影响,视为“空”,仅关注两条边界柱子 |
| 适用场景延伸 | 需找“多个凹槽”“最近更大/更小元素”的问题(如柱状图最大矩形) | 需找“最优双元素组合”“平衡两个变量(底+高)”的问题 |
四、思路触发点(trigger):快速联想合适解法
1. 第一步:通过“问题目标”区分问题类型
- 若问题目标是“总水量”“累加和” → 接雨水问题:
- 核心逻辑:每个位置独立储水,需计算所有位置的总和;
- 直观思路:暴力解法(遍历每个位置,找左右最高);
- 优化思路:优先双指针(空间 O(1),效率最优);若熟悉“最近更大元素”问题,可联想单调栈(思路迁移性强)。
- 若问题目标是“最大面积”“单个组合最大值” → 盛最多水的容器问题:
- 核心逻辑:寻找最优双元素组合,无需考虑其他元素;
- 直观思路:暴力解法(枚举所有双组合);
- 优化思路:唯一最优解是双指针(贪心移动短边),无其他高效解法。
2. 第二步:根据“效率需求”选择具体解法
- 若
n较小(如n < 1000),允许 O(n²) 时间:直接用暴力解法,代码简洁不易出错; - 若
n较大(如n > 10000),要求 O(n) 时间:- 接雨水:优先双指针(空间更优);若题目隐含“找边界”特征,用单调栈;
- 盛最多水:直接用双指针,无需其他选择。
3. 第三步:关键特征联想口诀
- 见“总水量”→ 想“每个位置的左右边界”→ 双指针/单调栈;
- 见“双柱子最大面积”→ 想“底×高平衡”→ 贪心双指针;
- 见“凹槽”“最近更大元素”→ 想“单调栈”(仅适用于接雨水);
- 见“无法倾斜”“最短边决定”→ 想“min(左, 右)”(两类问题通用公式特征)。
总结
接雨水和盛最多水的容器问题,虽场景相似,但核心差异在于“目标是总和还是单个最大值”:
- 接雨水是“多凹槽累加”,需关注每个位置的左右边界,解法围绕“减少左右最高值的重复计算”(双指针)或“匹配凹槽边界”(单调栈)展开;
- 盛最多水是“单组合最优”,需平衡底边长和高度,解法核心是“贪心移动短边”的双指针策略。
掌握以下核心结论,可快速破解这类问题:
- 先看目标:“总和”→ 接雨水,“单个最大”→ 盛最多水;
- 再看效率:O(n) 需求下,两类问题均优先双指针;接雨水可备选单调栈;
- 公式特征:“min(左, 右)”是通用核心,但接雨水需扣除当前柱高,盛最多水需乘索引差。
通过以上触发点,可在遇到类似问题时快速定位核心逻辑,避免混淆,选择最优解法。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/956718.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!