前言
本人算法能力较差,且第一次写题解,请多多指教。
问题描述
小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 个位置开始遍历,逐步扩大滑动窗口。
-
更新窗口的最小高度:
-
计算当前窗口面积:
-
检查左指针是否需要移动:(本人无法辨析左指针移动的时机,故放弃)
-
重新计算面积:
3. 完成遍历
失败反思及其他算法提示
参考豆包MarsCode AI
2.单调栈(询问AI现学算法)
思路解析
-
核心目标:对于每个柱子(高度值),我们需要找到它左右两侧最近的比它矮的柱子(边界),以便计算包含它的最大矩形面积。
- 左边界:第一个高度小于当前柱子高度的柱子。
- 右边界:第一个高度小于当前柱子高度的柱子。
什么是单调栈
单调栈是一种特定顺序的栈数据结构:
- 单调递增栈:从栈底到栈顶,元素的值严格递增。
- 单调递减栈:从栈底到栈顶,元素的值严格递减。
特点
-
通过单调性快速找到左/右边界:
-
如果是单调递增栈,栈中的值始终保持递增。
-
当前值与栈顶比较时:
- 如果当前值小于栈顶值,则可以确定当前值是栈顶元素右侧最近的较小值。
-
-
效率高:
- 每个元素最多被压入和弹出一次,总的复杂度是 O(n) 。
- 可以避免不必要的重复比较。
为什么本题可以用单调栈
对于直方图中的每个柱子,其面积是由它的高度和左右边界共同决定的:
- 左边界是左侧最近的比当前柱子矮的柱子。
- 右边界是右侧最近的比当前柱子矮的柱子。
- 单调栈通过维护一个递增/递减序列,能够在遍历的过程中快速找到这些边界。
具体逻辑
-
单调递增栈适合本题:
-
栈中存储柱子的索引(为了计算宽度)。
-
每次当前柱子高度小于栈顶柱子高度时,弹出栈顶:
-
此时,栈顶柱子的左右边界被确定:
- 右边界:当前柱子的索引。
- 左边界:栈中新的栈顶柱子的索引。
-
-
-
通过这种方式,能够高效计算以每个柱子为高的矩形面积。
具体代码
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得到提示,并给出示例代码。
利用了豆包MarsCode AI学习了新思路及其算法