import java.util.*;

/**
 * NC220 重复的DNA序列
 * @author d3y1
 */
public class Solution {
    // 滑动窗口大小gap 亦即DNA子串长度
    private final int L = 10;
    // 字符映射成整数
    private HashMap<Character,Integer> chToIntMap = new HashMap<Character,Integer>(){{
        put('A', 0);
        put('C', 1);
        put('G', 2);
        put('T', 3);
    }};
    // 整数映射成字符
    private HashMap<Integer,Character> intToChMap = new HashMap<Integer,Character>(){{
        put(0, 'A');
        put(1, 'C');
        put(2, 'G');
        put(3, 'T');
    }};

    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param DNA string字符串 1
     * @return string字符串一维数组
     */
    public String[] repeatedDNA (String DNA) {
        // return solution1(DNA);
        // return solution2(DNA);
        // return solution22(DNA);
        // return solution3(DNA);
        // return solution33(DNA);
        // return solution333(DNA);
        return solution3333(DNA);
        // return solution4(DNA);
    }

    /**
     * 滑动窗口: HashSet
     * @param DNA
     * @return
     */
    private String[] solution1(String DNA){
        List<String> results = new ArrayList<>();
        HashSet<String> subSet = new HashSet<>();

        int len = DNA.length();
        String sub;
        for(int i=0; i+L<=len; i++){
            sub = DNA.substring(i, i+L);
            // 当前sub: 第一次找到 且 今后还会找到
            if(!subSet.contains(sub) && DNA.substring(i+1).contains(sub)){
                subSet.add(sub);
                results.add(sub);
            }
        }

        return results.toArray(new String[results.size()]);
    }

    // /**
    //  * 滑动窗口: HashSet + HashMap
    //  * @param DNA
    //  * @return
    //  */
    // private String[] solution2(String DNA){
    //     List<SubDNA> resultList = new ArrayList<>();
    //     HashSet<String> subSet = new HashSet<>();
    //     HashSet<String> foundSet = new HashSet<>();
    //     HashMap<String, Integer> map = new HashMap<>();

    //     int len = DNA.length();
    //     String sub;
    //     for(int i=0; i+L<=len; i++){
    //         sub = DNA.substring(i, i+L);
    //         if(!subSet.contains(sub)){
    //             subSet.add(sub);
    //             if(!map.containsKey(sub)){
    //                 map.put(sub, i);
    //             }
    //         }else{
    //             if(!foundSet.contains(sub)){
    //                 foundSet.add(sub);
    //                 resultList.add(new SubDNA(sub, map.get(sub)));
    //             }
    //         }
    //     }

    //     // 序号升序
    //     Collections.sort(resultList, new Comparator<SubDNA>(){
    //         @Override
    //         public int compare(SubDNA o1, SubDNA o2){
    //             return o1.firstIdx-o2.firstIdx;
    //         }
    //     });

    //     String[] results = new String[resultList.size()];
    //     for(int i=0; i<resultList.size(); i++){
    //         results[i] = resultList.get(i).sub;
    //     }

    //     return results;
    // }

    /**
     * 滑动窗口: HashSet + HashMap
     * @param DNA
     * @return
     */
    private String[] solution22(String DNA){
        int n = DNA.length();

        // 哈希
        HashMap<String, Integer> idxMap = new HashMap<>();
        HashMap<String, Integer> cntMap = new HashMap<>();

        List<SubDNA> list = new ArrayList<>();
        // 当前子串
        String sub;
        // 当前子串出现次数
        int cnt;
        for(int i=0; i+L<=n; i++){
            sub = DNA.substring(i, i+L);

            cntMap.put(sub, cntMap.getOrDefault(sub,0)+1);
            cnt = cntMap.get(sub);

            // 首次出现
            if(cnt == 1){
                idxMap.put(sub, i);
            }
            // 二次出现
            else if(cnt == 2){
                list.add(new SubDNA(sub, idxMap.get(sub)));
            }
        }

        // 序号升序
        Collections.sort(list, new Comparator<SubDNA>(){
            @Override
            public int compare(SubDNA o1, SubDNA o2){
                return o1.firstIdx-o2.firstIdx;
            }
        });

        String[] results = new String[list.size()];
        for(int i=0; i<list.size(); i++){
            results[i] = list.get(i).sub;
        }

        return results;
    }

