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; } }