• 算法:(六)栈


    6.1 栈的应用

    面试题36:后缀表达式

    题目:后缀表达式是一种算数表达式,它的操作符在操作数的后面,输入一个用字符串数组表示的后缀表达式,请输出该后缀表达式计算的结果。

    注:关于中缀表达式转后缀表达式可见:复杂四则运算的中缀表达式转后缀表达式(逆波兰表达式)Java版

    public int calculate(ArrayList<String> rpn){
        Stack<Integer> numStack = new Stack<>();
        for (String s : rpn) {
            if(!isOperator(s)){
                numStack.push(Integer.parseInt(s));
            } else{
                int num1 = numStack.pop();
                int num2 = numStack.pop();
                switch (s){
                    // 计算的结果重新入栈
                    case "+":
                        numStack.push(num2 + num1);
                        continue;
                    case "-":
                        numStack.push(num2 - num1);
                        continue;
                    case "*":
                        numStack.push(num2 * num1);
                        continue;
                    case "/":
                        numStack.push(num2 / num1);
                        continue;
                }
            }
        }
        return numStack.pop();
    }
    
    public boolean isOperator(String str){
        // 判断是否是操作符
        return "+".equals(str) || "-".equals(str) || "*".equals(str) || "/".equals(str);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 表达式四则运算,包含中缀表达式转后缀表达式:
    public int calculate(String exp){
        // 获取逆波兰表达式
        ArrayList<String> rpn = getRPN(exp);
        Stack<Integer> numStack = new Stack<>();
        for (String s : rpn) {
            if(!isOperator(s)){
                numStack.push(Integer.parseInt(s));
            } else{
                int num1 = numStack.pop();
                int num2 = numStack.pop();
                int temp = 0;
                switch (s){
                    case "+":
                        temp = num2 + num1;
                        break;
                    case "-":
                        temp = num2 - num1;
                        break;
                    case "*":
                        temp = num2 * num1;
                        break;
                    case "/":
                        temp = num2 / num1;
                        break;
                }
                numStack.push(temp);
            }
        }
        return numStack.pop();
    }
    
    private ArrayList<String> getRPN(String exp) {
        // 中缀表达式转后缀表达式
        ArrayList<String> lex = lexical(exp);
        ArrayList<String> result = new ArrayList<>(lex.size());
        Stack<String> stack = new Stack<>();
        for (String s : lex) {
            if(!isOperator(s)){
                result.add(s);
            }else{
                if("(".equals(s)){
                    stack.push(s);
                } else if(")".equals(s)){
                    // 弹出"(" 和 ")"之间所有的运算符,但没弹出"("
                    while(!"(".equals(stack.peek())){
                        result.add(stack.pop());
                    }
                    // 弹出"("
                    stack.pop();
                } else if(!stack.isEmpty() && priority(s) > priority(stack.peek())){
                    // 比较运算符优先级
                    stack.push(s);
                } else{
                    // 优先级低,则将之前优先级高的运算符全部弹出,先计算
                    while(!stack.isEmpty() && priority(s) <= priority(stack.peek())){
                        result.add(stack.pop());
                    }
                    stack.push(s);
                }
            }
        }
        while(!stack.isEmpty()){
            result.add(stack.pop());
        }
        return result;
    }
    
    public boolean isOperator(String str){
        // 判断是否是操作符
        return "+".equals(str) || "-".equals(str) || "*".equals(str) || "/".equals(str) || "(".equals(str) || ")".equals(str);
    }
    
    public int priority(String str){
        // 获得运算符优先级
        // 左右括号入栈后优先级降到最低
        switch (str){
            case "*":
            case "/":
                return 2;
            case "+":
            case "-":
                return 1;
            default:
                return 0;
        }
    }
    
    private ArrayList<String> lexical(String exp) {
        // 词法分析器
        // 表达式预处理,去处空格,括号统一转变为小括号
        exp = exp.replace(" ", "").replace("{", "(").replace("}", ")")
            .replace("[", "(").replace("]", ")");
        ArrayList<String> result = new ArrayList<>();
        String token = "";
        for(int i = 0; i < exp.length(); i++){
            char c = exp.charAt(i);
            token += c;
            if(Character.isDigit(c)){
                if(i == exp.length() - 1 || !Character.isDigit(exp.charAt(i + 1))){
                    result.add(token);
                    token = "";
                }
            } else if(c == '-' && (i == 0 || exp.charAt(i - 1) == '(')){
                continue;
            } else {
                result.add(c + "");
                token = "";
            }
        }
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111

    面试题37:小行星碰撞

    题目:输入一个表示小行星的数组,数组中每个数字的绝对值表示小行星的大小,数字的正负号表示小行星的运动方向,正号表示小行星向右飞行,负号表示小行星向左飞行。乳沟两颗小行星相撞,那么体积较小的小行星将会爆炸消失。体积大的小行星不收影响。如果相撞的两颗小行星大小相同,那么他们都会爆炸消失。飞行方向相同的小行星永远不会相撞。求最终剩下的小行星。

    例如:有6颗小行星[4, 5, -6, 4, 8, -5],飞行图如下所示,它们相撞之后最终剩下三颗小行星[-6, 4, 8]

    public int[] asteroidCollision(int[] asteroids){
        Stack<Integer> stack = new Stack<>();
        for (int asteroid : asteroids) {
            // 小行星的值大于0,则直接入栈
            if(asteroid >= 0){
                stack.push(asteroid);
            } else {
                // 小行星的值小于0,需要和栈顶元素进行比较
                // 栈为空或者栈顶元素小于0,则可以直接入栈
                // 栈顶元素大于0,则会发生碰撞,需要解决碰撞
                while(!stack.isEmpty() && (stack.peek() > 0 && stack.peek() + asteroid < 0)){
                    stack.pop();
                }
                if(!stack.isEmpty() && (stack.peek() > 0 && stack.peek() + asteroid == 0)){
                    stack.pop();
                    continue;
                }
                if(!stack.isEmpty() && (stack.peek() > 0 && stack.peek() + asteroid > 0)){
                    continue;
                }
                stack.push(asteroid);
            }
        }
        return stack.stream().mapToInt(i -> i).toArray();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    面试题38:每日温度

    题目:输入一个数组,它的每个数字是某天的温度,请计算每天需要等几天才会出现更高的温度。例如,如果输入数组[35, 31, 33, 36, 34],那么输出为[3, 1, 1, 0, 0]。由于第一天的温度是35°C,要等三天才会出现更高的温度36°C,因此对应的输出为3。第四天的温度是36°C,后面没有更高的温度,它对应的输出为0。其他的以此类推。

    注:此题本质上就是找数组中下一个比它大的数,并计算下标差

    public int[] findNextMore(int[] dailyTemperature){
        // 用栈,栈中压入当天的天数下标
        // 用栈顶的元素那天的温度与当天温度作比较,决定是否弹栈,若弹栈,通过下标计算隔了多少天,存入结果集
        Stack<Integer> stack = new Stack<>();
        int[] result = new int[dailyTemperature.length];
        for (int i = 0; i < dailyTemperature.length; i++) {
            int temperature = dailyTemperature[i];
            while(!stack.isEmpty() && temperature > dailyTemperature[stack.peek()]){
                result[stack.peek()] = i - stack.peek();
                stack.pop();
            }
            stack.push(i);
        }
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    面试题39:直方图最大矩形面积

    题目:直方图是由排列在同一基线上的相邻柱子组成的图形。输入一个由非负数组成的数组,数组中的数字是直方图中柱子的高。求直方图中最大的句型面积。假设直方图柱子的宽都为1。例如,输入数组[3, 2, 5, 4, 6, 1, 4, 2],其中对应的直方图如下图所示,最大矩形面积为12。

    image-20221027013418178

    此题有多重解法:

    1. 暴力法:双循环,外层循环遍历每个坐标,内层循环计算从当前坐标开始的最大面积
    2. 二分法:找到低的高度,比较以最低高度计算的面积和最低高度两侧计算结果,取最大值
    3. 栈:栈中保存坐标;遍历每个坐标,若当前高度小于栈顶坐标对应的高度,则将栈顶坐标出栈,计算以此栈顶坐标对应的高度的矩形面积,直到栈顶坐标的高度小于当前高度,则压入当前高度的坐标。保证栈中的坐标对应的高度是单调递增的。关键点:刚开始给站压入一个哨兵坐标-1,可以避免很多栈空的判断
    public int getMaxArea(int[] histogram){
        // 第三种方法
        Stack<Integer> stack = new Stack<>();
        // 哨兵坐标,简化判断
        stack.push(-1);
        int result = 0;
        for (int i = 0; i < histogram.length; i++) {
            int temp = histogram[i];
            while(stack.peek() != -1 && temp < histogram[stack.peek()]){
                int prevHeight = histogram[stack.pop()];
                int prevMaxArea = prevHeight * (i - stack.peek() - 1);
                result = Math.max(result, prevMaxArea);
            }
            stack.push(i);
        }
        while(stack.peek() != -1){
            int tempHeight = histogram[stack.pop()];
            int tempMaxArea = tempHeight * (histogram.length - stack.peek() - 1);
            result = Math.max(result, tempMaxArea);
        }
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    面试题40:矩阵中的最大矩形

    题目:请在一个由0,1组成的矩阵中找到最大只包含1的矩形,并输出他的面积。如下图所示,最大的只包含1的矩阵面积是6。

    image-20221027034621312

    思路:转化为直方图求面积。以每一行为基准线,将值为1的列看做直方图的柱子。这样能构造4个直方图。然后求这四个直方图最大矩形的面积(面试题39)。

    public int maxRectangleInMatrix(int[][] matrix){
        if(matrix.length == 0 || matrix[0].length == 0){
            return 0;
        }
        int maxArea = 0;
        int[] height = new int[matrix[0].length];
        for (int[] row : matrix) {
            for(int i = 0; i < row.length; i++){
                if(row[i] == 0){
                    height[i] = 0;
                } else {
                    height[i]++;
                }
            }
            maxArea = Math.max(maxArea, getMaxArea(height));
        }
        return maxArea;
    }
    public int getMaxArea(int[] histogram){
        Stack<Integer> stack = new Stack<>();
        // 哨兵坐标,简化判断
        stack.push(-1);
        int result = 0;
        for (int i = 0; i < histogram.length; i++) {
            int temp = histogram[i];
            while(stack.peek() != -1 && temp < histogram[stack.peek()]){
                int prevHeight = histogram[stack.pop()];
                int prevMaxArea = prevHeight * (i - stack.peek() - 1);
                result = Math.max(result, prevMaxArea);
            }
            stack.push(i);
        }
        while(stack.peek() != -1){
            int tempHeight = histogram[stack.pop()];
            int tempMaxArea = tempHeight * (histogram.length - stack.peek() - 1);
            result = Math.max(result, tempMaxArea);
        }
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
  • 相关阅读:
    孤僻型性格分析,如何改变孤僻型性格?
    卷积神经网络 - 图像卷积
    LeetCode只出现一次的数字
    Java开发学习(二十四)----SpringMVC设置请求映射路径
    tsfresh:一款表现出色的自动化提取时序特征的 Python 工具包
    RabbitMQ实践——最大长度队列
    Fabric.js 图案笔刷
    [QCM6125][Android13] 默认允许使用usb权限
    信息学奥赛一本通:1127:图像旋转
    TCP IP 网络编程(七) 理解select和epoll的使用
  • 原文地址:https://blog.csdn.net/sd_960614/article/details/127544503