/**
* 问题:删除多余字符得到字典序最小的字符串
* 给定一个全是小写字母的字符串str,删除多余的字符,使得每一种字符都只保留一个,
* 并且要求最终结果字符串的字典序最小。
* 举例:
* str = "acbc",删掉第一个'c',得到"abc",是所有字符串中字典序最小的。
* str = "dbcacbca",删掉第一个'b',第一个'c',第二个'c',第二个'a',得到"dabc"。
* 是所有结果字符串字典序最小的。
* 解答:
* 不考虑怎么去删除,应当考虑怎么去挑选。str的结果字符串记为res,假设res的长度为
* N,其中有k种不同的字符,那么res的长度为K。思路是怎么在str中从左到右依次挑选出res[0],
* res[1],...,res[k-1]。举个例子,str[0..9]="baacbaccac",一共三种字符,所以要在str中
* 从左到右依次找到res[0..2]。
*
* 流程:
* 1.建立str[0..9]的字频统计,b有两个、a有四个、c有四个。
* 2.从左往右遍历str[0..9],遍历到字符的字频统计减一,当发现某一种字符的字频统计已经为0时,
* 遍历停止。在例子中当遍历完"baacb"时,字频统计b有0个、a有2个、c有3个,发现b的字频已经为
* 0,所以停止遍历,当前遍历到str[4]。str[5..9]为"accac"已经没有b了,而流程是在str中从左
* 到右依次挑选出res[0]、res[1]、res[2],所以,如果str[5..9]中的任何一个字符挑选成为res[0],
* 之后过程是在挑选未知的右边继续挑选,那么一定会错过b字符,所以在str[0..4]上挑选res[0]。
* 3.在str[0..4]上找到字典序最小的字符,即str[1]==‘a’,它就是res[0]。
* 4.在挑选字符str[1]的右边,字符串为"acbaccac",删掉所有的'a'字符变为"cbccc",令str="cbccc",
* 下面寻找str[1]。
* 5.建立str[0..4]的词频索引,b有1个,c有4个。
* 6.从左往右遍历str[0..4],遍历到的字符字频统计减一。当发现某一种字符的字频统计已经为0时,
* 遍历停止。当遍历完"cb"时,字频统计b有0个、c有3个,发现b的字频已经为0,所以停止遍历,当前
* 遍历到str[1]。str[2..4]为"ccc"已经没有b了。所以,如果str[2..4]中的任何一个字符挑选成为res[1],
* 之后过程是在挑选未知的右边继续挑选,那么一定会错过b字符,所以在str[0..1]上挑选res[1]。
* 7.在str[0..1]上找到字典序最小的字符,即str[1]==‘b’,它就是res[1]。
* 8.在挑选字符str[1]的右边,字符串为"ccc",删掉所有的'b'字符变为"ccc",令str="cbccc",下面
* 寻找str[2]。
* 9.建立str[0..2]的词频索引,c有3个
* 10.从左往右遍历str[0..2],遍历到的字符字频统计减一。当发现某一种字符的字频统计已经为0时,
* 遍历停止。当遍历完"ccc"时,字频统计c有0个,发现c的字频已经为0,所以停止遍历,当前遍历到
* str[2]。
* 11.在str[0..2]上找到字典序最小的字符,即str[0]==‘c’,它就是res[2]。
*
* 总结:
* 根据字频统计,遍历str时找到一个前缀str[0..R],然后再str[0..R]中找到最小ASCII码的字符
* str[X],就是结果字符串的当前字符。然后令str=(str[X+1..R]去掉所有str[X]得到的字符串),重复
* 整个流程,找到结果字符串的下一个字符,直到res生成完毕。
*/
public class DeleteElementByDictsort {
public static String deleteElements(String string){
if (string.equals(" ") || string == null){
return null;
}
char str[] = string.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){
//如果当前字符不在考虑则直接跳过
//如果当前字符出现次数减一之后,后面还能出现,直接跳过
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]范围内每种字符出现的次数都减少了
// --map 操作将字符词频减少
//需要把str[pick+1..R]中每种字符出现的次数加回来
for (int i = pick+1;i <= R;i++){
if (map[str[i]-'a'] != -1){
map[str[i] -'a']++;
}
}
//选出ASCII码最小的字符,以后就不需要考虑了
map[str[pick]-'a'] = -1;
//继续执行该过程
L = pick + 1;
R = L;
}
}
return String.valueOf(res , 0 ,index);
}
public static void main(String[] args) {
String test = "acbvcccaabdcbbc";
String result = deleteElements(test);
System.out.println(result);
}
}