232. 用栈实现队列
题目链接:click here
文章讲解:click here
题目描述:使用栈实现队列的下列操作:
push(x) – 将一个元素放入队列的尾部。
pop() – 从队列首部移除元素。
peek() – 返回队列首部的元素。
empty() – 返回队列是否为空。思路:
关键在于栈的输出是与输入反序的,而涉及到反序的输出,昨天的字符串恰好有启发,double 反转,就变顺序了,这里也一样,拿另一个栈先接收输出序列,再从这个栈输出的时候就是和输入顺序一致的,也就模拟了队列的行为。进一步的,讲解中总结这种方法为输入、输出栈模式,并且有一个细节,就是每次调用pop或者peak接口的时候,如果输出栈内仍然有元素,那么就直接输出,因为里面就是最早输入“队列”的元素。而如果输出栈是空的,则本次需要先倒腾一手,把输入栈里的东西全都倒入输出栈。这样就确保了一般情况下,输入、输出都是O(1)复杂度,只有输出栈为空的时候,需要更大的开销。并且,由于所有的元素只会进、出输入输出栈各一次,所以总体复杂度还是O(n)。
代码:
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
40class MyQueue
{
public:
stack<int> input_s;
stack<int> output_s;
MyQueue()
{
}
void push(int x)
{
input_s.push(x);
}
int pop()
{
int res = peek();
output_s.pop();
return res;
}
int peek()
{
if (output_s.empty())
{
while (!input_s.empty())
{
output_s.push(input_s.top());
input_s.pop();
}
}
return output_s.top();
}
bool empty()
{
return output_s.empty() && input_s.empty();
}
};讲解代码中,实现了pop函数,然后peak调用pop,然后再把pop出来的元素塞回输出栈,按我的写法是可以避免的。
时空复杂度分析:
O(n) 时间复杂度
O(n) 空间复杂度
225. 用队列实现栈
题目链接:click here
文章讲解:click here
题目描述:使用队列实现栈的下列操作:
push(x) – 元素 x 入栈
pop() – 移除栈顶元素
top() – 获取栈顶元素
empty() – 返回栈是否为空思路:
这里感觉用两个队列来模拟栈的行为还比较奇怪,因为栈无法实现反序的效果。就用一个栈吧,只能手动操作了,结合昨天字符串左右旋的思路,如果我把队列前方的n-1个元素都旋转到最后方,此时队列最前方的元素,也就是最后输入的元素,就是最先输出的,模拟了栈的行为。
细节是,这种旋转操作,如果不把最前方的元素pop出去,后续就很难维护整体的有序性了。所以这里代码要实现pop函数,然后top调用pop,并把pop出来的那个元素重新塞回队尾,也就是“栈顶”。
感觉讲解里对top直接用队列的back其实是作弊了。
代码:
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
38class MyStack
{
public:
queue<int> q;
MyStack()
{
}
void push(int x)
{
q.push(x);
}
int pop()
{
for (int i = 1; i < q.size(); i++)
{
q.push(q.front());
q.pop();
}
int res = q.front();
q.pop();
return res;
}
int top()
{
int top = pop();
q.push(top);
return top;
}
bool empty()
{
return q.empty();
}
};时空复杂度分析:
O(n) 时间复杂度
O(n) 空间复杂度
20. 有效的括号
题目链接:click here
文章讲解:click here
题目描述:给定一个只包括 ‘(‘,’)’,’{‘,’}’,’[‘,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。思路:
字符串里只有括号类型,要判断字符串是否满足上述条件,一个个翻译即可:- 左括号必须用相同类型的右括号闭合 + 左括号必须以正确的顺序闭合 = 读取到右括号时,它之前一定是对应的左括号
- 注意空字符串可被认为是有效字符串 = 特殊情况
这种近邻相消的题目,适合用栈来做,因为每次相消后,栈顶元素就自动变为更旧的那个元素,用于与新来的元素进行比对。用讲解的话来说就是:括号匹配是使用栈解决的经典问题。
具体的,从左往右读取字符串,左括号统统入栈,读取到右括号时,检测栈顶是否为对应的左括号,进而判断。
代码:写得非常直译思路
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
58class Solution
{
public:
bool isValid(string s)
{
if (s.size() % 2 != 0)
return false;
stack<char> sc;
for (auto c : s)
{
if (c == '(' || c == '[' || c == '{')
{
sc.push(c);
}
else
{
if (c == ')')
{
if (!sc.empty() && sc.top() == '(')
{
sc.pop();
}
else
{
return false;
}
}
if (c == ']')
{
if (!sc.empty() && sc.top() == '[')
{
sc.pop();
}
else
{
return false;
}
}
if (c == '}')
{
if (!sc.empty() && sc.top() == '{')
{
sc.pop();
}
else
{
return false;
}
}
}
}
return sc.empty();
}
};时空复杂度分析:
O(n) 时间复杂度
O(n) 空间复杂度讲解代码用到了技巧:在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Solution {
public:
bool isValid(string s) {
if (s.size() % 2 != 0) return false; // 如果s的长度为奇数,一定不符合要求
stack<char> st;
for (int i = 0; i < s.size(); i++) {
if (s[i] == '(') st.push(')');
else if (s[i] == '{') st.push('}');
else if (s[i] == '[') st.push(']');
// 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
// 第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false
else if (st.empty() || st.top() != s[i]) return false;
else st.pop(); // st.top() 与 s[i]相等,栈弹出元素
}
// 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
return st.empty();
}
};
1047. 删除字符串中的所有相邻重复项
题目链接:click here
文章讲解:click here
题目描述:给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
思路:
又是相邻匹配然后相消的,只不过上一题是左右括号要匹配,这道题直接是元素相等匹配。
消除后,要将剩余的元素重新由栈转string,需要注意反序的特性。代码:
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
28class Solution
{
public:
string removeDuplicates(string s)
{
stack<char> sc;
for (auto c : s)
{
if (!sc.empty() && sc.top() == c)
{
sc.pop();
}
else
{
sc.push(c);
}
}
string().swap(s);
int size = sc.size();
s.resize(size);
for (int i = size - 1; i >= 0; i--)
{
s[i] = sc.top();
sc.pop();
}
return s;
}
};时空复杂度分析:
O(n) 时间复杂度
O(n) 空间复杂度讲解代码:进一步优化,直接以string模拟栈,进而避免由栈转字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Solution {
public:
string removeDuplicates(string S) {
string result;
for(char s : S) {
if(result.empty() || result.back() != s) {
result.push_back(s);
}
else {
result.pop_back();
}
}
return result;
}
};时空复杂度分析:
O(n) 时间复杂度
O(1) 空间复杂度
今日收获
个人博客更新。若Blog有误或是有其他想交流的,左侧有联系方式。
复习:
今日核心思想:近邻匹配问题都是栈的强项用栈实现队列:模拟题,double反转后顺序不变的想法再次利用,输入输出栈模拟队列。
用队列实现栈:模拟题,旋转换位思想,注意top后,为了维护顺序不变,需要将pop出来的元素重塞回队尾。
有效的括号:近邻匹配,实现技巧上,通过将压入栈的内容取反,使得后续匹配到相反内容时,实际上是检测是否相等,这个想法后续可能会推广到更一般的function上。
删除字符串中的所有相邻重复项:近邻匹配,实现技巧上,直接用vector模拟栈,避免后续栈转vector的冗余操作。耗时:2h