03数据结构与算法刷题之【栈】篇

简介: 03数据结构与算法刷题之【栈】篇

剑指offer


剑指 Offer 31. 栈的压入、弹出序列【中等】


题目链接:剑指 Offer 31. 栈的压入、弹出序列


题目内容:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。


思路:利用栈


1、借用栈,先入栈,接着来进行尝试出栈操作,若是最终栈中没有元素了,说明true。


复杂度分析:时间复杂度O(n)、空间复杂度O(n)


class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Stack<Integer> stack = new Stack<>();
        int i = 0;
        for (int num: pushed) {
            stack.push(num);//先进行入栈
            while (!stack.isEmpty() && stack.peek() == popped[i]) {
                stack.pop();
                i++;
            }
        }
        return stack.isEmpty();
    }
}


牛客网


包含min函数的栈【简单】


题目链接: 包含min函数的栈


题目内容:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的 min 函数,输入操作时保证 pop、top 和 min 函数操作时,栈中一定有元素。


思路:采用两个栈来进行处理操作。


复杂度分析:


时间复杂度:O(1)
空间复杂度:O(n)
import java.util.*;
import java.util.Stack;
public class Solution {
    //栈1存储元素;栈2存储最小元素
    private Stack<Integer> stack1 = new Stack<Integer>();
    private Stack<Integer> stack2 = new Stack<Integer>();
    public void push(int node) {
        stack1.push(node);
        //若是当前栈2为空且当前元素<栈顶元素
        if (stack2.isEmpty() || node < stack2.peek()) {
            stack2.push(node);
        }else{
            stack2.push(stack2.peek());
        }
    }
    public void pop() {
        stack1.pop();
        stack2.pop();
    }
    public int top() {
        return stack1.peek();
    }
    public int min() {
        return stack2.peek();
    }
}


有效括号序列【简单】


学习:leetcode题解、 代码随想录—有效的括号


题目链接:有效括号序列


题目内容:给出一个仅包含字符’(‘,’)‘,’{‘,’}‘,’[‘和’]',的字符串,判断给出的字符串是否是合法的括号序列括号必须以正确的顺序关闭,"()“和”()[]{}“都是合法的括号序列,但”(]“和”([)]"不合法。


思路:借助栈数据结构,总共有三个类型[]{}(),若是碰到前半个,那么就入栈后半缀,若是非后半缀进行出栈并且进行比较即可!


若是左闭合符号就压入配对的右闭合符号,方便之后进行匹配。
示例:"()[{}]"   stack=[]
① 字符'(',压入')'  stack=['(']
② 字符')',与当前栈顶存放匹配,出栈  stack=[]
③ 字符'[',压入']'  stack=[']']
④ 字符'{',压入'}'  stack=[']','}']
⑤ 字符'}',与当前栈顶存放匹配,出栈 stack=[']']
⑥ 字符']',与当前栈顶存放匹配,出栈 stack=[]
所有字符都遍历一遍之后,判断栈中是否存在元素,为空表示有效括号;不为空表示无效括号。


复杂度分析:


时间复杂度:O(n),其中n为字符串长度,遍历整个字符串。
空间复杂度:O(n),最坏情况下记录整个字符串的右括号。
import java.util.*;
public class Solution {
    /**
     * 
     * @param s string字符串 
     * @return bool布尔型
     */
    public boolean isValid (String s) {
        //定义一个栈
        Stack<Character> stack = new Stack<Character>();
        for (int i = 0;i < s.length();i++) {
            Character ch = s.charAt(i);
            //三种情况:{}()[]
            if (ch == '{'){
                stack.push('}');
            }else if (ch == '(') {
                stack.push(')');
            }else if (ch == '['){
                stack.push(']');
            }else if (stack.isEmpty() || stack.pop() != ch) {
                return false;
            }
        }
        return stack.isEmpty();
    }
}




表达式求值【中等】


题目链接:表达式求值


题目内容:请写一个整数计算器,支持加减乘三种运算和括号。


思路:双栈操作。一个栈存储运算符,另一个栈存储数字(及运算结果)


