二分法求数组最大最小_05-最大子矩形-最大值减去最小值小于或等于num的子数组数量...

年轻即出发...

简书:https://www.jianshu.com/u/7110a2ba6f9e

知乎:https://www.zhihu.com/people/zqtao23/posts

GitHub源码:https://github.com/zqtao2332

个人网站:http://www.zqtaotao.cn/ (停止维护更新内容)

QQ交流群:606939954

咆哮怪兽一枚...嗷嗷嗷...趁你现在还有时间,尽你自己最大的努力。努力做成你最想做的那件事,成为你最想成为的那种人,过着你最想过的那种生活。也许我们始终都只是一个小人物,但这并不妨碍我们选择用什么样的方式活下去,这个世界永远比你想的要更精彩。

最后:喜欢编程,对生活充满激情



本节内容预告

实例1:最大子矩形

实例2:最大值减去最小值小于或等于num的子数组数量



实例1:最大子矩形

给定一个整型矩阵map,其中的值只有0和1两种,求其中全是1的所有矩形区域中,最大的矩形区域为1的数量。

例如:1110其中,最大的矩形区域有3个1,所以返回3。

再如:

1 0 1 1

1 1 1 1

1 1 1 0

其中,最大的矩形区域有6个1,所以返回6。

思路:

本题算法涉及到了两个知识点

1、数组压缩:将二维矩阵问题压缩为一维数组问题

2、单调栈

先理解一下这两个概念

数组压缩

通过合并矩阵中的数组的方式,将二维矩阵问题压缩为一维数组问题。

单调栈

栈的入栈顺序是递增或者递减的;两种状态可以很好的让我们求得一些特殊数据。

注意:传统单调栈针对的数据必须是不同的,所以当遇到相同数据时,使用单调栈需要进行简单处理。

单调增:快速得到任意元素左边最近比它小的数,右边最近比它小的数

如:3 4 5 2 1 6 可以快速得到 5左边最近比它小的数是4,右边最近比它小的数是2

单调减:同单调增相反。

本题解题策略

每次以第 i 行作为底,求当前构成的矩阵能得到的最大矩形。

1 0 1 1

1 1 1 1

1 1 1 0

第一次以0行为底

当前构成的矩阵
1 0 1 1

最大矩形 1 1

第二次以1行为底

当前构成的矩阵
1 0 1 1 

1 1 1 1

最大矩形 
1 1
1 1

第三次以2行为底

当前构成的矩阵
1 0 1 1 

1 1 1 1

1 1 1 0

最大矩形
1 1 1
1 1 1

这里为了直观的理解最大矩形,可以将每一个1都看做是一个小矩形,例如1 0 1 1构成的直方图

d028b097b260328f080ba292f0f17233.png

进行数组压缩时,遇到1 则在原直方图对应位置增加一个小矩形,遇到0则消除原直方图对应位置的所有矩形。例如第三次以2为底时进行压缩构成的直方图。

c928c03e34aed0a78941caf6827b6028.png

现在的问题就是给你一个数组,它代表一个直方图,求它的最大矩形面积。

对于任意的一个直方图,求解它的最大矩形,其实就是求解它的面积。

使用递增单调栈来找到两边比当前位置小的数,就可以直接来进行计算。

import java.util.Stack;

/**
 * @description: 最大矩形
 时间复杂度 O(N*M)
 * @version: 1.0
 */
public class Code_13_MaximalRectangle {

    public static int maxRecSize(int[][] map) {
        if (map == null || map.length == 0 || map[0].length == 0) {
            return 0;
        }

        int maxArea = 0; // 结果
        int[] height = new int[map[0].length]; // 数组压缩时的辅助数组,理解为矩形高度

        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[0].length; j++) { // 矩阵压缩
                height[j] = map[i][j] == 0 ? 0 : height[j] + 1; // 遇0,拆解当前位置所有的小矩形
            }

