LeetCode 187. 重复的DNA序列 / 352. 将数据流变为多个不相交区间 / 441. 排列硬币

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 187. 重复的DNA序列 / 352. 将数据流变为多个不相交区间 / 441. 排列硬币相关的知识,希望对你有一定的参考价值。

187. 重复的DNA序列

2021.10.8 每日一题

题目描述

所有 DNA 都由一系列缩写为 ‘A’,‘C’,‘G’ 和 ‘T’ 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。

编写一个函数来找出所有目标子串,目标子串的长度为 10,且在 DNA 字符串 s 中出现次数超过一次。

示例 1:

输入:s = “AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT”
输出:[“AAAAACCCCC”,“CCCCCAAAAA”]

示例 2:

输入:s = “AAAAAAAAAAAAA”
输出:[“AAAAAAAAAA”]

提示:

0 <= s.length <= 10^5
s[i] 为 ‘A’、‘C’、‘G’ 或 ‘T’

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/repeated-dna-sequences
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

用滑动窗口,遍历每一个长度为10的字符串,如果出现过,那么就加到结果集中

class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
        int l = s.length();
        List<String> res = new ArrayList<>();
        if(l < 10)
            return res;
        Set<String> set = new HashSet<>();
        Set<String> has = new HashSet<>();
        StringBuilder sb = new StringBuilder();
        int right = 10;
        sb.append(s.substring(0, 10));
        set.add(s.substring(0, 10));
        while(right < l){
            sb.deleteCharAt(0);
            sb.append(s.charAt(right));
            //当前字符串
            String temp = sb.toString();
            //如果当前字符串存在,那么说明重复,加到集合中
            if(set.contains(temp)){
                has.add(temp);
            }else
                set.add(temp);
            right++;
        }
        for(String temp : has)
            res.add(temp);
        return res;
    }
}

这里因为在处理每一个新的字符时会发生变化,所以需要10的复杂度
为了消除这个复杂度,可以用一个整数来表示这个字符串(因为这个字符串中只有四个字母)
官解是用二进制表示的,三叶姐是用哈希表示的,都很巧妙
官解的:

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;
        //二进制表示长度为10的字符串
        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;
    }
}

三叶姐的:
因为取的是哈希值,那么这里为什么要乘基数,我的理解是这样的
因为哈希值的比较需要在同一个量级上比较,而后面的字符串量级要比前面的大,所以需要乘基数使量级相同

class Solution {
    int N = (int)1e5+10, P = 131313;
    //当前长度字符串的哈希值,当前倍数
    int[] h = new int[N], p = new int[N];
    public List<String> findRepeatedDnaSequences(String s) {
        int n = s.length();
        List<String> ans = new ArrayList<>();
        p[0] = 1;
        for (int i = 1; i <= n; i++) {
            h[i] = h[i - 1] * P + s.charAt(i - 1);
            p[i] = p[i - 1] * P;
        }
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 1; i + 10 - 1 <= n; i++) {
            int j = i + 10 - 1;
            //当前长度为10的字符串的哈希值,注意这里要乘基数
            int hash = h[j] - h[i - 1] * p[j - i + 1];
            int cnt = map.getOrDefault(hash, 0);
            if (cnt == 1) ans.add(s.substring(i - 1, i + 10 - 1));
            map.put(hash, cnt + 1);
        }
        return ans;
    }
}

352. 将数据流变为多个不相交区间

2021.10.9 每日一题

题目描述

给你一个由非负整数 a1, a2, …, an 组成的数据流输入,请你将到目前为止看到的数字总结为不相交的区间列表。

实现 SummaryRanges 类:

SummaryRanges() 使用一个空数据流初始化对象。
void addNum(int val) 向数据流中加入整数 val 。
int[][] getIntervals() 以不相交区间 [starti, endi] 的列表形式返回对数据流中整数的总结。

示例:

输入:
[“SummaryRanges”, “addNum”, “getIntervals”, “addNum”, “getIntervals”, “addNum”, “getIntervals”, “addNum”, “getIntervals”, “addNum”, “getIntervals”]
[[], [1], [], [3], [], [7], [], [2], [], [6], []]
输出:
[null, null, [[1, 1]], null, [[1, 1], [3, 3]], null, [[1, 1], [3, 3], [7, 7]], null, [[1, 3], [7, 7]], null, [[1, 3], [6, 7]]]
解释:
SummaryRanges summaryRanges = new SummaryRanges();
summaryRanges.addNum(1); // arr = [1]
summaryRanges.getIntervals(); // 返回 [[1, 1]]
summaryRanges.addNum(3); // arr = [1, 3]
summaryRanges.getIntervals(); // 返回 [[1, 1], [3, 3]]
summaryRanges.addNum(7); // arr = [1, 3, 7]
summaryRanges.getIntervals(); // 返回 [[1, 1], [3, 3], [7, 7]]
summaryRanges.addNum(2); // arr = [1, 2, 3, 7]
summaryRanges.getIntervals(); // 返回 [[1, 3], [7, 7]]
summaryRanges.addNum(6); // arr = [1, 2, 3, 6, 7]
summaryRanges.getIntervals(); // 返回 [[1, 3], [6, 7]]

