算法练习--栈专题
删除字符串中的所有相邻重复项
给出由小写字母组成的字符串 s
,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 s
上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
- 输入:“abbaca”
- 输出:“ca”
- 解释:
例如,在 “abbaca” 中,我们可以删除 “bb” 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa” 可以执行重复项删除操作,所以最后的字符串为 “ca”。
提示:
1 <= s.length <= 105
s
仅由小写英文字母组成。
解题思路
利用栈去模拟这个字母消消乐的过程,没有必要真的去用数据结构的栈stack
,直接使用一个数组模拟即可,在本道题适合用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
28
29
30
31class Solution {
public:
string removeDuplicates(string s) {
stack<char> st;
st.push(s[0]);
int index = 1, n = s.size();
while(index < n)
{
if(st.empty()) st.push(s[index++]);
if(index == n) break;
char ch = st.top();
if(ch == s[index])
st.pop();
else
st.push(s[index]);
index++;
}
string ret;
while(!st.empty())
{
char ch = st.top();
st.pop();
ret += ch;
}
reverse(ret.begin(), ret.end());
return ret;
}
};
优雅代码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class Solution {
public:
// 函数:移除字符串中的所有相邻重复字符
string removeDuplicates(string s) {
string ret; // 创建一个空字符串 ret,用于存储处理后的结果
// 遍历字符串 s 中的每个字符
for(auto ch : s) {
// 如果 ret 不为空且当前字符与 ret 最后一个字符相同,删除 ret 最后一个字符
if(ret.size() && ch == ret.back()) {
ret.pop_back(); // 移除最后一个字符
}
// 否则,将当前字符添加到 ret 的末尾
else {
ret += ch;
}
}
// 返回处理后的字符串
return ret;
}
};
- 时间复杂度:$O(n)$,其中
n
是字符串s
的长度。 - 空间复杂度:$O(1)$,返回值的空间不计入。
比较含退格的字符串
题目:比较含退格的字符串
给定 s
和 t
两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true
。#
代表退格字符。
注意: 如果对空文本输入退格字符,文本继续为空。
示例 1:
- 输入: s = “ab#c”, t = “ad#c”
- 输出: true
- 解释: s 和 t 都会变成 “ac”。
示例 2:
- 输入: s = “ab##”, t = “c#d#”
- 输出: true
- 解释: s 和 t 都会变成 “”。
示例 3:
- 输入: s = “a#c”, t = “b”
- 输出: false
- 解释: s 会变成 “c”,但 t 仍然是 “b”。
提示:
1 <= s.length, t.length <= 200
s
和t
只含有小写字母以及字符'#'
解法一:栈模拟
利用栈去模拟字母串退格的过程,遍历字符串,分情况讨论:
- 如果当前字符是
'#'
:- 栈不为空,弹出栈顶元素。
- 栈为空,直接跳过不做处理(退格符
'#'
可能含有多个,栈不能存储'#'
)。
- 如果当前字符是小写字母:将其加入栈。
最后我们只需要比较退格处理后得到的两个字符串是否相等即可(不需要真的使用stack
,这样还需再做处理,所以用string
模拟即可)
1 | class Solution { |
- 时间复杂度:$O(m + n)$,
m
和n
分别为两个字符串的长度。 - 空间复杂度:$O(m + n)$,用于存储退格处理后的字符串。
解法二:双指针
具体可参考这个:御三五佬的题解
退格处理消去左边的字符,对右边的字符没有影响,所以我们可以有一个大致的思路:从后遍历字符串,遇到'#'
时就用变量记录下有多少个,遇到字母时指针向前移动消去多少个字符。
接着来看具体的分析,我们可以利用双指针i
和j
从后遍历字符串同时用两个变量skipS
和skipT
去记录遍历中两个字符串'#'
的数量,首先得对字符串s
做退格处理,如果当前字符为'#'
,skipS++, i--
;如果当前字符为字母且skipS
不为空,说明该字符需要被消去,skipS--, i--
,由于可能有多个字符需要被消去,故退格处理需要写成循环;如果当前字符为字母且skipS
为空,这时候就不需要退格处理,退出循环。对字符串t
同样需要做退格处理。
当两个字符串都经过退格处理后,需要比较i
和j
位置的字符串是否相等,不相等直接返回false
;当一个指针越界后但另外一个没有走完,也需要返回false
。比较完之后双指针再同时向前移动,整个大循环结束后说明能够一一匹配,返回true
。
1 | class Solution { |
- 时间复杂度:$O(m + n)$,
m
和n
分别为两个字符串的长度。 - 空间复杂度:$O(1)$
基本计算器 II
给你一个字符串表达式 s
,请你实现一个基本计算器来计算并返回它的值。
整数除法仅保留整数部分。
你可以假设给定的表达式总是有效的。所有中间结果将在 [-231, 231 - 1]
的范围内。
注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()
。
示例 1:
- 输入: s = “3+2*2”
- 输出: 7
示例 2:
- 输入: s = “ 3/2 “
- 输出: 1
示例 3:
- 输入: s = “ 3+5 / 2 “
- 输出: 5
提示:
1 <= s.length <= 3 * 105
s
由整数和算符('+', '-', '*', '/')
组成,中间由一些空格隔开s
表示一个 有效表达式- 表达式中的所有整数都是非负整数,且在范围
[0, 231 - 1]
内 - 题目数据保证答案是一个 32-bit 整数
解题思路:栈模拟
用一个数组st
去模拟栈,然后遍历字符串:
- 遇到空格时,跳过。
- 遇到数字时,提取完整的数字(支持多位数字),根据当前运算符对数字进行操作:
- 如果是加号,数字直接加入
st
。
- 如果是减号,数字作为负数加入
st
。 - 如果是乘号或除号,则更新
st
中最后一个元素的值(因为乘除是优先级更高的运算)。
- 如果是加号,数字直接加入
- 遇到运算符时,更新
Operator
。
所有的乘法和除法已经在 st
中通过修改最后一个元素完成了,最后只需要对 retArr
进行求和,得到最终的结果。
1 | class Solution { |
- 时间复杂度:$O(n)$,$n$ 为字符串 $s$ 长度。
- 空间复杂度:$O(n)$,模拟栈的数组 $st$ 的空间开销。
字符串解码
题目:394. 字符串解码
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string]
,表示其中方括号内部的 encoded_string
正好重复 k
次。注意 k
保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k
,例如不会出现像 3a
或 2[4]
的输入。
示例 1:
- 输入: s = “3[a]2[bc]”
- 输出:“aaabcbc”
示例 2:
- 输入: s = “3[a2[c]]”
- 输出:“accaccacc”
示例 3:
- 输入: s = “2[abc]3[cd]ef”
- 输出:“abcabccdcdcdef”
示例 4:
输入: s = “abc3[cd]xyz”
输出:“abccdcdcdxyz”
提示:
1 <= s.length <= 30
s
由小写英文字母、数字和方括号'[]'
组成s
保证是一个 有效 的输入。s
中所有整数的取值范围为[1, 300]
解题思路:栈模拟
利用两个栈ret
和nums
去存储最近的字符串与数字,遍历原始字符串,分情况处理:
- 如果当前字符是数字,将数字提取出来,加入
nums
。 - 如果当前字符是
'['
,将后面的字符串提取出来,加入ret
。 - 如果当前字符是
']'
,两个栈同时出栈,获取需要重复的次数cnt
和字符串str
,将str
追加到ret
的栈顶元素的末尾,重复cnt
次。 - 如果当前字符是字母,提取当前位置及其往后的字符串,将其加入
ret
。
注意:由于步骤3可能会对空栈进行操作,所以将ret
初始化带有一个空字符串的栈。
1 | class Solution { |
- 时间复杂度:$O(n)$,其中
n
是输入字符串的长度。 - 空间复杂度:$O(n)$,主要用于存储解码过程中构建的字符串。
验证栈序列
题目链接:946. 验证栈序列
给定 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 之前弹出。
提示:
1 <= pushed.length <= 1000
0 <= pushed[i] <= 1000
pushed
的所有元素 互不相同popped.length == pushed.length
popped
是pushed
的一个排列
解题思路:栈模拟
通过模拟栈的操作,验证最终是否以popped
数组的顺序进行所有出栈操作。遍历pushed
数组,将元素一个个压入栈中。每次压入栈后,检查栈顶元素是否与popped[i]
相等。如果相等,表示我们可以进行出栈操作,i
自增,继续进行下一步操作,直到栈顶元素与popped[i]
不相等。
遍历完pushed
数组,检查栈是否为空。如果栈为空,表示出栈操作成功完成了所有元素的匹配,返回true
;如果栈不为空,表示有些元素没有按popped
的顺序出栈,返回false
。
1 | class Solution { |
- 时间复杂度:$O(n)$,,其中
n
是数组pushed
和popped
的长度。 - 空间复杂度:$O(n)$,使用了一个栈来保存最多
n
个元素。