• 【LeetCode】187. 重复的DNA序列


    187. 重复的DNA序列

    难度:中等

    题目

    DNA序列 由一系列核苷酸组成,缩写为 'A', 'C', 'G''T'.。

    • 例如,"ACGAATTCCG" 是一个 DNA序列

    在研究 DNA 时,识别 DNA 中的重复序列非常有用。

    给定一个表示 DNA序列 的字符串 s ,返回所有在 DNA 分子中出现不止一次的 长度为 10 的序列(子字符串)。你可以按 任意顺序 返回答案。

    示例 1:

    输入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
    输出:["AAAAACCCCC","CCCCCAAAAA"]
    
    • 1
    • 2

    示例 2:

    输入:s = "AAAAAAAAAAAAA"
    输出:["AAAAAAAAAA"]
    
    • 1
    • 2

    提示:

    • 0 <= s.length <= 10^5
    • s[i]``==``'A''C''G' or 'T'

    个人题解

    思路:

    1. 哈希逐个判断即可
    class Solution {
        public List<String> findRepeatedDnaSequences(String s) {
            List<String> ansList = new ArrayList<>();
            Map<String, Boolean> singleExistMap = new HashMap<>();
            String temp;
            for (int left = 0, right = 10; right <= s.length(); left++, right++) {
                temp = s.substring(left, right);
                if (singleExistMap.containsKey(temp) && singleExistMap.get(temp)) {
                    ansList.add(temp);
                    singleExistMap.put(temp, Boolean.FALSE);
                }else if (!singleExistMap.containsKey(temp)){
                    singleExistMap.put(temp, Boolean.TRUE);
                }
            }
            return ansList;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    官方题解

    方法一:哈希表

    我们可以用一个哈希表统计 s 所有长度为 10 的子串的出现次数,返回所有出现次数超过 10 的子串。

    代码实现时,可以一边遍历子串一边记录答案,为了不重复记录答案,我们只统计当前出现次数为 2 的子串。

    class Solution {
        static final int L = 10;
    
        public List<String> findRepeatedDnaSequences(String s) {
            List<String> ans = new ArrayList<String>();
            Map<String, Integer> cnt = new HashMap<String, Integer>();
            int n = s.length();
            for (int i = 0; i <= n - L; ++i) {
                String sub = s.substring(i, i + L);
                cnt.put(sub, cnt.getOrDefault(sub, 0) + 1);
                if (cnt.get(sub) == 2) {
                    ans.add(sub);
                }
            }
            return ans;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    复杂度分析

    • 时间复杂度:O(NL),N是字符串 s 的长度,L = 10 即目标子串的长度
    • 空间复杂度:O(NL)
    方法二:哈希表 + 滑动窗口 + 位运算

    由于 s 中只含有 4 种字符,我们可以将每个字符用 2 个比特表示,即:

    • A 表示为二进制 00
    • C 表示为二进制 01
    • G 表示为二进制 10
    • T 表示为二进制 11

    如此一来,一个长为 10 的字符串就可以用 20 个比特表示,而一个 int 整数有 32 个比特,足够容纳该字符串,因此我们可以将 s 的每个长为 10 的子串用一个 int 整数表示(只用低 20 位)。

    注意到上述字符串到整数的映射是一一映射,每个整数都对应着一个唯一的字符串,因此我们可以将方法一中的哈希表改为存储每个长为 10 的子串的整数表示。

    如果我们对每个长为 10 的子串都单独计算其整数表示,那么时间复杂度仍然和方法一一样为O(NL)。为了优化时间复杂度,我们可以用一个大小固定为 10 的滑动窗口来计算子串的整数表示。设当前滑动窗口对应的整数表示为 x ,当我们要计算下一个子串时,就将滑动窗口向右移动一位,此时会有一个新的字符进入窗口,以及窗口最左边的字符离开窗口,这些操作对应的位运算,按计算顺序表示如下:

    • 滑动窗口向右移动一位:x = x << 2,由于每个字符用 2 个字符表示,所以要左移 2 位
    • 一个新的字符 ch 进入窗口:x = x | bin[ch] ,这里的 bin[ch] 为字符 ch 的对应二进制
    • 窗口最左边的字符离开窗口:x = x & ((1 << 20) - 1) ,由于我们只考虑 x 的低 20 位比特,需要将其余位置零,即与上 (1 << 20) - 1

    将这三步合并,就可以用 O(1) 的时间计算出下一个子串的整数表示,即 x = (( x << 2) | bin[ch]) & (1 << 20) - 1)

    class Solution {
        static final int L = 10;
        Map<Character, Integer> bin = new HashMap<Character, Integer>() {{
            put('A', 0);
            put('C', 1);
            put('G', 2);
            put('T', 3);
        }};
    
        public List<String> findRepeatedDnaSequences(String s) {
            List<String> ans = new ArrayList<String>();
            int n = s.length();
            if (n <= L) {
                return ans;
            }
            int x = 0;
            for (int i = 0; i < L - 1; ++i) {
                x = (x << 2) | bin.get(s.charAt(i));
            }
            Map<Integer, Integer> cnt = new HashMap<Integer, Integer>();
            for (int i = 0; i <= n - L; ++i) {
                x = ((x << 2) | bin.get(s.charAt(i + L - 1))) & ((1 << (L * 2)) - 1);
                cnt.put(x, cnt.getOrDefault(x, 0) + 1);
                if (cnt.get(x) == 2) {
                    ans.add(s.substring(i, i + L));
                }
            }
            return ans;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    复杂度分析

    • 时间复杂度:O(N),N是字符串 s 的长度
    • 空间复杂度:O(N)
  • 相关阅读:
    (未整理完)十月每日一题打卡
    苹果或挖走Meta AR公关总监,2022年的头显是真的要来了?
    华为交换机基础配置(telnet/ssh登录)
    羧酸研究:Lumiprobe 磺基花青7二羧酸
    【JavaScript原型链prototype详解】
    Lambda表达式:一篇文章带你通透
    标签类目体系(面向业务的数据资产设计方法论)-读书笔记5
    【数据结构】队列
    重学SpringBoot3-函数式Web
    GO安装以及配置(1)
  • 原文地址:https://blog.csdn.net/xxx1276063856/article/details/134235906