LeetCode 1838. 最高频元素的频数 /76. 最小覆盖子串/727. 最小窗口子序列(动规,难)
Posted Zephyr丶J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 1838. 最高频元素的频数 /76. 最小覆盖子串/727. 最小窗口子序列(动规,难)相关的知识,希望对你有一定的参考价值。
1838. 最高频元素的频数
2021.7.19 又是新的一周
题目描述
元素的 频数 是该元素在一个数组中出现的次数。
给你一个整数数组 nums 和一个整数 k 。在一步操作中,你可以选择 nums 的一个下标,并将该下标对应元素的值增加 1 。
执行最多 k 次操作后,返回数组中最高频元素的 最大可能频数 。
示例 1:
输入:nums = [1,2,4], k = 5
输出:3
解释:对第一个元素执行 3 次递增操作,对第二个元素执 2 次递增操作,此时 nums = [4,4,4] 。
4 是数组中最高频元素,频数是 3 。
示例 2:
输入:nums = [1,4,8,13], k = 5
输出:2
解释:存在多种最优解决方案:
- 对第一个元素执行 3 次递增操作,此时 nums = [4,4,8,13] 。4 是数组中最高频元素,频数是 2 。
- 对第二个元素执行 4 次递增操作,此时 nums = [1,8,8,13] 。8 是数组中最高频元素,频数是 2 。
- 对第三个元素执行 5 次递增操作,此时 nums = [1,4,13,13] 。13 是数组中最高频元素,频数是 2 。
示例 3:
输入:nums = [3,9,6], k = 2
输出:1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/frequency-of-the-most-frequent-element
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
我的第一个想法,就是计算将位置 i 前面的所有数变成位置 i 的数共需要多大
然后二分查找
class Solution {
public int maxFrequency(int[] nums, int k) {
int l = nums.length;
Arrays.sort(nums);
long[] pre = new long[l];
//计算之前所有位置的数变成当前位置i的数需要的大小
for(int i = 1; i < l; i++){
pre[i] = pre[i - 1] + (nums[i] - nums[i - 1]) * i;
}
int max = 1;
for(int i = 1; i < l; i++){
int left = 0;
int right = i;
//首先明确找的是什么,找到是满足条件最小的下标
while(left < right){
int mid = (right - left) / 2 + left;
if(pre[i] - pre[mid] - mid * (nums[i] - nums[mid]) > k){
left = mid + 1;
}else{
right = mid;
}
}
max = Math.max(max, i - left + 1);
}
return max;
}
}
或者说滑动窗口,添加变成当前位置所需要加的数,如果不满足小于k的话,减去左边的值
这里内层能用if,是因为是求最大值
class Solution {
public int maxFrequency(int[] nums, int k) {
Arrays.sort(nums);
int n = nums.length;
long total = 0;
int l = 0, res = 1;
for (int r = 1; r < n; ++r) {
total += (long) (nums[r] - nums[r - 1]) * (r - l);
if (total > k) {
total -= nums[r] - nums[l];
++l;
}
res = Math.max(res, r - l + 1);
}
return res;
}
}
76. 最小覆盖子串
2021.7.19 虾皮今天笔试出了个最小覆盖子序列,会员题,看不了就先做个这个
题目描述
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
示例 2:
输入:s = “a”, t = “a”
输出:“a”
示例 3:
输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
滑动窗口+哈希表,哈希表用来判断是否包含某个字母
然后要用一个变量来存储当前有多少个字母是达到了要求的个数,避免了直接比较两个map
另外需要特别注意一个点,写在代码后面
class Solution {
public String minWindow(String s, String t) {
int ls = s.length();
int lt = t.length();
char[] tt = t.toCharArray();
char[] ss = s.toCharArray();
Map<Character, Integer> mapt = new HashMap<>();
for(int i = 0; i < lt; i++){
mapt.put(tt[i], mapt.getOrDefault(tt[i], 0) + 1);
}
Map<Character, Integer> maps = new HashMap<>();
int l = 0;
int r = 0;
int match = 0;
int min = ls + 1;
int left = -1;
while(r < ls){
char c = ss[r];
if(mapt.containsKey(c)){
maps.put(c, maps.getOrDefault(c, 0) + 1);
if(maps.get(c).equals(mapt.get(c)))
match++;
}
while(match == mapt.size()){
if(r - l + 1 < min){
min = r - l + 1;
left = l;
}
if(mapt.containsKey(ss[l])){
maps.put(ss[l], maps.get(ss[l]) - 1);
if(maps.get(ss[l]).equals(mapt.get(ss[l]) - 1)){
match--;
}
}
l++;
}
r++;
}
return left < 0 ? "" : s.substring(left, left + min);
}
}
另外特别注意:我第一次写的代码中,比较逻辑我都是直接用的“==”,但是提交以后就会发现,最后一个示例报错,然后还怎么查都找不到问题
然后就想看看题解怎么写的, 发现真有一个人说了这个问题,原因是这样:
Integer是对象
Integer会缓存频繁使用的数值,
数值范围为-128到127,在此范围内直接返回缓存值。
超过该范围就会new 一个对象。这个范围是可以修改的,在vm中修改即可调整缓存大小。
然后最后一个示例中,应该是一个字母的个数超过了127,然后比较的时候,就导致创建了一个对象,然后对象用==相比较就是比较的地址,而不是数值了,所以会出错,以后要特别注意这一点
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
那么换成子序列的话,怎么做呢
727. 最小窗口子序列
题目描述
给定字符串 S and T,找出 S 中最短的(连续)子串 W ,使得 T 是 W 的 子序列 。
如果 S 中没有窗口可以包含 T 中的所有字符,返回空字符串 “”。
如果有不止一个最短长度的窗口,返回开始位置最靠左的那个。
示例 1:
输入:
S = “abcdebdde”, T = “bde”
输出:“bcde”
解释:
“bcde” 是答案,因为它在相同长度的字符串 “bdde” 出现之前。
“deb” 不是一个更短的答案,因为在窗口中必须按顺序出现 T 中的元素。
注:
所有输入的字符串都只包含小写字母。
S 长度的范围为 [1, 20000]。
T 长度的范围为 [1, 100]。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
斥巨资开了个力扣年费会员,然后来看这道题
之前网上查的时候很多方法都是滑动窗口,先向右滑找到右边界,然后向左,找到第一个不符合条件的位置;然后从这个位置再开始滑动,说实话,这种方法,我觉得感觉就是在暴力匹配,没啥技术含量,所以我就不写了。
还是看动态规划,第一个方法给的就是在S中不断找T的前缀,然后依次扩展
用两个数组来分别统计,以上一个T中的字母为结尾的前缀出现的位置(第一个字母出现的位置),例如pre[i] = j,就是s中从 j 位置开始到 i 位置结束,使得T前缀是其中的子序列(有点绕,看代码明白点)
另一个数组统计以当前T中的字母结尾的前缀出现位置。
其实两个数组统计的是一个东西,只不过因为要用上一个数组,所以不能直接覆盖
class Solution {
public String minWindow(String s1, String s2) {
//动态规划,依次匹配前缀
int l1 = s1.length();
int l2 = s2.length();
//初始化,匹配第一个字母
char[] c2 = s2.toCharArray();
int[] pre = new int[l1];
Arrays.fill(pre, -1);
//如果哪个匹配上了,就标记为开始的下标
for(int i = 0; i < l1; i++){
if(s1.charAt(i) == c2[0]){
pre[i] = i;
}
}
for(int i = 1; i < l2; i++){
char c = c2[i];
//上一个字母最后出现的下标
int last = -1;
int[] cur = new int[l1];
Arrays.fill(cur, -1);
for(int j = 0; j < l1; j++){
//如果当前字母相同,并且last不为-1,那么说明有个区间满足了条件,记录这个区间的开始值,也就是last
if(last != -1 && s1.charAt(j) == c){
cur[j] = last;
}
//更新last的值
if(pre[j] != -1)
last = pre[j];
}
for(int j = 0; j < l1; j++){
pre[j] = cur[j];
}
}
//最后,计算最小的长度
int min = l1 + 1;
int left = -1;
for(int i = 0; i < l1; i++){
if(pre[i] != -1){
int len = i - pre[i] + 1;
if(len < min){
left = pre[i];
min = len;
}
}
}
return left == -1 ? "" : s1.substring(left, left + min);
}
}
以上是关于LeetCode 1838. 最高频元素的频数 /76. 最小覆盖子串/727. 最小窗口子序列(动规,难)的主要内容,如果未能解决你的问题,请参考以下文章