栈的设计与算法例题

Posted 却把清梅嗅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了栈的设计与算法例题相关的知识,希望对你有一定的参考价值。

栈的实现

栈的实现比队列容易。动态数组 足以实现堆栈结构。这里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,概述:

  • https://leetcode-cn.com/explore/learn/card/queue-stack/218/stack-last-in-first-out-data-structure/875/

例题:

  • https://leetcode-cn.com/problems/min-stack/
  • https://leetcode-cn.com/problems/valid-parentheses/
  • https://leetcode-cn.com/problems/daily-temperatures/
  • https://leetcode-cn.com/problems/evaluate-reverse-polish-notation

关于我

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

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

以上是关于栈的设计与算法例题的主要内容,如果未能解决你的问题,请参考以下文章

C#程序设计之常用数据结构与算法

java数据结构与算法之栈(Stack)设计与实现

栈的应用:每日温度

Python数据结构与算法(3.1)——栈

算法原理与实践(栈与队列)

C语言 任意表达式求值。(栈的应用