去除重複字母(不同字元的最小序列)問題
作者:Grey
原文地址:去除重複字母(不同字元的最小序列)問題
題目描述
LeetCode 316. Remove Duplicate Letters
LeetCode 1081. Smallest Subsequence of Distinct Characters
思路
由於題目說明了,字串都是小寫字母,所以第一步,
我們可以通過設定一個含有26
個元素的陣列來統計每個字元出現的頻率
char[] str = s.toCharArray();
int[] map = new int[26];
for (char c : str) {
map[c - 'a']++;
}
通過如上方法,就可以得到每個字元的詞頻,
第二步,我們設定兩個指標
int l = 0;
int r = 0;
先移動r
指標,r
每次移動一個位置,就把這個位置字元的詞頻減少一個,如果r
某個時刻來到i
位置,i
位置的字元詞頻減少一個後就沒有了,這就說明從[l...r]
區間可以找到一個最小ASCII的字元開始結算了,假設[l...r]
的最小ASCII字元在p
位置,則我們需要做如下處理:
第一步: 將p
位置的字元加入結果字串中。
第二步:將p
位置的詞頻設定為-1
,便於標識p
位置的字元已經使用過了,下次再遇到可以直接跳過:map[str[p]-'a']=-1
第三步:將[p+1...r]
的字元詞頻重新加1,因為這部分的字元是減多了的。
第四步:r
和l
都來到p+1
位置,繼續上述處理流程。
示例圖如下:
原始串和詞頻表如下:
接下來r
位置字元的詞頻減少一個,r
來到下一個位置
此時沒有任何詞頻即將減少為0,繼續移動r
,
接下來移動r
,注:此時d
的詞頻即將減少到0,
接下來,就開始收集第一個元素,即在[l...r]
區間內,找到ASCII最小的元素,即2
號位置的a
元素,此時,將a
收集到結果字串中,並且記錄下a
此時位置,即p=2
。
然後,我們需要把整個字串中a
的詞頻設定為-1
,表示a
字元已經收集過了,即
map['a'-'a']=-1
。
最後,由於a
位置是收集到的位置,而r
已經遍歷到了3
號位置的d
元素,所以從a
所在的位置到r
位置中的所有元素,都要重新加一次詞頻(因為每次移動r
會刪除詞頻)。
完整程式碼如下:
public static String removeDuplicateLetters(String s) {
char[] str = s.toCharArray();
int[] map = new int[26];
for (char c : str) {
map[c - 'a']++;
}
StringBuilder sb = new StringBuilder();
int l = 0;
int r = 0;
while (r < str.length) {
if (map[str[r] - 'a'] == -1 || --map[str[r] - 'a'] > 0) {
// r在一個已經處理過的位置或者r上的詞頻減掉後沒有到0,說明現在
// 還沒有來到需要統計的時刻
// r放心++
r++;
} else {
// r位置的詞頻已經減少到0了
// 可以結算了
int p = l;
for (int i = l; i <= r; i++) {
if (map[str[i] - 'a'] != -1 && str[i] < str[p]) {
p = i;
}
}
if (map[str[p] - 'a'] != -1) {
// 結算的位置必須是有效位置
sb.append(str[p]);
// 結算完畢後,將這個位置的詞頻設定為-1,便於後續判斷此位置是否已經被用過
map[str[p] - 'a'] = -1;
}
for (int i = p + 1; i <= r; i++) {
// [p+1,r]之間的位置的字元,把詞頻還原回來,因為這部分詞頻是減多了的
if (map[str[i] - 'a'] != -1) {
map[str[i] - 'a']++;
}
}
l = p + 1;
r = l;
}
}
return sb.toString();
}