单调栈和窗口
滑动窗口
滑动窗口是一种想象出来的数据结构:
滑动窗口有左边界L和有边界R
在数组或者字符串或者- -个序列上,记为S,窗口就是S[…R]这一部分
L往右滑意味着一个样本出了窗口,R往右滑意味着一个样本进了窗口,L和R都只能往右滑
滑动内最大值和最小值的更新结构
窗口不管L还是R滑动之后,都会让窗口呈现新状况,
如何能够更快的得到窗口当前状况下的最大值和最小值?
最好平均下来复杂度能做到O(1)
利用单调双端队列
窗口本质:哪些数会依次成为最大数的优先级
平均时间复杂度为o(1)
剑指 Offer 59 - I. 滑动窗口的最大值
假设一个固定大小为W的窗口,依次划过arr,
返回每一次滑出状况的最大值
例如,arr = [4,3,5,4,3,3,6,7], W = 3
返回:[5,5,5,4,6,7]
public static int[] getMaxWindow(int[] arr, int w) {
if (arr == null || w < 1 || arr.length < w) {
return null;
}
// qmax 窗口最大值的更新结构
// 放下标
LinkedList<Integer> qmax = new LinkedList<Integer>();
int[] res = new int[arr.length - w + 1];
int index = 0;
for (int R = 0; R < arr.length; R++) {
while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]) {
qmax.pollLast();
}
qmax.addLast(R);
if (qmax.peekFirst() == R - w) {
qmax.pollFirst();
}
if (R >= w - 1) {
res[index++] = arr[qmax.peekFirst()];
}
}
return res;
}
求达标子数组的数量
给定一个整型数组arr,和一个整数num
某个arr中的子数组sub,如果想达标,必须满足:
sub中最大值 – sub中最小值 <= num,
返回arr中达标子数组的数量
public static int num(int[] arr, int sum) {
if (arr == null || arr.length == 0 || sum < 0) {
return 0;
}
int N = arr.length;
int count = 0;
// 存放数组下标,可以用下标取值
LinkedList<Integer> maxWindow = new LinkedList<>();
LinkedList<Integer> minWindow = new LinkedList<>();
int R = 0;
// 从0位置一直往后遍历
for (int L = 0; L < N; L++) {
while (R < N) {
while (!maxWindow.isEmpty() && arr[maxWindow.peekLast()] <= arr[R]) {
maxWindow.pollLast();
}
maxWindow.addLast(R);
while (!minWindow.isEmpty() && arr[minWindow.peekLast()] >= arr[R]) {
minWindow.pollLast();
}
minWindow.addLast(R);
if (arr[maxWindow.peekFirst()] - arr[minWindow.peekFirst()] > sum) {
// 已经到了不达标的位置
break;
} else {
R++;
}
}
//从 l开始有几个达标的数组
count += R - L;
if (maxWindow.peekFirst() == L) {
maxWindow.pollFirst();
}
if (minWindow.peekFirst() == L) {
minWindow.pollFirst();
}
}
return count;
}
优化一个题
数据状况
问题本身
对于 此题,当前范围内若达标,则缩小范围必达标
若不达标,则扩大范围必不达标,可以及时break
遇到题,先看看问题本身和范围是否能够建立单调性
然后选择对应流程及流程中所要的信息
单调栈
求解一个集合中,每个元素左边离自己最近且比自己小的元素是?,右边离自己最近且比自己小的元素是?
没有重复值:
准备一个栈
栈从底到顶,严格遵守从小到大
元素入栈,若栈空,直接进栈
若非空,检查栈顶是否大于当前元素,若大于栈顶,入栈
若小于栈顶,则将栈顶出栈,
出栈时记录信息,栈顶元素左边最小最近的元素就是栈顶元素的下一个元素
栈顶元素右边最小最近的元素就是当前元素(因为是它使栈顶出栈的)
然后将当前元素入栈
循环到数组结束
进行出栈
栈顶元素左边最小最近的元素就是栈顶元素的下一个元素
栈顶元素右边最小最近的元素就是null(即没有)
// arr = [ 3, 1, 2, 3]
// 0 1 2 3
// [
// 0 : [-1, 1]
// 1 : [-1, -1]
// 2 : [ 1, -1]
// 3 : [ 2, -1]
// ]
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++) { // 当遍历到i位置的数,arr[i]
while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {
int j = stack.pop();
int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();
res[j][0] = leftLessIndex;
res[j][1] = i;
}
stack.push(i);
}
while (!stack.isEmpty()) {
int j = stack.pop();
int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();
res[j][0] = leftLessIndex;
res[j][1] = -1;
}
return res;
}
有重复值:
栈中元素为一个list
元素入栈,若栈空,直接进栈
若非空,检查栈顶是否大于当前元素,若大于栈顶list的最后一个元素,入栈
若小于栈顶,则将栈顶出栈,
出栈时记录信息,
对于出栈元素的list,遍历每个元素进行记录
若栈空,则比他们小的数就是-1(即没有)
若非空,则是出栈元素的下一个元素的list的最后一个元素
栈顶元素右边最小最近的元素就是当前元素(因为是它使栈顶出栈的)
然后将当前元素入栈,若当前元素与栈顶元素的list的最后一个元素相等,则加入到这个list中(相当于压缩操作)
循环到数组结束
进行出栈
栈顶元素左边最小最近的元素就是栈顶元素的下一个元素
栈顶元素右边最小最近的元素就是null(即没有)
public static int[][] getNearLess(int[] arr) {
int[][] res = new int[arr.length][2];
Stack<List<Integer>> stack = new Stack<>();
for (int i = 0; i < arr.length; i++) { // i -> arr[i] 进栈
while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) {
List<Integer> popIs = stack.pop();
int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);
for (Integer popi : popIs) {
res[popi][0] = leftLessIndex;
res[popi][1] = i;
}
}
if (!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]) {
stack.peek().add(Integer.valueOf(i));
} else {
ArrayList<Integer> list = new ArrayList<>();
list.add(i);
stack.push(list);
}
}
while (!stack.isEmpty()) {
List<Integer> popIs = stack.pop();
int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);
for (Integer popi : popIs) {
res[popi][0] = leftLessIndex;
res[popi][1] = -1;
}
}
return res;
}
题目三 子树组特定信息的问题
给定一个只包含正数的数组arr,arr中任何一个子数组sub,
一定都可以算出(sub累加和 )* (sub中的最小值)是什么,
那么所有子数组中,这个值最大是多少?
暴力求解:
枚举所有子树组,不断更新最大值
//暴力求解
public static int max1(int[] arr) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
for (int j = i; j < arr.length; j++) {
int minNum = Integer.MAX_VALUE;
int sum = 0;
for (int k = i; k <= j; k++) {
sum += arr[k];
minNum = Math.min(minNum, arr[k]);
}
max = Math.max(max, minNum * sum);
}
}
return max;
}
预处理技巧:
前缀和数组
前缀和数组中,0位置上是arr【0】,sum【i】=arr【i】+sum【i】
一次遍历即可生成前缀和数组
若求 L- R 上的前缀和,则 sum【R】-sum【L】 即可得到
先找出以当前元素为最小值的 最大数组。
3 4 5 6 3 2
最大数组为 3 4 5 6 ,因为如果扩大到 2 最小值就变了
所以0 位置的3 做最小值的 满足题目要求的值为 (3+4+5+6)*3
且前缀和可以去前缀和数组中找 (减少累加操作)
然后以每个元素去求解,就可以找到最大的值
对于每个元素,找到前面等于自己的位置,找到后面等于自己的位置,
满足。 前面的元素》= 当前元素 《= 当前元素
public static int max2(int[] arr) {
int size = arr.length;
int[] sums = new int[size];
sums[0] = arr[0];
for (int i = 1; i < size; i++) {
sums[i] = sums[i - 1] + arr[i];
}
int max = Integer.MIN_VALUE;
Stack<Integer> stack = new Stack<Integer>();
for (int i = 0; i < size; i++) {
while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {
int j = stack.pop();
max = Math.max(max, (stack.isEmpty() ? sums[i - 1] : (sums[i - 1] - sums[stack.peek()])) * arr[j]);
}
stack.push(i);
}
while (!stack.isEmpty()) {
int j = stack.pop();
max = Math.max(max, (stack.isEmpty() ? sums[size - 1] : (sums[size - 1] - sums[stack.peek()])) * arr[j]);
}
return max;
}
84. 柱状图中最大的矩形
给定一个非负数组arr,代表直方图
返回直方图的最大长方形面积
求以i位置为高的时候的最大长方形面积
利用单调栈很容易的得到其以i位置为高的最左边界和最右边界
求所有位置为高的长方形面积中的最大值
法一
class Solution {
public int largestRectangleArea(int[] heights) {
int res = 0;
Deque<Integer> stack = new ArrayDeque<>();
int[] new_heights = new int[heights.length + 2];
for (int i = 1; i < heights.length + 1; i++) new_heights[i] = heights[i - 1];
//System.out.println(Arrays.toString(new_heights));
for (int i = 0; i < new_heights.length; i++) {
//System.out.println(stack.toString());
while (!stack.isEmpty() && new_heights[stack.peek()] > new_heights[i]) {
int cur = stack.pop();
res = Math.max(res, (i - stack.peek() - 1) * new_heights[cur]);
}
stack.push(i);
}
return res;
}
}
作者:powcai
链接:https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/zhao-liang-bian-di-yi-ge-xiao-yu-ta-de-zhi-by-powc/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
法二:
class Solution {
public int largestRectangleArea(int[] heights) {
if (heights == null || heights.length == 0) {
return 0;
}
int maxArea = 0;
Stack<Integer> stack = new Stack<Integer>();
for(int i=0;i<heights.length;i++){
// 栈不为空时进入此分支
while(!stack.isEmpty() && heights[i] <= heights[stack.peek()] ){
// 长
int j = stack.pop();
// 最大的宽
int k = stack.isEmpty() ? -1 : stack.peek();
// i 代表使栈内最大元素弹出前的位置, k 代表 最大元素弹出的位置, 所以宽为 i -k - 1
int curArea = (i - k - 1) * heights[j];
maxArea = Math.max(maxArea, curArea);
}
// 当前元素下标进入栈
stack.push(i);
}
System.out.println(heights.length);
while (!stack.isEmpty()) {
// 长
int j = stack.pop();
// 宽
int k = stack.isEmpty() ? -1 : stack.peek();
// 进入到此while 时, 宽度 i 已经到达数组最大值 对应到坐标中。就是 数组的长度
// 为了计算宽 ,补了一个虚拟的块
int curArea = (heights.length - k - 1) * heights[j];
System.out.println(curArea);
maxArea = Math.max(maxArea, curArea);
}
return maxArea;
}
}
85. 最大矩形
相当于躺到的最大矩形,可以前用前缀和的思想建立一个二维数组,其中的元素代表当前列往后连续的1 的个数为多少
法一:
class Solution {
public int maximalRectangle(char[][] matrix) {
//f(matrix.length==1 && matrix[0].length==1 &&matrix[0][0]==1)return 1;
//if(matrix.length==1 && matrix[0].length==1 &&matrix[0][0]==0)return 0;
if(matrix.length==0 || matrix==null )return 0;
int [][] temp = new int[matrix.length][matrix[0].length];
for(int i=0;i<matrix.length;i++){
for(int j=0;j<matrix[0].length;j++){
int k = show(i,j,matrix);
temp[i][j]=k;
}
}
// for(int i=0;i<matrix.length;i++){
// for(int j=0;j<matrix[0].length;j++){
// System.out.print(temp[i][j]+" ");
// }
// System.out.println();
// }
int max=0;
for(int i=0;i<matrix[0].length;i++){
int []a = new int[matrix.length];
for(int j=0;j<a.length;j++){
a[j]=temp[j][i];
}
int res= largestRectangleArea(a);
//System.out.println(" "+res);
max = Math.max(max,res);
}
return max;
}
public int show(int i,int j,char [][]matrix){
int sum=0;
for(int k=j;k<matrix[0].length;k++){
if(matrix[i][k]-'0'==1){
sum++;
}else{
break;
}
}
return sum;
}
public int largestRectangleArea(int[] heights) {
int res = 0;
Deque<Integer> stack = new ArrayDeque<>();
int[] new_heights = new int[heights.length + 2];
for (int i = 1; i < heights.length + 1; i++) new_heights[i] = heights[i - 1];
//System.out.println(Arrays.toString(new_heights));
for (int i = 0; i < new_heights.length; i++) {
//System.out.println(stack.toString());
while (!stack.isEmpty() && new_heights[stack.peek()] > new_heights[i]) {
int cur = stack.pop();
res = Math.max(res, (i - stack.peek() - 1) * new_heights[cur]);
}
stack.push(i);
}
return res;
}
}
题解中看到的更好的解法:
public int maximalRectangle(char[][] matrix) {
if (matrix.length == 0) {
return 0;
}
int[] heights = new int[matrix[0].length];
int maxArea = 0;
for (int row = 0; row < matrix.length; row++) {
//遍历每一列,更新高度
for (int col = 0; col < matrix[0].length; col++) {
if (matrix[row][col] == '1') {
heights[col] += 1;
} else {
heights[col] = 0;
}
}
//调用上一题的解法,更新函数
maxArea = Math.max(maxArea, largestRectangleArea(heights));
}
return maxArea;
}
public int largestRectangleArea(int[] heights) {
int maxArea = 0;
Stack<Integer> stack = new Stack<>();
int p = 0;
while (p < heights.length) {
//栈空入栈
if (stack.isEmpty()) {
stack.push(p);
p++;
} else {
int top = stack.peek();
//当前高度大于栈顶,入栈
if (heights[p] >= heights[top]) {
stack.push(p);
p++;
} else {
//保存栈顶高度
int height = heights[stack.pop()];
//左边第一个小于当前柱子的下标
int leftLessMin = stack.isEmpty() ? -1 : stack.peek();
//右边第一个小于当前柱子的下标
int RightLessMin = p;
//计算面积
int area = (RightLessMin - leftLessMin - 1) * height;
maxArea = Math.max(area, maxArea);
}
}
}
while (!stack.isEmpty()) {
//保存栈顶高度
int height = heights[stack.pop()];
//左边第一个小于当前柱子的下标
int leftLessMin = stack.isEmpty() ? -1 : stack.peek();
//右边没有小于当前高度的柱子,所以赋值为数组的长度便于计算
int RightLessMin = heights.length;
int area = (RightLessMin - leftLessMin - 1) * height;
maxArea = Math.max(area, maxArea);
}
return maxArea;
}
作者:windliang
链接:https://leetcode-cn.com/problems/maximal-rectangle/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-1-8/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。