单调栈结构(及进阶) & 求最大子矩阵的大小
单调栈结构(及进阶)
题目:单调栈结构
《程序员代码面试指南》第8题 P20 难度:尉★★☆☆
该题用的暴力解法运行超时了,单调栈解法实在是没想出来,放弃了,还是太菜了┭┮﹏┭┮
题解的思路是从数组最左边到最右边遍历一次,准备一个栈stack,为空时直接放入元素位置(索引);
不为空时,必须严格保证栈顶到栈底的位置所代表的值为单调递减的:即当前位置的元素比栈顶的位置所代表的值大,则直接放入;
小,则一直弹出栈顶直到栈顶的位置所代表的值小于当前位置的元素。
遍历结束后,再次把stack剩下的元素逐一弹出。
用一个二维数组res[arr.length][2]来存储每个元素左边和右边距离它最近且比它小的值,[][0]和[][1]分别存储左和右。
每个元素在弹出时就记录下左右距离它最近最小的位置,如果其不是栈底,则弹出后的栈顶的位置为其左边距离它最近最小的位置;如果是栈底,则直接给[][0]赋值-1,即它左边没有比它更小的值。而当前遍历到的位置则为右边距离它最近最小的位置。遍历结束后,栈中剩余元素的[][1]都赋值为-1,即右边没有比它更小的值了,然后逐一弹出的过程中,[][0]的赋值规则相同。
至于为什么在栈中当前位置下面的位置就是左边离它最近最小的位置,并且当前遍历到的位置就是右边离它最近最小的位置的证明见书P23(看了下主要是用的反证法)
牛客的题解代码如下:
import java.util.Scanner;
import java.util.Stack;
public class Main {
public static int[][] getNearLessNoRepeat(int[] arr) {
int[][] res = new int[arr.length][2];
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < arr.length; i++) {
// 如果当前遍历到的数组的值小,需要弹出
while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {
int popIndex = stack.pop();
int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();
res[popIndex][0] = leftLessIndex;
res[popIndex][1] = i;
}
// 否则当前遍历到的数组的值大,压入不会破坏stack从栈顶到栈底递减的顺序
stack.push(i);
}
// 遍历结束,清算栈中剩下的位置,因为没有当前遍历到的位置,右边位置一律为-1
while (!stack.isEmpty()) {
int popIndex = stack.pop();
int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();
res[popIndex][0] = leftLessIndex;
res[popIndex][1] = -1;
}
return res;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNextInt()) {
int n = sc.nextInt();
int[] data = new int[n];
for (int i = 0; i < data.length; i++) {
data[i] = sc.nextInt();
}
int[][] result = getNearLessNoRepeat(data);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < result.length; i++) {
sb.append(result[i][0]).append(" ").append(result[i][1]).append('\n');
}
System.out.print(sb);
}
}
}
原问题中,每个元素都是不重复的,所以只需要判断大于或者小于即可。
但是进阶问题中,数组中的元素可能存在重复的了,所以思路存在一定的变化。
书上只用了个具体例子,然后直接贴上了代码。我在这里再用我弱鸡的语言组织能力概述一遍(⊙o⊙)
与原问题不同,进阶问题中的stack存放的是list列表。每个list中存放的是相同值的全部位置索引(没有相同的则为只有一个元素的list)。
入栈时(当前位置的值大于等于栈顶list中位置所代表的值),如果与栈顶的list中位置所代表的值不相同,则创建新list,并将当前遍历到的位置放入list,再将list入栈;如果相同,则直接调用栈顶list.add(i)方法将当前位置放入栈顶list中。
出栈时(xxx小于xxx,或最后出栈),左边最近最小的位置为栈中下一个list中最晚放入的位置(如果已经是栈底了还是赋值-1);右边最近最小的值仍是当前遍历到的位置(或-1)。
牛客题解代码如下:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(reader.readLine());
int[] arr = new int[N];
String[] s = reader.readLine().split(" ");
reader.close();
for (int i = 0; i < arr.length; i++) {
arr[i] = Integer.parseInt(s[i]);
}
int[][] res = new int[arr.length][2];
Stack<List<Integer>> stack = new Stack<>();
for (int i = 0; i < arr.length; i++) {
while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) {
List<Integer> popIndexs = stack.pop();
int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);
for (Integer popIndex : popIndexs) {
res[popIndex][0] = leftLessIndex;
res[popIndex][1] = i;
}
}
if (!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]) {
stack.peek().add(i);
} else {
ArrayList<Integer> list = new ArrayList<>();
list.add(i);
stack.push(list);
}
}
while (!stack.isEmpty()) {
List<Integer> popIndexs = stack.pop();
int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);
for (Integer popIndex : popIndexs) {
res[popIndex][0] = leftLessIndex;
res[popIndex][1] = -1;
}
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < res.length; i++) {
sb.append(res[i][0] + " " + res[i][1] + "\n");
}
System.out.println(sb.toString());
}
}
一定要注意IO
求最大子矩阵的大小
题目:求最大子矩阵的大小
本题算是上一题进阶的进阶了。。比上一题的进阶更上一层楼(我当然依旧没想出来),好难。
本题的核心在于将矩阵分为N行,分别以每一行为底求其最大的子矩阵。一共求N次,取其中最大的子矩阵。
具体步骤如下:首先以每一行做切割,统计以当前行作为底的情况下,每个位置往上的1的数量,使用height数组来表示。
对于height数组,可以用直方图来直观的表示。例如height={2,1,5,6,2,3}做直方图如下:

在每一个直方图中求最大矩形的面积,即对于每一根柱子扩展出去的最大矩形。这里又回归到了上一题单调栈结构,求左边和右边最近最小的位置。
对于N行都是这样去做,最后求出N行中最大的矩形,即矩形区域最大的子矩阵。
牛客题解代码如下:
/* 一行一行分析 */
import java.util.Scanner;
import java.util.Stack;
public class Main{
public static int findMaxFize(int[][] arr){
if(arr == null || arr.length == 0 || arr[0].length == 0) return 0;
int maxArea = 0;
int[] h = new int[arr[0].length];
for(int i = 0; i < arr.length; i++){
for(int j = 0; j < arr[0].length; j++){
h[j] = arr[i][j] == 0 ? 0 : h[j]+1;
}
maxArea = Math.max(maxArea, maxRecFromBottom(h));
}
return maxArea;
}
public static int maxRecFromBottom(int[] height){
if(height == null || height.length == 0) return 0;
int maxArea = 0;
Stack<Integer> stack = new Stack<Integer>();
for(int i = 0; i < height.length; i++){
while(!stack.isEmpty() && height[i] <= height[stack.peek()]){
int j = stack.pop();
int k = stack.isEmpty() ? -1 : stack.peek();
int curArea = (i - k - 1) * height[j];
maxArea = Math.max(maxArea, curArea);
}
stack.push(i);
}
while(!stack.isEmpty()){
int j = stack.pop();
int k = stack.isEmpty() ? -1 : stack.peek();
int curArea = (height.length - k - 1) * height[j];
maxArea = Math.max(maxArea, curArea);
}
return maxArea;
}
public static void main(String args[]){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int[][] arr = new int[n][m];
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++)
arr[i][j] = sc.nextInt();
}
int res = findMaxFize(arr);
System.out.println(res);
}
}
(2道题 想+做(虽然没做出来)+看题解+写博客 花了差不多2天,是真的难啊┭┮﹏┭┮)

浙公网安备 33010602011771号