单调栈

作者: Phoebe_Liu | 来源:发表于2021-11-24 19:46 被阅读0次
  1. 316.去除重复字母
    (https://leetcode-cn.com/problems/remove-duplicate-letters)
    image
String removeDuplicateLetters(String s) {
    Stack<Character> stk = new Stack<>();

    // 维护一个计数器记录字符串中字符的数量
    // 因为输入为 ASCII 字符,大小 256 够用了
    int[] count = new int[256];
    for (int i = 0; i < s.length(); i++) {
        count[s.charAt(i)]++;
    }

    boolean[] inStack = new boolean[256];
    for (char c : s.toCharArray()) {
        // 每遍历过一个字符,都将对应的计数减一
        count[c]--;

        if (inStack[c]) continue;

        while (!stk.isEmpty() && stk.peek() > c) {
            // 若之后不存在栈顶元素了,则停止 pop
            if (count[stk.peek()] == 0) {
                break;
            }
            // 若之后还有,则可以 pop
            inStack[stk.pop()] = false;
        }
        stk.push(c);
        inStack[c] = true;
    }

    StringBuilder sb = new StringBuilder();
    while (!stk.empty()) {
        sb.append(stk.pop());
    }
    return sb.reverse().toString();
}

这里也可以想到为什么要用「栈」这种数据结构,因为先进后出的结构允许我们立即操作刚插入的字符,如果用「队列」的话肯定是做不到的。

  1. 移掉K位数字


    截屏2021-11-24 下午7.03.05.png

我们可以用一个栈维护当前的答案序列,栈中的元素代表截止到当前位置,删除不超过 kk 次个数字后,所能得到的最小整数。

class Solution {
    public String removeKdigits(String num, int k) {
        if(k == 0) return num;
        if(k == num.length()) return "0";
        Stack<Character> s = new Stack<>();
        int index = 0;
        while(index < num.length()){
            while(k > 0 && s.size() > 0 && num.charAt(index) < s.peek()){
                s.pop();
                k--;
            }
            //前导0情况的处理
            if(s.size() == 0 && num.charAt(index) == '0') {
                index++;
                continue;
            }
            s.push(num.charAt(index));
            index++;
        }
        String ans = "";
        while(k > 0){
            s.pop();
            k--;
        }
        if(s.size() == 0) return "0";
        while(!s.isEmpty()) ans += s.pop();
        return new StringBuffer(ans).reverse().toString();
    }
}
  1. 拼接最大数
    和第一道题类似,只不过这一次是两个数组,而不是一个,并且是求最大数。

最大最小是无关紧要的,关键在于是两个数组,并且要求从两个数组选取的元素个数加起来一共是 k。

然而在一个数组中取 k 个数字,并保持其最小(或者最大),我们已经会了(利用单调栈)。但是如果问题扩展到两个,会有什么变化呢?

实际上,问题本质并没有发生变化。 假设我们从 nums1 中取了 k1 个,从 num2 中取了 k2 个,其中 k1 + k2 = k。而 k1 和 k2 这 两个子问题我们是会解决的。由于这两个子问题是相互独立的,因此我们只需要分别求解,然后将结果合并即可。

假如 k1 和 k2 个数字,已经取出来了。那么剩下要做的就是将这个长度分别为 k1 和 k2 的数字,合并成一个长度为 k 的数组合并成一个最大的数组。

以题目的 nums1 = [3, 4, 6, 5] nums2 = [9, 1, 2, 5, 8, 3] k = 5 为例。 假如我们从 num1 中取出 1 个数字,那么就要从 nums2 中取出 4 个数字。

运用第一题的方法,我们计算出应该取 nums1 的 [6],并取 nums2 的 [9,5,8,3]。 如何将 [6] 和 [9,5,8,3],使得数字尽可能大,并且保持相对位置不变呢?

截屏2021-11-24 下午7.20.21.png
class Solution {
    public int[] maxNumber(int[] nums1, int[] nums2, int k) {
        int m = nums1.length;
        int n = nums2.length;
        int[] ans = new int[k];
        int len = Math.min(k, m);
        for (int i=Math.max(0, k-n); i<=len; i++) {
            int[] sub1 = maxKArray(nums1, i);
            int[] sub2 = maxKArray(nums2, k-i);
            int[] array = combineArray(sub1, sub2, k);
            for (int j=0; j<k; j++) {
                if (array[j] == ans[j]) continue;
                if (array[j] > ans[j])  ans = array;
                break;
            }
        }
        return ans;
    }
    
    public int[] maxKArray(int[] nums, int k) {
        if (k == 0) return new int[0];
        
        int[] res = new int[k];
        int cursor = -1;
        for (int i=0; i<nums.length; i++) {
            while (cursor>=0 && nums[i]>res[cursor] && nums.length-i>k-cursor-1) {
                cursor--;
            }
            if (cursor < k-1)
                res[++cursor] = nums[i];
        }
        return res;
    }
    
    public int[] combineArray(int[] nums1, int[] nums2, int k) {
        int[] res = new int[k];
        int i = 0;
        int i1 = 0;
        int i2 = 0;
        while (i1 < nums1.length && i2 < nums2.length)
            res[i++] = deepCompare(nums1, nums2, i1, i2)? nums1[i1++] : nums2[i2++];
        while (i1 < nums1.length)
            res[i++] = nums1[i1++];
        while (i2 < nums2.length)
            res[i++] = nums2[i2++];
        return res;
    }

    public boolean deepCompare(int[] nums1, int[] nums2, int i1, int i2) {
        while (i1 < nums1.length && i2 < nums2.length) {
            if (nums1[i1] == nums2[i2]) {
                i1++;
                i2++;
                continue;
            }
            return nums1[i1] > nums2[i2];
        }
        return i1 < nums1.length;
    }
}
  1. Largest Rectangle in Histogram

我们归纳一下枚举「高」的方法:

首先我们枚举某一根柱子 i 作为高h=heights[i];

随后我们需要进行向左右两边扩展,使得扩展到的柱子的高度均不小于 h。换句话说,我们需要找到左右两侧最近的高度小于 h 的柱子,这样这两根柱子之间(不包括其本身)的所有柱子高度均不小于 h,并且就是 ii 能够扩展到的最远范围。
这样我们在枚举到第 i 根柱子的时候,就可以先把所有高度大于等于 height[i] 的 j 值全部移除,剩下的 j 值中高度最高的即为答案。在这之后,我们将 i 放入数据结构中,开始接下来的枚举。此时,我们需要使用的数据结构也就呼之欲出了,它就是栈。

栈中存放了 j 值。从栈底到栈顶,j 的值严格单调递增,同时对应的高度值也严格单调递增;

当我们枚举到第 ii 根柱子时,我们从栈顶不断地移除 height[j]≥height[i] 的 j 值。在移除完毕后,栈顶的 j 值就一定满足 height[j]<height[i],此时 j 就是 i 左侧且最近的小于其高度的柱子。

这里会有一种特殊情况。如果我们移除了栈中所有的 j 值,那就说明 i 左侧所有柱子的高度都大于 height[i],那么我们可以认为 i 左侧且最近的小于其高度的柱子在位置 j=−1,它是一根「虚拟」的、高度无限低的柱子。这样的定义不会对我们的答案产生任何的影响,我们也称这根「虚拟」的柱子为「哨兵」。
我们再将 i 放入栈顶。

栈中存放的元素具有单调性,这就是经典的数据结构「单调栈」了。

复杂度分析:
时间复杂度:O(N)。
空间复杂度:O(N)。

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        int[] left = new int[n];
        int[] right = new int[n];
        
        Deque<Integer> mono_stack = new ArrayDeque<Integer>();
        for (int i = 0; i < n; ++i) {
            while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                mono_stack.pop();
            }
            left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
            mono_stack.push(i);
        }

        mono_stack.clear();
        for (int i = n - 1; i >= 0; --i) {
            while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                mono_stack.pop();
            }
            right[i] = (mono_stack.isEmpty() ? n : mono_stack.peek());
            mono_stack.push(i);
        }
        
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
        }
        return ans;
    }
}

  1. Maximal Rectangle
    我们首先计算出矩阵的每个元素的左边连续 1的数量,使用二维数组 left 记录,其中left[i][j] 为矩阵第 i行第 j 列元素的左边连续 1的数量。
