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