青训营X豆包MarsCode--最大矩形面积 题解 | 豆包MarsCode AI刷题

48 阅读7分钟

问题描述

小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

Code:

using namespace std;
int solution(int n, int array[]) {
    // Edit your code here
    int currenth,currentw;
    int sum=0;
    for(int i=0;i<n;i++){
        currenth=array[i];
        currentw=1;
        for(int j=i-1;j>=0;j--){
            if(array[j]>=currenth){
                currentw+=1;
            }else{
                break;
            }
        }
        for(int j=i+1;j<n;j++){
            if(array[j]>=currenth){
                currentw+=1;
            }else{
                break;
            }
        }
        sum=max(sum,currenth*currentw);
    }
    return sum;
}

int main() {
    // Add your test cases here
    
    std::cout << (solution(5, new int[5]{1, 2, 3, 4, 5}) == 9) << std::endl;
    return 0;
}

代码梳理与分析

功能分析

  • 目标

    • 给定一个整数数组,代表一组矩形柱的高度,找到其中能构成的最大矩形面积
    • 条件是矩形的宽度不能超过相邻柱子的范围(只能延展到高度大于等于当前柱子高度的位置)。
  • 方法

    • 对每个柱子,依次检查向左和向右能够延展的最大宽度,然后计算以该柱子为高度的矩形面积。
    • 遍历所有柱子,找出能够形成的最大矩形面积。

核心逻辑

代码分为三个部分

  1. 遍历数组中的每个柱子 array[i],将其视为矩形的高度。

  2. 向左和向右扩展

    • 左侧扩展:找到左边第一个比当前柱子矮的位置,计算左侧能覆盖的宽度。
    • 右侧扩展:找到右边第一个比当前柱子矮的位置,计算右侧能覆盖的宽度。
  3. 计算矩形面积,并更新最大值。