class Solution {
   public int maximalRectangle(char[][] matrix) {
       int m = matrix.length;
       if (m == 0) {
           return 0;
       }
       int n = matrix[0].length;
       int[][] left = new int[m][n];

       for (int i = 0; i < m; i++) {
           for (int j = 0; j < n; j++) {
               if (matrix[i][j] == '1') {
                   left[i][j] = (j == 0 ? 0 : left[i][j - 1]) + 1;
               }
           }
       }

       int ret = 0;
       for (int j = 0; j < n; j++) { // 对于每一列,使用基于柱状图的方法
           int[] up = new int[m];
           int[] down = new int[m];

           Deque<Integer> stack = new LinkedList<Integer>();
           for (int i = 0; i < m; i++) {
               while (!stack.isEmpty() && left[stack.peek()][j] >= left[i][j]) {
                   stack.pop();
               }
               up[i] = stack.isEmpty() ? -1 : stack.peek();
               stack.push(i);
           }
           stack.clear();
           for (int i = m - 1; i >= 0; i--) {
               while (!stack.isEmpty() && left[stack.peek()][j] >= left[i][j]) {
                   stack.pop();
               }
               down[i] = stack.isEmpty() ? m : stack.peek();
               stack.push(i);
           }

           for (int i = 0; i < m; i++) {
               int height = down[i] - up[i] - 1;
               int area = height * left[i][j];
               ret = Math.max(ret, area);
           }
       }
       return ret;
   }
}

