|
|
|
@ -1,74 +1,61 @@
|
|
|
|
|
package class24;
|
|
|
|
|
|
|
|
|
|
// 本题重写了
|
|
|
|
|
// 不用看课上的解释了
|
|
|
|
|
// 这道题最优解是单调栈的解法
|
|
|
|
|
// 看注释能看懂
|
|
|
|
|
// 本题测试链接 : https://leetcode.com/problems/remove-duplicate-letters/
|
|
|
|
|
public class Code06_RemoveDuplicateLettersLessLexi {
|
|
|
|
|
|
|
|
|
|
// 在str中,每种字符都要保留一个,让最后的结果,字典序最小 ,并返回
|
|
|
|
|
public static String removeDuplicateLetters1(String str) {
|
|
|
|
|
if (str == null || str.length() < 2) {
|
|
|
|
|
return str;
|
|
|
|
|
public static String removeDuplicateLetters(String s) {
|
|
|
|
|
int[] cnts = new int[26];
|
|
|
|
|
boolean[] enter = new boolean[26];
|
|
|
|
|
for (int i = 0; i < s.length(); i++) {
|
|
|
|
|
cnts[s.charAt(i) - 'a']++;
|
|
|
|
|
}
|
|
|
|
|
int[] map = new int[256];
|
|
|
|
|
for (int i = 0; i < str.length(); i++) {
|
|
|
|
|
map[str.charAt(i)]++;
|
|
|
|
|
}
|
|
|
|
|
int minACSIndex = 0;
|
|
|
|
|
for (int i = 0; i < str.length(); i++) {
|
|
|
|
|
minACSIndex = str.charAt(minACSIndex) > str.charAt(i) ? i : minACSIndex;
|
|
|
|
|
if (--map[str.charAt(i)] == 0) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 0...break(之前) minACSIndex
|
|
|
|
|
// str[minACSIndex] 剩下的字符串str[minACSIndex+1...] -> 去掉str[minACSIndex]字符 -> s'
|
|
|
|
|
// s'...
|
|
|
|
|
return String.valueOf(str.charAt(minACSIndex)) + removeDuplicateLetters1(
|
|
|
|
|
str.substring(minACSIndex + 1).replaceAll(String.valueOf(str.charAt(minACSIndex)), ""));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static String removeDuplicateLetters2(String s) {
|
|
|
|
|
char[] str = s.toCharArray();
|
|
|
|
|
// 小写字母ascii码值范围[97~122],所以用长度为26的数组做次数统计
|
|
|
|
|
// 如果map[i] > -1,则代表ascii码值为i的字符的出现次数
|
|
|
|
|
// 如果map[i] == -1,则代表ascii码值为i的字符不再考虑
|
|
|
|
|
int[] map = new int[26];
|
|
|
|
|
for (int i = 0; i < str.length; i++) {
|
|
|
|
|
map[str[i] - 'a']++;
|
|
|
|
|
}
|
|
|
|
|
char[] res = new char[26];
|
|
|
|
|
int index = 0;
|
|
|
|
|
int L = 0;
|
|
|
|
|
int R = 0;
|
|
|
|
|
while (R != str.length) {
|
|
|
|
|
// 如果当前字符是不再考虑的,直接跳过
|
|
|
|
|
// 如果当前字符的出现次数减1之后,后面还能出现,直接跳过
|
|
|
|
|
if (map[str[R] - 'a'] == -1 || --map[str[R] - 'a'] > 0) {
|
|
|
|
|
R++;
|
|
|
|
|
} else { // 当前字符需要考虑并且之后不会再出现了
|
|
|
|
|
// 在str[L..R]上所有需要考虑的字符中,找到ascii码最小字符的位置
|
|
|
|
|
int pick = -1;
|
|
|
|
|
for (int i = L; i <= R; i++) {
|
|
|
|
|
if (map[str[i] - 'a'] != -1 && (pick == -1 || str[i] < str[pick])) {
|
|
|
|
|
pick = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 把ascii码最小的字符放到挑选结果中
|
|
|
|
|
res[index++] = str[pick];
|
|
|
|
|
// 在上一个的for循环中,str[L..R]范围上每种字符的出现次数都减少了
|
|
|
|
|
// 需要把str[pick + 1..R]上每种字符的出现次数加回来
|
|
|
|
|
for (int i = pick + 1; i <= R; i++) {
|
|
|
|
|
if (map[str[i] - 'a'] != -1) { // 只增加以后需要考虑字符的次数
|
|
|
|
|
map[str[i] - 'a']++;
|
|
|
|
|
}
|
|
|
|
|
// 单调栈
|
|
|
|
|
// 从左到右只保留依次变大的字符
|
|
|
|
|
// 比如: a c e....
|
|
|
|
|
char[] stack = new char[26];
|
|
|
|
|
int size = 0;
|
|
|
|
|
for (int i = 0; i < s.length(); i++) {
|
|
|
|
|
// 假设当前字符是d
|
|
|
|
|
char cur = s.charAt(i);
|
|
|
|
|
// 如果d已经在单调栈里,不进入!
|
|
|
|
|
// 因为单调栈里每种字符只保留一个
|
|
|
|
|
if (!enter[cur - 'a']) {
|
|
|
|
|
// 如果d不在单调栈里,进入!
|
|
|
|
|
enter[cur - 'a'] = true;
|
|
|
|
|
// 如果单调栈里已经有 :
|
|
|
|
|
// a c e f
|
|
|
|
|
// 当前字符是d
|
|
|
|
|
// 那么f弹出、e弹出、然后d进入,
|
|
|
|
|
// 单调栈变成 : a c d
|
|
|
|
|
// 但是!
|
|
|
|
|
// 如果后面还有f,f才能弹出
|
|
|
|
|
// 如果后面还有e,e才能弹出
|
|
|
|
|
// 如果一个字符要弹出,但是后面已经没有这种字符了,不能弹出!
|
|
|
|
|
// 因为一旦弹出,再也没有机会收集到这种字符了!因为后面没有了
|
|
|
|
|
// 这就是核心逻辑
|
|
|
|
|
// 所以 : size > 0,表示单调栈里有字符
|
|
|
|
|
// stack[size - 1] > cur,表示单调栈当前最右的字符,比当前字符大,那么它弹出
|
|
|
|
|
// cnts[stack[size - 1] - 'a'] > 0,重要限制 : 后面还有单调栈当前最右的字符,才能弹出
|
|
|
|
|
// 缺一不可
|
|
|
|
|
while (size > 0 && stack[size - 1] > cur && cnts[stack[size - 1] - 'a'] > 0) {
|
|
|
|
|
// 这种字符要弹出了,所以标记这种字符出去了
|
|
|
|
|
enter[stack[size - 1] - 'a'] = false;
|
|
|
|
|
// 单调栈大小缩减了,也就是弹出了
|
|
|
|
|
size--;
|
|
|
|
|
}
|
|
|
|
|
// 选出的ascii码最小的字符,以后不再考虑了
|
|
|
|
|
map[str[pick] - 'a'] = -1;
|
|
|
|
|
// 继续在str[pick + 1......]上重复这个过程
|
|
|
|
|
L = pick + 1;
|
|
|
|
|
R = L;
|
|
|
|
|
// 当前字符进入了单调栈
|
|
|
|
|
stack[size++] = cur;
|
|
|
|
|
}
|
|
|
|
|
// 当前字符的词频调整,调整后表示后面还有多少个当前字符
|
|
|
|
|
// 注意词频表更新
|
|
|
|
|
cnts[cur - 'a']--;
|
|
|
|
|
}
|
|
|
|
|
return String.valueOf(res, 0, index);
|
|
|
|
|
// 单调栈里的字符,拼字符串返回
|
|
|
|
|
return String.valueOf(stack, 0, size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|