LeetCode 栈

LC20. 有效的括号

  1. class Solution {
  2. public boolean isValid(String s) {
  3. //使用栈
  4. Stack<Character> stack = new Stack<>();
  5. for (int i = 0; i < s.length(); i ++){
  6. if (s.charAt(i) == '(')
  7. stack.push(s.charAt(i));
  8. if (s.charAt(i) == '[')
  9. stack.push(s.charAt(i));
  10. if (s.charAt(i) == '{')
  11. stack.push(s.charAt(i));
  12. if ( s.charAt(i) == ')'){
  13. if (!stack.empty() && stack.peek() == '(')
  14. stack.pop();
  15. else return false;
  16. }
  17. if (s.charAt(i) == ']'){
  18. if (!stack.empty() && stack.peek() == '[')
  19. stack.pop();
  20. else return false;
  21. }
  22. if (s.charAt(i) == '}'){
  23. if (!stack.empty() && stack.peek() == '{')
  24. stack.pop();
  25. else return false;
  26. }
  27. }
  28. if (stack.empty())
  29. return true;
  30. return false;
  31. }
  32. }

ArrayList存放最小值 LC155. 最小栈

思路

使用ArrayList存放到目前为止,栈中的最小值。

每次push入栈的时候,把栈的最小值插入ArrayList中

pop出栈的时候,删除对应的ArrayList最后一个值

getMin返回ArrayList的最后一个值

  1. class MinStack {
  2. ArrayList<Integer> list;
  3. Stack<Integer> s;
  4. int idx; int minv = Integer.MAX_VALUE;
  5. public MinStack() {
  6. list = new ArrayList();
  7. s = new Stack<Integer>();
  8. idx = -1;
  9. }
  10. public void push(int x) {
  11. s.push(x);
  12. minv = Math.min(x, idx >= 0 ? list.get(idx) : Integer.MAX_VALUE);
  13. list.add(minv);
  14. idx++;
  15. }
  16. public void pop() {
  17. list.remove(idx--);
  18. s.pop();
  19. }
  20. public int top() {
  21. return s.peek();
  22. }
  23. public int min() {
  24. return list.get(idx);
  25. }
  26. }

后序遍历入栈150. 逆波兰表达式求值

表达式树

  1. 表达式中所有数字节点都是叶子节点
  2. 所有操作数节点都是内部节点
  3. 后缀表达式 通过后序遍历树的方式创建
  4. 中缀表达式 通过中序遍历树的方式创建
  5. 我们发现,进行后序遍历的时候,如果根节点被创建了,那么它的左右子节点都被创建了。

通过这些性质,我们可以通过给定的后续表达式:

  1. 每当遇到一个点,就加入栈中
  2. 每当我们遇到一个操作数(根节点),就可以把它的左右子节点(都pop出栈,计算这一个子树表达式的值)
  1. class Solution {
  2. String[] oper = new String[]{"+", "*", "/", "-"};
  3. public int evalRPN(String[] tokens) {
  4. //后缀转中缀表达式
  5. Stack<Integer> s = new Stack<Integer>();
  6. HashSet<String> op = new HashSet<String>();
  7. for (int i = 0; i < 4; i ++)
  8. op.add(oper[i]);
  9. int n = tokens.length;
  10. for (int i = 0; i < n; i ++){
  11. if (!op.contains(tokens[i])){
  12. s.push(Integer.valueOf(tokens[i]));
  13. }
  14. else{
  15. Integer res = new Integer(0);
  16. Integer n1 = s.pop();
  17. Integer n2 = s.pop();
  18. String op1 = tokens[i];
  19. if (op1.equals("+")) res = n1 + n2;
  20. else if (op1.equals("-")) res = n2 - n1;
  21. else if (op1.equals("*")) res = n1 * n2;
  22. else res = n2 / n1;
  23. s.push(res);
  24. // System.out.println(s);
  25. }
  26. }
  27. return s.peek();
  28. }
  29. }

