参考英文discuss:
https://leetcode.com/problems/remove-duplicate-letters/discuss/76768/A-short-O(n)-recursive-greedy-solution
这个leetcode discuss上的答案对于我个人而言非常难以理解,而且这哥们写的解释过于抽象,在钻研了一番之后,写出了自己对这个问题的理解,希望给读者朋友们一个参考
原题描述:
Given a string which contains only lowercase letters, remove duplicate letters so that every letter appear once and only once. You must make sure your result is the smallest in lexicographical order among all possible results.
Example 1:
Input: "bcabc"
Output: "abc"
Example 2:
Input: "cbacdcbc"
Output: "acdb"
在解决这个问题之前先要明白两件事,不明白没法做,
- 结果不能改变原字符串中字符的相对顺序.
- 结果是最小字典序(希望详细了解的可以自己去查一下),比如
abcd
这四个字母最小字典序就是abcd
,第二是abdc
,要输出的结果就是这四个字母的最小排列(字典序),并且不能改变原字符串的相对顺序.
详细解释见代码注释
public String removeDuplicateLetters(String s) {
final int n = s.length();
if (n == 0) {
return "";
}
int[] cnt = new int[26];
int pos = 0;
// 这里不做详细解释,楞看都看得懂
for (int i = 0; i < n; ++i) {
++cnt[s.charAt(i) - 'a'];
}
for (int i = 0; i < n; ++i) {
// 找相对字典序最小的字符,定位为pos
if (s.charAt(i) < s.charAt(pos)) {
pos = i;
}
// 1. 在[i...m]已经没有对应的字符s[i]了,在这里结束循环
// 2. 因为此时会选择字典序最小的字符,这个字符是pos,s[pos](相对字典序最小的字符,且pos <= i,s[pos]<=s[i])
// Note : 这里才是最关键的,根据贪心的原理, 原集合S中选出一个最优子集A,得到的子集S-A总是存在最优选择,最优解与贪心选择组合即可得到原问题的最优解.
if (--cnt[s.charAt(i) - 'a'] == 0) {
break;
}
}
// 3. 此时去掉所有在字符串中出现的s[pos],并将s[pos]加到当前递归过程中生成的子字符串的第一个字符.
// 4. 继续递归子字符串.
return s.charAt(pos) +
removeDuplicateLetters(s.substring(pos + 1).replaceAll("" + s.charAt(pos), ""));
}