代码详细讲解
- 函数定义与变量初始化部分:
public static int solution(int n, int[] array) {
int maxArea = 0;
// 辅助数组,用于记录每个元素左边第一个比它小的元素的索引
int[] leftLess = new int[n];
// 辅助数组,用于记录每个元素右边第一个比它小的元素的索引
int[] rightLess = new int[n];
solution 函数是解决问题的核心方法,它接收两个参数,n 表示数组 array 的长度,array 就是包含各个高度值的数组。首先定义了 maxArea 变量并初始化为 0,这个变量将用于记录最终要返回的最大矩形面积,在后续的计算过程中会不断更新它的值,以确保它始终保存着当前找到的最大面积值。
接着创建了两个长度为 n 的整数数组 leftLess 和 rightLess,它们的作用十分关键,分别用于记录数组中每个元素左边和右边第一个比它小的元素的索引。这两个辅助数组会在后续通过特定的遍历和算法来填充准确的值,而这些值对于准确计算以每个元素为最小高度的矩形面积是必不可少的。
- 第一次从左到右遍历(利用单调栈找左边第一个比它小的元素索引) :
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < n; i++) {
while (!stack.isEmpty() && array[stack.peek()] >= array[i]) {
stack.pop();
}
leftLess[i] = stack.isEmpty()? -1 : stack.peek();
stack.push(i);
}
这里创建了一个 Stack<Integer> 类型的栈 stack,它将作为实现单调栈逻辑的核心数据结构。
然后开始遍历数组 array,对于每一个元素 array[i],进入内层的 while 循环逻辑。这个 while 循环的目的是维护栈内元素的单调递增特性(也就是保证栈顶元素对应在数组中的值是最小的)。只要栈不为空,并且栈顶元素在数组中对应的值(通过 array[stack.peek()] 获取)大于等于当前元素 array[i],就把栈顶元素弹出。这样做的原因是,这些被弹出的元素,其右边遇到了更小的元素(也就是当前的 array[i]),所以它们不符合单调递增的要求了,需要被弹出。
当从 while 循环出来后,就需要确定当前元素 array[i] 的左边第一个比它小的元素索引了。如果此时栈为空,说明当前元素左边没有比它小的元素了,那么就将 leftLess[i] 设置为 -1;否则,将 leftLess[i] 设置为栈顶元素的索引(也就是找到了当前元素左边第一个比它小的元素索引),最后把当前元素的索引 i 压入栈中,以便后续继续处理后面的元素。
通过这样的一次从左到右的遍历,利用单调栈的特性,巧妙地填充了 leftLess 数组,每个元素都记录下了其左边第一个比它小的元素索引,为后续计算矩形面积打下了基础。
- 第二次从右到左遍历(利用单调栈找右边第一个比它小的元素索引) :
stack.clear();
for (int i = n - 1; i >= 0; i--) {
while (!stack.isEmpty() && array[stack.peek()] >= array[i]) {
stack.pop();
}
rightLess[i] = stack.isEmpty()? n : stack.peek();
stack.push(i);
}
在进行第二次遍历之前,首先需要清空之前使用过的栈 stack,因为这次要重新利用它来实现从右到左的单调栈操作。
然后从数组的末尾开始向前遍历(索引 i 从 n - 1 递减到 0),和第一次遍历类似,对于每个元素 array[i],同样进入内层的 while 循环。这个循环的作用依然是维护栈内元素的单调递增特性,只要栈不为空并且栈顶元素对应在数组中的值大于等于当前元素,就把栈顶元素弹出。
当从这个 while 循环出来后,要确定当前元素 array[i] 的右边第一个比它小的元素索引。如果此时栈为空,意味着当前元素右边没有比它小的元素了,那就将 rightLess[i] 设置为 n(可以理解为超出了数组的范围);否则,将 rightLess[i] 设置为栈顶元素的索引,也就是找到了当前元素右边第一个比它小的元素索引。最后,把当前元素的索引 i 压入栈中,继续处理前一个元素。
经过这次从右到左的遍历,rightLess 数组也被准确地填充了,每个元素都记录下了其右边第一个比它小的元素索引,至此,两个辅助数组 leftLess 和 rightLess 都准备好了。
- 计算每个元素为最小高度的矩形面积并取最大值:
for (int i = 0; i < n; i++) {
int width = rightLess[i] - leftLess[i] - 1;
int area = width * array[i];
maxArea = Math.max(maxArea, area);
}
return maxArea;
这部分代码是整个算法的最后一步,也是计算出最终结果的关键所在。通过遍历数组中的每一个元素,来计算以每个元素为最小高度时所能构成的矩形面积,并从中找出最大值作为最终的返回结果。
对于数组中的每个元素 array[i],首先根据已经填充好的 leftLess 和 rightLess 数组来计算矩形的宽度 width,计算公式是 rightLess[i] - leftLess[i] - 1。这个公式的含义是,通过右边第一个比它小的元素索引减去左边第一个比它小的元素索引再减去 1,就得到了以当前元素为最小高度时,能够构成矩形的宽度(也就是包含当前元素在内的相邻元素个数)。
接着,按照题目给定的计算矩形面积的公式 area = width * array[i],计算出以当前元素为最小高度的矩形面积 area。
然后,通过 maxArea = Math.max(maxArea, area) 语句,将当前计算出的矩形面积 area 和已经记录的最大矩形面积 maxArea 进行比较,如果当前面积更大,就更新 maxArea 的值,使其始终保持为到目前为止找到的最大矩形面积。
最后,遍历完整个数组后,maxArea 中保存的就是所有可能的矩形面积中的最大值了,将其作为函数的返回值返回,也就完成了整个计算过程,成功找出了对于任意 k,R(k) 的最大值。