文章目录
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(双端队列)来模拟一个栈,只要是标准的栈操作即可。
示例:
输入:
["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
思路
队列:先进先出
使用栈来模式队列的行为,如果仅仅用一个栈,是一定不行的,所以需要两个栈一个输入栈,一个输出栈,这里要注意输入栈和输出栈的关系。
难点
在push数据的时候,只要数据放进输入栈就好,但在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入),再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。
最后如何判断队列为空呢?如果进栈和出栈都为空的话,说明模拟的队列为空了。
public class MyQueue {
Stack<Integer> stackIn;
Stack<Integer> stackOut;
public MyQueue() {
stackIn = new Stack<>(); // 负责进栈
stackOut = new Stack<>(); // 负责出栈
}
public void push(int x) {
stackIn.push(x);
}
public int pop() {
if (stackOut.isEmpty()) {
while (!stackIn.empty()) {
stackOut.push(stackIn.peek());
stackIn.pop();
}
}
return stackOut.pop();
}
public int peek() {
if (stackOut.isEmpty()) {
while (!stackIn.empty()) {
stackOut.push(stackIn.peek());
stackIn.pop();
}
}
return stackOut.peek();
}
public boolean empty() {
return stackIn.isEmpty() && stackOut.isEmpty();
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
225. 用队列实现栈
请你仅使用两个队列实现一个后入先出(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(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
-
你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。
示例:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
思路
栈:先进后出
用2个队列:用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。
难点
用1个队列:一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时在去弹出元素就是栈的顺序了。
public class MyStack {
// Deque 接口继承了 Queue 接口
// 所以 Queue 中的 add、poll、peek等效于 Deque 中的 addLast、pollFirst、peekFirst
Deque<Integer> que1;
public MyStack() {
que1 = new ArrayDeque<>();
}
public void push(int x) {
que1.addLast(x);
}
public int pop() {
// 将最后一个元素前面的元素全部取出
// 队列大小
int size = que1.size();
// 最后一个入队的除外
size--;
// 将前面的元素全部拿出来重新入队
while (size-- > 0) {
que1.addLast(que1.peekFirst()); // 加到队尾
que1.pollFirst(); // 队头弹出
}
return que1.pollFirst(); // 弹出现在的第一个元素
}
public int top() {
return que1.peekLast(); // 返回队尾
}
public boolean empty() {
return que1.isEmpty();
}
}
/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/
20. 有效的括号
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例1:
输入:s = "()"
输出:true
示例2:
输入:s = "()[]{}"
输出:true
示例3:
输入:s = "(]"
输出:false
思路
- 遍历到左括号时将相匹配的右括号放入栈中(方便匹配)
- 遍历到右括号时判断是否与栈顶元素相等
难点1:剪枝操作
- 剪枝操作:如果字符串长度为奇数,那么括号是肯定不会匹配的,可以直接返回false
难点2:三种不匹配的情况
- 字符串里左方向的括号多余了,所以不匹配。
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
- 括号没有多余,但是括号的类型没有匹配上。
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
- 字符串里右方向的括号多余了,所以不匹配。
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
那么什么时候说明左括号和右括号全都匹配了呢?就是字符串遍历完之后,栈是空的,就说明全都匹配了。
在判断时需要先判断第三种情况:栈已经为空了(以免对空栈操作),再判断第二种情况:左右括号不匹配
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
// 剪枝操作
if (s.length() % 2 == 1) {
return false;
}
// 遍历
for (int i = 0; i < s.length(); i++) {
// 碰到左括号,就把相应的右括号入栈
if (s.charAt(i) == '(') {
stack.push(')');
}else if (s.charAt(i) == '{') {
stack.push('}');
}else if (s.charAt(i) == '[') {
stack.push(']');
}else if (stack.isEmpty() || stack.peek() != s.charAt(i)) {
// 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
// 第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false
return false;
}else {
//如果是右括号和栈顶元素匹配
stack.pop();
}
}
// 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
return stack.isEmpty();
}
1047. 删除字符串中的所有相邻重复项
给出由小写字母组成的字符串 S
,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
思路
该题也是匹配问题,可以用堆栈来解决
栈里应该放的是前面已经遍历过的、不重复的元素,在遍历下一个元素的时候。在栈里查看是否和上一个元素相同,相同则将栈顶pop掉
难点
在遍历数组存放进栈时,首先判断栈是否为空或者是否有相同元素,然后再进行push操作。
在最后对字符串的加减时,不能用str += deque.pop()
,要用str = deque.pop() + str
方法一:Deque 作为堆栈
public String removeDuplicates(String s) {
// 使用 Deque 作为堆栈
Deque<Character> que = new ArrayDeque<>();
// 遍历
for (int i = 0; i < s.length(); i++) {
if (que.isEmpty() || que.peek() != s.charAt(i)) {
que.push(s.charAt(i));
}else {
que.pop();
}
}
// 剩下的元素即是不重复的元素
String str = "";
while (!que.isEmpty()) {
str = que.pop() + str;
}
return str;
}
方法二:字符串直接作为栈
拿字符串直接作为栈,省去了栈还要转为字符串的操作。
// 拿字符串直接作为栈,省去了栈还要转为字符串的操作
public String removeDuplicates1(String s) {
// 将 res 当做栈
StringBuffer res = new StringBuffer();
// len为 res 的长度
int len = -1;
for (int i = 0; i < s.length(); i++) {
if (len < 0 || res.charAt(len) != s.charAt(i)) {
res.append(s.charAt(i));
len++;
}else {
res.deleteCharAt(len);
len--;
}
}
return res.toString();
}