演算法之單調棧

it_was發表於2020-10-11

難度困難:triumph:

給定一個僅包含 0 和 1 的二維二進位制矩陣,找出只包含 1 的最大矩形,並返回其面積。
示例:
輸入:
[
[“1”,”0”,”1”,”0”,”0”],
[“1”,”0”,”1“,”1“,”1“],
[“1”,”1”,”1“,”1“,”1“],
[“1”,”0”,”0”,”1”,”0”]
]
輸出: 6

給定陣列arr,其中arr[i]表示1為底,高為arr[i]的矩形,則陣列arr可以表示一個柱狀圖。這裡求該柱狀圖所包含的矩形中,面積最大的矩形。
例如:int arr[] = {2, 4, 7, 3, 5, 4, 6, 9, 4};
則該陣列可表示如下的柱狀圖:
演算法之單調棧
在該柱狀圖中,面積最大矩形是8 * 3 = 24;

思路:這道題用單調棧解決會很方便

單調棧:棧的一種,其內元素按照從小到大(或者從大到小)遞增(遞減)的棧:fire: :fire: :fire:

針對陣列{2, 4, 7, 3, 5, 4, 6, 9, 4}

  1. 首先構造一個單調遞增棧。

    為什麼選取單調遞增棧呢?因為此題需要求最大面積,如果選用單調遞減棧,當一個元素出棧的時候,其兩邊並不是該矩形所能擴的最遠位置!當選用遞增棧的時候,因為兩邊高度都比他小,所以該矩形能擴的區域是確定的。

  2. 不斷入棧,碰到比棧頂元素小的元素,迴圈讓其出棧,並計算出棧元素所能形成的最大矩形區域。如下圖所示,2,4,7 按照遞增順序入棧(下標),此時由於 3 比棧頂元素 7 小,所以棧頂需要彈出
    演算法之單調棧
    此時 7 的右邊界已經找到,即當前的 下標3。而 7 的左邊界就是其棧的下一個元素 4 的下標1,由此可以計算出 7 所能擴出的最大矩形面積 即 7 * (3 - 1 - 1) = 7
    演算法之單調棧

  3. 元素 7 彈出之後,由於 4 也比 3 大,所以 4 也需要彈出,過程同上,如下圖,4 此時的面積為:
    4 * (3 - 0 - 1)
    演算法之單調棧

  4. 當遍歷完成之後,棧內所剩元素如下,我們可以發現每個元素都能向右擴到陣列的最右端,左邊界就是棧內下一個元素的位置!!!
    演算法之單調棧

話不多說,上程式碼:

public int getMaxArea(int[] nums) {
        Stack<Integer> stack = new Stack<>(); //設定一個從小到大單調棧
        int left = 0;
        int max = 0;
        for (int i = 0; i < nums.length; i++) {
            while (!stack.isEmpty() && nums[i] < nums[stack.peek()]) {
                int cur = stack.pop();
                left = stack.isEmpty() ? 0 : stack.peek() + 1; 
                //左邊界為 stack.peek() + 1,但要注意空棧的處理
                max = Math.max(max, nums[cur] * (i - left));
                //右邊界為當前 i !
            }
            stack.push(i);
        }
        while (!stack.isEmpty()) {
            int cur = stack.pop();
            left = stack.isEmpty() ? 0 : stack.peek() + 1;
            max = Math.max(max, nums[cur] * (nums.length - left));
            //右邊界此時為陣列的最右端
        }
        System.out.println(max);
        return max;
    }

仔細一想,其實求二維陣列中包含1的最大矩形的面積與上面的過程大同小異:在遍歷每一層的時候,將上面的1累加,作為當前的高度,但要注意一旦該層有0,則是無法形成具有高度的柱子的!
程式碼如下:

public int maximalRectangle(char[][] matrix) {
        if(matrix==null||matrix.length==0){
            return 0;
        }
        int[] ddx = new int[matrix[0].length];
        int max =0;
        for(int i=0;i<matrix.length;i++){
            for(int j=0;j<matrix[i].length;j++){
                ddx[j]=(matrix[i][j] == '0' )? 0:ddx[j]+1;
            }

            max=Math.max(max,getMaxArea(ddx));
        }
        return max;
    }

小Q在週末的時候和他的小夥伴來到大城市逛街,一條步行街上有很多高樓,共有n座高樓排成一行。
小Q從第一棟一直走到了最後一棟,小Q從來沒有看到過這麼多高樓,所以他想知道他在每棟樓的位置處能看到多少棟樓呢?(當前面的樓的高度大於等於後面的樓時,後面的樓將被擋住)
輸入描述:
輸入第一行將包含一個數字n,表示樓的棟數,接下來的一行將包含n個數字wi(1<=i<=n),代表一棟樓的高度。
1<=n<=100000;
1<=wi<=100000;
輸出描述:
輸出一行,包含空格分隔的m個數字vi,分別代表小Q在第i棟樓的時候能看到的樓的數量。

思路

此題用單調棧就非常清晰,先正向遍歷一遍,看每個樓左邊能看到樓的數量,再反向遍歷一遍,求出每個樓右邊看到的樓的數量,最後相加即可!

public static void getSeeCounts(int[] height) {
        Stack<Integer> stack = new Stack<>(); //維護一個從大到小單調棧,因為當前位置的樓只能看到左邊或右邊遞增的樓,看不到遞減的樓
        int len = height.length;
        int[] left_Count = new int[len]; //記錄每個樓左邊看到樓的數量
        int[] right_Count = new int[len];//記錄每個樓右邊看到樓的數量
        for (int i = 0; i < height.length; i++) {
            left_Count[i] = stack.size();
            while(!stack.isEmpty() && height[i] > height[stack.peek()]){
                stack.pop();
            }
            stack.push(i);
        }
        stack.clear();
        for (int i = len - 1; i >= 0; i--) {
            right_Count[i] = stack.size(); 
            //每個樓能看到的就是當前遞增樓的數量,即單調棧的容量
            while(!stack.isEmpty() && height[i] > height[stack.peek()]){
                stack.pop();
            }
            stack.push(i);
        }
        for(int i = 0;i<len;i++){
            left_Count[i]+=right_Count[i] + 1; //加一是因為還有自己本身
        }

        System.out.println(Arrays.toString(left_Count));
        System.out.println(Arrays.toString(right_Count));
    }
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章