    /**
     * DNA子串
     */
    private class SubDNA {
        String sub;
        int firstIdx;

        public SubDNA(String sub, int firstIdx){
            this.sub = sub;
            this.firstIdx = firstIdx;
        }
    }

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

    // /**
    //  * 哈希 + 双指针(滑动窗口)
    //  * @param DNA
    //  * @return
    //  */
    // private String[] solution3(String DNA){
    //     int n = DNA.length();

    //     // 哈希
    //     HashMap<String, Integer> map = new HashMap<>();
    //     HashSet<String> resultSet = new HashSet<>();
    //     // PriorityQueue<SubDNA> minHeap = new PriorityQueue<>((o1,o2) -> (o1.firstIdx-o2.firstIdx));
    //     PriorityQueue<SubDNA> minHeap = new PriorityQueue<>(new Comparator<SubDNA>(){
    //         @Override
    //         public int compare(SubDNA o1, SubDNA o2){
    //             return o1.firstIdx-o2.firstIdx;
    //         }
    //     });

    //     String target;
    //     // 双指针(滑动窗口)
    //     for(int i=0,j=i+L; j<=n; i++,j++){
    //         target = DNA.substring(i,j);
    //         if(map.containsKey(target)){
    //             if(!resultSet.contains(target)){
    //                 resultSet.add(target);
    //                 minHeap.offer(new SubDNA(target,map.get(target)));
    //             }
    //         }else{
    //             map.put(target,i);
    //         }
    //     }

    //     int size = minHeap.size();
    //     String[] result = new String[size];
    //     int i = 0;
    //     while(!minHeap.isEmpty()){
    //         result[i++] = minHeap.poll().sub;
    //     }

    //     return result;
    // }

    // /**
    //  * 哈希 + 双指针(滑动窗口)
    //  * @param DNA
    //  * @return
    //  */
    // private String[] solution33(String DNA){
    //     int n = DNA.length();

    //     // 哈希
    //     HashMap<String, Integer> firstMap = new HashMap<>();
    //     HashMap<String, Integer> cntMap = new HashMap<>();
    //     // PriorityQueue<SubDNA> minHeap = new PriorityQueue<>(Comparator.comparingInt(o -> o.firstIdx));
    //     PriorityQueue<SubDNA> minHeap = new PriorityQueue<>(new Comparator<SubDNA>(){
    //         @Override
    //         public int compare(SubDNA o1, SubDNA o2){
    //             return o1.firstIdx-o2.firstIdx;
    //         }
    //     });

    //     String target;
    //     // 双指针(滑动窗口)
    //     for(int i=0,j=i+L; j<=n; i++,j++){
    //         target = DNA.substring(i,j);
    //         if(firstMap.containsKey(target)){
    //             if(cntMap.get(target) == 1){
    //                 cntMap.put(target,2);
    //                 minHeap.offer(new SubDNA(target,firstMap.get(target)));
    //             }
    //         }else{
    //             firstMap.put(target,i);
    //             cntMap.put(target,1);
    //         }
    //     }

    //     int size = minHeap.size();
    //     String[] result = new String[size];
    //     int i = 0;
    //     while(!minHeap.isEmpty()){
    //         result[i++] = minHeap.poll().sub;
    //     }

    //     return result;
    // }

