📌 题目链接:leetcode.cn/problems/co…
🔍 难度:中等 | 🏷️ 标签:数组、双指针、贪心、面积最大化
⏱️ 目标时间复杂度:O(n)
💾 空间复杂度:O(1)
🎯 本题是「双指针」思想的经典应用之一,常出现在面试中考察你对贪心策略与边界推理能力的理解。虽然看似简单,但背后蕴含着极强的逻辑严谨性——为什么只能移动较短的柱子?为什么不能回退?
🔍 本文将从问题建模 → 算法设计 → 正确性证明 → 代码实现 → 面试拓展五个维度深入解析这道题,帮助你在日常复习时快速回顾核心逻辑,轻松应对面试中的变种考题!
🧠 一、题目分析:如何“盛水”?
给定一个长度为 n 的整数数组 height,每个元素代表一根垂直于 x 轴的柱子的高度。
我们要从中选出两条柱子,使得它们与 x 轴构成的“容器”能容纳最多的水。
⚠️ 容器的容量由两个因素决定:
- ✅ 两柱之间的距离(宽度)
- ✅ 两条柱子中较短的一根的高度(短板效应)
👉 所以最大水量 = min(height[i], height[j]) * (j - i),其中 i < j
🎯 目标:在所有可能的 (i, j) 组合中,找到使该表达式最大的那一对。
🚀 二、核心算法:双指针法(Two Pointers)⚡
✅ 为什么用双指针?
我们从最宽的情况开始(左端点 + 右端点),然后逐步缩小范围,尝试寻找更大的面积。
❓ 为什么不暴力枚举所有组合?
因为暴力解法的时间复杂度是 O(n²),而本题可以利用贪心+单调性优化到 O(n)!
✅ 关键洞察:面积由“短板”和“宽度”共同决定,宽度只会减小,所以必须靠提升“短板”来争取更大面积。
🔁 指针移动策略
初始化:
left = 0, right = n - 1
每一步计算当前面积,并移动较矮的那一侧的指针:
if (height[left] < height[right])
left++;
else
right--;
💡 为什么只移动较矮的?这是整个算法的灵魂所在!
🧪 三、正确性证明:为何不能回退?🤔
假设当前状态为:
i' i j j'
a[i'] a[i] a[j] a[j']
设当前面积为:
s = min(a[i], a[j]) × (j - i)
现在考虑是否应该移动 i 或 j。
🔄 情况一:a[i] <= a[j]
我们移动 i 到 i'(即左指针右移)
新面积:
s' = min(a[i'], a[j]) × (j - i')
由于 a[i] <= a[j],所以 min(a[i], a[j]) = a[i]
又因为 a[i'] >= a[i](不一定,但我们可以讨论极限情况)—— 实际上我们不知道 a[i'] 是大还是小,但我们知道:
⚠️ 宽度
(j - i')一定小于(j - i),所以如果高度不变或下降,面积必然减少。
但关键是:如果我们不移动较矮的柱子,就永远无法突破当前瓶颈!
📌 结论:只有通过移动较矮的柱子,才有可能遇到更高的柱子,从而提升“短板”,弥补宽度损失。
🧩 数学推导验证
令:
- 当前面积:
s = min(h[i], h[j]) * (j - i) - 假设
h[i] < h[j],我们考虑移动i到i+1
新面积:
s' = min(h[i+1], h[j]) * (j - i - 1)
虽然宽度减少了 1,但如果 h[i+1] > h[i],那么 min(h[i+1], h[j]) 可能大于原来的 h[i],从而整体面积可能更大!
✅ 所以必须移动较矮的一边,否则永远无法获得更优解!
🔄 为什么不能回退?
比如:我们已经把 left 移动到了某个位置,发现面积变小了,能不能再往回走?
❌ 不行!
🧠 原因在于:一旦我们放弃了一个位置,它所参与的所有组合都已经被排除了。而由于我们是从外向内收缩,且每次只移动较矮的一侧,这意味着我们不会重复访问已处理过的区域。
📌 更重要的是:这个过程是不可逆的贪心决策,基于以下事实:
若
h[left] < h[right],则无论right向左移动多少步,h[left]都是当前左边的最小值,因此不可能再形成比当前更大的面积(除非右边出现更高柱子)。
所以:只要移动了,就不回头!
📦 四、完整代码实现(C++ + 测试)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 双指针法
class Solution {
public:
int maxArea(vector<int>& height) {
int n = height.size();
int left = 0, right = n - 1; // 初始化左右指针
int ret = 0; // 记录最大面积
while (left < right) {
// 计算当前左右指针构成的容器面积
int ans = min(height[left], height[right]) * (right - left);
ret = max(ret, ans); // 更新最大面积
// 移动指向较短垂直线的指针
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return ret;
}
};
//测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// 测试用例1:[1,8,6,2,5,4,8,3,7] 期望输出:49
vector<int> height1 = {1,8,6,2,5,4,8,3,7};
Solution solution;
cout << "测试用例1 [1,8,6,2,5,4,8,3,7] 的结果:" << solution.maxArea(height1) << "\n";
// 测试用例2:[1,1] 期望输出:1
vector<int> height2 = {1,1};
cout << "测试用例2 [1,1] 的结果:" << solution.maxArea(height2) << "\n";
return 0;
}
📊 五、算法分析
| 项目 | 内容 |
|---|---|
| 时间复杂度 | O(n) —— 每个元素最多被访问一次,左右指针总共移动 n 次 |
| 空间复杂度 | O(1) —— 只使用了常量级额外空间 |
| 适用场景 | 适用于“两端收缩”、“面积/体积最大化”、“找最优区间”等问题 |
✅ 这是一种典型的 “从两边向中间逼近” 的双指针模式,常见于:
- 最大/最小面积
- 三数之和
- 两数之和(有序数组版)
- 滑动窗口(变体)
🎯 六、面试重点 & 高频考点 💼
1. ❓ 问:为什么一定要移动较短的柱子?能否证明其正确性?
✅ 回答模板:
“因为面积由短板和宽度决定。当我们将较短的柱子向内移动时,虽然宽度减少,但存在机会遇到更高的柱子,从而提升短板高度。反之,如果移动较高的柱子,新的短板仍受限于原短板,且宽度减少,面积必然下降。因此,只有移动较短的柱子才有可能获得更大面积。”
“此外,该策略保证了我们在每一步都做出局部最优选择,最终收敛到全局最优解,属于一种贪心 + 单调性的结合。”
2. ❓ 问:有没有可能漏掉最优解?
✅ 回答:
“不会。因为我们从最宽的位置开始,逐步缩小搜索空间,且每次移动都是基于‘不能让短板变得更短’的原则。由于任何未被访问的组合都会包含至少一个已经被跳过的柱子,而这些柱子要么高度不够,要么已被淘汰,所以不会影响最终结果。”
3. ❓ 问:能否用暴力方法解决?有什么缺点?
✅ 回答:
“可以,但时间复杂度为 O(n²),对于大数据集会超时。而且没有体现算法思维的优化意识,容易被面试官质疑‘有没有更好的办法’。”
4. ❓ 问:这题和“三数之和”有什么关系?
✅ 回答:
“两者都用了双指针技术,但目的不同。‘盛水’是求最大面积,而‘三数之和’是求特定和值。前者依赖贪心思想,后者依赖排序+夹逼搜索。”
🔄 七、扩展思考:变形题 & 应用场景
| 变形题 | 思路 |
|---|---|
| 盛最多雨水(接雨水) | 类似思路,但需要维护最高点 |
| 最大矩形面积(直方图) | 使用栈,但也可以用双指针辅助 |
| 最大子数组和 | 不适用,需动态规划 |
| 最长连续递增子序列 | 可用滑动窗口 |
💡 提示:双指针的本质是“降低搜索空间”,适合用于:
- 有序数组
- 区间问题
- 和/差/积/面积最优化
✅ 八、总结:记住这几点!
- 🧱 短板效应:面积由最短边决定。
- 🔁 只移短板:永远不要移动较高的那一侧。
- 🔄 不可回退:指针只能向前,不能后退。
- 📈 宽度递减:所以必须靠提高高度来补偿。
- 🎯 贪心 + 单调性:是本题的核心数学基础。
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📣 下一期预告:LeetCode 热题 100 第6题 —— 三数之和(中等)
🔹 题目:给定一个包含 n 个整数的数组 nums,判断是否存在三个元素 a, b, c ,使得 a + b + c = 0?找出所有满足条件且不重复的三元组。
🔹 核心思路:先排序,然后固定一个数,用双指针查找另外两个数,避免重复。
🔹 考点:双指针、排序、去重、枚举优化。
🔹 难度:中等,是面试中最常见的“多指针”问题之一,务必掌握!
💡 提示:去重是难点!注意三层循环的剪枝与重复判断!
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!
📌 一起冲冲冲!🚀