📝 第 390 场周赛记录-快手
1. 每个字符最多出现两次的最长子字符串
给你一个字符串 s
,请找出满足每个字符最多出现两次的最长子字符串,并返回该子字符串的 最大 长度。
示例 1:
输入: s = "bcbbbcba"
输出: 4
解释:
以下子字符串长度为 4,并且每个字符最多出现两次:"bcbbbcba"
。
示例 2:
输入: s = "aaaa"
输出: 2
解释:
以下子字符串长度为 2,并且每个字符最多出现两次:"aaaa"
。
提示:
2 <= s.length <= 100
s
仅由小写英文字母组成。
✏️ 题解:
求连续子字符串,且需要遍历,用滑动窗口
解决。
class Solution {
public int maximumLengthSubstring(String s) {
// 滑动窗口 + 哈希(小写英文字母可以用数组记录数字
// 每个字符最多出现两次:则子字符串的长度最小都为 2
if(s.length() == 2)return 2;
int len = 0;
int[] letter_cnt = new int[26];
int i = 0;
int j = 1;
// j - i >= 1恒成立
letter_cnt[s.charAt(i)-'a']++;
// letter_cnt[s[j]-'a']++;
while(j < s.length()){
if(letter_cnt[s.charAt(j)-'a'] == 2){
// 字符串中重复字符出现次数超过 2
while(s.charAt(i) != s.charAt(j)){
letter_cnt[s.charAt(i) - 'a']--;
i++;
}
letter_cnt[s.charAt(i) - 'a']--;
i++;
}else{
letter_cnt[s.charAt(j)-'a']++;
len = Math.max(j+1-i,len);
j++;
}
}
return len;
}
}
2. 执行操作使数据元素之和大于等于 K
给你一个正整数 k
。最初,你有一个数组 nums = [1]
。
你可以对数组执行以下 任意 操作 任意 次数(可能为零):
- 选择数组中的任何一个元素,然后将它的值 增加
1
。 - 复制数组中的任何一个元素,然后将它附加到数组的末尾。
返回使得最终数组元素之 和 大于或等于 k
所需的 最少 操作次数。
示例 1:
输入:k = 11
输出:5
解释:
可以对数组 nums = [1]
执行以下操作:
- 将元素的值增加
1
三次。结果数组为nums = [4]
。 - 复制元素两次。结果数组为
nums = [4,4,4]
。
最终数组的和为 4 + 4 + 4 = 12
,大于等于 k = 11
。
执行的总操作次数为 3 + 2 = 5
。
示例 2:
输入:k = 1
输出:0
解释:
原始数组的和已经大于等于 1
,因此不需要执行操作。
提示:
1 <= k <= 105
✏️ 题解:
这道题采用贪心
的思路,要先加后减,通过找规律发现开根的方式可以获得最小的操作次数。(a 次加操作、 b 次复制操作
和 a 次复制操作、 b 次加操作
是一样的,因为是求和)。
class Solution {
public int minOperations(int k) {
// 先加后减
if(k == 1)return 0;
int e = (int)Math.sqrt(k);// 加操作
int q = (int)Math.ceil((k*1.0)/e); // 复制操作
return e+q-2;
}
}
3. 最高频率的 ID
你需要在一个集合里动态记录 ID 的出现频率。给你两个长度都为 n
的整数数组 nums
和 freq
,nums
中每一个元素表示一个 ID ,对应的 freq
中的元素表示这个 ID 在集合中此次操作后需要增加或者减少的数目。
- 增加 ID 的数目:如果
freq[i]
是正数,那么freq[i]
个 ID 为nums[i]
的元素在第i
步操作后会添加到集合中。 - 减少 ID 的数目:如果
freq[i]
是负数,那么-freq[i]
个 ID 为nums[i]
的元素在第i
步操作后会从集合中删除。
请你返回一个长度为 n
的数组 ans
,其中 ans[i]
表示第 i
步操作后出现频率最高的 ID 数目 ,如果在某次操作后集合为空,那么 ans[i]
为 0 。
示例 1:
输入:nums = [2,3,2,1], freq = [3,2,-3,1]
输出:[3,3,2,2]
解释:
第 0 步操作后,有 3 个 ID 为 2 的元素,所以 ans[0] = 3
。
第 1 步操作后,有 3 个 ID 为 2 的元素和 2 个 ID 为 3 的元素,所以 ans[1] = 3
。
第 2 步操作后,有 2 个 ID 为 3 的元素,所以 ans[2] = 2
。
第 3 步操作后,有 2 个 ID 为 3 的元素和 1 个 ID 为 1 的元素,所以 ans[3] = 2
。
示例 2:
输入:nums = [5,5,3], freq = [2,-2,1]
输出:[2,0,1]
解释:
第 0 步操作后,有 2 个 ID 为 5 的元素,所以 ans[0] = 2
。
第 1 步操作后,集合中没有任何元素,所以 ans[1] = 0
。
第 2 步操作后,有 1 个 ID 为 3 的元素,所以 ans[2] = 1
。
提示:
1 <= nums.length == freq.length <= 105
1 <= nums[i] <= 105
-105 <= freq[i] <= 105
freq[i] != 0
- 输入保证任何操作后,集合中的元素出现次数不会为负数。
✏️ 题解:
直接看代码,思路是比较清晰的,主要是要选好数据结构,这道题的核心就是选择 TreeMap,不然会超时...
class Solution {
// int[] de;
// long[] cnt;
// public void init(int[] nums){
// Set<Integer> hashset = new HashSet<>();
// for(int num:nums){
// hashset.add(num);
// }
// int i = 0;
// cnt = new long[hashset.size()];
// Arrays.fill(cnt,0);
// de = new int[hashset.size()];
// for(int num: hashset){
// de[i++] = num;
// }
// }
// public int getId(int x){
// return Arrays.binarySearch(de,x);
// }
public long[] mostFrequentIDs(int[] nums, int[] freq) {
// 方法 1:大根堆 + 哈希表 (通过 563/622,超时)
// PriorityQueue<Integer> pq = new PriorityQueue<>((a,b) -> b - a);
// Map<Integer, Integer> hashmap = new HashMap<>();
// int n = nums.length;
// long[] ans = new long[n];
// for(int i = 0; i < n; i++){
// int num = nums[i];
// int fre = freq[i];
// if(hashmap.containsKey(num)){
// // 说明之前存到过集合中,因此也要同步更新大根堆
// int pre_cnt = hashmap.get(num);
// // 减小的话可以先不删除
// pq.remove(pre_cnt);
// hashmap.put(num,hashmap.get(num)+fre);
// pq.add(hashmap.get(num));
// }else{
// // 说明之前没有存到过集合中
// hashmap.put(num, fre);
// pq.add(fre);
// }
// ans[i] = pq.peek();
// }
// return ans;
// 方法 2:哈希表(通过 562/622,超时)
// Map<Integer, Integer> hashmap = new HashMap<>();
// int n = nums.length;
// long[] ans = new long[n];
// for(int i = 0; i < n; i++){
// int num = nums[i];
// int fre = freq[i];
// if(hashmap.containsKey(num)){
// // 说明之前存到过集合中,因此也要同步更新大根堆
// int pre_cnt = hashmap.get(num);
// hashmap.put(num,hashmap.get(num)+fre);
// }else{
// // 说明之前没有存到过集合中
// hashmap.put(num, fre);
// }
// ans[i] = Collections.max(hashmap.values());
// }
// return ans;
// 方法 3:数组(通过522/622,超时)
// int n = nums.length;
// long[] ans = new long[n];
// init(nums);
// for(int i = 0; i < n; i++){
// int num = nums[i];
// int fre = freq[i];
// int idx = getId(num);
// cnt[idx] += fre;
// ans[i] = Arrays.stream(cnt).max().getAsLong();
// }
// return ans;
// 方法 4:TreeMap + HashMap
int n = nums.length;
TreeMap<Long, Integer> count = new TreeMap<>(Collections.reverseOrder()); // key是id个数,val是各个个数对应id的数目
count.put(0L, 1000000);
HashMap<Integer, Long> map = new HashMap<>(); // key是id,val是id对应的个数
long[] res = new long[n];
for(int i = 0; i < n; i++){
int id = nums[i];
long before = map.getOrDefault(id, 0L); // 旧值
long cur = before + freq[i]; // 新值
count.compute(before, (k, v) -> v == 1 ? null : v - 1); // 更新旧值,这里compute第二个参数为null,则对应的键值对会被删除
count.compute(cur, (k, v) -> v == null ? 1 : v + 1); // 更新新值
map.put(id, cur);
res[i] = count.firstKey(); // 注意,firstKey()只有TreeMap才有,如果count声明为Map是会报错的。
}
return res;
}
}
4. 最长公共后缀查询(未完成)
给你两个字符串数组 wordsContainer
和 wordsQuery
。
对于每个 wordsQuery[i]
,你需要从 wordsContainer
中找到一个与 wordsQuery[i]
有 最长公共后缀 的字符串。如果 wordsContainer
中有两个或者更多字符串有最长公共后缀,那么答案为长度 最短 的。如果有超过两个字符串有 相同 最短长度,那么答案为它们在 wordsContainer
中出现 更早 的一个。
请你返回一个整数数组 ans
,其中 ans[i]
是 wordsContainer
中与 wordsQuery[i]
有 最长公共后缀 字符串的下标。
示例 1:
输入:wordsContainer = ["abcd","bcd","xbcd"], wordsQuery = ["cd","bcd","xyz"]
输出:[1,1,1]
解释:
我们分别来看每一个 wordsQuery[i]
:
- 对于
wordsQuery[0] = "cd"
,wordsContainer
中有最长公共后缀"cd"
的字符串下标分别为 0 ,1 和 2 。这些字符串中,答案是下标为 1 的字符串,因为它的长度为 3 ,是最短的字符串。 - 对于
wordsQuery[1] = "bcd"
,wordsContainer
中有最长公共后缀"bcd"
的字符串下标分别为 0 ,1 和 2 。这些字符串中,答案是下标为 1 的字符串,因为它的长度为 3 ,是最短的字符串。 - 对于
wordsQuery[2] = "xyz"
,wordsContainer
中没有字符串跟它有公共后缀,所以最长公共后缀为""
,下标为 0 ,1 和 2 的字符串都得到这一公共后缀。这些字符串中, 答案是下标为 1 的字符串,因为它的长度为 3 ,是最短的字符串。
示例 2:
输入:wordsContainer = ["abcdefgh","poiuygh","ghghgh"], wordsQuery = ["gh","acbfgh","acbfegh"]
输出:[2,0,2]
解释:
我们分别来看每一个 wordsQuery[i]
:
- 对于
wordsQuery[0] = "gh"
,wordsContainer
中有最长公共后缀"gh"
的字符串下标分别为 0 ,1 和 2 。这些字符串中,答案是下标为 2 的字符串,因为它的长度为 6 ,是最短的字符串。 - 对于
wordsQuery[1] = "acbfgh"
,只有下标为 0 的字符串有最长公共后缀"fgh"
。所以尽管下标为 2 的字符串是最短的字符串,但答案是 0 。 - 对于
wordsQuery[2] = "acbfegh"
,wordsContainer
中有最长公共后缀"gh"
的字符串下标分别为 0 ,1 和 2 。这些字符串中,答案是下标为 2 的字符串,因为它的长度为 6 ,是最短的字符串。
提示:
1 <= wordsContainer.length, wordsQuery.length <= 104
1 <= wordsContainer[i].length <= 5 * 103
1 <= wordsQuery[i].length <= 5 * 103
wordsContainer[i]
只包含小写英文字母。wordsQuery[i]
只包含小写英文字母。wordsContainer[i].length
的和至多为5 * 105
。wordsQuery[i].length
的和至多为5 * 105
。
✏️ 题解:
待定...