复杂度分析:


时间复杂度:O(n)
空间复杂度:O(n)
import java.util.*;
public class Solution {
    private Map<Character, Integer> map = new HashMap<Character, Integer>(){
        {
            put('-', 1);
            put('+', 1);
            put('*', 2);
        }
    };
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 返回表达式的值
     * @param s string字符串 待计算的表达式
     * @return int整型
     */
    //触发计算的几种情况:①碰到)。②新运算符即将入栈【判断前栈中的一个运算符是否等级高于当前要插入的,若是>=那么先进行计算】。③整个字符串遍历结束后将栈中的进行处理。
    public int solve (String s) {
        //使用两个栈
        Stack<Character> ops = new Stack<>();
        Stack<Integer> nums = new Stack<>();
        //将所有的空格去除掉
        s = s.replaceAll(" ", "");
        int length = s.length();
        //开始进行遍历
        for (int i = 0;i < length;i++) {
            char ch = s.charAt(i);
            if (ch == '(') {
                ops.push(ch);
            }else if (ch == ')') {
                //开始进行计算()中的表达式
                while (!ops.isEmpty() && ops.peek() != '(') {
                    calc(nums, ops);
                }
                //将ops中最后一个(移除
                ops.pop();
            }else if (isNumber(ch)) {
                //可能是十位数、百位数...
                int num = 0;
                int j = i;
                while (j < length && isNumber(s.charAt(j))) {
                    //注意:这里需要j++
                    num = num * 10 + s.charAt(j++) - '0';
                }
                //添加到栈中
                nums.push(num);
                i = j - 1;//因为for循环之后会i++,j此时肯定在非数字上目前
            }else {
                //非(、)、数字情况
                //1、+*-,待插入的是-,此时栈中有运算符优先级更高的
                while (!ops.isEmpty() && ops.peek() != '(') {
                    Character peekCh = ops.peek();
                    //若是当前在栈中权重更高,那么优先进行计算栈中的元素
                    if (map.get(peekCh) >= map.get(ch)) {
                        calc(nums, ops);
                    }else {
                        break;
                    }
                }
                //将新的运算符入栈
                ops.push(ch);
            }
        }
        //遍历完成之后,还要进行剩余内容的计算
        while (!ops.isEmpty()) {
            calc(nums, ops);
        }
        return nums.pop();
    }
    //计算运算符
    public void calc(Stack<Integer> nums, Stack<Character> ops) {
        if (nums.size() < 2 && ops.size() < 1) {
            return;
        }
        //取出一个符号
        char ch = ops.pop();
        Integer num2 = nums.pop();
        Integer num1 = nums.pop();
        int res = 0;
        if (ch == '+') {
            res = num1 + num2;
        }else if (ch == '-') {
            res = num1 - num2;
        }else if (ch == '*') {
            res = num1 * num2;
        }
        nums.push(res);
    }
    public boolean isNumber(Character ch) {
        return Character.isDigit(ch);
    }
}



leetcode


232. 用栈实现队列【简单】


学习:leetcode题解、 代码随想录—232.用栈实现队列


题目链接:232. 用栈实现队列


题目内容:


请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:

void push(int x) 将元素 x 推到队列的末尾

int pop() 从队列的开头移除并返回元素

int peek() 返回队列开头的元素

boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:

你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。

你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。


示例 1:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
1 <= x <= 9
最多调用 100 次 push、pop、peek 和 empty
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)


思路:


1、双栈实现队列


思路: 一个栈用来进行进栈(栈1),另一个栈用来出栈(栈2)。出栈的时候,先判断栈2是否为空,为空了统一将栈1中的元素依次出栈进栈2,不为空不进行该操作,避免有些后进的之后被pop出去。


class MyQueue {
    private Stack<Integer> stack1;//负责进栈
    private Stack<Integer> stack2;//负责出栈
    public MyQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
    public void push(int x) {
        stack1.push(x);
    }
    public int pop() {
        dumpStack1();
        return stack2.pop();
    }
    public int peek() {
        dumpStack1();
        return stack2.peek();
    }
    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }
    public void dumpStack1(){
        //判断栈2是否为空,空了才会将stack1的元素压入(避免后进的元素入了栈2)
        if(stack2.isEmpty()){
            while(!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
        }
    }
}



