qingmei2 / blogs

📝 The Android programing blogs(简体中文).

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

栈的设计与算法例题

qingmei2 opened this issue · comments

栈的设计与算法例题

栈的实现

栈的实现比队列容易。动态数组 足以实现堆栈结构。这里LeetCode官方提供了一个简单的实现供参考:

// "static void main" must be defined in a public class.
class MyStack {
    private List<Integer> data;               // store elements
    public MyStack() {
        data = new ArrayList<>();
    }
    /** Insert an element into the stack. */
    public void push(int x) {
        data.add(x);
    }
    /** Checks whether the queue is empty or not. */
    public boolean isEmpty() {
        return data.isEmpty();
    }
    /** Get the top item from the queue. */
    public int top() {
        return data.get(data.size() - 1);
    }
    /** Delete an element from the queue. Return true if the operation is successful. */
    public boolean pop() {
        if (isEmpty()) {
            return false;
        }
        data.remove(data.size() - 1);
        return true;
    }
};

例题

155. 最小栈

  • 难度:Easy

题目描述

设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。

  • push(x) -- 将元素 x 推入栈中。
  • pop() -- 删除栈顶的元素。
  • top() -- 获取栈顶元素。
  • getMin() -- 检索栈中的最小元素。

解题思路及实现

官方 栈和队列 标签中,对栈的实现方式描述为:

栈的实现比队列容易。动态数组足以实现堆栈结构。

// 官方实现的描述,直接使用了ArrayList
class MyStack {
    private List<Integer> data;               // store elements
    public MyStack() {
        data = new ArrayList<>();
    }
}

因此做本题的时候,下意识觉得使用 ArrayList 就足够了,结果发现不通过,原因是题目中额外要求 在常数时间内检索到最小元素,而我则是 暴力循环遍历所有元素找到最小值 , 时间复杂度为O(N), 这样当然不行。

原来 getMin() 函数有时间限制,那么用一个成员记录最小值的状态应该就可以了吧...

试了一下竟然真的可以,这也算偷奸耍滑通过了......(我只是把循环的操作从getMin()转移到了pop())...

class MinStack {

    private List<Integer> data;
    // 1.用一个成员记录栈内最小值
    private int min = Integer.MAX_VALUE;

    public MinStack() {
        data = new ArrayList<>();
    }

    // 2.写入时,看情况更新最小值
    public void push(int x) {
        if (min > x) {
            min = x;
        }
        data.add(x);
    }

    // 3.出栈时,更新最小值,这里时间复杂度为 O(N)
    public void pop() {
        verifyNotEmpty();
        data.remove(data.size() - 1);

        min = Integer.MAX_VALUE;
        for (Integer num : data) {
            if (min > num) {
                min = num;
            }
        }
    }

    public int top() {
        verifyNotEmpty();
        return data.get(data.size() - 1);
    }

    // 4.这样就能保证,获取最小值的函数,执行时间为常数时间了 =w=
    public int getMin() {
        verifyNotEmpty();
        return min;
    }

    private void verifyNotEmpty() {
        if (data.isEmpty()) {
            throw new IllegalArgumentException("Stack is empty");
        }
    }
}

20. 有效的括号

  • 难度:Easy

题目描述

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

有效字符串需满足:

  • 1.左括号必须用相同类型的右括号闭合。
  • 2.左括号必须以正确的顺序闭合。
    注意空字符串可被认为是有效字符串。

解题思路及实现

说实话栈相关的题目还是不太好第一时间联想到的,这里解决方案参考官方题解

https://leetcode-cn.com/problems/valid-parentheses/solution/you-xiao-de-gua-hao-by-leetcode/

实现代码:

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();

        Map<Character, Character> mapping = new HashMap<>();
        mapping.put('}', '{');
        mapping.put(')', '(');
        mapping.put(']', '[');

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            if (mapping.containsKey(c)) {
                // 闭括号
                char top = stack.isEmpty() ? '#' : stack.pop();

                if (top != mapping.get(c)) {
                    return false;
                }
            } else {
                // 新的开符号,直接压入栈中
                stack.push(c);
            }
        }
        return stack.isEmpty();
    }
}

739. 每日温度

  • 难度:Medium

题目描述

根据每日气温列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

解题思路及实现

这道题难点在于题目的理解,题目看懂了,其实解决起来比较简单。

1.暴力破解法

暴力破解法是最直接想到的方式,时间复杂度 O(N^2)

class Solution {
   public int[] dailyTemperatures(int[] T) {
        int[] ans = new int[T.length];

        for (int i = 0; i < T.length; i++) {
            // 最后一个元素,对应一定是0
            if (i == T.length - 1) {
                ans[T.length - 1] = 0;
                continue;
            }
            int times = 0;
            for (int j = i + 1; j < T.length; j++) {
                if (T[j] > T[i]) {
                    ans[i] = ++times;
                    break;
                }
                if (j == T.length - 1) {
                    ans[i] = 0;
                }
                times++;
            }
        }
        return ans;
    }
}

2.使用栈

说实话这个解决方案真的没想到,并且是题目标签提示了的,参考官方题解

https://leetcode-cn.com/problems/daily-temperatures/solution/mei-ri-wen-du-by-leetcode/

class Solution {
    public int[] dailyTemperatures(int[] T) {
        int[] ans = new int[T.length];
        Stack<Integer> stack = new Stack();
        for (int i = T.length - 1; i >= 0; --i) {
            while (!stack.isEmpty() && T[i] >= T[stack.peek()]) stack.pop();
            ans[i] = stack.isEmpty() ? 0 : stack.peek() - i;
            stack.push(i);
        }
        return ans;
    }
}

通过使用和动态的更新栈,将数据在数组中的索引作为栈的元素进行处理,使得代码极大的被简化,并且思路更高级,因为每个索引最多做一次压栈和出栈的操作。因此时间复杂度为 O(N)

150. 逆波兰表达式求值

  • 难度:Medium

题目描述

根据逆波兰表示法,求表达式的值。

有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:

  • 整数除法只保留整数部分。
  • 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

解题思路及实现

同样是使用栈解决,将数字元素压入栈,遇到符号时,从栈顶取出两个元素进行求值,并将结果再次存入栈顶,最终将栈顶的结果弹出进行返回即可:

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

参考 & 感谢

文章绝大部分内容节选自LeetCode,概述:

例题:

关于我

Hello,我是 却把清梅嗅 ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 博客 或者 GitHub

如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?