相关文章

  • 单调栈和应用实践

    什么是单调栈 单调栈的定义:单调栈即满足单调性的栈结构。与单调队列相比,其只在一端进行进出。 如何使用单调栈 单调...

  • 1.单调栈

    一、单调栈定义 单调栈(monotone-stack)是指栈内元素(栈底到栈顶)都是(严格)单调递增或者单调递减的...

  • 单调栈 2020-06-12(未经允许,禁止转载)

    1.单调栈 指栈内元素保持单调性的栈结构,分为单调增栈(栈底到栈顶元素递增)和单调减栈(栈底到栈顶元素递减) 2....

  • LeetCode刷题指北----单调栈

    1.什么是单调栈?有什么好处? 定义: 单调栈就是栈内元素递增或者单调递减的栈,并且只能在栈顶操作。单调栈的维护是...

  • C语言之单调栈

    单调栈 What 单调栈也是栈的一种,满足先进后出的原则,另外,单调栈中的元素是有序的,分为两种 单调递增栈:单调...

  • 腾讯笔试--逛街

    主要的知识点是:单调栈,该题牢牢记得:栈中记录当前楼能看到的元素 单调栈是单调递增栈,栈顶是最小值单调栈存的是能看...

  • 题解——单调栈

    单调栈题解 单调栈结构 牛客链接 方法:单调栈 算法 这里维护一个单调递增栈,可以找到比当前元素要小的元约定:当前...

  • 单调栈

    leetcode - 42. 接雨水单调栈即元素严格单调递增或单调递减的栈,只需要在元素入栈的时候保持栈的单调性即...

  • Java版算法模版总结(2)

    本次233酱介绍下单调栈、单调队列、并查集、KMP算法,欢迎交流指正~ 单调栈 「单调栈」首先是一种基于栈的数据结...

  • 单调栈

    对应力扣题目-739. 每日温度 什么是单调栈? 单调递增栈,从栈底到栈顶依次递增(单调非递减栈:允许有相等) 单...

网友评论

      本文标题:单调栈

      本文链接:https://www.haomeiwen.com/subject/ygwntrtx.html