目录
题目3:1343. 大小为 K 且平均值大于等于阈值的子数组数目
题目7:1461. 检查一个字符串是否包含所有长度为 K 的二进制子串
题目1:1456. 定长子串中元音的最大数目
思路分析:
这个题目就是窗口大小固定为k的滑动窗口,在遍历数组的时候,始终保持窗口的元素有k个,判断这k个元素种元音的数量,当窗口往后滑动的时候,需要将窗口最前面的元素去除,然后再添加一个新的元素。
代码实现:
public int maxVowels(String s, int k) {
//将字符串转换为字符数组
char ch[]=s.toCharArray();
int cnt=0,max=0;
for(int i=0;i<ch.length;i++){
if(ch[i]=='a'||ch[i]=='e'||ch[i]=='i'||ch[i]=='o'||ch[i]=='u'){
cnt++;
}
//窗口还未达到k的时候,后续的逻辑操作不执行
if(i<k-1){
continue;
}
max=Math.max(max,cnt);
//窗口最前面的元素,被踢出去,更新窗口内元音字母的个数
char tmp=ch[i-k];
if(tmp=='a'||tmp=='e'||tmp=='i'||tmp=='o'||tmp=='u'){
cnt--;
}
}
return max;
}
题目2:643. 子数组最大平均数 I
思路分析:
这个题目同样是始终维持一个定长的窗口,窗口大小为k,在遍历数组的时候,窗口的前面不断删除元素,尾部不断添加元素,但是窗口的大小一直是k。找到最大平均值就行。
代码实现:
public double findMaxAverage(int[] nums, int k) {
double sum=0;
double max=Integer.MIN_VALUE;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
//如果窗口内的元素还未到k个,就不做后续的逻辑处理
if(i<k-1){
continue;
}
double avg=sum/k;
//找到最大的平均值
max=Math.max(max,avg);
sum-=nums[i-k+1];
}
return max;
}
题目3:1343. 大小为 K 且平均值大于等于阈值的子数组数目
思路分析:
同样是一样的方式,维持一个固定大小的滑动窗口就行
代码实现:
public int numOfSubarrays(int[] arr, int k, int threshold) {
//定义总和
int sum=0;
//统计大于threshold的数目
int cnt=0;
for(int i=0;i<arr.length;i++){
sum+=arr[i];
if(i<k-1){
continue;
}
int avg=sum/k;
if(avg>=threshold) cnt++;
//删除窗口最前面的元素,方便后续添加新的元素。
sum-=arr[i-k+1];
}
return cnt;
}
题目4:2090. 半径为 k 的子数组平均值
思路分析:
这个题目需要思维稍微转变一点,求半径为k的子数组,那么也就是说这个窗口是2*k+1这么大,我们只需要维护这么一个大小的窗口就行,满足条件的就求出平均值,不满足条件的统统都是-1。
代码实现:
class Solution {
public int[] getAverages(int[] nums, int k) {
//定义一个存储平均值的数组
int avgArr[]=new int[nums.length];
//先对所有值都赋值-1,如果有满足后续条件的才更新具体的值,否则不满足条件的都是-1
Arrays.fill(avgArr,-1);
long sum=0L;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
//维护一个窗口2*k+1 这也就是说i前面k个元素和i后面k个元素都存在的情况下
if(i<2*k){
continue;
}
//求出当前窗口内的平均值
int avg=(int)(sum/(2*k+1));
avgArr[i-k]=avg;
sum-=nums[i-2*k];
}
return avgArr;
}
}
题目5:2379. 得到 K 个黑块的最少涂色次数
思路分析:
这个题目只需要求出k范围内,白色块最少的数目就行。因为题目中就是求最少涂成黑色块的次数,那么只需要找到固定窗口内最少白色块数目就行。
代码实现:
class Solution {
public int minimumRecolors(String blocks, int k) {
char ch[]=blocks.toCharArray();
int cnt=0;
int min=Integer.MAX_VALUE;
for(int i=0;i<ch.length;i++){
if(ch[i]=='W') cnt++;
if(i<k-1) continue;
min=Math.min(min,cnt);
if(ch[i-k+1]=='W') cnt--;
}
return min;
}
}
题目6:1052. 爱生气的书店老板
思路分析:
这个题目稍微难一点,因为这个题目不仅仅需要考虑minutes局部内的最优解,还需要考虑全局,因为题目中说的时一天营业下来,最多有多少客户满意。这题目需要分两步求解。
首先,求出一天内,满意顾客的总数目(也就是老板没有生气的时候)。
其次,就是求出minutes内,顾客不满意的最大数目,我们只需要让这一部分顾客满意了,那么就是局部最优解。
以上两者加起来就是最终一天内,顾客满意的最大数目。
代码实现:
class Solution {
public int maxSatisfied(int[] customers, int[] grumpy, int minutes) {
//1.先计算出那些老板未生气时候,满意用户的总数目
int sum=0;
for(int i=0;i<customers.length;i++){
if(grumpy[i]==0) sum+=customers[i];
}
//2.算出在连续的minutes分钟内,老板生气时,不满意用户的最大数目max
//只需要让这一批不满意用户,变为满意,加上sum就行
int cnt=0;
int max=Integer.MIN_VALUE;
for(int i=0;i<customers.length;i++){
//累加minutes分钟内,不满意用户的数量
if(grumpy[i]==1) cnt+=customers[i];
if(i<minutes-1) continue;
max=Math.max(max,cnt);
if(grumpy[i-minutes+1]==1) cnt-=customers[i-minutes+1];
}
return max+sum;
}
}
题目7:1461. 检查一个字符串是否包含所有长度为 K 的二进制子串
思路分析:
这个题目的意思就是,在字符串s中,找到那些长度为k的子串。这些子串是否恰好满足于长度为k的字符串的所有情况。例如k=2,可能的存在的二进制字符串时00,01,10,11。然后我们只需要看看字符串s中,是否以上四种情况的子串都出现了,如果都出现了,则返回true。这个题目很明显可以借助hash表的去重特性,将字符串中所有长度为k的子串全部存在哈希表中,去重以后,判断hash表中元素数目是否等于2^k就行。
代码实现:
public boolean hasAllCodes(String s, int k) {
//k=2 可以有二进制子串00 01 10 11
//所以我们可以借助hash表去重的特性,遍历一遍字符串s,每次都取长度为k的子串
//将这些子串添加进hash表中,最终判断一下hash表中的元素数目是否等于2^k即可
HashSet<String> hashSet=new HashSet<>();
int left=0;
int cnt=(int)Math.pow(2,k);
for(int i=0;i<s.length();i++){
if(i<k-1) continue;
hashSet.add(s.substring(left,i+1));
left++;
}
return hashSet.size()==cnt;
}
题目8:2841. 几乎唯一子数组的最大和
思路分析:
这个题目其实很好理解,但是代码不好实现,肯定还是滑动窗口的思想,但是怎么实现窗口内独立元素的记数呢,需要借助hashmap,用hashmap统计元素出现的次数。如果当前元素在hashmap中没有出现,说明它是唯一元素。在窗口往前滑动的时候,将移除的那个元素在hashmap中更新一下。
-
滑动窗口:
-
维护一个固定大小为
k
的窗口,窗口在数组上滑动。 -
每次滑动时,窗口会移除一个旧元素(窗口最左边的元素),并加入一个新元素(窗口最右边的元素)。
-
-
哈希表的作用:
-
使用哈希表记录窗口内每个元素的出现次数。
-
通过哈希表可以快速判断一个元素是否是窗口内的唯一元素。
-
当元素被移除时,更新哈希表中该元素的计数。如果某个元素的计数降为 0,说明该元素在窗口中完全消失,需要减少独立元素的计数。
-
-
独立元素的计数:
-
使用一个变量
uniqueCnt
来记录当前窗口内独立元素的个数。 -
当新元素加入窗口时,如果该元素在哈希表中的计数为 0,说明它是新的独立元素,
uniqueCnt
加 1。 -
当旧元素被移除时,如果该元素在哈希表中的计数降为 0,说明它不再是窗口内的独立元素,
uniqueCnt
减 1。
-
代码实现:
class Solution {
public long maxSum(List<Integer> nums, int m, int k) {
//HashMap用来统计元素出现的次数
HashMap<Integer,Integer> hashMap=new HashMap<>();
//记录每个窗口内不相同元素的个数
int uniqueCnt=0;
//记录窗口内的和
long sum=0;
long max=0;
for(int i=0;i<nums.size();i++){
//对于每个元素,先把它累加起来
sum+=nums.get(i);
/*
再将元素加入hashmap中,如果hashmap中不存在该元素,就赋值为1,
表示这个元素在窗口内第一次出现
*/
hashMap.put(nums.get(i),hashMap.getOrDefault(nums.get(i),0)+1);
//如果元素第一次出现,就记录一下这种唯一元素的个数
if(hashMap.get(nums.get(i))==1) uniqueCnt++;
if(i<k-1) continue;
//当窗口内的唯一元素达到m,说明这就是一个满足条件的唯一子数组
if(uniqueCnt>=m) max=Math.max(max,sum);
//窗口开始挪动
sum-=nums.get(i-k+1);
//并且在hashmap中也更新一下
hashMap.put(nums.get(i-k+1),hashMap.get(nums.get(i-k+1))-1);
//如果元素在hashmap中不存在了,更新一下uniqueCnt的数目
if(hashMap.get(nums.get(i-k+1))==0) uniqueCnt--;
}
return max;
}
}
题目9:2461. 长度为 K 子数组中的最大和
思路分析:
思路和实现方式同上一题,直接秒杀
代码实现:
class Solution {
public long maximumSubarraySum(int[] nums, int k) {
HashMap<Integer,Integer> hashMap=new HashMap<>();
long sum=0,max=0,unique=0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
hashMap.put(nums[i],hashMap.getOrDefault(nums[i],0)+1);
if(hashMap.get(nums[i])==1) unique++;
if(i<k-1) continue;
if(unique==k) max=Math.max(max,sum);
sum-=nums[i-k+1];
hashMap.put(nums[i-k+1],hashMap.get(nums[i-k+1])-1);
if(hashMap.get(nums[i-k+1])==0) unique--;
}
return max;
}
}
题目10:1423. 可获得的最大点数
思路分析:
这个题目利用反向思维,既然要取牌,而且每次都是从最左边或者最右边取牌,就保证了剩下这些牌的连续性,那么剩下的这些牌,我们是不是可以看作是一个窗口,那么我们只需要找到最小窗口和,就说明取出去的那些牌的点数一定是最大的。
代码实现:
class Solution {
public int maxScore(int[] cardPoints, int k) {
//需要取k张牌,那也就是说会留下cardPoints.length-k张牌
//所以如果留下来的这一部分牌的和是最小值,那就说明取出去的k张牌的和是最大值
int size=cardPoints.length-k;
int sum=0;
for(int num:cardPoints) sum+=num;
//如果k张牌都取,直接返回结果就行
if(size==0) return sum;
int min=Integer.MAX_VALUE,res=0;
for(int i=0;i<cardPoints.length;i++){
res+=cardPoints[i];
if(i<size-1) continue;
//找到最小窗口数组和
min=Math.min(res,min);
res-=cardPoints[i-size+1];
}
//用总和减去剩下牌的最小和,就求出取出牌的最大和
return sum-min;
}
}
题目11:1652. 拆炸弹
思路分析:
这个题目的难度在于循环数组如何解决,其余的并不难。
代码实现:
public int[] decrypt(int[] code, int k) {
int arr[]=new int[code.length];
if(k==0) return arr;
for(int i=0;i<code.length;i++){
int sum=0;
if(k>0){
for(int j=1;j<=k;j++){
sum+=code[(i+j)%code.length];
}
}else{
for(int j=1;j<=-k;j++){
sum+=code[(i-j+code.length)%code.length];
}
}
arr[i]=sum;
}
return arr;
}
题目12:1297. 子串的最大出现次数
思路分析:
首先,这个题目感觉难点在于窗口不固定,但是如果仔细思考一下问题,就会发现,如果一个长字符串重复出现,那么这个长字符串的子串也是重复出现的。例如,abc,abc,ab。这一组数据中,abc出现了两次,那么ab也相当于重复了两次,并且最后一个字符串也是ab,ab就出现了三次,所以如果选择短字符串,可能得到更大的子串出现次数。那么我们就可以确定窗口的大小是minSize了。
代码实现:
class Solution {
public int maxFreq(String s, int maxLetters, int minSize, int maxSize) {
int left=0;
int max=0;
//需要借助hashmap,用来记录字符串出现的次数
HashMap<String,Integer> map=new HashMap<>();
for(int i=0;i<s.length();i++){
if(i<minSize-1) continue;
//找到长度为minSize的子串
String tmp=s.substring(left,i+1);
//并且子串中不同字母个数是小于maxLetters才行,记录满足条件的子串的出现次数
if(count(tmp)<=maxLetters){
map.put(tmp,map.getOrDefault(tmp,0)+1);
//找到子串出现的最大次数
max=Math.max(max,map.get(tmp));
}
//窗口开始移动
left++;
}
return max;
}
//统计子串中,不同字母的出现次数
public int count(String string){
char ch[]=string.toCharArray();
int cnt[]=new int[26];
int unique=0;
for(int i=0;i<ch.length;i++){
cnt[ch[i]-'a']++;
//说明该字母是第一次出现
if(cnt[ch[i]-'a']==1) unique++;
}
return unique;
}
}