1047. 删除字符串中的所有相邻重复项【中等】


学习:leetcode题解、 代码随想录—删除字符串中的所有相邻重复项


题目链接:1047. 删除字符串中的所有相邻重复项


题目内容:


给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。


示例:
输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
提示:
1 <= S.length <= 20000
S 仅由小写英文字母组成。


思路:


1、栈解决


思路:遍历字符时,每次先判断当前栈顶元素是否与要入栈的元素相同,如果相同出栈,不相同入栈。最后将栈中的字符进行一一拼接返回。


示例:"abbaca" stack=[]
① 字符'a',当前栈为空直接入栈  stack=['a']
② 字符'b',栈不为空,与栈顶元素a比较,不相同入栈  stack=['a','b']
③ 字符'b',栈不为空,与栈顶元素b比较,相同出栈  stack=['a']
④ 字符'a',栈不为空,与栈顶元素b比较,相同出栈  stack=[]
⑤ 字符'c',当前栈为空直接入栈  stack=['c']
⑥ 字符'a',栈不为空,与栈顶元素c比较,不相同入栈  stack=['a','c']
最终从前往往后将元素移出进行拼接,返回"ac"


代码:


class Solution {
    public String removeDuplicates(String s) {
        Deque<Character> stack = new LinkedList<>();
        for (char c : s.toCharArray()) {
            //当前栈不为空,且栈顶与当前字符相同出栈
            if(!stack.isEmpty() && stack.peek() == c){
                stack.pop();
            }else{//否则直接入栈
                stack.push(c);
            }
        }
        StringBuilder str = new StringBuilder();
        while(!stack.isEmpty()){
            str = str.append(stack.pollLast());
        }
        return str.toString();
    }
}



2、字符串作栈


思路:使用字符串作栈的好处就是可以省去上面提交中拼接的操作,最终留在字符串里的就是要返回出去的。


示例:"abbaca"  str="" top=-1
① 字符'a' 字符串(栈)为空,直接拼接 str="a"  top=0
② 字符'b' 字符串(栈)不为空,与栈顶不相同,直接拼接 str="ab" top=1
③ 字符'b' 字符串(栈)不为空,与栈顶相同,删除指定元素 str="a" top=0
④ 字符'a' 字符串(栈)不为空,与栈顶相同,删除指定元素 str="" top=-1
⑤ 字符'c' 字符串(栈)为空,直接拼接 str="c"  top=0
⑥ 字符'b' 字符串(栈)不为空,与栈顶不相同,直接拼接 str="ca" top=1
最终直接返回str="ca"


代码:


class Solution {
    //目的是拿到不重复的字符拼接内容
    public String removeDuplicates(String s) {
        //直接来拿字符串来作为栈进行操作,最终剩下来的就是不重复的
        StringBuilder str = new StringBuilder();
        int top = -1;
        for (char c : s.toCharArray()) {
            if(top >= 0 && c == str.charAt(top)){
                str.deleteCharAt(top--);
            }else{
                str.append(c);
                top++;
            }
        }
        return str.toString();
    }
}



3、双指针(原数组上操作)


思路:直接对原数组进行操作,不相邻的重复元素直接覆盖旧元素,最终直接截取原数组指定长度内容返回。


fast指针用来进行遍历一遍字符串的,[0,slow)永远表示的是非相邻重复项
示例:s="abbaca"  slow=0,fast=0
① 字符'a' s[0]=s[0] (s="abbaca") slow=1,fast=1  | [0,slow)=> "a"
② 字符'b' s[1]=s[1] (s="abbaca") slow=2,fast=2  | [0,slow)=> "ab"
③ 字符'b' s[2]=s[2] (s="abbaca")(s[2]==s[1] => b=b重复,slow-1) slow=1,fast=3    | [0,slow)=> "a"
④ 字符'a' s[1]=s[3] (s="aabaca")(s[1]==s[0] => a=a重复,slow-1) slow=0,fast=4    | [0,slow)=> ""
⑤ 字符'c' s[0]=s[4] (s="cabaca")(slow=0,slow+1) slow=1,fast=5     | [0,slow)=> "c"
⑥ 字符'a' s[1]=s[5] (s="cabaca")(s[1]!=s[0],slow++) slow=1,fast=4    | [0,slow)=> "ca"
最终直接返回"ca"即可


