難度困難
給定一個僅包含 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;
思路:這道題用單調棧解決會很方便
單調棧:棧的一種,其內元素按照從小到大(或者從大到小)遞增(遞減)的棧
針對陣列{2, 4, 7, 3, 5, 4, 6, 9, 4}
首先構造一個單調遞增棧。
為什麼選取單調遞增棧呢?因為此題需要求最大面積,如果選用單調遞減棧,當一個元素出棧的時候,其兩邊並不是該矩形所能擴的最遠位置!當選用遞增棧的時候,因為兩邊高度都比他小,所以該矩形能擴的區域是確定的。
不斷入棧,碰到比棧頂元素小的元素,迴圈讓其出棧,並計算出棧元素所能形成的最大矩形區域。如下圖所示,2,4,7 按照遞增順序入棧(下標),此時由於 3 比棧頂元素 7 小,所以棧頂需要彈出
此時 7 的右邊界已經找到,即當前的下標3
。而 7 的左邊界就是其棧的下一個元素 4 的下標1
,由此可以計算出 7 所能擴出的最大矩形面積 即7 * (3 - 1 - 1) = 7
元素 7 彈出之後,由於 4 也比 3 大,所以 4 也需要彈出,過程同上,如下圖,4 此時的面積為:
4 * (3 - 0 - 1)
當遍歷完成之後,棧內所剩元素如下,我們可以發現每個元素都能向右擴到陣列的最右端,左邊界就是棧內下一個元素的位置!!!
話不多說,上程式碼:
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 協議》,轉載必須註明作者和本文連結