1. 栈的实现 1.1 设计一个支持增量操作的栈 请你设计一个支持对其元素进行增量操作的栈。
实现自定义栈类 CustomStack
:
CustomStack(int maxSize)
:用 maxSize
初始化对象,maxSize
是栈中最多能容纳的元素数量。
void push(int x)
:如果栈还未增长到 maxSize
,就将 x
添加到栈顶。
int pop()
:弹出栈顶元素,并返回栈顶的值,或栈为空时返回 -1 。
void inc(int k, int val)
:栈底的 k
个元素的值都增加 val
。如果栈中元素总数小于 k
,则栈中的所有元素都增加 val
。
思路
使用数组进行模拟即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class CustomStack { List<Integer> stack; int size; public CustomStack (int maxSize) { stack=new ArrayList <>(maxSize); size=maxSize; } public void push (int x) { if (stack.size()<size){ stack.add(x); } } public int pop () { return stack.isEmpty()?-1 :stack.remove(stack.size()-1 ); } public void increment (int k, int val) { int s=stack.size()<k?stack.size():k; for (int i=0 ;i<s;i++){ stack.set(i,stack.get(i)+val); } } }
1.2 最小栈 设计一个支持 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack
类:
MinStack()
初始化堆栈对象。
void push(int val)
将元素val推入堆栈。
void pop()
删除堆栈顶部的元素。
int top()
获取堆栈顶部的元素。
int getMin()
获取堆栈中的最小元素。
思路
维护两个栈,一个是当前栈,一个是最小栈。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class MinStack { Deque<Integer> stack; Deque<Integer> minStack; public MinStack () { stack=new LinkedList <>(); minStack=new LinkedList <>(); minStack.push(Integer.MAX_VALUE); } public void push (int val) { stack.push(val); minStack.push(minStack.peek()<=val?minStack.peek():val); } public void pop () { stack.pop(); minStack.pop(); } public int top () { return stack.peek(); } public int getMin () { return minStack.peek(); } }
2. 栈的应用 2.1 有效的括号 给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
思路
使用栈进行模拟,每次比较栈顶元素与当前元素是否配对。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public boolean isValid (String s) { Deque<Character> hep=new LinkedList <>(); int n=s.length(); for (int i=0 ;i<n;i++){ char k=s.charAt(i); if (k=='(' ||k=='[' ||k=='{' ){ hep.push(k); }else { if (hep.isEmpty()){ return false ; }else { char r=hep.poll(); if ((k==')' &&r=='(' )||(k==']' &&r=='[' )||(k=='}' &&r=='{' )){ continue ; }else { return false ; } } } } return hep.isEmpty(); } }
2.2 移除无效的括号 给你一个由 '('
、')'
和小写字母组成的字符串 s
。
你需要从字符串中删除最少数目的 '('
或者 ')'
(可以删除任意位置的括号),使得剩下的「括号字符串」有效。
请返回任意一个合法字符串。
有效「括号字符串」应当符合以下 任意一条 要求:
空字符串或只包含小写字母的字符串
可以被写作 AB
(A
连接 B
)的字符串,其中 A
和 B
都是有效「括号字符串」
可以被写作 (A)
的字符串,其中 A
是一个有效的「括号字符串」
思路
栈思想模拟,仅一种括号类型,无需使用栈,仅需一个变量存储当前状态即可。
首先删除多余的)
,然后根据left
的数量删除多余的左括号。
注意:删除左括号时,要从字符串末尾开始删除,因为从左往右遍历,若左括号多余,则是右端先多出左括号。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Solution { public String minRemoveToMakeValid (String s) { int n=s.length(); int left=0 ; StringBuilder cnt=new StringBuilder (); for (int i=0 ;i<n;i++){ char k=s.charAt(i); if (k=='(' ){ cnt.append(k); left++; }else if (k==')' ){ if (left==0 ){ continue ; }else { left--; cnt.append(k); } }else { cnt.append(k); } } int over=left; int size=cnt.length(); StringBuilder target=new StringBuilder (); for (int i=size-1 ;i>=0 ;i--){ if (over>0 &&cnt.charAt(i)=='(' ){ over--; continue ; } target.append(cnt.charAt(i)); } return target.reverse().toString(); } }
2.3 验证栈序列 给定 pushed
和 popped
两个序列,每个序列中的 值都不重复 ,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true
;否则,返回 false
。
思路
模拟入栈与出栈操作。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public boolean validateStackSequences (int [] pushed, int [] popped) { int n=pushed.length; Deque<Integer> stack=new LinkedList <>(); int index=0 ; int id=0 ; while (id<n||index<n){ if (stack.isEmpty()||stack.peek()!=popped[index]){ if (id>=n) return false ; else stack.push(pushed[id++]); }else { stack.pop(); index++; } } return true ; } }
2.4 用栈操作构建数组 给你一个数组 target
和一个整数 n
。每次迭代,需要从 list = { 1 , 2 , 3 ..., n }
中依次读取一个数字。
请使用下述操作来构建目标数组 target
:
"Push"
:从 list
中读取一个新元素, 并将其推入数组中。
"Pop"
:删除数组中的最后一个元素。
如果目标数组构建完成,就停止读取更多元素。
题目数据保证目标数组严格递增,并且只包含 1
到 n
之间的数字。
请返回构建目标数组所用的操作序列。如果存在多个可行方案,返回任一即可。
思路
按照题意进行栈模拟即可
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public List<String> buildArray (int [] target, int n) { List<String> cnt=new ArrayList <>(); String push="Push" ,pop="Pop" ; int len=target.length,id=0 ; for (int i=1 ;i<=n;i++){ if (id==len) return cnt; cnt.add(push); if (i==target[id]){ id++; }else { cnt.add(pop); } } return cnt; } }
3. 表达式求值 3.1 逆波兰表达式求值 给你一个字符串数组 tokens
,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
有效的算符为 '+'
、'-'
、'*'
和 '/'
。
每个操作数(运算对象)都可以是一个整数或者另一个表达式。
两个整数之间的除法总是 向零截断 。
表达式中不含除零运算。
输入是一个根据逆波兰表示法表示的算术表达式。
答案及所有中间计算结果可以用 32 位 整数表示。
逆波兰表达式:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 )
。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * )
。
逆波兰表达式主要有以下两个优点:
去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + *
也可以依据次序计算出正确结果。
适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
思路
栈模拟,遇到操作数则将其入栈,遇到运算符则运算,并将结果入栈。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int evalRPN (String[] tokens) { Deque<Integer> op=new LinkedList <>(); int x=0 ,y=0 ; for (String token:tokens){ if (token.equals("+" )||token.equals("-" )||token.equals("*" )||token.equals("/" )){ switch (token.charAt(0 )){ case '+' :op.push(op.poll()+op.poll());break ; case '-' :x=op.poll();y=op.poll();op.push(y-x);break ; case '*' :op.push(op.poll()*op.poll());break ; case '/' :x=op.poll();y=op.poll();op.push(y/x);break ; } }else { op.push(Integer.parseInt(token)); } } return op.poll(); } }
3.2 基本计算器 II 给你一个字符串表达式 s
,请你实现一个基本计算器来计算并返回它的值。
整数除法仅保留整数部分。
你可以假设给定的表达式总是有效的。所有中间结果将在 [-231, 231 - 1]
的范围内。
注意: 不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()
。
思路
栈模拟,由于表达式中运算符仅有加减乘除,操作数均为非负数,对于加减操作,可以直接将对应操作数推入栈中;对于乘除操作,则推出栈顶元素进行运算后再将结果推入栈中。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public int calculate (String s) { int n=s.length(); Deque<Integer> cnt=new LinkedList <>(); char pre='+' ; int num=0 ; for (int i=0 ;i<n;i++){ if (Character.isDigit(s.charAt(i))){ num=num*10 +s.charAt(i)-'0' ; } if (!Character.isDigit(s.charAt(i))&&s.charAt(i)!=' ' ||i==n-1 ){ switch (pre){ case '+' :cnt.push(num);break ; case '-' :cnt.push(-num);break ; case '*' :cnt.push(cnt.pop()*num);break ; case '/' :cnt.push(cnt.pop()/num);break ; } pre=s.charAt(i); num=0 ; } } int ans=0 ; while (!cnt.isEmpty()){ ans+=cnt.poll(); } return ans; } }
3.3 基本计算器 给你一个字符串表达式 s
,请你实现一个基本计算器来计算并返回它的值。
注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()
。
思路
思路与上一题解类似,表达式中的运算符只有加减,对于左括号,存储它前面的运算符号,对于右括号,直接出栈即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution { public int calculate (String s) { int n=s.length(); int i=0 ; Deque<Integer> cnt=new LinkedList <>(); int flag=1 ; cnt.push(flag); int answer=0 ; while (i<n){ if (s.charAt(i)==' ' ){ i++; }else if (s.charAt(i)=='+' ){ flag=cnt.peek(); i++; }else if (s.charAt(i)=='-' ){ flag=-cnt.peek(); i++; }else if (s.charAt(i)=='(' ){ cnt.push(flag); i++; }else if (s.charAt(i)==')' ){ cnt.pop(); i++; }else { int ans=0 ; while (i<n&&Character.isDigit(s.charAt(i))){ ans=ans*10 +s.charAt(i)-'0' ; i++; } answer+=flag*ans; } } return answer; } }
4. 队列的实现 4.1 设计循环队列 设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k)
: 构造器,设置队列长度为 k 。
Front
: 从队首获取元素。如果队列为空,返回 -1 。
Rear
: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value)
: 向循环队列插入一个元素。如果成功插入则返回真。
deQueue()
: 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty()
: 检查循环队列是否为空。
isFull()
: 检查循环队列是否已满。
思路
通过数组或链表模拟队列。
数组模拟:front
指针指向队首,tail
指针指向最后一个未被占用的空间,即队尾之后。
注意:数组模拟时注意队列为空与队列已满的判断条件。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 class MyCircularQueue { private int front; private int tail; private int size; private int [] queue; public MyCircularQueue (int k) { size=k+1 ; queue=new int [size]; front=0 ; tail=0 ; } public boolean enQueue (int value) { if (isFull()){ return false ; } queue[tail]=value; tail=(tail+1 )%size; return true ; } public boolean deQueue () { if (isEmpty()){ return false ; } front=(front+1 )%size; return true ; } public int Front () { if (isEmpty()){ return -1 ; } return queue[front]; } public int Rear () { if (isEmpty()){ return -1 ; } return queue[(tail-1 +size)%size]; } public boolean isEmpty () { return front==tail; } public boolean isFull () { return (tail+1 )%size==front; } }
4.2 设计循环双端队列 设计实现双端队列。
实现 MyCircularDeque
类:
MyCircularDeque(int k)
:构造函数,双端队列最大为 k
。
boolean insertFront()
:将一个元素添加到双端队列头部。 如果操作成功返回 true
,否则返回 false
。
boolean insertLast()
:将一个元素添加到双端队列尾部。如果操作成功返回 true
,否则返回 false
。
boolean deleteFront()
:从双端队列头部删除一个元素。 如果操作成功返回 true
,否则返回 false
。
boolean deleteLast()
:从双端队列尾部删除一个元素。如果操作成功返回 true
,否则返回 false
。
int getFront()
):从双端队列头部获得一个元素。如果双端队列为空,返回 -1
。
int getRear()
:获得双端队列的最后一个元素。 如果双端队列为空,返回 -1
。
boolean isEmpty()
:若双端队列为空,则返回 true
,否则返回 false
。
boolean isFull()
:若双端队列满了,则返回 true
,否则返回 false
。
思路
双端队列模拟,思路同题解一。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 class MyCircularDeque { private int front; private int tail; private int size; private int [] deque; public MyCircularDeque (int k) { size=k+1 ; deque=new int [size]; front=tail=0 ; } public boolean insertFront (int value) { if (isFull()){ return false ; } front=(front-1 +size)%size; deque[front]=value; return true ; } public boolean insertLast (int value) { if (isFull()){ return false ; } deque[tail]=value; tail=(tail+1 )%size; return true ; } public boolean deleteFront () { if (isEmpty()){ return false ; } front=(front+1 )%size; return true ; } public boolean deleteLast () { if (isEmpty()){ return false ; } tail=(tail-1 +size)%size; return true ; } public int getFront () { if (isEmpty()){ return -1 ; } return deque[front]; } public int getRear () { if (isEmpty()){ return -1 ; } return deque[(tail-1 +size)%size]; } public boolean isEmpty () { return front==tail; } public boolean isFull () { return (tail+1 )%size==front; } }
5. 栈和队列相互实现 5.1 用队列实现栈 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push
、top
、pop
和 empty
)。
实现 MyStack
类:
void push(int x)
将元素 x 压入栈顶。
int pop()
移除并返回栈顶元素。
int top()
返回栈顶元素。
boolean empty()
如果栈是空的,返回 true
;否则,返回 false
。
注意:
你只能使用队列的基本操作 —— 也就是 push to back
、peek/pop from front
、size
和 is empty
这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
思路
维护两个队列,outStack
存储栈内元素,inStack
作为辅助队列,辅助入栈。
每次进行入栈操作时,先将其插入inStack
中,然后再将outStack
中元素依次插入inStack
中,再将二者交换,即实现了栈。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class MyStack { Deque<Integer> inStack; Deque<Integer> outStack; public MyStack () { inStack=new LinkedList <>(); outStack=new LinkedList <>(); } public void push (int x) { inStack.offer(x); while (!outStack.isEmpty()){ inStack.offer(outStack.poll()); } Deque<Integer> tmp=outStack; outStack=inStack; inStack=tmp; } public int pop () { return outStack.poll(); } public int top () { return outStack.peek(); } public boolean empty () { return outStack.isEmpty(); } }
5.2 用栈实现队列 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class MyQueue { Deque<Integer> inQueue; Deque<Integer> outQueue; public MyQueue () { inQueue=new LinkedList <>(); outQueue=new LinkedList <>(); } public void push (int x) { inQueue.push(x); } public int pop () { if (outQueue.isEmpty()){ while (!inQueue.isEmpty()){ outQueue.push(inQueue.pop()); } } return outQueue.pop(); } public int peek () { if (outQueue.isEmpty()){ while (!inQueue.isEmpty()){ outQueue.push(inQueue.pop()); } } return outQueue.peek(); } public boolean empty () { return inQueue.isEmpty()&&outQueue.isEmpty(); } }