用栈实现队列
题目描述
使用栈实现队列的下列操作:
push(x) – 将一个元素放入队列的尾部。
pop() – 从队列首部移除元素。
peek() – 返回队列首部的元素。
empty() – 返回队列是否为空。
示例:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false
说明:
- 你只能使用标准的栈操作 – 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
- 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
- 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)。
解题思路
总体思路
引入两个栈:inStack、outStack 来模拟队列的先入先出操作,具体流程如下:
- 当队列需要插入元素时,将元素直接push到inStack中
- 当队列需要弹出元素时,首先需要调用inToOut函数,判断outStack栈中是否有元素可被弹出,如果没有就从inStack中将元素都push进outStack中
- 如果需要查看队列队首元素,可以在保证outStack不为空的情况下直接调用outStack的top方法,或者可以使用自己实现的pop方法得到outStack的第一个元素值,然后将该值重新插入回去
- 当inStack和outStack均为空时,说明队列为空
时空分析
- 时间复杂度: push和empty为O(1), pop和peek为O(n)
- 空间复杂度: O(n)
代码实现
测试地址:https://leetcode.cn/problems/implement-queue-using-stacks/description/
// 使用两个栈实现的队列类
class MyQueue {
private:
stack<int> inStack; // 输入栈,用于入队操作
stack<int> outStack; // 输出栈,用于出队操作
// 将输入栈中的所有元素转移到输出栈
void inToOut() {
// 仅当输出栈为空时执行,保证了队列的FIFO顺序
if (outStack.empty()) {
while (!inStack.empty()) {
// 将输入栈顶元素压入输出栈
outStack.push(inStack.top());
// 弹出输入栈顶元素
inStack.pop();
}
}
}
public:
// 构造函数,不需要初始化操作
MyQueue() {}
// 入队操作
void push(int x) {
// 将元素压入输入栈
inStack.push(x);
}
// 出队操作
int pop() {
// 确保输出栈有元素可供出队
inToOut();
// 获取输出栈顶元素,即队列前端元素
int x = outStack.top();
// 弹出输出栈顶元素
outStack.pop();
// 返回出队的元素
return x;
}
// 查看队列前端元素
int peek() {
// 确保输出栈有元素可供查看
// inToOut();
// 原始peek实现
// return outStack.top();
// 使用pop函数获取队列前端元素,再将其压回输出栈
int x = this->pop();
outStack.push(x);
// 返回队列前端元素
return x;
}
// 判断队列是否为空
bool empty() {
// 当输入栈和输出栈都为空时,队列为空
return (inStack.empty() && outStack.empty());
}
};
用队列实现栈
题目描述
使用队列实现栈的下列操作:
- push(x) – 元素 x 入栈
- pop() – 移除栈顶元素
- top() – 获取栈顶元素
- empty() – 返回栈是否为空
注意:
- 你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
- 你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
- 你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。
解题思路
总体思路
使用一个队列来模拟栈的相关操作,具体步骤如下:
- 入栈直接调用队列自带的push方法即可
- 出栈时需要先得到队列的长度m,然后将前m-1个元素弹出并插入到队尾,然后将第m元素弹出即可
- 查看栈顶元素对应着队列的最后一个元素,因此可以直接调用队列的back方法即可
- 判断空栈可以直接调用队列的empty方法
时空分析
- 时间复杂度: pop为O(n),其他为O(1)
- 空间复杂度: O(n)
代码实现
测试地址:https://leetcode.cn/problems/implement-queue-using-stacks/description/
class MyStack {
private:
queue<int> q; // 使用单个队列q存储所有元素
public:
// 构造函数,不需要初始化操作
MyStack() {}
// 入栈操作
void push(int x) {
// 直接在队列尾部添加元素
q.push(x);
}
// 出栈操作
int pop() {
// 获取队列的当前大小减1,即除了队尾元素外的元素数量
int m = q.size();
m--;
// 将队列的前面m个元素从队头移除并重新加入到队尾
// 这样做是为了在队头留下原本的队尾元素以供弹出
while (m--) {
q.push(q.front()); // 将队头元素添加到队尾
q.pop(); // 移除队头元素
}
// 此时队头的元素即为原队列的尾部元素,也是栈顶元素
int x = q.front(); // 获取队头元素
q.pop(); // 移除队头元素,即栈顶元素
return x; // 返回栈顶元素
}
// 获取栈顶元素
int top() {
// 直接返回队列尾部元素,即为栈顶元素
return q.back();
}
// 判断栈是否为空
bool empty() {
// 如果队列为空,则栈也为空
return q.empty();
}
};
有效的括号
题目描述
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 注意空字符串可被认为是有效字符串。
示例 1:
- 输入: “()”
- 输出: true
示例 2:
- 输入: “()[]{}”
- 输出: true
示例 3:
- 输入: “(]”
- 输出: false
示例 4:
- 输入: “([)]”
- 输出: false
示例 5:
- 输入: “{[]}”
- 输出: true
解题思路
总体思路
分析题意可知共有三种不符合要求的字符串格式
因此利用栈的特性,在遍历字符发现左括号时,在栈中插入对应右括号,当字符为右括号时,只需要判断当前符号是否和栈顶元素一致,如果一致说明左右括号匹配,弹出判断下一个字符即可,具体实现流程如下:
- 初始化:定义一个字符类型的栈结构,用于存放字符串中对应的括号
- 剪枝操作:判断传入字符串的长度是否为奇数个,如果是的话,括号必然无法两两匹配,返回false
- 字符匹配:遍历字符串,当字符为(、{、[时,分别向栈中插入与之对应的右括号,当字符串中发现右括号时,判断栈顶元素和当前字符是否一致,如果一致说明存在与之匹配的左括号
- 异常判断:如果字符串没有遍历完时,发现栈空了,说明有多余的右括号,如果在遍历完字符串后,栈仍然不为空,说明有多余左括号
时空分析
- 时间复杂度: O(n)
- 空间复杂度: O(n)
代码实现
测试地址:https://leetcode.cn/problems/valid-parentheses/
class Solution {
private:
//定义字符类型的栈
stack<char> charStack;
public:
bool isValid(string s) {
//剪枝操作,如果字符串为奇数,那么肯定会有多余的括号
if (s.size() % 2 != 0) {
return false;
}
for (int i = 0; i < s.size(); i++) {
//遍历字符串,当字符为(、{、[时,分别向栈中插入与之对应的右括号
if (s[i] == '(') {
charStack.push(')');
} else if (s[i] == '{') {
charStack.push('}');
} else if (s[i] == '[') {
charStack.push(']');
//当字符串中发现右括号时,判断栈顶元素和当前字符是否一致,如果一致说明存在与之匹配的左括号
//如果字符串没有遍历完时,发现栈空了,说明有多余的右括号
} else if (charStack.empty() || charStack.top() != s[i]) {
return false;
} else {
charStack.pop();
}
}
//如果在遍历完字符串后,栈仍然不为空,说明有多余左括号
if (!charStack.empty()) {
return false;
}
return true;
}
};
删除字符串中的所有相邻重复项
题目描述
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
- 输入:“abbaca”
- 输出:“ca”
- 解释:例如,在 “abbaca” 中,我们可以删除 “bb” 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa” 可以执行重复项删除操作,所以最后的字符串为 “ca”。
提示:
- 1 <= S.length <= 20000
- S 仅由小写英文字母组成
解题思路
栈实现
总体思路
通过分析题意,需要处理相邻重复的元素,当进行两两元素判断时,可以采用栈的方式进行操作,具体过程如下:
- 遍历字符串,如果栈是空栈或者栈顶元素不等于当前字符,说明相邻字符不重复,将当前字符压入栈中
- 如果当前字符等于栈顶元素,说明相邻字符属于重复元素,需要弹出栈顶元素
- 当遍历完成后,如果栈任然不为空,说明字符串存在相邻不重复项,可以定义一个字符串依次拼接栈弹出的元素生成结果字符串
- 由于栈的弹出是逆序操作,因此需要对结果字符串进行翻转,返回结果字符串即可
时空分析
- 时间复杂度:O(n)
- 空间复杂度:O(n)
字符串实现
总体思路
使用字符串模拟上述栈实现操作,遍历字符串,如果结果字符串为空或者当前字符不等于结果字符串尾部,将当前字符添加到结果字符串尾部,反之将结果字符串尾部元素移出,完成字符串遍历后,返回结果字符串即可。
时空分析
- 时间复杂度:O(n)
- 空间复杂度:O(1),返回值不计空间复杂度
代码实现
测试地址:https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/
栈实现:
class Solution {
public:
string removeDuplicates(string s) {
// 创建一个栈来存储字符
stack<char> st;
// 遍历输入字符串 s 中的每个字符
for (char c : s) {
// 如果栈为空或者当前字符与栈顶字符不同,则将当前字符压入栈中
if (st.empty() || c != st.top()) {
st.push(c);
}
// 如果当前字符与栈顶字符相同,则将栈顶字符弹出,即移除重复字符
else {
st.pop();
}
}
// 创建一个空字符串 ss 用于存储结果
string ss = "";
// 将栈中的字符依次弹出并添加到字符串 ss 的前端
while (!st.empty()) {
ss += st.top();
st.pop();
}
// 反转字符串 ss,因为之前是逆序添加的
reverse(ss.begin(), ss.end());
// 返回处理后的字符串
return ss;
}
};
字符串实现:
class Solution {
public:
string removeDuplicates(string s) {
string ss; //结果字符串
//遍历字符串s
for (char c : s) {
//如果结果字符串为空,或者当前字符不等于结果字符串的结尾字符则将当前字符插入到结果字符串中
if (ss.empty() || c != ss.back()) {
ss.push_back(c);
} else {
//如果当前字符等于结果字符串的结尾字符,说明发现重复相邻的字符,删除结果字符串最后一个字符。
ss.pop_back();
}
}
return ss;
}
};