    // /**
    //  * 哈希 + 双指针(滑动窗口) + 位运算
    //  *
    //  * 由于DNA中只含有4种字符, 可以将每个字符用2个比特表示, 即:
    //  * A 表示为二进制 00
    //  * C 表示为二进制 01
    //  * G 表示为二进制 10
    //  * T 表示为二进制 11
    //  *
    //  * 如此, 一个长为10的字符串就可以用20个比特表示, 而一个int整数有32个比特, 足够容纳该字符串
    //  * 因此可以将DNA的每个长为10的子串用一个int整数表示(只用低20位)
    //  *
    //  * 注意到上述字符串到整数的映射是一一映射, 每个整数都对应着一个唯一的字符串
    //  * 因此可以将上面的哈希表改为存储每个长为10的子串的整数表示
    //  *
    //  * 如果对每个长为10的子串都单独计算其整数表示, 那么时间复杂度为O(NL)
    //  * 为了优化时间复杂度, 我们可以用一个大小固定为10的滑动窗口来计算子串的整数表示
    //  * 设当前滑动窗口对应的整数表示为target, 当要计算下一个子串时, 
    //  * 就将滑动窗口向右移动一位, 此时会有一个新的字符进入窗口, 以及窗口最左边的字符离开窗口, 
    //  * 这些操作对应的位运算, 按计算顺序表示如下:
    //  *
    //  * 滑动窗口向右移动一位: target = target<<2, 由于每个字符用2个比特表示, 所以要左移2位(原来的低位变成了高位)
    //  * 一个新的字符ch进入窗口: target = target|bin[ch], 这里bin[ch]为字符ch的对应二进制
    //  * 窗口最左边的字符离开窗口: target = target&((1<<20)-1), 由于我们只考虑target的低20位比特, 需要将其余位置零, 即与上(1<<20)-1
    //  * 将这三步合并, 就可以用O(1)的时间计算出下一个子串的整数表示, 即 target = ((target<<2)|bin[ch]) & ((1<<20)-1)
    //  *
    //  * @param DNA
    //  * @return
    //  */
    // private String[] solution333(String DNA){
    //     int n = DNA.length();
    //     if(n <= L){
    //         return new String[0];
    //     }

    //     // 哈希
    //     HashMap<Integer, Integer> firstMap = new HashMap<>();
    //     HashMap<Integer, Integer> cntMap = new HashMap<>();
    //     // PriorityQueue<SubDNA> minHeap = new PriorityQueue<>(Comparator.comparingInt(o -> o.firstIdx));
    //     PriorityQueue<SubDNA> minHeap = new PriorityQueue<>(new Comparator<SubDNA>(){
    //         @Override
    //         public int compare(SubDNA o1, SubDNA o2){
    //             return o1.firstIdx-o2.firstIdx;
    //         }
    //     });

    //     String sub;
    //     int target = 0;
    //     for(int i=0; i<L-1; i++) {
    //         target = (target<<2) | chToIntMap.get(DNA.charAt(i));
    //     }
    //     // 双指针(滑动窗口)
    //     for(int i=0,j=i+L; j<=n; i++,j++){
    //         sub = DNA.substring(i,j);
    //         // 位运算
    //         target = ((target<<2) | chToIntMap.get(DNA.charAt(j-1))) & ((1<<(L*2))-1);
    //         if(firstMap.containsKey(target)){
    //             if(cntMap.get(target) == 1){
    //                 cntMap.put(target,2);
    //                 minHeap.offer(new SubDNA(sub,firstMap.get(target)));
    //             }
    //         }else{
    //             firstMap.put(target,i);
    //             cntMap.put(target,1);
    //         }
    //     }

    //     int size = minHeap.size();
    //     String[] result = new String[size];
    //     int i = 0;
    //     while(!minHeap.isEmpty()){
    //         result[i++] = minHeap.poll().sub;
    //     }

    //     return result;
    // }