缓存栈232. 用栈实现队列

思路

  1. 建立两个栈 一个作为缓存栈s2,一个是实际操作栈s1
  2. push : 就对s1进行push和empty的操作
  3. pop : 需要s1把除了栈顶的所有元素 都pop,并放入s2中,再pop s1的栈顶元素,最后把s2元素压回s1
  4. peek : 需要s1把除了栈顶的所有元素 都pop,并放入s2中,返回s1的栈顶元素t,最后把s2元素压回s1,
  5. empty s1.isEmpty
  6. 均摊操作还是o(1)的
  1. class MyQueue {
  2. Stack<Integer> s1, s2;
  3. /** Initialize your data structure here. */
  4. public MyQueue() {
  5. s1 = new Stack<Integer>();
  6. s2 = new Stack<Integer>();
  7. }
  8. /** Push element x to the back of queue. */
  9. public void push(int x) {
  10. s1.push(x);
  11. }
  12. /** Removes the element from in front of queue and returns that element. */
  13. public int pop() {
  14. while (s1.size() > 1){
  15. s2.push(s1.pop());
  16. }
  17. int t = s1.pop();
  18. while (!s2.isEmpty())
  19. s1.push(s2.pop());
  20. return t;
  21. }
  22. /** Get the front element. */
  23. public int peek() {
  24. while (s1.size() > 1){
  25. s2.push(s1.pop());
  26. }
  27. int t = s1.peek();
  28. while (!s2.isEmpty())
  29. s1.push(s2.pop());
  30. return t;
  31. }
  32. /** Returns whether the queue is empty. */
  33. public boolean empty() {
  34. return s1.isEmpty();
  35. }
  36. }
  37. /**
  38. * Your MyQueue object will be instantiated and called as such:
  39. * MyQueue obj = new MyQueue();
  40. * obj.push(x);
  41. * int param_2 = obj.pop();
  42. * int param_3 = obj.peek();
  43. * boolean param_4 = obj.empty();
  44. */

后入先出 388. 文件的最长绝对路径

自己的思路

  1. 遍历的时候计算\t的个数,如果是递增的,就把\t后的字符串长度加入Stack中
  2. 如果不是递增的,就统计长度 = stack中字符的长度 + stack.size() - 1,更新maxv pop掉所有元素
  3. 最后要再计算一次stack中的字符长度(最后一个字符)
  4. 最后最长路径要加上最开始的根目录字符
  5. Stack存放 根目录到父目录的长度和父目录的级数
  6. t就是目录级数 0是根目录

由于我没有判断最后一个是否为文件,也没有加入根目录,当最长路径没有文件或者只出现根目录的时候就会报错

  1. class Solution {
  2. public int lengthLongestPath(String input) {
  3. Stack<List<Integer>> s = new Stack<List<Integer>>();
  4. int n = input.length();
  5. int t = 0, st = -1, ed = -1, root = 0;
  6. int maxv = Integer.MIN_VALUE;
  7. for (int i = 0; i < n; i ++){
  8. if (root == 0 && input.charAt(i) == '\n') root = i + 1;
  9. if (input.charAt(i) == '\t'){
  10. t ++;
  11. st = i + 1;
  12. continue;
  13. }
  14. if (i - 1 >= 0 && (t > 0 && input.charAt(i) == '\n') || i == n - 1){
  15. if (i == n - 1) ed = i;
  16. else ed = i - 1;
  17. }
  18. if (st != -1 && ed != -1){
  19. int len = ed - st + 1;
  20. if (s.isEmpty() || t > s.peek().get(1)){
  21. List<Integer> list= new ArrayList<Integer>();
  22. list.add(len);
  23. list.add(t);
  24. s.push(list);
  25. }
  26. else{
  27. maxv = Math.max(maxv, total(s));
  28. List<Integer> list= new ArrayList<Integer>();
  29. list.add(len);
  30. list.add(t);
  31. s.push(list);
  32. }
  33. t = 0;
  34. st = -1;
  35. ed = -1;
  36. }
  37. }
  38. return Math.max(maxv, total(s)) + root;
  39. }
  40. public int total( Stack<List<Integer>> s){
  41. int totalnum = 0;
  42. int num = s.size();
  43. while (!s.isEmpty()){
  44. List<Integer> tmp = s.pop();
  45. totalnum += tmp.get(0);
  46. }
  47. return totalnum + num - 1;
  48. }
  49. }

