双指针-盛最多水的容器(No.11)

盛最多水的容器

🌊 问题描述

给定长度为 n 的整数数组 height,数组中每个元素代表一条垂线的高度。第 i 条垂线的端点为 (i, 0)(i, height[i])
目标:找到两条垂线,使其与 x 轴围成的容器盛水量最大,返回最大水量。

限制条件:容器不能倾斜。

📌 示例解析

示例 1
输入数组示意图

  • 输入:[1, 8, 6, 2, 5, 4, 8, 3, 7]
  • 输出:49
  • 解释:最大盛水量由高度为 87 的垂线围成(间距为 7),水量为 min(8,7) × 7 = 49

示例 2

  • 输入:[1, 1]
  • 输出:1
🧠 解题思路(双指针法)
  1. 核心逻辑:盛水量由「较短垂线的高度」和「两垂线间距」共同决定,即 水量 = min(左高度, 右高度) × 间距
  2. 指针初始化:左指针 left 指向数组起始,右指针 right 指向数组末尾。
  3. 移动策略
    • 每次计算当前指针围成的水量,并更新最大值。
    • 移动「较短的垂线指针」(因为移动较长指针无法增加水量,只会减小间距)。
  4. 终止条件:左右指针相遇时结束。
💻 代码实现
/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
    let n = height.length
    if (n < 2) return 0;
    let i = 0, j = n-1;
    let res = []
    while(j> i) {
    	// 计算当前水量
        res.push(Math.min(height[i], height[j]) * (j -i))
        if(height[i] < height[j]) {
            i++
        } else {
            j--
        }
    }
    return Math.max(...res)
};
📊 复杂度分析
  • 时间复杂度:O(n),仅需一次遍历数组。
  • 空间复杂度:O(1),仅使用常数级额外空间。
🌟 关键优化点
  • 直接维护最大水量,避免存储所有中间结果,空间复杂度从 O(n) 优化至 O(1)。
  • 移动指针时,始终选择较短的一侧,确保每次迭代都可能找到更大的水量。
