1.題目
題目地址(1209. 刪除字串中的所有相鄰重複項 II - 力扣(LeetCode))
https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string-ii/
題目描述
給你一個字串 s
,「k
倍重複項刪除操作」將會從 s
中選擇 k
個相鄰且相等的字母,並刪除它們,使被刪去的字串的左側和右側連在一起。
你需要對 s
重複進行無限次這樣的刪除操作,直到無法繼續為止。
在執行完所有刪除操作後,返回最終得到的字串。
本題答案保證唯一。
示例 1:
輸入:s = "abcd", k = 2 輸出:"abcd" 解釋:沒有要刪除的內容。
示例 2:
輸入:s = "deeedbbcccbdaa", k = 3 輸出:"aa" 解釋: 先刪除 "eee" 和 "ccc",得到 "ddbbbdaa" 再刪除 "bbb",得到 "dddaa" 最後刪除 "ddd",得到 "aa"
示例 3:
輸入:s = "pbbcggttciiippooaais", k = 2 輸出:"ps"
提示:
1 <= s.length <= 10^5
2 <= k <= 10^4
s
中只含有小寫英文字母。
2.題解
2.1 暴力列舉
思路
總體思路是從頭開始遍歷字串,如果遇到相同用計數器計數直到:
1.遇見不同的但是總個數<k, 清空計數器,繼續向後查詢
2.總個數達到k個,這時候需要開始刪除元素,然後我們需要重新遍歷字串
然後什麼時候結束呢?
這裡的思路十分巧妙,如果我們從頭到尾遍歷一遍字串,但是字串的個數沒有發生變化(沒有發生刪除操作),說明此時已經找不到要刪除的元素了,結束即可。
具體的處理過程這裡兩種思路:
1.設定計數器count初始值為1(表明記錄第一個值),我每次檢測當前的和下一個是否相等,相等則++計數器,這裡計算的總個數是包括下一個在內的!!!
所以下面如果要進行刪除的話,是從i+1開始刪除,而不是i,刪除k個元素(由於第一個元素和i+1相差k個元素,實際上k箇中包含了i+1本身,所以我們實際上往回倒k-1個元素即可)
也就是s.erase(i + 1 - k + 1, k);
2.設定計數器count初始值為1,每次檢測當前的和前一個是否相等,如果相等++計數器,這裡計算的總個數就是包含當前個在內的總個數!!!
這裡為了防止越界,單獨處理一下i==0
的情況,
程式碼1-以next為基準數
- 語言支援:C++
C++ Code:
class Solution {
public:
string removeDuplicates(string s, int k) {
int length = -1;
// 迴圈直到字串長度不再變化,表示沒有更多的重複項可以刪除
while(length != s.length()) {
length = s.length();
for(int i = 0, count = 1; i < s.length() - 1; i++) {
if(s[i] != s[i + 1]) {
count = 1; // 如果字元不同,計數器重置為1
} else {
// 如果計數器達到k,刪除這k個字元
if(++count == k) {
s.erase(i + 1 - k + 1, k);
break; // 刪除後跳出迴圈,從頭開始檢查新的字串
}
}
}
}
return s;
}
};
程式碼2-以cur為基準數
class Solution {
public:
string removeDuplicates(string s, int k) {
int length = -1;
while (length != s.size()) {
length = s.size();
for (int i = 0, count = 1; i < s.size(); ++i) {
if (i == 0 || s[i] != s[i - 1]) {
count = 1;
} else if (++count == k) {
s.erase(i - k + 1, k);
break;
}
}
}
return s;
}
};
複雜度分析
- 時間複雜度:\(\mathcal{O}(n^2/k)\),其中\(n\)是字串的長度。字串掃描不超過\(n/k\)次。
- 空間複雜度:\(\mathcal{O}(1)\)。某些語言會建立字串的副本,但演算法只在字串本身上操作。
2.2 記憶計數
思路
使用一個記憶陣列記憶每個位置的連續數情況,每次新一個的記憶陣列大小由:1.前一個記憶陣列大小 和 2.當前數和前一個數是否相等 兩個條件共同判斷
這裡注意在我們處於位置i進行刪除後,要進行回退操作,這裡回退k個,恰好退到刪除數列首端的前一個,這個地方已經統計過記憶陣列了,迴圈結束後i++自動變到了我們待求記憶陣列的第一個數上
程式碼
class Solution {
public:
string removeDuplicates(string s, int k) {
int n = s.length();
vector<int> count(n);
for (int i = 0; i < n; i++) {
if(i == 0 || s[i] != s[i-1]){
count[i] = 1;
}else{
count[i] = count[i-1] + 1;
if(count[i] == k){
s.erase(i - k + 1, k);
i = i - k; // 退到這k個數的前一個,結尾處i++, 就到了刪除後新尾端的第一個
}
}
}
return s;
}
};
複雜度分析
- 時間複雜度:\(O(n)\),其中\(n\)是字串長度。每個字元僅被處理一次。
- 空間複雜度:\(\mathcal{O}(n)\),儲存每個字元的計數器。
2.3 棧
思路
用棧代替上面的陣列,遇到相同的則將棧頂元素加一,不同的則壓入一個新的‘1’, 如果棧頂元素 == k, 則彈出棧, 進行字串刪除
程式碼
class Solution {
public:
string removeDuplicates(string s, int k) {
int n = s.length();
stack<int> stk;
for (int i = 0; i < n; i++) {
if(i == 0 || s[i] != s[i-1]){
stk.push(1);
}else if(++stk.top() == k){
stk.pop();
s.erase(i - k + 1, k);
i = i - k;
}
}
return s;
}
};
複雜度分析
·時間複雜度:\(\mathcal{O}(n)\),其中\(n\)是字串長度。每個字元只處理一次。
·空間複雜度:\(\mathcal{O}(n)\),棧空間。
2.4 棧重建
思路
我們之前都是將計數器儲存在棧中,然後原地修改字串。
如果將計數器 和 字元都儲存在棧中,則不需要修改字串,只需要根據棧中結果重建字串即可。
程式碼
class Solution {
public:
string removeDuplicates(string s, int k) {
int length = -1;
int n = s.length();
vector<pair<int, char>> count;
for (int i = 0; i < s.length(); ++i) {
if(count.empty() || s[i] != count.back().second){
count.emplace_back(1, s[i]);
}else if(++count.back().first == k){
count.pop_back();
}
}
s = "";
for(int i = 0; i < count.size(); i++){
s += string(count[i].first, count[i].second);
}
return s;
}
};
複雜度分析
·時間複雜度:\(\mathcal{O}(n)\),其中\(n\)是字串長度。每個字元只處理一次。
·空間複雜度:\(\mathcal{O}(n)\),棧空間。
2.5 雙指標
思路
這裡對於字串裁剪操作使用雙指標又進行了一次最佳化
關鍵點在s[j] = s[i];
和j -= k;
上
1.如果沒有發生刪除,s[j] = s[i];
沒有任何影響
2.如果發生刪除,j -= k;
進行回退操作,接下來j和i是有一個位置差的,後面的s[i]就會覆蓋掉前面的[j]完成變相刪除操作
程式碼
string removeDuplicates(string s, int k) {
auto j = 0;
stack<int> counts;
for (auto i = 0; i < s.size(); ++i, ++j) {
s[j] = s[i];
if (j == 0 || s[j] != s[j - 1]) {
counts.push(1);
} else if (++counts.top() == k) {
counts.pop();
j -= k;
}
}
return s.substr(0, j);
}
複雜度分析
·時間複雜度:\(\mathcal{O}(n)\),其中\(n\)是字串長度。每個字元只處理一次。
·空間複雜度:\(\mathcal{O}(n)\),棧空間。