1. 程式人生 > 其它 >Hard | LeetCode 84. 柱狀圖中最大的矩形 | 單調棧

Hard | LeetCode 84. 柱狀圖中最大的矩形 | 單調棧

84. 柱狀圖中最大的矩形

給定 n 個非負整數,用來表示柱狀圖中各個柱子的高度。每個柱子彼此相鄰,且寬度為 1 。

求在該柱狀圖中,能夠勾勒出來的矩形的最大面積。

以上是柱狀圖的示例,其中每個柱子的寬度為 1,給定的高度為 [2,1,5,6,2,3]

圖中陰影部分為所能勾勒出的最大矩形面積,其面積為 10 個單位。

示例:

輸入: [2,1,5,6,2,3]
輸出: 10

解題思路

方法一:暴力

列舉左右邊界的方法。先列舉左邊界, 然後從左邊界開始, 列舉右邊界, 在列舉右邊界過程記錄左右邊界之間的最小的高度。然後在所有的列舉邊界之內找到最大的值即可。
時間複雜度:O(N^2)
空間複雜度:O(1)

public int largestRectangleArea1(int[] heights) {
    int n = heights.length;
    int ans = 0;
    // 列舉左邊界
    for (int left = 0; left < n; ++left) {
        int minHeight = Integer.MAX_VALUE;
        // 列舉右邊界
        for (int right = left; right < n; ++right) {
            // 找到left至right這一段的最小的高度
            minHeight = Math.min(minHeight, heights[right]);
            // 計算面積
            ans = Math.max(ans, (right - left + 1) * minHeight);
        }
    }
    return ans;
}

列舉所有的高度, 然後在此高度向左右延伸, 知道遇到比當前高度小的柱子就停止延伸。

public int largestRectangleArea(int[] heights) {
    int n = heights.length;
    int ans = 0;
    for (int mid = 0; mid < n; ++mid) {
        // 列舉高
        int height = heights[mid];
        int left = mid, right = mid;
        // 確定左右邊界, 左右邊界的值恰好大於等於當前高度
        while (left - 1 >= 0 && heights[left - 1] >= height) {
            --left;
        }
        while (right + 1 < n && heights[right + 1] >= height) {
            ++right;
        }
        // 計算面積
        ans = Math.max(ans, (right - left + 1) * height);
    }
    return ans;
}

方法二: 單調棧

方法一向左右延伸的方法實際上可以使用單調棧實現。向左右延伸就是要找到比當前高度低的第一個高度。藉助一個遞增單調棧即可。在當前高度進棧時, 將棧內比當前高度高的元素全部出棧, 那麼棧頂元素就是比當前高度低的第一個高度。就這樣就找到當前高度的左邊界。
同理用這種方法可以找到當前高度的右邊界。
時間複雜度:O(N)
空間複雜度:O(N)

public int largestRectangleArea3(int[] heights) {
    ArrayDeque<Integer> stack = new ArrayDeque<>();
    int[] left = new int[heights.length];
    int[] right = new int[heights.length];
    // 先通過單調棧找所有高度的左邊界
    for (int i = 0; i < heights.length; i++) {
        while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
            stack.pop();
        }
        left[i] = (stack.isEmpty()) ? -1 : stack.peek();
        stack.push(i);
    }
    stack.clear();
    // 再通過單調棧找所有高度的右邊界
    for (int i = heights.length - 1; i >= 0; i--) {
        while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
            stack.pop();
        }
        right[i] = (stack.isEmpty()) ? heights.length : stack.peek();
        stack.push(i);
    }
    // 根據左右邊界, 找到最大的面積
    int area = 0;
    for (int i = 0; i < heights.length; i++) {
        area = Math.max(area, (right[i] - left[i] - 1) * heights[i]);
    }
    return area;
}

其實在通過單調棧找左邊界的時候, 右邊界也同時找到了。

就是在棧內的元素出棧時, 代表棧內的元素比當前的高度大。反過來的意思是, 當前元素是要出棧的那個元素的右邊的第一個元素。所以在出棧的過程, 可以找到所有出棧元素的右邊界, 就是當前i。

而最後遍歷完, 棧中還保留著的元素的右邊界就是N。因為棧內元素右邊已經沒有比它小的元素了。

public int largestRectangleArea(int[] heights) {
    ArrayDeque<Integer> stack = new ArrayDeque<>();
    int[] left = new int[heights.length];
    int[] right = new int[heights.length];
    Arrays.fill(right, heights.length);
    // 通過單調棧找到當前高度的左邊界
    for (int i = 0; i < heights.length; i++) {
        while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
            // 將出棧元素的右邊界設定為當前位置
            right[stack.pop()] = i;
        }
        left[i] = (stack.isEmpty()) ? -1 : stack.peek();
        stack.push(i);
    }
    stack.clear();
    // // 根據左右邊界, 找到最大的面積
    int area = 0;
    for (int i = 0; i < heights.length; i++) {
        area = Math.max(area, (right[i] - left[i] - 1) * heights[i]);
    }
    return area;
}