1. 题目
2. 解法
有两种常见的方法:
- 一种是使用数组和滑动窗口,
- 另一种是使用单调栈。
数组和滑动窗口
思路
使用数组和滑动窗口的方法的思路是:
- 对于每个元素,如果我们知道它左右两边第一个比它小的元素的下标,那么我们就可以计算出以它为高度的最大矩形的宽度,即右边界减去左边界再减一。
- 为了找到每个元素的左右边界,我们可以使用两个数组left和right来存储它们。对于left数组,我们从左往右遍历原数组,用一个临时变量tmp来记录当前元素的左边界,初始值为i-1。然后我们比较tmp和i位置的元素,如果tmp位置的元素大于等于i位置的元素,说明tmp不是i的左边界,我们需要更新tmp为left[tmp],即tmp位置元素的左边界。这样我们就可以跳过一些不必要的比较,直到找到第一个比i位置元素小的元素或者越界。最后我们把tmp赋值给left[i]。对于right数组,我们从右往左遍历原数组,用类似的方法来更新right[i]。
- 在得到了left和right数组后,我们再遍历一遍原数组,对于每个元素,计算以它为高度的最大矩形面积,并更新最大值。
代码逻辑
使用数组和滑动窗口的方法的代码逻辑是:
- 定义一个函数largestRectangleArea,接受一个整型数组heights作为参数,返回一个整型值作为结果。
- 判断heights的长度是否为0或1,如果是,则直接返回heights[0]或0。
- 定义两个整型数组left和right,分别用来存储每个元素的左右边界,初始化left[0]为-1,right[len-1]为len。
- 定义一个整型变量len,用来存储heights的长度。
- 用一个for循环从1到len-1遍历heights数组,定义一个整型变量tmp,初始化为i-1。用一个while循环判断tmp是否大于等于0且heights[tmp]是否大于等于heights[i],如果是,则更新tmp为left[tmp]。最后把tmp赋值给left[i]。
- 用一个for循环从len-2到0遍历heights数组,定义一个整型变量tmp,初始化为i+1。用一个while循环判断tmp是否小于len且heights[tmp]是否大于等于heights[i],如果是,则更新tmp为right[tmp]。最后把tmp赋值给right[i]。
- 定义一个整型变量maxArea,用来存储最大矩形面积,初始化为0。
- 用一个for循环从0到len-1遍历heights数组,定义一个整型变量width,用来存储以当前元素为高度的最大矩形的宽度,计算方法为right[i]-left[i]-1。定义一个整型变量area,用来存储以当前元素为高度的最大矩形面积,计算方法为width*heights[i]。用maxArea和area比较并更新maxArea。
- 返回maxArea作为结果。
具体实现
public int largestRectangleArea(int[] heights) { int len = heights.length; if (len == 0) { return 0; } if (len == 1) { return heights[0]; } // 使用数组来模拟栈,避免频繁的出栈入栈操作 int[] left = new int[len]; int[] right = new int[len]; // 初始化 left[0] = -1; right[len - 1] = len; // 遍历数组,计算每个元素左边第一个比它小的元素的下标 for (int i = 1; i < len; i++) { int tmp = i - 1; while (tmp >= 0 && heights[tmp] >= heights[i]) { tmp = left[tmp]; } left[i] = tmp; } // 遍历数组,计算每个元素右边第一个比它小的元素的下标 for (int i = len - 2; i >= 0; i--) { int tmp = i + 1; while (tmp < len && heights[tmp] >= heights[i]) { tmp = right[tmp]; } right[i] = tmp; } // 遍历数组,计算每个元素能构成的最大矩形面积 int maxArea = 0; for (int i = 0; i < len; i++) { int width = right[i] - left[i] - 1; int area = width * heights[i]; maxArea = Math.max(maxArea, area); } return maxArea; }
使用单调栈
思路
使用单调栈的方法的思路是:
- 为了找到每个元素的左右边界,我们可以使用一个单调递增的栈来存储元素的下标。这样,栈顶元素就是当前元素的左边界,而当遇到一个比栈顶元素小的元素时,就说明找到了栈顶元素的右边界。
- 我们遍历数组,对于每个元素,我们先判断栈是否为空或者当前元素是否大于等于栈顶元素,如果是,则将当前元素的下标入栈。如果不是,则说明当前元素是栈顶元素的右边界,我们就弹出栈顶元素,并计算以它为高度的最大矩形面积。如果此时栈为空,说明栈顶元素没有左边界,否则栈顶元素就是它的左边界。我们重复这个过程,直到栈为空或者当前元素大于等于栈顶元素。
- 在遍历完数组后,如果栈不为空,说明还有一些元素没有找到右边界,我们可以假设它们的右边界是数组的长度。我们再次弹出栈顶元素,并计算以它为高度的最大矩形面积。如果此时栈为空,说明栈顶元素没有左边界,否则栈顶元素就是它的左边界。我们重复这个过程,直到栈为空。
代码逻辑
使用单调栈的方法的代码逻辑是:
- 定义一个函数largestRectangleArea,接受一个整型数组heights作为参数,返回一个整型值作为结果。
- 判断heights的长度是否为0或1,如果是,则直接返回heights[0]或0。
- 定义一个整型变量len,用来存储heights的长度。
- 定义一个整型变量maxArea,用来存储最大矩形面积,初始化为0。
- 定义一个整型栈stack,用来存储元素的下标,保持栈内元素对应的高度单调递增。
- 用一个for循环从0到len-1遍历heights数组,定义一个整型变量height,用来存储当前元素的高度。
- 用一个while循环判断栈是否为空或者height是否小于栈顶元素对应的高度,如果是,则定义一个整型变量curHeight,用来存储弹出的栈顶元素对应的高度。定义一个整型变量leftBoundary,用来存储弹出的栈顶元素的左边界,如果此时栈为空,则赋值为-1,否则赋值为栈顶元素。定义一个整型变量rightBoundary,用来存储弹出的栈顶元素的右边界,赋值为当前遍历到的下标i。定义一个整型变量width,用来存储以弹出的栈顶元素为高度的最大矩形的宽度,计算方法为rightBoundary-leftBoundary-1。定义一个整型变量area,用来存储以弹出的栈顶元素为高度的最大矩形面积,计算方法为width*curHeight。用maxArea和area比较并更新maxArea。
- 在while循环结束后,将当前遍历到的下标i入栈。
- 在for循环结束后,如果栈不为空,则说明还有一些元素没有找到右边界。我们再次用一个while循环
具体实现
public int largestRectangleArea(int[] heights) { int len = heights.length; if (len == 0) { return 0; } if (len == 1) { return heights[0]; } // 使用栈来存储元素下标,保持栈内元素对应的高度单调递增 Stack<Integer> stack = new Stack<>(); // 遍历数组,对于每个元素,找到它左边和右边第一个比它小的元素的下标 int maxArea = 0; for (int i = 0; i < len; i++) { int height = heights[i]; // 如果栈不为空且当前高度小于栈顶高度,说明找到了右边界 while (!stack.isEmpty() && height < heights[stack.peek()]) { // 弹出栈顶元素,计算以它为高度的最大矩形面积 int curHeight = heights[stack.pop()]; // 如果栈为空,说明左边界不存在,否则左边界就是栈顶元素 int leftBoundary = stack.isEmpty() ? -1 : stack.peek(); // 右边界就是当前元素 int rightBoundary = i; // 计算宽度和面积 int width = rightBoundary - leftBoundary - 1; int area = width * curHeight; maxArea = Math.max(maxArea, area); } // 将当前元素下标入栈 stack.push(i); } // 如果遍历完数组后栈不为空,说明还有一些元素没有找到右边界 while (!stack.isEmpty()) { // 弹出栈顶元素,计算以它为高度的最大矩形面积 int curHeight = heights[stack.pop()]; // 如果栈为空,说明左边界不存在,否则左边界就是栈顶元素 int leftBoundary = stack.isEmpty() ? -1 : stack.peek(); // 右边界就是数组的长度 int rightBoundary = len; // 计算宽度和面积 int width = rightBoundary - leftBoundary - 1; int area = width * curHeight; maxArea = Math.max(maxArea, area); } return maxArea; }
3. 总结