yxc的思路

  1. 遍历的时候遇到\t 更新级数t,更新下一次坐标

    1. 如果级数t >= stack.size() ,把\t后的字符串长度加入Stack中,更新当前路径的长度sum
    2. 如果t < stack.size(),说明当前文件不是这个目录下的, 回溯,更新为当前文件路径sum,当最后一个是文件的时候,计算最终答案是sum + stack.size() - 1 ,否则为上一个路径的sum

可以明显看到yxc的思路非常的简化,代码量少了一半

具体原因是考虑到了回溯的情况,当前级数 <= 栈中存放的级数,就回溯,否则加入栈并计算最值

class Solution {
    public int lengthLongestPath(String input) {
        Stack<Integer> stack = new Stack<Integer>(); 
        int n = input.length();
        int maxv = 0, sum = 0;
        for (int st = 0; st < n;){
            int t = 0;
            while (st < n && input.charAt(st) == '\t'){
                t++; st++;
            }
            int ed = st;
            while (ed < n && input.charAt(ed) != '\n'){
                ed ++;
            }
            int len = ed - st;
            while (stack.size() > t){
                sum -= stack.peek(); stack.pop(); 
            }
            stack.push(len); sum += stack.peek();
            if (input.substring(st, ed).contains("."))
                maxv = Math.max(maxv, sum + stack.size() - 1);
            st = ed + 1;
        }
        return maxv;
    }
}

单调栈

单调栈只是一种结果的表现形式

怎么想到使用单调栈呢?如何给单调栈定义呢?

有点类似二分

找到第一个和当前数不一样的数字。

形象地来说,找到第一个某一方面比当前角色强/弱的角色

单调栈中的栈首就是这个角色。

整个栈都满足这一性质,但栈首是最接近不满足的。

普通单调栈 每日温度

class Solution {
    public int[] dailyTemperatures(int[] t) {
        /**
            单调栈 反向第一个>当前数的下标
         */
        int[] res = new int[t.length];
        Stack<Integer> stack = new Stack<>();
        for (int i = t.length - 1; i >= 0; i --){
            while (!stack.isEmpty() && t[stack.peek()] <= t[i]) stack.pop();
            if (!stack.isEmpty()) res[i] = stack.peek() - i;
            stack.push(i);
        }
        return res;
    }
}

特殊意义单调栈456. 132 模式

o(n^3)暴搜的方法,超时

class Solution {
    public boolean find132pattern(int[] nums) {
        int n = nums.length;
        boolean flag = false;
        for (int i = 0; i < n; i ++){
            for (int j = i + 1; j < n; j++){
                int k = j + 1;
                while (k < n){
                   if (nums[i] < nums[k] && nums[k] < nums[j]) {
                        flag = true; return flag;
                    } 
                    k++;
                }
            }
        }
        return flag;
    }
}

单调栈思路o (n)

对于每个numi 找到numk,其中numk满足这样一个性质:

存在j < k, 有 numj > numk

我们只要遍历每个i,找到对于numi来说,最满足性质的numk(我们使用right来存放)即可

那么我们怎么来求这个right呢?

这里有一个性质

我们从后向前遍历(保证单调递减栈中元素的下标单调递减),把所有不满足性质的numk存入栈中,最终栈是单调的

假设这个性质成立

栈 - 图1

最后再把numi存入栈,保证每次操作存入的数都是递减的,这样整体栈单调递减

