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

141 阅读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

问题理解

我们需要在一个数组中找到任意 k 个相邻元素所能形成的最大矩形面积。这个矩形面积的计算方式是:k 个相邻元素中的最小值乘以 k

数据结构选择

由于我们需要频繁地查找和计算最小值,使用一个能够高效维护最小值的数据结构会很有帮助。常见的选择包括:

  • 单调栈:可以用来在 O(n) 时间内找到每个元素作为最小值时,左右两边能扩展的最大范围。
  • 滑动窗口:通过滑动窗口来动态计算当前窗口内的最小值,并更新最大面积。

算法步骤

  1. 初始化:定义一个变量 max_area 来记录最大面积。

  2. 遍历所有可能的 k:从 k = 1 到 k = n(数组长度)。

  3. 对于每个 k

    • 使用滑动窗口或单调栈来计算当前 k 个相邻元素的最小值。
    • 计算当前 k 个元素形成的矩形面积,并与 max_area 比较,更新 max_area
  4. 返回 max_area

def solution(n, array):
    max_area = 0  # 初始化最大面积

    # 遍历所有可能的 k
    for k in range(1, n + 1):
        # 使用滑动窗口计算当前 k 个相邻元素的最小值
        min_height = float('inf')  # 初始化最小高度
        for i in range(n - k + 1):
            # 计算当前窗口的最小值
            current_min = min(array[i:i + k])
            # 计算当前 k 个元素形成的矩形面积
            current_area = k * current_min
            # 更新 max_area
            if current_area > max_area:
                max_area = current_area

    return max_area

if __name__ == "__main__":
    print(solution(5, [1, 2, 3, 4, 5]) == 9)
    print(solution(6, [5, 4, 3, 2, 1, 6]) == 9)
    print(solution(4, [4, 4, 4, 4]) == 16)

这个算法的时间复杂度是 O(n^2),因为我们需要遍历所有可能的 k,并对每个 k 进行一次滑动窗口操作。

成功解决!!!

1732786845245.png

个人思考及拓展

解决这道题的时候我学习了以下相关的知识点:

1. 单调栈

单调栈是一种特殊的数据结构,通常用于解决与“下一个更大/更小元素”相关的问题。在这个问题中,我们可以使用单调栈来优化计算最大矩形面积的过程。

应用场景

  • 计算最大矩形面积:单调栈可以帮助我们在 O(n) 时间内找到每个元素作为最小值时,左右两边能扩展的最大范围。
def largestRectangleArea(heights):
    stack = []
    max_area = 0
    index = 0

    while index < len(heights):
        if not stack or heights[index] >= heights[stack[-1]]:
            stack.append(index)
            index += 1
        else:
            top_of_stack = stack.pop()
            area = (heights[top_of_stack] *
                    ((index - stack[-1] - 1) if stack else index))
            max_area = max(max_area, area)

    while stack:
        top_of_stack = stack.pop()
        area = (heights[top_of_stack] *
                ((index - stack[-1] - 1) if stack else index))
        max_area = max(max_area, area)

    return max_area

2. 滑动窗口

滑动窗口是一种常见的算法技巧,用于在数组或字符串中找到满足特定条件的子数组或子字符串。

应用场景

  • 计算子数组的最小值:滑动窗口可以帮助我们在 O(n) 时间内计算固定大小的子数组的最小值。
from collections import deque

def slidingWindowMin(nums, k):
    dq = deque()
    result = []

    for i in range(len(nums)):
        while dq and nums[dq[-1]] >= nums[i]:
            dq.pop()
        dq.append(i)

        if dq[0] == i - k:
            dq.popleft()

        if i >= k - 1:
            result.append(nums[dq[0]])

    return result

3. 动态规划

动态规划是一种通过将问题分解为子问题来解决复杂问题的方法。虽然在这个问题中动态规划不是最优解,但它可以用于解决类似的问题。

应用场景

  • 计算最大子数组和:动态规划可以用于计算最大子数组和,类似于最大矩形面积的问题。
def maxSubArray(nums):
    max_sum = nums[0]
    current_sum = nums[0]

    for num in nums[1:]:
        current_sum = max(num, current_sum + num)
        max_sum = max(max_sum, current_sum)

    return max_sum

4. 分治法

分治法是一种将问题分解为更小的子问题,然后递归地解决这些子问题的方法。虽然在这个问题中分治法不是最优解,但它可以用于解决类似的问题。

应用场景

  • 计算最大子数组和:分治法可以用于计算最大子数组和,类似于最大矩形面积的问题。
def maxSubArray(nums):
    def divide_and_conquer(nums, left, right):
        if left == right:
            return nums[left]

        mid = (left + right) // 2

        left_max = divide_and_conquer(nums, left, mid)
        right_max = divide_and_conquer(nums, mid + 1, right)
        cross_max = maxCrossingSum(nums, left, mid, right)

        return max(left_max, right_max, cross_max)

    def maxCrossingSum(nums, left, mid, right):
        left_sum = float('-inf')
        current_sum = 0
        for i in range(mid, left - 1, -1):
            current_sum += nums[i]
            left_sum = max(left_sum, current_sum)

        right_sum = float('-inf')
        current_sum = 0
        for i in range(mid + 1, right + 1):
            current_sum += nums[i]
            right_sum = max(right_sum, current_sum)

        return left_sum + right_sum

    return divide_and_conquer(nums, 0, len(nums) - 1)