每日一题 为了工作 2020 0417 第四十六题

/**
 * 问题:删除多余字符得到字典序最小的字符串
 *       给定一个全是小写字母的字符串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);
    }
}

  

posted @ 2020-04-17 13:21  雪瞳  阅读(151)  评论(0编辑  收藏  举报