力扣- -去除重复字母
力扣- -去除重復字母
文章目錄
- 力扣- -去除重復字母
- 一、題目描述
- 二、分析
- 三、代碼
- 四、問題描述
- 五、代碼
一、題目描述
二、分析
-
題目的要求總結出來有三點:
-
要求一、要去重。
-
要求二、去重字符串中的字符順序不能打亂s中字符出現的相對順序。
-
要求三、在所有符合上一條要求的去重字符串中,字典序最小的作為最終結果。
上述三條要求中,要求三可能有點難理解,舉個例子:
- 比如說輸入字符串s = “babc”,去重且符合相對位置的字符串有兩個,分別是"bac"和"abc",但是我們的算法得返回"abc",因為它的字典序更小。
- 按理說,如果我們想要有序的結果,那就得對原字符串排序對吧,但是排序后就不能保證符合s中字符出現順序了,這似乎是矛盾的。
- 我們先暫時忽略要求三,用「棧」來實現一下要求一和要求二:
-
這段代碼的邏輯很簡單吧,就是用布爾數組inStack記錄棧中元素,達到去重的目的,此時棧中的元素都是沒有重復的。
-
如果輸入s = “bcabc”,這個算法會返回"bca",已經符合要求一和要求二了,但是題目希望要的答案是"abc"對吧。
-
那我們想一想,如果想滿足要求三,保證字典序,需要做些什么修改?
-
在向棧stk中插入字符'a'的這一刻,我們的算法需要知道,字符'a'的字典序和之前的兩個字符'b'和'c'相比,誰大誰小?
-
如果當前字符'a'比之前的字符字典序小,就有可能需要把前面的字符 pop 出棧,讓'a'排在前面,對吧?
那么,我們先改一版代碼:
String removeDuplicateLetters(String s) {Stack<Character> stk = new Stack<>();boolean[] inStack = new boolean[256];for (char c : s.toCharArray()) {if (inStack[c]) continue;// 插入之前,和之前的元素比較一下大小// 如果字典序比前面的小,pop 前面的元素while (!stk.isEmpty() && stk.peek() > c) {// 彈出棧頂元素,并把該元素標記為不在棧中inStack[stk.pop()] = false;}stk.push(c);inStack[c] = true;}StringBuilder sb = new StringBuilder();while (!stk.empty()) {sb.append(stk.pop());}return sb.reverse().toString(); }-
這段代碼也好理解,就是插入了一個 while 循環,連續 pop 出比當前字符小的棧頂字符,直到棧頂元素比當前元素的字典序還小為止。是不是有點「單調棧」的意思了?
-
這樣,對于輸入s = “bcabc”,我們可以得出正確結果"abc"了。
-
但是,如果我改一下輸入,假設s = "bcac",按照剛才的算法邏輯,返回的結果是"ac",而正確答案應該是"bac",分析一下這是怎么回事?
-
很容易發現,因為s中只有唯一一個'b',即便字符'a'的字典序比字符'b'要小,字符'b'也不應該被 pop 出去。
-
那問題出在哪里?
-
我們的算法在stk.peek() > c時才會 pop 元素,其實這時候應該分兩種情況:
-
情況一、如果stk.peek()這個字符之后還會出現,那么可以把它 pop 出去,反正后面還有嘛,后面再 push 到棧里,剛好符合字典序的要求。
-
情況二、如果stk.peek()這個字符之后不會出現了,前面也說了棧中不會存在重復的元素,那么就不能把它 pop 出去,否則你就永遠失去了這個字符。
-
回到s = "bcac"的例子,插入字符'a'的時候,發現前面的字符'c'的字典序比'a'大,且在'a'之后還存在字符'c',那么棧頂的這個'c'就會被 pop 掉。
-
while 循環繼續判斷,發現前面的字符'b'的字典序還是比'a'大,但是在'a'之后再沒有字符'b'了,所以不應該把'b'pop 出去。
-
那么關鍵就在于,如何讓算法知道字符'a'之后有幾個'b'有幾個'c'呢?
也不難,只要再改一版代碼:
三、代碼
String removeDuplicateLetters(String s) {Stack<Character> stk = new Stack<>();// 維護一個計數器記錄字符串中字符的數量// 因為輸入為 ASCII 字符,大小 256 夠用了int[] count = new int[256];for (int i = 0; i < s.length(); i++) {count[s.charAt(i)]++;}boolean[] inStack = new boolean[256];for (char c : s.toCharArray()) {// 每遍歷過一個字符,都將對應的計數減一count[c]--;if (inStack[c]) continue;while (!stk.isEmpty() && stk.peek() > c) {// 若之后不存在棧頂元素了,則停止 popif (count[stk.peek()] == 0) {break;}// 若之后還有,則可以 popinStack[stk.pop()] = false;}stk.push(c);inStack[c] = true;}StringBuilder sb = new StringBuilder();while (!stk.empty()) {sb.append(stk.pop());}return sb.reverse().toString(); }- C++代碼
- 我們用了一個計數器count,當字典序較小的字符試圖「擠掉」棧頂元素的時候,在count中檢查棧頂元素是否是唯一的,只有當后面還存在棧頂元素的時候才能擠掉,否則不能擠掉。
至此,這個算法就結束了,時間空間復雜度都是 O(N)。
-
你還記得我們開頭提到的三個要求嗎?我們是怎么達成這三個要求的?
-
要求一、通過inStack這個布爾數組做到棧stk中不存在重復元素。
-
要求二、我們順序遍歷字符串s,通過「棧」這種順序結構的 push/pop 操作記錄結果字符串,保證了字符出現的順序和s中出現的順序一致。
這里也可以想到為什么要用「棧」這種數據結構,因為先進后出的結構允許我們立即操作剛插入的字符,如果用「隊列」的話肯定是做不到的。
- 要求三、我們用類似單調棧的思路,配合計數器count不斷 pop 掉不符合最小字典序的字符,保證了最終得到的結果字典序最小。
當然,由于棧的結構特點,我們最后需要把棧中元素取出后再反轉一次才是最終結果。
四、問題描述
五、代碼
class Solution { public:string smallestSubsequence(string text) {stack<char> sta;vector<int> count(256,0);for(auto& e : text){count[e - '0']++;}vector<bool> instack(256,false);for(auto& e : text){count[e - '0']--;if(instack[e - '0'] == true){continue;}while(!sta.empty() && sta.top() > e){if(count[sta.top() - '0'] == 0){break;}instack[sta.top() - '0'] = false;sta.pop();}sta.push(e);instack[e - '0'] = true;}string ret;while(!sta.empty()){ret += sta.top();sta.pop();}reverse(ret.begin(),ret.end());return ret;} };總結
以上是生活随笔為你收集整理的力扣- -去除重复字母的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 力扣--让字符串成为回文串的最少插入次数
- 下一篇: 缓存系统中的三座大山