剑指Offer(专项突破版)刷题笔记 | 第六章 栈

631 阅读5分钟

栈的基础知识

  • 先入后出
  • 插入和删除操作都在栈顶
  • push(入栈)、pop(出栈)、peek(返回栈顶元素)

栈的应用

Q36:后缀表达式

题目(中等):后缀表达式是一种算术表达式,它的操作符在操作数的后面。输入一个用字符数组表示的后缀表达式,请输出该后缀表达式的计算结果。假设输入的一定是有效的后缀表达式

示例 1:

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

解题思路

遇到操作数入栈,遇到操作符,弹出两个操作数计算后入栈,如此反复

public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<Integer>();
        for(String token:tokens){
            switch(token){
                case "+":
                case "-":
                case "*":
                case "/":
                    int num2 = stack.pop();
                    int num1 = stack.pop();
                    stack.push(calculate(num1,num2,token));
                    break;
                default:stack.push(Integer.parseInt(token));
            }
        } 
        return stack.pop();
    }

    private int calculate(int num1,int num2,String operator){
        switch(operator){
            case "+":return num1 + num2;
            case "-":return num1 - num2;
            case "*":return num1 * num2;
            case "/":return num1 / num2;
            default: return 0;
        }
    }

Q37:小行星碰撞

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

示例 1:

输入:asteroids = [5,10,-5]
输出:[5,10]
解释:10 和 -5 碰撞后只剩下 10 。 5 和 10 永远不会发生碰撞。

示例 2:

输入:asteroids = [8,-8]
输出:[]
解释:8 和 -8 碰撞后,两者都发生爆炸。

示例 3:

输入:asteroids = [-2,-1,1,2]
输出:[-2,-1,1,2]
解释:-2 和 -1 向左移动,而 1 和 2 向右移动。 由于移动方向相同的行星不会发生碰撞,所以最终没有行星发生碰撞。

解题思路

碰撞:只有在左的小行星向右运动遇见在右的小行星向左运动才会发生碰撞(即正数在左,负数在右时)

入栈:栈外向右运动的小行星(正数)或栈空时或栈内元素向左运动(负数)

出栈:碰撞时,若不相等需要用while进行多次判断,相等两者爆炸消失,一次即可

public int[] asteroidCollision(int[] asteroids) {
    Stack<Integer> stack = new Stack<Integer>();
    for(int asteroid:asteroids){
        while(!stack.empty() && stack.peek()>0 && stack.peek()<-asteroid){
            stack.pop();
        }

        if(!stack.empty() && stack.peek() > 0 && stack.peek() == -asteroid){
            stack.pop();
        }else if(asteroid > 0 || stack.empty() || stack.peek() < 0){
            stack.push(asteroid);
        }
    }
    return stack.stream().mapToInt(i->i).toArray();

}

Q38:每日温度

题目(中等):请根据每日 气温 列表 temperatures ,重新生成一个列表,要求其对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替

示例 1:

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

示例 2:

输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]

示例 3:

输入: temperatures = [30,60,90]
输出: [1,1,0]

解题思路

与上题一样还是分析什么时候出栈什么时候入栈

出栈:栈不为空且栈顶元素比当前元素小(遇到更高的温度)

入栈:栈为空或栈顶元素比要入栈元素大(栈为空的判断要写在前面,否则在栈空的时候调用peek()会报错)

注意:

  • 存储在栈中的为温度的下标,弹出时将当前下标与栈顶元素相减写入下标为栈顶元素的result数组
  • 数组元素创建时初始化为0,无需多此一举
public int[] dailyTemperatures(int[] temperatures) {
    int [] result = new int[temperatures.length];

    Stack<Integer> stack = new Stack<Integer>();
    for(int i = 0;i < temperatures.length;i++){
        while(!stack.empty() && temperatures[stack.peek()] < temperatures[i]){
            int index = stack.pop();
            result[index] = i - index;
        }
        if(stack.empty() || temperatures[stack.peek()] >= temperatures[i]){
            stack.push(i);
        }
    }
    return result;
}