代码细节
  1. 初始化

    int currenth, currentw;
    int sum = 0;
    
    • currenth 表示当前柱子的高度。
    • currentw 表示以当前柱子为高度的矩形宽度。
    • sum 用来存储矩形面积的最大值。
  2. 遍历柱子

    for (int i = 0; i < n; i++) {
        currenth = array[i]; // 当前柱子的高度
        currentw = 1;        // 初始宽度为1(仅包含当前柱子)
    
  3. 向左扩展

    for (int j = i - 1; j >= 0; j--) {
        if (array[j] >= currenth) {
            currentw += 1; // 左侧柱子满足条件,宽度增加
        } else {
            break; // 左侧柱子不满足条件,停止扩展
        }
    }
    
  4. 向右扩展

    for (int j = i + 1; j < n; j++) {
        if (array[j] >= currenth) {
            currentw += 1; // 右侧柱子满足条件,宽度增加
        } else {
            break; // 右侧柱子不满足条件,停止扩展
        }
    }
    
  5. 计算面积并更新最大值

    sum = max(sum, currenth * currentw);
    
  6. 返回最大矩形面积

    return sum;
    

示例执行流程

测试用例

输入:

n = 5, array = {1, 2, 3, 4, 5};

流程分析

  • i = 0(柱子高度为1):

    • 左侧无扩展(宽度=0)。
    • 右侧扩展到索引4(宽度=4)。
    • 面积:1 * 5 = 5
  • i = 1(柱子高度为2):

    • 左侧扩展到索引0(宽度=1)。
    • 右侧扩展到索引4(宽度=3)。
    • 面积:2 * 4 = 8
  • i = 2(柱子高度为3):

    • 左侧扩展到索引0(宽度=2)。
    • 右侧扩展到索引4(宽度=2)。
    • 面积:3 * 3 = 9
  • i = 3(柱子高度为4):

    • 左侧扩展到索引0(宽度=3)。
    • 右侧扩展到索引4(宽度=1)。
    • 面积:4 * 3 = 8
  • i = 4(柱子高度为5):

    • 左侧扩展到索引0(宽度=4)。
    • 右侧无扩展(宽度=0)。
    • 面积:5 * 1 = 5

最大矩形面积为 9


代码性能分析

  1. 时间复杂度

    • 外层循环遍历所有柱子,时间复杂度为 O(n)
    • 每次循环中,左右各一次扩展操作,每次最多扩展到数组的两端,最坏情况为 O(n)
    • 总时间复杂度为 O(n^2),在数组较大时性能较差。
  2. 空间复杂度

    • 仅使用几个变量存储临时值,空间复杂度为 O(1)

优点与缺点

  • 优点

    • 简单直观,容易理解。
    • 不需要额外的数据结构。
  • 缺点

    • 时间复杂度较高,无法应对较大的数据量。
    • 每次扩展操作的范围是重复计算,效率低。

优化与改进

可以利用单调栈优化时间复杂度至 O(n)

  • 单调栈存储柱子的索引,维护一个递增序列。
  • 在遇到当前柱子高度小于栈顶柱子时,计算栈顶柱子作为高度的最大矩形面积,并更新最大值。
  • 通过栈的操作避免了重复遍历。

总结心得

  1. 思路清晰:当前代码是基于暴力解法,按照题目要求逐步构建解决方案。

  2. 可优化性

    • 使用单调栈优化扩展过程,可以显著提高效率。
  3. 算法设计原则

    • 应尽量减少重复计算,特别是在需要处理较大数据量时,时间复杂度的优化尤为重要。
  4. 学习体会

    • 通过这个题目可以熟悉矩形面积问题的基本解决思路,同时了解如何分析和优化算法。

经验总结:最大矩形面积问题

通过实现和分析这道算法题,以下是一些个人总结的经验:


1. 解决问题需要清晰的思路

  • 在面对复杂问题时,将问题拆分为可管理的子任务是关键。

    • 本题中,矩形面积 = 高度 × 宽度。明确宽度和高度的确定条件后,问题的解决变得更加直观。
  • 暴力解法提供了一个基础解决方案,是理解问题的起点。

  • 在暴力解法的基础上,可以发现问题的低效部分,例如重复计算,从而寻找优化的方向。


2. 算法优化的本质:减少冗余计算

  • 优化算法的目标是减少不必要的重复操作。

    • 本题中,暴力解法需要从当前柱子分别向左右扩展,耗费 O(n^2) 时间。
    • 单调栈优化则通过预先维护索引信息,避免重复扩展,直接将时间复杂度降低到 O(n)

3. 数据结构的选择是算法优化的关键

  • 单调栈是本题优化的核心:

    • 栈中存储的索引不仅有序,还包含了隐式的范围信息,省去了逐个扩展的过程。
    • 利用栈的“后进先出”特性,可以快速计算矩形的宽度。
  • 合适的数据结构往往能够有效提升算法性能。例如:

    • 单调栈适用于需要动态维护顺序的场景。
    • 哈希表适合快速查找。
    • 优先队列则适合处理动态最大或最小值的问题。

4. 测试案例设计的重要性

  • 充分的测试可以验证算法的正确性和鲁棒性。测试用例应包含:

    • 基本情况:如数组中只有一个柱子。
    • 特殊情况:如所有柱子高度相等,或高度为零。
    • 边界情况:如数组为空或只有一个元素。
  • 本题中的测试用例:

    cpp
    复制代码
    solution({1, 2, 3, 4, 5});    // 单调递增
    solution({5, 4, 3, 4, 5});    // 中间最低
    solution({2, 1, 5, 6, 2, 3}); // 混合数据
    

5. 暴力解法和优化解法并存的价值

  • 暴力解法

    • 虽然效率低,但实现简单,适合快速验证算法是否正确。
    • 对于小规模问题,这种方法的时间消耗是可以接受的。
  • 优化解法

    • 提升性能,适合大规模数据的处理。
    • 通过引入额外的数据结构(如栈),可以充分发挥时间复杂度上的优势。