Files
leetcode-master/problems/1047.删除字符串中的所有相邻重复项.md
2025-03-17 10:52:41 +08:00

524 lines
15 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
* [做项目多个C++、Java、Go、测开、前端项目](https://www.programmercarl.com/other/kstar.html)
* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
* [背八股40天挑战高频面试题](https://www.programmercarl.com/xunlian/bagu.html)
> 匹配问题都是栈的强项
# 1047. 删除字符串中的所有相邻重复项
[力扣题目链接](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/)
给出由小写字母组成的字符串 S重复项删除操作会选择两个相邻且相同的字母并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
* 输入:"abbaca"
* 输出:"ca"
* 解释:例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
提示:
* 1 <= S.length <= 20000
* S 仅由小写英文字母组成。
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html)[栈的好戏还要继续!| LeetCode1047. 删除字符串中的所有相邻重复项](https://www.bilibili.com/video/BV12a411P7mw),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
## 思路
### 正题
本题要删除相邻相同元素,相对于[20. 有效的括号](https://programmercarl.com/0020.%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.html)来说其实也是匹配问题20. 有效的括号 是匹配左右括号,本题是匹配相邻元素,最后都是做消除的操作。
本题也是用栈来解决的经典题目。
那么栈里应该放的是什么元素呢?
我们在删除相邻重复项的时候,其实就是要知道当前遍历的这个元素,我们在前一位是不是遍历过一样数值的元素,那么如何记录前面遍历过的元素呢?
所以就是用栈来存放,那么栈的目的,就是存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。
然后再去做对应的消除操作。 如动画所示:
![1047.删除字符串中的所有相邻重复项](https://code-thinking.cdn.bcebos.com/gifs/1047.删除字符串中的所有相邻重复项.gif)
从栈中弹出剩余元素此时是字符串ac因为从栈里弹出的元素是倒序的所以再对字符串进行反转一下就得到了最终的结果。
C++代码 :
```CPP
class Solution {
public:
string removeDuplicates(string S) {
stack<char> st;
for (char s : S) {
if (st.empty() || s != st.top()) {
st.push(s);
} else {
st.pop(); // s 与 st.top()相等的情况
}
}
string result = "";
while (!st.empty()) { // 将栈中元素放到result字符串汇总
result += st.top();
st.pop();
}
reverse (result.begin(), result.end()); // 此时字符串需要反转一下
return result;
}
};
```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
当然可以拿字符串直接作为栈,这样省去了栈还要转为字符串的操作。
代码如下:
```CPP
class 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),返回值不计空间复杂度
### 题外话
这道题目就像是我们玩过的游戏对对碰,如果相同的元素挨在一起就要消除。
可能我们在玩游戏的时候感觉理所当然应该消除,但程序又怎么知道该如何消除呢,特别是消除之后又有新的元素可能挨在一起。
此时游戏的后端逻辑就可以用一个栈来实现(我没有实际考察对对碰或者爱消除游戏的代码实现,仅从原理上进行推断)。
游戏开发可能使用栈结构,编程语言的一些功能实现也会使用栈结构,实现函数递归调用就需要栈,但不是每种编程语言都支持递归,例如:
**递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中**,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
相信大家应该遇到过一种错误就是栈溢出,系统输出的异常是`Segmentation fault`(当然不是所有的`Segmentation fault` 都是栈溢出导致的) ,如果你使用了递归,就要想一想是不是无限递归了,那么系统调用栈就会溢出。
而且**在企业项目开发中,尽量不要使用递归**在项目比较大的时候由于参数多全局变量等等使用递归很容易判断不充分return的条件非常容易无限递归或者递归层级过深**造成栈溢出错误(这种问题还不好排查!)**
## 其他语言版本
### Java
使用 Deque 作为堆栈
```Java
class Solution {
public String removeDuplicates(String S) {
//ArrayDeque会比LinkedList在除了删除元素这一点外会快一点
//参考https://stackoverflow.com/questions/6163166/why-is-arraydeque-better-than-linkedlist
ArrayDeque<Character> deque = new ArrayDeque<>();
char ch;
for (int i = 0; i < S.length(); i++) {
ch = S.charAt(i);
if (deque.isEmpty() || deque.peek() != ch) {
deque.push(ch);
} else {
deque.pop();
}
}
String str = "";
//剩余的元素即为不重复的元素
while (!deque.isEmpty()) {
str = deque.pop() + str;
}
return str;
}
}
```
拿字符串直接作为栈,省去了栈还要转为字符串的操作。
```Java
class Solution {
public String removeDuplicates(String s) {
// 将 res 当做栈
// 也可以用 StringBuilder 来修改字符串,速度更快
// StringBuilder res = new StringBuilder();
StringBuffer res = new StringBuffer();
// top为 res 的长度
int top = -1;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// 当 top >= 0,即栈中有字符时,当前字符如果和栈中字符相等,弹出栈顶字符,同时 top--
if (top >= 0 && res.charAt(top) == c) {
res.deleteCharAt(top);
top--;
// 否则,将该字符 入栈同时top++
} else {
res.append(c);
top++;
}
}
return res.toString();
}
}
```
拓展:双指针
```java
class Solution {
public String removeDuplicates(String s) {
char[] ch = s.toCharArray();
int fast = 0;
int slow = 0;
while(fast < s.length()){
// 直接用fast指针覆盖slow指针的值
ch[slow] = ch[fast];
// 遇到前后相同值的就跳过即slow指针后退一步下次循环就可以直接被覆盖掉了
if(slow > 0 && ch[slow] == ch[slow - 1]){
slow--;
}else{
slow++;
}
fast++;
}
return new String(ch,0,slow);
}
}
```
### Python
```python
# 方法一,使用栈
class Solution:
def removeDuplicates(self, s: str) -> str:
res = list()
for item in s:
if res and res[-1] == item:
res.pop()
else:
res.append(item)
return "".join(res) # 字符串拼接
```
```python
# 方法二,使用双指针模拟栈,如果不让用栈可以作为备选方法。
class Solution:
def removeDuplicates(self, s: str) -> str:
res = list(s)
slow = fast = 0
length = len(res)
while fast < length:
# 如果一样直接换不一样会把后面的填在slow的位置
res[slow] = res[fast]
# 如果发现和前一个一样,就退一格指针
if slow > 0 and res[slow] == res[slow - 1]:
slow -= 1
else:
slow += 1
fast += 1
return ''.join(res[0: slow])
```
### Go
使用栈
```go
func removeDuplicates(s string) string {
stack := make([]rune, 0)
for _, val := range s {
if len(stack) == 0 || val != stack[len(stack)-1] {
stack = append(stack, val)
} else {
stack = stack[:len(stack)-1]
}
}
var res []rune
for len(stack) != 0 { // 将栈中元素放到result字符串汇总
res = append(res, stack[len(stack)-1])
stack = stack[:len(stack)-1]
}
// 此时字符串需要反转一下
l, r := 0, len(res)-1
for l < r {
res[l], res[r] = res[r], res[l]
l++
r--
}
return string(res)
}
```
拿字符串直接作为栈,省去了栈还要转为字符串的操作
```go
func removeDuplicates(s string) string {
var stack []byte
for i := 0; i < len(s);i++ {
// 栈不空 且 与栈顶元素不等
if len(stack) > 0 && stack[len(stack)-1] == s[i] {
// 弹出栈顶元素 并 忽略当前元素(s[i])
stack = stack[:len(stack)-1]
}else{
// 入栈
stack = append(stack, s[i])
}
}
return string(stack)
}
```
### JavaScript:
法一:使用栈
```js
var removeDuplicates = function(s) {
const result = []
for(const i of s){
if(i === result[result.length-1]){
result.pop()
}else{
result.push(i)
}
}
return result.join('')
};
```
法二:双指针(模拟栈)
```js
// 原地解法(双指针模拟栈)
var removeDuplicates = function(s) {
s = [...s];
let top = -1; // 指向栈顶元素的下标
for(let i = 0; i < s.length; i++) {
if(top === -1 || s[top] !== s[i]) { // top === -1 即空栈
s[++top] = s[i]; // 入栈
} else {
top--; // 推出栈
}
}
s.length = top + 1; // 栈顶元素下标 + 1 为栈的长度
return s.join('');
};
```
### TypeScript
```typescript
function removeDuplicates(s: string): string {
const helperStack: string[] = [];
let i: number = 0;
while (i < s.length) {
let top: string = helperStack[helperStack.length - 1];
if (top === s[i]) {
helperStack.pop();
} else {
helperStack.push(s[i]);
}
i++;
}
let res: string = '';
while (helperStack.length > 0) {
res = helperStack.pop() + res;
}
return res;
};
```
### C:
方法一:使用栈
```c
char * removeDuplicates(char * s){
//求出字符串长度
int strLength = strlen(s);
//开辟栈空间。栈空间长度应为字符串长度+1为了存放字符串结束标志'\0'
char* stack = (char*)malloc(sizeof(char) * strLength + 1);
int stackTop = 0;
int index = 0;
//遍历整个字符串
while(index < strLength) {
//取出当前index对应字母之后index+1
char letter = s[index++];
//若栈中有元素,且栈顶字母等于当前字母(两字母相邻)。将栈顶元素弹出
if(stackTop > 0 && letter == stack[stackTop - 1])
stackTop--;
//否则将字母入栈
else
stack[stackTop++] = letter;
}
//存放字符串结束标志'\0'
stack[stackTop] = '\0';
//返回栈本身作为字符串
return stack;
}
```
方法二:双指针法
```c
char * removeDuplicates(char * s){
//创建快慢指针
int fast = 0;
int slow = 0;
//求出字符串长度
int strLength = strlen(s);
//遍历字符串
while(fast < strLength) {
//将当前slow指向字符改为fast指向字符。fast指针+1
char letter = s[slow] = s[fast++];
//若慢指针大于0且慢指针指向元素等于字符串中前一位元素删除慢指针指向当前元素
if(slow > 0 && letter == s[slow - 1])
slow--;
else
slow++;
}
//在字符串结束加入字符串结束标志'\0'
s[slow] = 0;
return s;
}
```
### Swift
```swift
func removeDuplicates(_ s: String) -> String {
var stack = [Character]()
for c in s {
if stack.last == c {
stack.removeLast()
} else {
stack.append(c)
}
}
return String(stack)
}
```
### C#:
```csharp
public string RemoveDuplicates(string s) {
//拿字符串直接作为栈,省去了栈还要转为字符串的操作
StringBuilder res = new StringBuilder();
foreach(char c in s){
if(res.Length > 0 && res[res.Length-1] == c){
res.Remove(res.Length-1, 1);
}else{
res.Append(c);
}
}
return res.ToString();
}
```
### PHP:
```php
class Solution {
function removeDuplicates($s) {
$stack = new SplStack();
for($i=0;$i<strlen($s);$i++){
if($stack->isEmpty() || $s[$i] != $stack->top()){
$stack->push($s[$i]);
}else{
$stack->pop();
}
}
$result = "";
while(!$stack->isEmpty()){
$result.= $stack->top();
$stack->pop();
}
// 此时字符串需要反转一下
return strrev($result);
}
}
```
### Scala:
```scala
object Solution {
import scala.collection.mutable
def removeDuplicates(s: String): String = {
var stack = mutable.Stack[Int]()
var str = "" // 保存最终结果
for (i <- s.indices) {
var tmp = s(i)
// 如果栈非空并且栈顶元素等于当前字符,那么删掉栈顶和字符串最后一个元素
if (!stack.isEmpty && tmp == stack.head) {
str = str.take(str.length - 1)
stack.pop()
} else {
stack.push(tmp)
str += tmp
}
}
str
}
}
```
### Rust:
```rust
impl Solution {
pub fn remove_duplicates(s: String) -> String {
let mut stack = vec![];
let mut chars: Vec<char> = s.chars().collect();
while let Some(c) = chars.pop() {
if stack.is_empty() || stack[stack.len() - 1] != c {
stack.push(c);
} else {
stack.pop();
}
}
stack.into_iter().rev().collect()
}
}
```
### Ruby
```ruby
def remove_duplicates(s)
#数组模拟栈
stack = []
s.each_char do |chr|
if stack.empty?
stack.push chr
else
head = stack.pop
#重新进栈
stack.push head, chr if head != chr
end
end
return stack.join
end
```