Q39:直方图的最大矩形面积

题目(困难):给定非负整数数组 heights ,数组中的数字用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积

示例 1:

输入: heights = [2,1,5,6,2,3]
输出: 10
解释: 最大的矩形为图中红色区域,面积为 10

示例 2:

输入: heights = [2,4]
输出: 4

解题思路

  1. 递归实现

    复杂度在O(nlogn)O(nlogn)~O(n2)O(n^2),无法通过leetcode,对于大数组会超出时间限制

    递归三种情况

    • 最大矩形在最矮元素的左侧
    • 最大矩形在最矮元素的右侧
    • 最大矩形通过最矮元素
//递归实现
public int largestRectangleArea(int[] heights) {
    return calculator(heights,0,heights.length-1);
}

public int calculator(int[] heights,int start,int end){
    //宽为1时,最大的矩形面积就是自身
    if(start == end) return heights[start];
    //获得最小高度元素的下标
    int minIndex = start;
    for (int i = start;i <= end;i++){
        minIndex = heights[i] < heights[minIndex] ? i : minIndex;
    }
    //最大矩形通过最矮元素
    int right = 0,left = 0;
    int area = heights[minIndex]*(end - start + 1);

    //左侧或右侧
    if(minIndex==start){
        right = calculator(heights,minIndex+1,end);
    }else if(minIndex==end){
        left = calculator(heights,start,minIndex-1);
    } else{
        right = calculator(heights,minIndex+1,end);
        left = calculator(heights,start,minIndex-1);
    }

    area = Math.max(left,area);
    return Math.max(area,right);
}
  1. 单调栈法

    考察以栈顶的柱子为顶的最大矩形面积,保证保存在栈中的柱子的高度是按递增排序的,若当前柱子高于栈顶柱子则入栈,小于则出栈,栈中存的是下标。

    入栈:当前柱子高于等于栈顶柱子(当前元素大于栈顶元素)或栈为空

    出栈:当前柱子低于栈顶柱子,多次弹出(利用while),返回(下标差)\*(当前栈顶元素)

    所有元素在pop时计算以它为顶的最大矩形

    • 为什么是(i - stack.peek() -1)

      与前一个栈内元素(下标)之间缺失的都是比pop出的栈顶元素大的,所以要从栈中的前一个元素开始考虑

    • 为什么是heights.length-stack.peek()-1 左边缺失的和右边的都比pop出的元素要大

//单调栈法
public int largestRectangleArea(int[] heights) {
    Stack<Integer> stack = new Stack<Integer>();
    stack.push(-1);//为了使最后出栈时,stack.peek()不发生空异常
    int maxArea = 0;
    for(int i = 0;i < heights.length;i++){
        while(stack.peek() != -1 && heights[stack.peek()] > heights[i]){
            int index = stack.pop();
            int area = (i - stack.peek() -1)*heights[index];
            maxArea = Math.max(maxArea, area);
        }
        stack.push(i);
    }
    while (stack.peek() != -1){
        int index = stack.pop();
        int area = (heights.length-stack.peek()-1)*heights[index];
        maxArea = Math.max(maxArea,area);
    }
    return maxArea;
}

Q40:矩阵中的最大矩形

题目(困难):给定一个由 0 和 1 组成的矩阵 matrix ,找出只包含 1 的最大矩形,并返回其面积

示例 1:

输入: matrix = ["10100","10111","11111","10010"]
输出: 6
解释: 最大矩形如上图所示。
public int maximalRectangle(String[] matrix) {
    if(matrix.length == 0 || matrix[0].length() == 0) return 0;

    int[] heights = new int[matrix[0].length()];
    int maxArea = 0;
    for(String row:matrix){
        for(int i = 0;i < row.length();i++){
            if(row.charAt(i) == '0') heights[i] = 0;
            else heights[i]++;
        }
        maxArea = Math.max(maxArea,largestRectangleArea(heights));
    }
    return maxArea;
}