1. 有效的括号
OJ:有效的括号
给定一个只包括
'('
,')'
,'{'
,'}'
,'['
,']'
的字符串s
,判断字符串是否有效。有效字符串需满足:
(1)左括号必须用相同类型的右括号闭合。
(2)左括号必须以正确的顺序闭合。
(3)每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
解题思路
(1)左括号,直接入栈
(2)右括号,与栈顶的左括号进行匹配,如果不匹配直接返回false;否则继续循环
循环结束后,如果栈空则匹配,否则左括号比右括号多肯定不匹配
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '[' || c == '{' || c == '('){
stack.push(c);
}else {
if (stack.isEmpty()){
return false;
}
char temp = stack.peek();
if (c == '}' && temp == '{'){
stack.pop();
continue;
}
if (c == ']' && temp == '['){
stack.pop();
continue;
}
if (c == ')' && temp == '('){
stack.pop();
continue;
}
return false;
}
}
return stack.isEmpty();
}
2. 逆波兰表达式求值
OJ:
给你一个字符串数组
tokens
,表示一个根据 逆波兰表示法 (也叫后缀表达式:将运算符写在操作数之后)表示的算术表达式。请你计算该表达式。返回一个表示表达式值的整数。
示例 1:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
解题思路
对tokens数组进行遍历,依次获取到每个元素,如果:
(1)该元素是数字(注意:不是运算符肯定是数字),将该数字入栈
(2)该元素是运算符,从栈顶获取该运算符对应的右左操作数,进行相应的操作,最后将结果入栈
(3)循环结束后,栈顶的元素就是最终表达式的结果
public int evalRPN(String[] tokens) {
Stack<Integer> s = new Stack<>();
for(String e : tokens){
// 如果是数字,直接入栈
if(!(e.equals("+") || e.equals("-") || e.equals("*") || e.equals("/"))){
s.push(Integer.valueOf(e));
}else{
// 说明e是个操作符
// 从栈顶取两个数字作为该操作符的右左操作数,
// 注意:栈是后进先出,所以先取到的是右操作数,后取到的是左操作数
int right = s.pop();
int left = s.pop();
// 进行对应的操作,然后将结果入栈
switch(e){
case "+":
s.push(left + right);break;
case "-":
s.push(left - right);break;
case "*":
s.push(left * right);break;
case "/":
s.push(left / right);break;
}
}
}
// 最终栈顶元素就是最后的结果
return s.peek();
}
}
3. 最小栈
OJ:最小栈
设计一个支持
push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。实现
MinStack
类:
MinStack()
初始化堆栈对象。
void push(int val)
将元素val推入堆栈。
void pop()
删除堆栈顶部的元素。
int top()
获取堆栈顶部的元素。
int getMin()
获取堆栈中的最小元素。
示例 1:
输入: ["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出: [null,null,null,null,-3,null,0,-2]
解释: MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop(); minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
解题思路
- 思路1
(1)利用两个栈,一个为正常栈(保存所有的进出栈数据),一个为最小栈(只有当数据为最小时,才会入栈)
(2)核心在于:入栈时,若数据最小,需要两个栈都要入栈,出栈时,如果出的数据为最小,需要更新最小栈
Stack<Integer> dataSt; // 存放数据
Stack<Integer> minSt; // 存放最小值
public MinStack() {
dataSt = new Stack<>();
minSt = new Stack<>();
}
// 入栈时:
// 数据栈:每次都要压入一个元素
// 最小值栈:当最小值栈为空 或者 当前插入元素小于最小值栈栈顶元素时入栈
public void push(int val) {
dataSt.push(val);
if(minSt.isEmpty() || val <= minSt.peek()){
minSt.push(val);
}
}
// 出栈时:
// 最小值栈:栈顶与数据栈栈顶元素相等时才可以出,注意使用equals比较
// 数值栈每次都要出一个元素
public void pop() {
if(dataSt.peek().equals(minSt.peek())){
minSt.pop();
}
dataSt.pop();
}
public int top() {
return dataSt.peek();
}
public int getMin() {
return minSt.peek();
}
- 思路2
利用两个栈,一个正常栈s1,一个存最小栈s2,两个栈为空时,直接入栈;栈不为空时,s1正常入栈,判断入栈元素与栈s2的栈顶元素的大小,比栈顶元素小,元素入s2栈;比栈顶元素大,将栈顶元素再次入栈。
出栈时两个栈顶元素一起出栈,检查栈顶元素时,检查栈s1的,检查最小元素时,检查s2的栈顶元素。
// 保存数值
Stack<Integer> s1 = new Stack<>();
// 保存最小数
Stack<Integer> s2 = new Stack<>();
public MinStack() {
s1 = new Stack<>();
s2 = new Stack<>();
}
public void push(int val) {
s1.push(val);
if (s2.isEmpty()){
s2.push(val);
}else {
int temp = s2.peek();
if (val < temp){
s2.push(val);
}else {
s2.push(temp);
}
}
}
public void pop() {
s1.pop();
s2.pop();
}
public int top() {
return s1.peek();
}
public int getMin() {
return s2.peek();
}
4. 验证栈序列
OJ:验证栈序列
给定
pushed
和popped
两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回true
;否则,返回false
。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行: push(1), push(2), push(3), push(4), pop() -> 4, push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
解题思路
入栈:当栈为空,或者栈顶元素和待出栈元素不相等时
注意:入栈时要保证有元素,如果没有元素则一定不相等
出栈:当栈顶元素待出栈元素相同时出栈
循环进行上述过程即可,直到所有的元素全部出栈
将pushed数组的元素入栈,判断栈顶元素与popped[i]是否相等,相等出栈再将i++,直到栈顶元素与popped[i]不相等,进去下一次循环。最后栈为空时,栈序列正确,不为空栈序列错误。
public boolean validateStackSequences(int[] pushed, int[] popped) {
int i = 0;
Stack<Integer> stack = new Stack<>();
for (int val:pushed
) {
stack.push(val);
while (!stack.isEmpty()&&stack.peek() == popped[i]){
stack.pop();
i++;
}
}
return stack.isEmpty();
}
5. 用队列实现栈
OJ:用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(
push
、top
、pop
和empty
)。实现
MyStack
类:
void push(int x)
将元素 x 压入栈顶。
int pop()
移除并返回栈顶元素。
int top()
返回栈顶元素。
boolean empty()
如果栈是空的,返回true
;否则,返回false
。
解题思路
- 思路1
借助两个队列来模拟实现栈。一个队列是辅助的,起到导元素的作用。
入栈:先将元素放入a队列中,如果b不空,将b中所有元素导入到a中。此时,刚刚入栈的元素刚好在a的队头,然后将a和b队列交换下。即:b队列队头相当于栈顶,b队列队尾相当于栈底
出栈: 直接从b中poll()即可
获取栈顶元素:直接从b中peek()即可
private Queue<Integer> a; // a是用来辅助导元素的
private Queue<Integer> b; // 元素全部放在b中
public MyStack() {
a = new LinkedList<>();
b = new LinkedList<>();
}
public void push(int x) {
// 先把元素放入a队列中
a.offer(x);
while(!b.isEmpty()){
a.offer(b.poll());
}
Queue<Integer> temp = a;
a = b;
b = temp;
}
public int pop() {
return b.poll();
}
public int top() {
return b.peek();
}
public boolean empty() {
return b.isEmpty();
}
- 思路2
先将元素放入队列中,再将元素前面的所有元素依次入队
Queue<Integer> queue = new LinkedList<>();
public MyStack() {
}
public void push(int x) {
if (queue.isEmpty()){
queue.offer(x);
return;
}
int size = queue.size();
queue.offer(x);
while (size != 0){
queue.offer(queue.poll());
size--;
}
}
public int pop() {
return queue.poll();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.isEmpty();
}
6. 用栈实现队列
OJ:用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(
push
、pop
、peek
、empty
):实现
MyQueue
类:
void push(int x)
将元素 x 推到队列的末尾
int pop()
从队列的开头移除并返回元素
int peek()
返回队列开头的元素
boolean empty()
如果队列为空,返回true
;否则,返回false
解题思路
利用两个栈模拟实现队列
s2作为辅助栈,s1作为真正存储的栈。
Stack<Integer> s1 = new Stack<>();
Stack<Integer> s2 = new Stack<>();
public MyQueue() {
}
public void push(int x) {
while (!s1.isEmpty()){
s2.push(s1.pop());
}
s1.push(x);
while (!s2.isEmpty()){
s1.push(s2.pop());
}
}
public int pop() {
return s1.pop();
}
public int peek() {
return s1.peek();
}
public boolean empty() {
return s1.isEmpty();
}
7. 设计循环队列
OJ:设计循环队列
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k)
: 构造器,设置队列长度为 k 。
Front
: 从队首获取元素。如果队列为空,返回 -1 。
Rear
: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value)
: 向循环队列插入一个元素。如果成功插入则返回真。
deQueue()
: 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty()
: 检查循环队列是否为空。
isFull()
: 检查循环队列是否已满。
解题思路
参考这一篇:循环队列
private int head;
private int tail;
private int size;
int data[];
public MyCircularQueue(int k) {
data = new int[k + 1];
head = tail = 0;
}
public boolean enQueue(int value) {
if(isFull()){
return false;
}
data[tail] = value;
tail = (tail + 1) % data.length;
size ++;
return true;
}
public boolean deQueue() {
if(isEmpty()){
return false;
}
head = (head + 1) % data.length;
size --;
return true;
}
public int Front() {
if(isEmpty()){
return -1;
}
return data[head];
}
public int Rear() {
if(isEmpty()){
return -1;
}
return data[(tail - 1 + data.length) % data.length];
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == data.length - 1;
}