import java.util.*;

/**
 * NC375 去除重复字母
 * @author d3y1
 */
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param str string字符串
     * @return string字符串
     */
    public String removeDuplicateLetters (String str) {
        // return solution1(str);
        // return solution2(str);
        return solution3(str);
    }

    /**
     * 贪心+单调栈
     * @param str
     * @return
     */
    private String solution2(String str){
        int n = str.length();

        HashMap<Character, Integer> lastIndexMap = new HashMap<>();
        char cur;
        for(int i=n-1; i>=0; i--){
            cur = str.charAt(i);
            if(!lastIndexMap.containsKey(cur)){
                lastIndexMap.put(cur, i);
            }
        }

        Deque<Integer> deque = new LinkedList<>();
        HashSet<Character> usedSet = new HashSet<>();
        // 贪心
        for(int i=0; i<n; i++){
            cur = str.charAt(i);
            // 测试用例: ambcadcb -> ambcd
            // 测试用例: mabcadcb -> mabcd
            if(usedSet.contains(cur)){
                continue;
            }
            // 单调栈(单调增)
            // lastIndexMap.get(str.charAt(deque.peekLast()))>i -> 表示栈顶字符在右侧(后面)有重复
            while(!deque.isEmpty() && str.charAt(deque.peekLast())>cur && lastIndexMap.get(str.charAt(deque.peekLast()))>i){
                usedSet.remove(str.charAt(deque.pollLast()));
            }
            deque.offerLast(i);
            usedSet.add(cur);
        }

        StringBuilder result = new StringBuilder();
        while(!deque.isEmpty()){
            result.append(str.charAt(deque.pollFirst()));
        }

        return result.toString();
    }

    /**
     * 贪心+单调栈
     * @param str
     * @return
     */
    private String solution3(String str){
        int n = str.length();
        char[] letters = str.toCharArray();
        int[] lastIndex = new int[26];

        // 当前字符 最后索引位置
        for(int i=0; i<n; i++){
            lastIndex[letters[i]-'a'] = i;
        }

        boolean[] isUsed = new boolean[26];
        Deque<Character> deque = new ArrayDeque<>();
        char cur;
        // 贪心
        for(int i=0; i<n; i++){
            cur = letters[i];
            if(isUsed[cur-'a']){
                continue;
            }
            // 单调栈(单调增)
            // lastIndex[deque.peekLast()-'a']>i -> 表示栈顶字符在右侧(后面)有重复
            while(!deque.isEmpty() && deque.peekLast()>cur && lastIndex[deque.peekLast()-'a']>i){
                isUsed[deque.pollLast()-'a'] = false;
            }
            deque.offerLast(cur);
            isUsed[cur-'a'] = true;
        }

        StringBuilder result = new StringBuilder();
        while(!deque.isEmpty()){
            result.append(deque.pollFirst());
        }

        return result.toString();
    }

    //////////////////////////////////////////////////////////////////////////////////////

    /**
     * 贪心+单调栈
     * @param str
     * @return
     */
    private String solution1(String str){
        Stack<Character> stack = new Stack<>();
        boolean[] isVisited = new boolean[26];
        int[] lastIndex = new int[26];
        char[] chars = str.toCharArray();
        int len = chars.length;

        for(int i=0; i<len; i++){
            lastIndex[chars[i]-'a'] = i;
        }

        // 贪心
        for(int i=0; i<len; i++){
            if(isVisited[chars[i]-'a']){
                continue;
            }
            // 单调栈(单调增)
            // lastIndex[stack.peek()-'a']>i -> 表示栈顶字符在右侧(后面)有重复
            while(!stack.isEmpty() && stack.peek()>chars[i] && lastIndex[stack.peek()-'a']>i){
                isVisited[stack.pop()-'a'] = false;
            }
            stack.push(chars[i]);
            isVisited[chars[i]-'a'] = true;
        }

        StringBuilder sb = new StringBuilder();
        while(!stack.isEmpty()){
            sb.append(stack.pop());
        }

        return sb.reverse().toString();
    }
}