代码:


//快慢指针,时间复杂度O(n),空间复杂度O(1)
public String removeDuplicates(String s) {
    //定义双指针
    int slow = 0;//左指针的目的是①检测是否有相等,有后退,无前进。②实时更新当前最新位置值(方便下次进行对比以及旧值的覆盖,旧的值已无用)
    int fast = 0;//右指针负责的工作是进行遍历作用
    char[] chars = s.toCharArray();
    while(fast < s.length()){
        chars[slow] = chars[fast];
        if(slow > 0 && chars[slow]==chars[slow-1]){
            slow--;
        }else{
            slow++;
        }
        fast++;
    }
    //[0,slow)区间值
    return new String(chars,0,slow);
}




150. 逆波兰表达式求值【中等】


学习:leetcode题解、 代码随想录—150. 逆波兰表达式求值


题目链接:150. 逆波兰表达式求值


题目内容:


根据 逆波兰表示法,求表达式的值。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
注意 两个整数之间的除法只保留整数部分。
可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。


思路:


1、栈解决(后缀表达式)


题目给的就是后缀表达式


思路:若是数字直接入栈,碰到运算符出栈两次进行相应运算后,将运算结果入栈,之后重复即可。其中需要注意的是给的是字符串数组,需要进行转换以及要注意/、-时,要拿后出栈的运算前出栈的。


示例:["2", "1", "+", "3", "*","3", "/" , "5" , "-"]  stack=[]
① "2" 是数字直接转换入栈  stack=[2]
② "1" 是数字直接转换入栈  stack=[2,1]
③ "+" 是运算符,出栈两次 第一次出1,第二次2 1+2=3入栈 stack=[3]
④ "3" 是数字直接转换入栈  stack=[3,3]
⑤ "*" 是运算符,出栈两次 第一次出3,第二次3 3*3=9入栈 stack=[9]
⑥ "3" 是数字直接转换入栈  stack=[9,3]
⑦ "/" 是运算符,出栈两次 第一次出3,第二次9 注意这里要拿后一个除前一个9/3=3入栈 stack=[3]
⑧ "5" 是数字直接转换入栈  stack=[3,5]
⑨ "-" 是运算符,出栈两次 第一次出5,第二次3 注意这里要拿后一个减前一个3-5=-2入栈 stack=[-2]
最后出栈即为结果-2


代码:


public int evalRPN(String[] tokens) {
    Deque<Integer> stack = new LinkedList<>();
    for (String token : tokens) {
        //若是数字
        if (!isOper(token)) {
            stack.push(Integer.valueOf(token));
        } else if (Objects.equals("+", token)) {
            stack.push(stack.pop() + stack.pop());
        } else if (Objects.equals("-", token)) {
            stack.push(-stack.pop() + stack.pop());//-的话,要注意出栈元素熟悉,后出的-先出的
        } else if (Objects.equals("*", token)) {
            stack.push(stack.pop() * stack.pop());
        } else {
            //除法操作与上面的同理,后出的/先出的
            int num2 = stack.pop();
            int num1 = stack.pop();
            stack.push(num1 / num2);
        }
    }
    return stack.pop();
}
//判断是否为运算符
public boolean isOper(String str) {
    if (str.length() == 1 && (str.charAt(0) < '0' || str.charAt(0) > '9')) {
        return true;
    }
    return false;
}


相关文章
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
222 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
37 1
|
4天前
|
算法
【算法】栈
栈相关算法题,供参考,附有链接地址及板书
|
1月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
55 5
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
1月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
1月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
52 4
|
1月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
50 0
|
2月前
数据结构(栈与列队)
数据结构(栈与列队)
23 1