力扣-1209. 刪除字串中的所有相鄰重複項 II

DawnTraveler發表於2024-05-22

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)\),棧空間。

相關文章