提示:

0 <= val <= 10^4
最多调用 addNum 和 getIntervals 方法 3 * 10^4 次

进阶:如果存在大量合并,并且与数据流的大小相比,不相交区间的数量很小,该怎么办?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/data-stream-as-disjoint-intervals
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

这个题呢,主要就是用来学习TreeMap的用法,做了这么多题,其实对TreeMap的使用还是非常少的
这里用了两个方法,一个ceilingEntry和一个floorEntry
通过TreeMap来处理这道题,就非常简单了

class SummaryRanges {
    //再学一下TreeMap
    TreeMap<Integer, Integer> map;

    public SummaryRanges() {
        map = new TreeMap<>();
    }
    
    public void addNum(int val) {
        //取大于val的第一个区间和小于val的第一个区间
        Map.Entry<Integer, Integer> up = map.ceilingEntry(val + 1);   //这个方法找的是第一个大于等于val+1的值
        Map.Entry<Integer, Integer> down = map.floorEntry(val);       //这个方法找的是第一个小于等于val的值
        //分情况讨论
        //如果已经包含在已有的区间里面了
        if(down != null && val >= down.getKey() && val <= down.getValue())
            return;
        //如果与两个区间都不相交或为null
        else if((down == null || down.getValue() < val - 1) && (up == null || up.getKey() > val + 1))
            map.put(val, val);
        //如果与down相连
        else if(down != null && down.getValue() == val - 1 && (up == null || up.getKey() > val + 1))
            map.put(down.getKey(), val);
        //如果与up区间相连
        else if((down == null || down.getValue() < val - 1) && (up != null && up.getKey() == val + 1)){
            map.put(val, up.getValue());
            map.remove(val + 1);
        //如果恰好在中间
        }else{
            map.put(down.getKey(), up.getValue());
            map.remove(val + 1);
        }
    }
    
    public int[][] getIntervals() {
        int[][] res = new int[map.size()][2];
        int idx = 0;
        for(Map.Entry<Integer, Integer> entry : map.entrySet()){
            res[idx][0] = entry.getKey();
            res[idx++][1] = entry.getValue();
        }
        return res;
    }
}

/**
 * Your SummaryRanges object will be instantiated and called as such:
 * SummaryRanges obj = new SummaryRanges();
 * obj.addNum(val);
 * int[][] param_2 = obj.getIntervals();
 */

441. 排列硬币

2021.10.10 每日一题

题目描述

你总共有 n 枚硬币,并计划将它们按阶梯状排列。对于一个由 k 行组成的阶梯,其第 i 行必须正好有 i 枚硬币。阶梯的最后一行 可能 是不完整的。

给你一个数字 n ,计算并返回可形成 完整阶梯行 的总行数。

示例 1:


输入:n = 5
输出:2
解释:因为第三行不完整,所以返回 2 。

示例 2:

输入:n = 8
输出:3
解释:因为第四行不完整,所以返回 3 。

提示:

1 <= n <= 2^31 - 1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/arranging-coins
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

没啥问题,可以二分或者直接数学求解,这里就不写了

class Solution {
    public int arrangeCoins(int n) {
        int count = 0;
        long temp = 0;
        while(temp <= n){
            count++;
            temp += count;
        }
        return count - 1;
    }
}

以上是关于LeetCode 187. 重复的DNA序列 / 352. 将数据流变为多个不相交区间 / 441. 排列硬币的主要内容,如果未能解决你的问题,请参考以下文章

[LeetCode] 187. Repeated DNA Sequences 求重复的DNA序列

LeetCode187 重复的DNA序列

Leetcode No.187 重复的DNA序列(滑动窗口)

Leetcode No.187 重复的DNA序列(滑动窗口)

Leetcode刷题100天—187. 重复的DNA序列(哈希表)—day61

Leetcode刷题100天—187. 重复的DNA序列(哈希表)—day61