问题描述
小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)时间内找到每个元素作为最小值时,左右两边能扩展的最大范围。 - 滑动窗口:通过滑动窗口来动态计算当前窗口内的最小值,并更新最大面积。
算法步骤
-
初始化:定义一个变量
max_area来记录最大面积。 -
遍历所有可能的
k:从k = 1到k = n(数组长度)。 -
对于每个
k:- 使用滑动窗口或单调栈来计算当前
k个相邻元素的最小值。 - 计算当前
k个元素形成的矩形面积,并与max_area比较,更新max_area。
- 使用滑动窗口或单调栈来计算当前
-
返回
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 进行一次滑动窗口操作。
成功解决!!!
个人思考及拓展
解决这道题的时候我学习了以下相关的知识点:
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)