class Solution {
    public boolean find132pattern(int[] nums) {
        Stack<Integer> s = new Stack<Integer>();
        int right = Integer.MIN_VALUE;
        for (int i = nums.length - 1; i >= 0; i --){
            if (nums[i] < right) return true;
            while (!s.isEmpty() && nums[i] > s.peek()){
                right = Math.max(s.peek(), right);
                s.pop();
            }
            s.push(nums[i]);
        }
        return false;
    }
}

典型单调栈 + 循环数组503. 下一个更大元素 II

思路

这是一个典型的单调栈问题,我们只需要开两倍数组就可以了

要注意我将答案覆盖到原数组,因此需要将原数组的元素保存一下

class Solution {
    public int[] nextGreaterElements(int[] nums) {
        //典型的单调栈问题,不过增加了一个循环数组
        //对于循环数组来说,我们在区间dp中遇到过,开两倍的空间就可以了
        Stack<Integer> s = new Stack<Integer>();
        int n = nums.length;
        if (n == 0) return nums;
        int[] nums2 = new int[2 * n];
        int[] res = new int[n];
        for (int i = 0; i < n; i++){
            nums2[i] = nums[i];
            nums2[i + n] = nums[i];
        }
        for (int i = 2 * n - 1; i >= 0; i--){
            while (!s.isEmpty() && s.peek() <= nums2[i]) s.pop();
            int tmp = nums2[i];
            if (s.isEmpty()) nums2[i] = -1;
            else nums2[i] = s.peek();
            s.push(tmp);
        }
        for (int i = 0; i < n; i ++) res[i] = nums2[i];
        return res;
    }
}

栈中数据的意义 42. 栈的压入、弹出序列

我的思路

压入序列T, 弹出序列S, 当前数为t、s

  • 将t压入栈,对当前栈顶元素t进行判断

    • 如果 t == s,将t弹出栈, 两个序列都遍历下一个数
    • 否则T遍历下一个数
  • 将后一个数t压入栈,循环操作

  • 如果遍历结束

正确思路

  • 将t压入栈,循环当前栈顶元素t进行判断

    • 如果 t == s,将t弹出栈, S序列遍历下一个数
  • 遍历结束后,如果栈为空代表匹配,否则不匹配

能看出来,差别就是t、s判断的时候,我没有添加循环判断。循环判断保证当前序列的一致性,因此需要循环判断

本质上是对栈的认知不够,当t压入栈中,栈中存放的是t为首的弹出序列。因此要循环整个栈。

class Solution {
    public boolean isPopOrder(int [] pushV,int [] popV) {
        Stack<Integer> s = new Stack<Integer>();
        int l1 = pushV.length, l2 = popV.length;
        if (l1 == 0 && l2 == 0) return true;
        if (l1 != l2) return false;

        for (int i = 0, j = 0; i < l1; i++){
            s.push(pushV[i]);
            while (s.size() > 0 && s.peek() == popV[j]) {
                s.pop();
                j ++; 
            }
        }
        if (s.size() == 0) return true;
        else return false;
    }
}

特殊单调栈42. 接雨水 - 力扣

class Solution {
    public int trap(int[] height) {
        //单调栈,每次先判断当前height[i] > height[stack.peek()]
        //如果stack的第二个栈顶left还存在,说明可能形成凹槽,计算宽度 i - left -1
        // 高度 min(h[i], h[left]) - h[peek]
        //计算完之后相当于把坑填平了,
        //peek pop出去,接着把i存入栈中
        int num = height.length;
        if (num == 0) return 0;
        Stack<Integer> s = new Stack<>();
        s.push(0); int res = 0;
        for (int i = 1; i < num; i ++){
            while (s.size() > 1 && height[i] > height[s.peek()]){
                int bottom = s.pop(), left = s.peek();
                int width = i - left - 1, h = Math.min(height[i], height[left]) - height[bottom];
                if (width > 0 && h > 0)
                    res += width * h; 
            }
            s.push(i);
        }
        return res;
    }
}