            // 求解当前行为底构成的直方图面积,即最大矩形
            maxArea = maxRecFromBottom(height);
        }
        return maxArea;
    }

    //
    public static int maxRecFromBottom(int[] height) {
        if (height == null || height.length == 0) {
            return 0;
        }
        int maxArea = 0;

        Stack<Integer> toBiggerStack = new Stack<>(); // 递增单调栈, 存储的是下标
        for (int i = 0; i < height.length; i++) {
            while (!toBiggerStack.isEmpty() && height[i] <= height[toBiggerStack.peek()]) {
                int index = toBiggerStack.pop();
                // 左边最近小于弹出的数的下标
                int leftSmallNumIndex = toBiggerStack.isEmpty() ? -1 : toBiggerStack.peek();
                int curArea = (i - leftSmallNumIndex - 1) * height[index]; // 关键i - leftSNI - 1
                maxArea = Math.max(maxArea, curArea);
            }
            toBiggerStack.push(i);
        }

        while (!toBiggerStack.isEmpty()) { // 单调栈中依然有元素未弹出
            int index = toBiggerStack.pop();
            int leftSmallNumIndex = toBiggerStack.isEmpty() ? -1 : toBiggerStack.peek();
            // 注意,此时可以肯定index后的元素一定都是大于等于height[index] 的
            // 所以本身加上后面的长度是height.length - leftSNI - 1
            // 如直方图全是递增数 4 5 6 7 ,单调栈剩余 4 5 ,弹出5时 ,左边最近比5 小的下标是0
            // 4 - 0 -1 = 3 就是 5 6 7 三列的宽度;
            int curArea = (height.length - leftSmallNumIndex - 1) * height[index];
            maxArea = Math.max(maxArea, curArea);
        }
        return maxArea;
    }

    public static void main(String[] args) {
        int[][] map = {{1, 0, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 0},};
        System.out.println(maxRecSize(map));
    }
}

实例2:最大值减去最小值小于或等于num的子数组数量

给定数组arr和整数num,共返回有多少个子数组满足如下情况:

max (arr[i.. j])-min (arr[i..j]) <= num

max(arr [i..j]) 表示子数组arr[i..j]中的最大值,

min (arr[i..j]) 表示子数组arr[i..j]中的最小值。

此题最优时间复杂度O(N)

滑动窗口

L、R更新结构,R+1 进数,L+1,出数

双端队列

实质就是双链表。维护两个双端队列来实时更新滑动窗口的最大值和最小值。

如果全局最大和全局最小都满足,那么任意滑动窗口中的子数组也是满足的。

计算子数组时,每次只计算以滑动窗口的左边界为启点的子数组数量有多少,实际就是滑动窗口的长度。

import java.util.LinkedList;

/**
 * @description: 最大值减去最小值小于或等于num的子数组数量
 * 本题需要滑动窗口和两个双端队列配合
 * 维护两个双端队列来 实时 更新滑动窗口的最大值和最小值
 */
public class Code_14_AllLessNumSubArray {

    public static int getNum(int[] arr, int num) {
        if (arr == null || arr.length == 0) {
            return 0;
        }

        // 维护两个双端队列
        LinkedList<Integer> qmin = new LinkedList<>(); // 头小尾大
        LinkedList<Integer> qmax = new LinkedList<>(); // 头大尾小

        // 滑动窗口
        int L = 0;
        int R = 0;

        int res = 0;
        while (L < arr.length) {

            while (R < arr.length) {

                // 小值队列更新维护
                while (!qmin.isEmpty() && arr[qmin.peekLast()] >= arr[R]) {
                    qmin.pollLast(); // 小值双端队列更新,遇大则弹出队列中的元素,直到合适
                }
                qmin.push(R);

                // 大值队列更新维护
                while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]){
                    qmax.pollLast();
                }
                qmax.push(R);

                if (arr[qmax.getFirst()] - arr[qmin.getFirst()] > num){
                    break; // 如果子数组的最大值-最小值都不满足条件,那么无论窗口怎样滑动都不可能满足题目要求,跳出
                }
                R++;
            }

            // 排除掉无效的下标,上一个循环 L 向右移动可能导致双端队列中的最大值和最小值失效
            if (qmin.peekFirst() == L) {
                qmin.pollFirst();
            }
            if (qmax.peekFirst() == L) {
                qmax.pollFirst();
            }

            // 滑动窗口如果是满足的 那么以 L为开头的子数组有 R-L个
            res += R - L;
            L++;
        }
        return res;
    }

    // for test
    public static int[] getRandomArray(int len) {
        if (len < 0) {
            return null;
        }
        int[] arr = new int[len];
        for (int i = 0; i < len; i++) {
            arr[i] = (int) (Math.random() * 10);
        }
        return arr;
    }

    // for test
    public static void printArray(int[] arr) {
        if (arr != null) {
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i] + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        int[] arr = getRandomArray(30);
        int num = 5;
        printArray(arr);
        System.out.println(getNum(arr, num));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值