算法:(六)栈

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);
}
  • 表达式四则运算,包含中缀表达式转后缀表达式:
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;
}

面试题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();
}

面试题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;
}

面试题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;
}

面试题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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值