去除重複字母(不同字符的最小序列)問題
作者: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();
}