<think>好的,用户之前问过如何在MFC单文档绘图系统中实现撤销功能,我给出了基于栈结构的解决方案。现在用户再次提问,要求使用链表或容器,并且限制最多撤销次数为10次,同时避免内存泄漏。需要结合用户提供的引用内容,但要注意引用可能不完全相关,需要仔细分析。 首先,用户这次的需求更具体,需要完整的代码,并且要求使用链表或容器。之前的回答用了std::stack,但用户可能希望更灵活的数据结构,比如链表或者deque,这样方便限制最大数量。用户还提到了内存泄漏的问题,所以需要注意资源的释放。 用户提供的引用中,引用[1]提到内存管理技巧,如对象池和内存分配策略,这可能提醒我要使用智能指针或者对象池来管理CDC对象,防止泄漏。引用[5]关于窗口风格,可能和当前问题无关。引用[2]和[3]是关于列表框的操作,可能也不相关,但引用[4]中的功能结构可能需要类似的历史记录管理。 接下来,我需要调整数据结构,将栈改为双端队列(deque),这样可以方便地限制最大数量。当超过10次操作时,自动删除最旧的操作。同时,使用智能指针如std::unique_ptr来管理CDC对象,确保在弹出或删除时自动释放内存,避免泄漏。 然后,绘制操作的捕获部分需要修改,每次保存当前画布到CDC对象,并将该对象存入undoList。如果超过10次,删除最前面的元素。撤销和重做操作需要处理将当前状态移到redoList,并应用之前的画布状态。 还需要考虑如何复制CDC对象,可能需要深拷贝位图,而不仅仅是保存指针。之前的示例中直接保存了CDC指针,但可能存在问题,需要确保每个CDC对象都有自己的位图数据,避免覆盖。 在实现撤销功能时,当用户触发撤销,将当前的画布状态保存到redoList,然后从undoList取出最新的状态进行恢复。同样,重做操作则相反。同时,限制redoList的大小,防止内存占用过多。 最后,测试代码是否正确处理了内存,尤其是在达到最大撤销次数时,旧的对象是否被正确释放。使用智能指针可以简化这部分,确保没有内存泄漏。 总结,需要修改数据结构为deque,使用智能指针,实现深拷贝CDC内容,并处理撤销和重做的逻辑,同时限制最大次数为10。确保每一步操作都正确管理内存,避免泄漏。</think>### MFC单文档绘图系统撤销功能实现(链表/容器版) 根据需求,我们将采用`std::deque`容器实现有限次数的撤销功能,结合智能指针避免内存泄漏。以下是完整实现方案: #### 1. 文档类声明(DrawingDoc.h) ```cpp #include <deque> #include <memory> class CDrawingDoc : public CDocument { protected: std::deque<std::unique_ptr<CDC>> m_undoList; // 撤销队列 std::deque<std::unique_ptr<CDC>> m_redoList; // 重做队列 static const int MAX_UNDO_STEPS = 10; // 最大撤销步数[^1] // 创建画布快照的深层拷贝 std::unique_ptr<CDC> CreateCanvasSnapshot(CDC& sourceDC) { std::unique_ptr<CDC> pMemDC = std::make_unique<CDC>(); CBitmap bitmap; pMemDC->CreateCompatibleDC(&sourceDC); bitmap.CreateCompatibleBitmap(&sourceDC, sourceDC.GetDeviceCaps(HORZRES), sourceDC.GetDeviceCaps(VERTRES)); pMemDC->SelectObject(&bitmap); BitBlt(pMemDC->GetSafeHdc(), 0, 0, sourceDC.GetDeviceCaps(HORZRES), sourceDC.GetDeviceCaps(VERTRES), &sourceDC, 0, 0, SRCCOPY); return pMemDC; } public: void SaveState(CDC& currentDC) { // 限制最大撤销步数 if(m_undoList.size() >= MAX_UNDO_STEPS) { m_undoList.pop_front(); } m_undoList.push_back(CreateCanvasSnapshot(currentDC)); m_redoList.clear(); // 新操作会清空重做队列 } bool Undo(CDC& targetDC) { if(m_undoList.empty()) return false; // 保存当前状态到重做队列 m_redoList.push_back(CreateCanvasSnapshot(targetDC)); if(m_redoList.size() > MAX_UNDO_STEPS) { m_redoList.pop_front(); } // 恢复撤销状态 std::unique_ptr<CDC> pPrevState = std::move(m_undoList.back()); m_undoList.pop_back(); BitBlt(targetDC.GetSafeHdc(), 0, 0, targetDC.GetDeviceCaps(HORZRES), targetDC.GetDeviceCaps(VERTRES), pPrevState->GetSafeHdc(), 0, 0, SRCCOPY); return true; } bool Redo(CDC& targetDC) { if(m_redoList.empty()) return false; // 保存当前状态到撤销队列 m_undoList.push_back(CreateCanvasSnapshot(targetDC)); if(m_undoList.size() > MAX_UNDO_STEPS) { m_undoList.pop_front(); } // 恢复重做状态 std::unique_ptr<CDC> pNextState = std::move(m_redoList.back()); m_redoList.pop_back(); BitBlt(targetDC.GetSafeHdc(), 0, 0, targetDC.GetDeviceCaps(HORZRES), targetDC.GetDeviceCaps(VERTRES), pNextState->GetSafeHdc(), 0, 0, SRCCOPY); return true; } }; ``` #### 2. 视图类实现(DrawingView.cpp) ```cpp // 鼠标释放时保存状态 void CDrawingView::OnLButtonUp(UINT nFlags, CPoint point) { CDrawingDoc* pDoc = GetDocument(); CDC* pDC = GetDC(); // 保存当前绘图状态 pDoc->SaveState(*pDC); ReleaseDC(pDC); CView::OnLButtonUp(nFlags, point); Invalidate(); } // 撤销操作 void CDrawingView::OnEditUndo() { CDrawingDoc* pDoc = GetDocument(); CDC* pDC = GetDC(); if(pDoc->Undo(*pDC)) { ReleaseDC(pDC); Invalidate(); } } // 重做操作 void CDrawingView::OnEditRedo() { CDrawingDoc* pDoc = GetDocument(); CDC* pDC = GetDC(); if(pDoc->Redo(*pDC)) { ReleaseDC(pDC); Invalidate(); } } ``` #### 3. 关键技术说明 1. **内存管理优化**: - 使用`std::unique_ptr`自动管理CDC对象生命周期 - 通过`CreateCanvasSnapshot`实现位图的深层拷贝 - 队列满时自动弹出最旧元素(FIFO策略) 2. **撤销/重做机制**: ```mermaid graph LR A[新操作] --> B[保存到Undo队列] B --> C{Undo队列是否已满?} C -->|Yes| D[移除最旧元素] C -->|No| E[保持队列] F[Undo操作] --> G[转移最新状态到Redo队列] G --> H[应用前一个状态] I[Redo操作] --> J[转移最新状态到Undo队列] J --> K[应用后一个状态] ``` 3. **性能保障**: - 位图拷贝使用`BitBlt`硬件加速 - 固定队列长度避免内存无限增长 - 采用移动语义减少拷贝开销 #### 4. 内存泄漏防护措施 1. 所有CDC对象通过智能指针管理 2. 在队列弹出元素时自动调用析构函数 3. 使用`std::deque`代替原始指针数组 4. 每次新操作自动清空重做队列 --- ### 相关问题 1. 如何在绘制过程中实现实时预览而不影响撤销堆栈? 2. 当需要保存绘图历史到文件时,应该如何序列化这些CDC对象? 3. 如果希望不同绘图工具(如直线/圆形)有独立的撤销堆栈,应该如何设计数据结构? 4. 如何在此方案基础上实现选择区域的局部撤销功能? [^1]: 内存管理优化技术可参考对象池模式 [^2]: 容器操作接口规范可参考MFC控件操作规范 [^3]: 消息处理机制需遵循MFC框架规范
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值