import java.util.HashMap;
import java.util.HashSet;
import java.util.Stack;

public class RemoveDuplicateLetters {
    // 方法一:暴力法,贪心策略递归
    public String removeDuplicateLetters1(String s){
        // 递归的基准情形
        if ( s.length() == 0 ) return "";

        // 希望找到当前最左侧的字母,位置记为position
        int position = 0;
        // 遍历字符串
        for (int i = 0; i < s.length(); i++){
            // 只有当前字母比已经找到的position位置的字母要小,才有资格继续判断
            if (s.charAt(i) < s.charAt(position)){
                // 定义一个布尔变量,表示当前i位置的字母是否可以替换position位置的字母
                boolean isReplaceable = true;

                // 遍历i之前的所有字母,判断是否在i后面重复出现
                for (int j = position; j < i; j++){
                    // 定义一个布尔变量,表示j位置的字母是否重复出现
                    boolean isDuplicated = false;
                    // 遍历i后面的所有字母,看j位置的字母是否重复出现
                    for (int k = i + 1; k < s.length(); k++){
                        if (s.charAt(k) == s.charAt(j)){
                            isDuplicated = true;
                            break;
                        }
                    }
                    // 如果任一字母不重复出现,就不能替换当前position,后面的字母不用判断
                    if (!isDuplicated){
                        isReplaceable = false;
                        break;
                    }
                }
                if (isReplaceable) position = i;
            }
        }

        // 遍历结束,position位置的字母就是结果中最左侧的元素
        return s.charAt(position) + removeDuplicateLetters1(s.substring(position+1).replaceAll(s.charAt(position) + "", ""));
    }

    // 方法二:贪心策略改进
    public String removeDuplicateLetters2(String s){
        // 递归的基准情形
        if ( s.length() == 0 ) return "";

        // 希望找到当前最左侧的字母,位置记为position
        int position = 0;

        // 定义一个count数组,保存所有26个字母在字符串中出现的频次
        int[] count = new int[26];
        for (int i = 0; i < s.length(); i++)
            count[s.charAt(i) - 'a'] ++;    // count[0]保存a的个数;count[1]保存b的个数

        // 遍历字符串,找到当前最左端字母
        for (int i = 0; i < s.length(); i++){
            // 把当前字符和position位置比较,如果更小就替换
            if (s.charAt(i) < s.charAt(position))
                position = i;
            // 每遇到一个字符,count值就要减1
            // 如果遇到count减为0,就直接退出,以当前最小的字母作为最左端字符
            if (--count[s.charAt(i) - 'a'] == 0)
                break;
        }

        // 递归调用
        return s.charAt(position) + removeDuplicateLetters2(s.substring(position+1).replaceAll(s.charAt(position) + "", ""));
    }

    // 方法三:使用栈进行优化
    public String removeDuplicateLetters(String s){
        // 定义一个字符栈,保存去重之后的结果
        Stack<Character> stack = new Stack<>();

        // 为了快速判断一个字符是否在栈中出现过,用一个Set来保存元素是否出现
        HashSet<Character> seenSet = new HashSet<>();

        // 为了快速判断一个字符是否在某个位置之后重复出现,用一个HashMap来保存字母出现在字符串的最后位置
        HashMap<Character, Integer> lastOccur = new HashMap<>();

        // 遍历字符串,将最后一次出现的位置保存进map
        for (int i = 0; i < s.length(); i++){
            lastOccur.put(s.charAt(i), i);
        }

        // 遍历字符串,判断每个字符是否需要入栈
        for (int i = 0; i < s.length(); i++){
            char c = s.charAt(i);
            // 只有在c没有出现过的情况下,才判断是否入栈
            if (!seenSet.contains(c)){
                // c入栈之前,要判断之前栈顶元素,是否在后面会重复出现;如果重复出现就可以删除
                while ( !stack.isEmpty() && c < stack.peek() && lastOccur.get(stack.peek()) > i ){
                    seenSet.remove(stack.pop());
                }
                stack.push(c);
                seenSet.add(c);
            }
        }

        // 将栈中的元素保存成字符串输出
        StringBuilder stringBuilder = new StringBuilder();
        for (Character c: stack)
            stringBuilder.append(c.charValue());

        return stringBuilder.toString();
    }

    public static void main(String[] args) {
        String str1 = "bcabc";
        String str2 = "cbacdcbc";
        RemoveDuplicateLetters removeDuplicateLetters = new RemoveDuplicateLetters();

        System.out.println(removeDuplicateLetters.removeDuplicateLetters(str1));
        System.out.println(removeDuplicateLetters.removeDuplicateLetters(str2));
    }
}