算法升级之路(七)-盛最多水的容器

文章介绍了如何解决‘盛最多水的容器’问题,通过分析发现,最优策略是不断移动较小高度的线,直至两线重合,计算最小高度乘以宽度得到最大面积。代码展示了使用双指针方法实现的解决方案。

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。
原题链接: 盛最多水的容器

解题思路:
这道题乍一看没什么思路,用暴力循环的话太麻烦了,要从第一个循环剩下的所有,第二个循环剩下的所有,第三个。。。
我也是看了答案之后才明白怎么算,其实很简单。

这个盛水的面积= min(height[i],height[j])*(j-i);

从i=0 和j=heigh.length-1算起
在这个状态下只要挪动板子,(j-i)都是会变小

min(height[i],height[j] 只有变大才有机会变大

由于求的是最小值,那么只有最小值变大,面积才有变大的可能。
所以每次挪动最小的板子,直到两个板子重合,就能得出最大面积。

代码

 public int maxArea(int[] height) {
        int area = 0;
        int front =0;
        int backend =height.length-1;
        while(front!=backend){
          int   current =Math.min(height[front],height[backend])*(backend-front);
          if(current>area){
              area = current;
          }
          if(height[front]<height[backend]){
              front++;
          }else{
              backend--;
          }
        }
        return area;


    }

这个算法让我有种我当年上高中事,做题做不出,老师一讲就听懂了的感觉,梦回高三。
我啥时候才能到这种水平呀

我们来对上述 C++ 程序中所使用的**算法、数据结构(容器)和设计思路**进行一次深度剖析。这不仅有助于理解程序如何工作,还能提升你对 C++ 高级编程技巧的理解。 --- ## 🔍 一、整体问题再回顾 题目要求判断两个异性是否可以结婚: - 同性 → `Never Mind` - 异性但有共同祖先在五代以内(含)→ `No` - 异性且无五代内共同祖先 → `Yes` 关键在于:**快速找出两个人各自向上追溯最多5代的所有祖先,并检查是否有交集**。 --- ## 🧩 二、核心算法详解:BFS 上溯 + 集合比较 ### ✅ 使用的算法:广度优先搜索(BFS) 我们在函数 `getAncestorsWithin5()` 中使用了 **Breadth-First Search (BFS)** 来遍历每个人的祖先链。 #### ❓ 为什么用 BFS 而不是 DFS? - 我们的目标是“在不超过5代的范围内”,收集所有祖先。 - BFS 天然适合按“层次”扩展(第1层父母,第2层祖父母……),便于控制深度。 - 深度限制为5,每推进一层 `depth+1`,一旦达到5就停止向下。 - 相比之下,DFS 可能会先深入某一支(比如一直往上找父亲的父亲……),虽然也能实现,但不如 BFS 清晰直观。 #### ⚙️ BFS 实现细节: ```cpp queue<pair<string, int>> q; q.push({id, 0}); while (!q.empty()) { auto [cur, depth] = q.front(); q.pop(); if (depth >= 5) continue; // 加入父亲母亲,并将他们入队,depth+1 if (p.father != "-1") { ancestors.insert(p.father); q.push({p.father, depth + 1}); } ... } ``` > 这里 `depth` 表示当前节点距离原始人的代数: > - depth=0:本人 > - depth=1:父母 > - depth=2:祖父母 > - ... > - depth=5:超过五服,不再追溯 ⚠️ 注意:我们只将“祖先”加入结果集合 `ancestors`,不包括自己。 --- ## 🧰 三、关键容器及其作用分析 C++ STL 提供的强大容器让这个逻辑变得简洁高效。下面我们逐个解析: --- ### 1. `map<string, Person>` —— 主存储结构 ```cpp map<string, Person> persons; ``` #### 功能: - 以 **ID 字符串**作为键(key),快速查找任意一个人的信息。 - 支持 O(log N) 的插入与查询(底层是红黑树)。 - ID 是固定5位字符串(如 "00001"),天然适合作为 key。 #### 示例访问: ```cpp persons["00001"].gender; // 得到性别 persons["00001"].father; // 得到父亲ID ``` #### 替代方案对比: | 容器 | 时间复杂度 | 是否推荐 | |------|-----------|---------| | `unordered_map` | 平均 O(1) | 更快,适用于大数据量 | | `vector` + 线性查找 | O(N) | 不推荐 | ✅ 推荐升级为 `unordered_map<string, Person>` 以获得常数时间平均查找速度。 --- ### 2. `set<string>` —— 存储祖先集合 ```cpp set<string> ancestors; ``` #### 功能: - 自动去重地存储祖先 ID。 - 插入时自动排序(基于字典序),便于后续操作。 - 在最后做交集判断时,可用迭代器或 `count()` 快速查找。 #### 为何不用 `vector`? - 若用 `vector`,需手动去重(同一个人可能通过不同路径被多次访问,例如堂亲共祖)。 - `set` 插入即自动排重,安全可靠。 #### 性能考量: - 插入:O(log A),A 是集合大小(一般很小,最多几百) - 查找:O(log A) 对于本题的小规模祖先数量来说完全足够。 --- ### 3. `queue<pair<string, int>>` —— BFS 辅助队列 ```cpp queue<pair<string, int>> q; ``` #### 结构含义: - `pair<string, int>`:第一个元素是当前处理的人 ID,第二个是其距原始人的“代数”(depth) - `queue` 实现先进先出,保证按层扩展 #### 举例说明: 从 `"00021"` 开始 BFS: 1. 入队:("00021", 0) 2. 出队后查其父母 → 假设父亲是 "11100",母亲是 "00020" 3. 入队:("11100", 1), ("00020", 1) 4. 下一轮处理这两个,继续上溯…… 直到 depth ≥ 5 停止。 --- ### 4. `unordered_set` vs `set`?可优化点! 虽然用了 `set`,但我们并不关心顺序,只关心是否存在某个祖先。 因此更优选择是: ```cpp unordered_set<string> ancestors; // 哈希集合,平均 O(1) 插入/查找 ``` > 对于大规模数据或频繁调用 `getAncestorsWithin5()` 的场景,性能提升明显。 --- ## 🔎 四、关键函数解析:`getAncestorsWithin5` ```cpp set<string> getAncestorsWithin5(const string& id) ``` ### 输入: - 当前人 ID(字符串) ### 输出: - 所有在五代以内(不含本人)的祖先集合 ### 内部流程: 1. 初始化空集合 `ancestors` 2. 创建 BFS 队列,起始点为 `(id, 0)` 3. 遍历队列: - 取出当前人及其 depth - 如果 depth < 5,将其父母加入集合并入队 4. 返回祖先集合 > ✅ 此函数具有幂等性(相同输入总返回相同输出),非常适合加缓存(记忆化) --- ## 💡 五、潜在优化方向 ### ✅ 1. 记忆化(Memoization)避免重复计算 目前每次查询都要重新计算祖先集合。如果 K 很大(最多 10^4),而 N ≤ 10^4,则可能重复计算同一个人的祖先。 #### 改进方法: ```cpp map<string, set<string>> memo; set<string> getAncestorsWithin5(const string& id) { if (memo.find(id) != memo.end()) return memo[id]; // ...原BFS逻辑... return memo[id] = ancestors; } ``` > 时间复杂度从 O(K × 宽度×深度) → O(N × 宽度×深度) + O(K) 大幅降低重复开销。 --- ### ✅ 2. 使用 `unordered_set` 替代 `set` ```cpp using AncestorSet = unordered_set<string>; map<string, AncestorSet> memo; ``` 加快插入和查找速度。 --- ### ✅ 3. 交集判断优化 当前代码: ```cpp bool hasCommon = false; for (const auto& a : ancA) { if (ancB.count(a)) { hasCommon = true; break; } } ``` 这是 O(|A| × log|B|)。若改为: ```cpp // 小集合遍历,大集合查询 const auto& small = ancA.size() < ancB.size() ? ancA : ancB; const auto& large = &small == &ancA ? ancB : ancA; for (const auto& a : small) { if (large.count(a)) { hasCommon = true; break; } } ``` 可减少平均比较次数。 --- ## 📊 六、时间与空间复杂度分析 | 项目 | 复杂度 | 说明 | |------|--------|------| | 单次 `getAncestorsWithin5` | O(2^5) = O(1) | 最多访问 2^5=32 个祖先节点(满二叉树) | | 总体预处理 | O(N) | 每人最多算一次(带 memo) | | 查询 K 对情侣 | O(K) | 每次只需查两个集合是否有交集 | | 空间复杂度 | O(N) | 存储所有人信息 + 每人最多一个祖先集合 | ✅ 整体效率很高,适合 N ≤ 1e4 的数据规模。 --- ## 🔄 、完整逻辑流程图解 ``` 输入所有人信息 ↓ 构建 map<string, Person> ↓ 读取K对情侣 ↓ 对每对(a,b): ├─ 是否同性? → 是 → "Never Mind" └─ 否则: ├─ 获取a的五代祖先集合 A ├─ 获取b的五代祖先集合 B └─ A ∩ B ≠ ∅ ? → 是 → "No" : "Yes" ``` --- ## ✅ 八、总结:技术要点一览 | 技术点 | 用途 | 优势 | |-------|------|------| | `map/unordered_map` | 快速索引人物信息 | O(1)~O(logN) 查找 | | `set/unordered_set` | 存储祖先集合,自动去重 | 安全高效 | | `queue` + `pair` | 实现 BFS 层序遍历 | 控制深度精确 | | BFS 算法 | 上溯家谱 | 层次清晰,易于剪枝 | | 集合交集判断 | 判断是否近亲 | 逻辑严谨 | | 记忆化(可选) | 避免重复计算 | 提升性能 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值