    /**
     * 哈希 + 双指针(滑动窗口) + 位运算
     *
     * 由于DNA中只含有4种字符, 可以将每个字符用2个比特表示, 即:
     * A 表示为二进制 00
     * C 表示为二进制 01
     * G 表示为二进制 10
     * T 表示为二进制 11
     *
     * 如此, 一个长为10的字符串就可以用20个比特表示, 而一个int整数有32个比特, 足够容纳该字符串
     * 因此可以将DNA的每个长为10的子串用一个int整数表示(只用低20位)
     *
     * 注意到上述字符串到整数的映射是一一映射, 每个整数都对应着一个唯一的字符串
     * 因此可以将上面的哈希表改为存储每个长为10的子串的整数表示
     *
     * 如果对每个长为10的子串都单独计算其整数表示, 那么时间复杂度为O(NL)
     * 为了优化时间复杂度, 我们可以用一个大小固定为10的滑动窗口来计算子串的整数表示
     * 设当前滑动窗口对应的整数表示为target, 当要计算下一个子串时, 
     * 就将滑动窗口向右移动一位, 此时会有一个新的字符进入窗口, 以及窗口最左边的字符离开窗口, 
     * 这些操作对应的位运算, 按计算顺序表示如下:
     *
     * 滑动窗口向右移动一位: target = target<<2, 由于每个字符用2个比特表示, 所以要左移2位(原来的低位变成了高位)
     * 一个新的字符ch进入窗口: target = target|bin[ch], 这里bin[ch]为字符ch的对应二进制
     * 窗口最左边的字符离开窗口: target = target&((1<<20)-1), 由于我们只考虑target的低20位比特, 需要将其余位置零, 即与上(1<<20)-1
     * 将这三步合并, 就可以用O(1)的时间计算出下一个子串的整数表示, 即 target = ((target<<2)|bin[ch]) & ((1<<20)-1)
     *
     * @param DNA
     * @return
     */
    private String[] solution3333(String DNA){
        int n = DNA.length();
        if(n <= L){
            return new String[0];
        }

        // 哈希
        HashMap<Integer, Integer> idxMap = new HashMap<>();
        HashMap<Integer, Integer> cntMap = new HashMap<>();
        // PriorityQueue<SeqDNA> minHeap = new PriorityQueue<>(Comparator.comparingInt(o -> o.firstIdx));
        PriorityQueue<SeqDNA> minHeap = new PriorityQueue<>(new Comparator<SeqDNA>(){
            @Override
            public int compare(SeqDNA o1, SeqDNA o2){
                return o1.firstIdx-o2.firstIdx;
            }
        });

        // 长度等于10的DNA子串的整数表示
        int sub = 0;
        for(int i=0; i<L-1; i++) {
            sub = (sub<<2) | chToIntMap.get(DNA.charAt(i));
        }
        // 当前子串出现次数
        int cnt;
        // 双指针(滑动窗口)
        for(int i=0,j=i+L; j<=n; i++,j++){
            // 位运算
            sub = ((sub<<2) | chToIntMap.get(DNA.charAt(j-1))) & ((1<<(L*2))-1);
            
            cntMap.put(sub, cntMap.getOrDefault(sub,0)+1);
            cnt = cntMap.get(sub);
            // 首次出现
            if(cnt == 1){
                idxMap.put(sub, i);
            }
            // 二次出现
            else if(cnt == 2){
                minHeap.offer(new SeqDNA(sub, idxMap.get(sub)));
            }
        }

        int size = minHeap.size();
        String[] result = new String[size];
        int i = 0;
        int idx;
        while(!minHeap.isEmpty()){
            idx = minHeap.poll().firstIdx;
            result[i++] = DNA.substring(idx, idx+L);
            // result[i++] = convertToDNAStr(minHeap.poll().sub);
        }

        return result;
    }

    /**
     * DNA子串的整数表示 转化为 DNA字符串
     * @param sub
     * @return
     */
    private String convertToDNAStr(int sub){
        StringBuilder sb = new StringBuilder();
        for(int i=1; i<=L; i++){
            sb.append(intToChMap.get(sub%4));
            sub /= 4;
        }

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

    /**
     * DNA子串
     */
    private class SeqDNA {
        // 长度等于10的DNA子串的整数表示
        int sub;
        // 子串首次出现位置
        int firstIdx;

        public SeqDNA(int sub, int firstIdx){
            this.sub = sub;
            this.firstIdx = firstIdx;
        }
    }

    /**
     * 哈希 + 双指针(滑动窗口) + 字符串
     * @param DNA
     * @return
     */
    private String[] solution4(String DNA){
        int n = DNA.length();

        // 哈希
        HashSet<String> resultSet = new HashSet<>();
        ArrayList<String> list = new ArrayList<>();

        String target;
        int lastIdx;
        // 双指针(滑动窗口)
        for(int i=0,j=i+L; j<=n; i++,j++){
            target = DNA.substring(i,j);
            if(!resultSet.contains(target)){
                // 字符串
                lastIdx = DNA.lastIndexOf(target);
                if(i < lastIdx){
                    resultSet.add(target);
                    list.add(target);
                }
            }
        }

        int size = list.size();
        String[] result = new String[size];
        list.toArray(result);

        return result;
    }
}