前言
后进者先出,先进者后出,这就是典型的“栈”结构
以下是一些基本练习
-
用数组实现一个顺序栈
public class ArrayStack { private int[] stack; //存储数据的数组 private int top;//栈顶指针 public void ArrayStack(int capacity) { stack = new int[capacity]; top = -1; } //入栈 public void push(int value) { if (top == stack.length - 1) { throw new RuntimeException("栈已满"); } stack[++top] = value; } //出栈 public int pop() { if (top == -1) { throw new RuntimeException("栈已空"); } return stack[top--]; } //获取栈顶元素 public int peek() { if (top == -1) { throw new RuntimeException("stack is empty"); } return stack[top]; } //获取栈中元素个数 public int size() { return top + 1; }}
-
用链表实现一个链式栈
public class LinkedListStack { private ListNode top; //栈顶节点 //入栈 public void push(int value) { ListNode node = new ListNode(value); node.next = top; top = node; } //出栈 public int pop() { if (top == null) { throw new RuntimeException("stack is empty"); } ListNode node = top; top = top.next; return node.val; } public int peek() { if (top == null) { throw new RuntimeException("stack is empty"); } return top.val; } public int size() { if (top == null) { return 0; } int size = 1; ListNode node = top; while (node.next != null) { size++; node = node.next; } return size; }}
-
编程模拟实现一个浏览器的前进、后退功能
public class Browser { private Stack history; private Stack forword; private String current; public Browser(){ history = new Stack<>(); forword = new Stack<>(); current = ""; } public void visit(String url){ history.push(current); current = url; forword.clear(); } public void back(){ if(history.isEmpty()){ throw new RuntimeException("history is empty"); } forword.push(current); current = history.pop(); } public void forword(){ if(forword.isEmpty()){ throw new RuntimeException("forword is empty"); } history.push(current); current = forword.pop(); } public String current(){ return current; } }
1,有效的括号
题目
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
题解
使用栈 。利用栈的先进后出原理 进行判断
public boolean isValid(String s) { if(s==null || s.length()%2 !=0){ return false; } Map<Character,Character> map = new HashMap<Character,Character>(){{ put(')','('); put(']','['); put('}','{'); }}; Stack<Character> stack = new Stack(); for(int i=0;i<s.length();i++ ){ Character c=s.charAt(i); if(map.containsKey(c)){ if(stack.isEmpty()){ return false; } Character top=stack.pop(); if(!top.equals(map.get(c))){ return false; } }else{ stack.push(c); } } if(stack.isEmpty()){ return true; } return false;
2,最长有效的括号
题目:
给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
题解:
对于这个问题相信大多数人对于这题的第一直觉是找到每个可能的子串后判断它的有效性,但这样的时间复杂度会达到 O(n3),无法通过所有测试用例。
但是通过栈,我们可以在遍历给定字符串的过程中去判断到目前为止扫描的子串的有效性,同时能得到最长有效括号的长度。
-
具体做法是我们始终保持栈底元素为当前已经遍历过的元素中「最后一个没有被匹配的右括号的下标」,这样的做法主要是考虑了边界条件的处理,栈里其他元素维护左括号的下标:
-
对于遇到的每个 ‘(’,我们将它的下标放入栈中
-
对于遇到的每个 ‘)’ ,我们先弹出栈顶元素表示匹配了当前右括号: 如果栈为空,说明当前的右括号为没有被匹配的右括号,我们将其下标放入栈中来更新我们之前提到的「最后一个没有被匹配的右括号的下标」 如果栈不为空,当前右括号的下标减去栈顶元素即为「以该右括号为结尾的最长有效括号的长度」 我们从前往后遍历字符串并更新答案即可。
-
需要注意的是,如果一开始栈为空,第一个字符为左括号的时候我们会将其放入栈中,这样就不满足提及的「最后一个没有被匹配的右括号的下标」,为了保持统一,我们在一开始的时候往栈中放入一个值为 −1的元素。
public int longestValidParentheses(String s) { if(s == null || s.length() < 2){ return 0; } int max = 0; Stack<Integer> stack = new Stack<>(); stack.push(-1); for(int i= 0;i<s.length();i++){ char c = s.charAt(i); if(c == '('){ stack.push(i); }else{ stack.pop(); if(stack.isEmpty()){ stack.push(i); }else{ int len = i-stack.peek(); if(len > max){ max=len; } } } } return max;}
复杂度分析
- 时间复杂度: O(n),nnn 是给定字符串的长度。我们只需要遍历字符串一次即可。
- 空间复杂度: O(n)。栈的大小在最坏情况下会达到 n,因此空间复杂度为 O(n) 。
3,逆波兰表达式求值
题目
给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
'+'、'-'、'*'和'/'。 - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
题解
逆波兰表达式由波兰的逻辑学家卢卡西维兹提出。逆波兰表达式的特点是:没有括号,运算符总是放在和它相关的操作数之后。因此,逆波兰表达式也称后缀表达式。
逆波兰表达式严格遵循「从左到右」的运算。计算逆波兰表达式的值时,使用一个栈存储操作数,从左到右遍历逆波兰表达式,进行如下操作:
-
如果遇到操作数,则将操作数入栈;
-
如果遇到运算符,则将两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数入栈。 整个逆波兰表达式遍历完毕之后,栈内只有一个元素,该元素即为逆波兰表达式的值。
public int evalRPN(String[] tokens) { Deque stack = new LinkedList(); int length = tokens.length; for (int i = 0; i < length; i++) { String token = tokens[i]; if (isOperate(token)) { Integer right = stack.pop(); Integer left = stack.pop(); stack.push(operate(left, right, token)); } else { stack.push(Integer.parseInt(token)); } } return stack.pop();}private Integer operate(Integer left, Integer right, String token) { switch (token) { case "+": return left + right; case "-": return left - right; case "": return left * right; case "/": return left / right; default: return 0; }}private boolean isOperate(String token) { return token.equals("+") || token.equals("-") || token.equals("") || token.equals("/");}