刷题实践:最大矩形面积问题 | 豆包MarsCode AI 刷题

146 阅读5分钟

前言

本人算法能力较差,且第一次写题解,请多多指教。


问题描述

小S最近在分析一个数组 h1,h2,...,hNh1​,h2​,...,hN​,数组的每个元素代表某种高度。小S对这些高度感兴趣的是,当我们选取任意 kk 个相邻元素时,如何计算它们所能形成的最大矩形面积。

对于 kk 个相邻的元素,我们定义其矩形的最大面积为:

R(k)=k×min(h[i],h[i+1],...,h[i+k−1])R(k)=k×min(h[i],h[i+1],...,h[i+k−1])

即,R(k)R(k) 的值为这 kk 个相邻元素中的最小值乘以 kk。现在,小S希望你能帮他找出对于任意 kk,R(k)R(k) 的最大值。


测试样例

样例1:

输入:n = 5, array = [1, 2, 3, 4, 5]
输出:9

样例2:

输入:n = 6, array = [5, 4, 3, 2, 1, 6]
输出:9

样例3:

输入:n = 4, array = [4, 4, 4, 4]
输出:16


1.滑动窗口(本人用此方法失败)

思路解析

通过分析R(K)的定义与题目要求,我们可以发现,本题可以抽象为:求子数组中最小值与与宽度乘积的最大值。即可以使用滑动窗口的方法解决。

算法步骤

动态调整窗口的左边界和右边界,以计算窗口内的最大矩形面积 R(k)。

1. 初始化变量

  • 定义滑动窗口的左右边界:

    • l(左指针):初始化为 0。
    • r(右指针):表示当前窗口的右边界,遍历数组。
  • 其他变量:

    • height:记录当前窗口的最小高度,初始化为数组的第一个元素。
    • ans:记录全局最大矩形面积,初始化为第一个元素的值。

2. 右指针开始遍历数组

让右指针 r 从数组的第 1 个位置开始遍历,逐步扩大滑动窗口。

  1. 更新窗口的最小高度

  2. 计算当前窗口面积

  3. 检查左指针是否需要移动:(本人无法辨析左指针移动的时机,故放弃)

  4. 重新计算面积

3. 完成遍历


失败反思及其他算法提示

参考豆包MarsCode AI

image.png


2.单调栈(询问AI现学算法)

思路解析
  1. 核心目标:对于每个柱子(高度值),我们需要找到它左右两侧最近的比它矮的柱子(边界),以便计算包含它的最大矩形面积。

    • 左边界:第一个高度小于当前柱子高度的柱子。
    • 右边界:第一个高度小于当前柱子高度的柱子。

什么是单调栈

单调栈是一种特定顺序的栈数据结构

  • 单调递增栈:从栈底到栈顶,元素的值严格递增。
  • 单调递减栈:从栈底到栈顶,元素的值严格递减。
特点
  1. 通过单调性快速找到左/右边界

    • 如果是单调递增栈,栈中的值始终保持递增。

    • 当前值与栈顶比较时:

      • 如果当前值小于栈顶值,则可以确定当前值是栈顶元素右侧最近的较小值。
  2. 效率高

    • 每个元素最多被压入和弹出一次,总的复杂度是 O(n)
    • 可以避免不必要的重复比较。

为什么本题可以用单调栈

对于直方图中的每个柱子,其面积是由它的高度和左右边界共同决定的

  1. 左边界是左侧最近的比当前柱子矮的柱子
  2. 右边界是右侧最近的比当前柱子矮的柱子
  3. 单调栈通过维护一个递增/递减序列,能够在遍历的过程中快速找到这些边界。
具体逻辑
  1. 单调递增栈适合本题:

    • 栈中存储柱子的索引(为了计算宽度)。

    • 每次当前柱子高度小于栈顶柱子高度时,弹出栈顶:

      • 此时,栈顶柱子的左右边界被确定:

        • 右边界:当前柱子的索引。
        • 左边界:栈中新的栈顶柱子的索引。
  2. 通过这种方式,能够高效计算以每个柱子为高的矩形面积。

具体代码

public class Main {
    public static int solution(int n, int[] array) {
        
        // 使用栈存储柱状图的索引
        Stack<Integer> stack = new Stack<>();
        int maxArea = 0; // 最大矩形面积
        int index = 0; // 当前索引

        // 遍历所有柱状图
        while (index < n) {
            // 如果当前柱子的高度大于栈顶柱子的高度,压入栈
            if (stack.isEmpty() || array[index] >= array[stack.peek()]) {
                stack.push(index);
                index++;
            } else {
                // 弹出栈顶元素
                int topOfStack = stack.pop();
                // 计算以弹出柱子为高度的矩形面积
                int area = array[topOfStack] *
                        (stack.isEmpty() ? index : index - stack.peek() - 1);
                // 更新最大矩形面积
                maxArea = Math.max(maxArea, area);
            }
        }

        // 栈中剩余的柱子处理
        while (!stack.isEmpty()) {
            int topOfStack = stack.pop();
            int area = array[topOfStack] *
                    (stack.isEmpty() ? index : index - stack.peek() - 1);
            maxArea = Math.max(maxArea, area);
        }

        return maxArea;
    }

    public static void main(String[] args) {
        // Add your test cases here
        
        System.out.println(solution(5, new int[]{1, 2, 3, 4, 5}) == 9);
    }
}


刷题小结

  • 滑动窗口的失败原因

    • 边界条件难以维护。
    • 复杂度高。
  • 单调栈的优越性

    • 高效解决左右边界问题。
    • 思路清晰,适合矩形面积相关问题。

豆包MarsCode AI 刷题的使用

在本题目中,我意识到要使用滑动窗口算法,但具体算法实现尚有模糊之处,我使用豆包Marscode AI辅助完成代码实现。

此处我不好把握移动左窗口的时机,通过询问AI得到提示,并给出示例代码。

image.png

利用了豆包MarsCode AI学习了新思路及其算法

image.png