滑动窗口
文章目录
- 滑动窗口
- 模板框架
- [76. 最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/)
- [567. 字符串的排列](https://leetcode-cn.com/problems/permutation-in-string/)
- [438. 找到字符串中所有字母异位词](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/)
- [3. 无重复字符的最长子串](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/)
- [643. 子数组最大平均数 I](https://leetcode-cn.com/problems/maximum-average-subarray-i/)
- [187. 重复的DNA序列](https://leetcode-cn.com/problems/repeated-dna-sequences/)
- [424. 替换后的最长重复字符](https://leetcode-cn.com/problems/longest-repeating-character-replacement/)
- [480. 滑动窗口中位数](https://leetcode-cn.com/problems/sliding-window-median/)
- [594. 最长和谐子序列](https://leetcode-cn.com/problems/longest-harmonious-subsequence/)
- [992. K 个不同整数的子数组](https://leetcode-cn.com/problems/subarrays-with-k-different-integers/)
- [1423. 可获得的最大点数](https://leetcode-cn.com/problems/maximum-points-you-can-obtain-from-cards/)
- [1004. 最大连续1的个数 III](https://leetcode-cn.com/problems/max-consecutive-ones-iii/)
- [1052. 爱生气的书店老板](https://leetcode-cn.com/problems/grumpy-bookstore-owner/)
- [1208. 尽可能使字符串相等](https://leetcode-cn.com/problems/get-equal-substrings-within-budget/)
- [1438. 绝对差不超过限制的最长连续子数组](https://leetcode-cn.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/)
- [1838. 最高频元素的频数](https://leetcode-cn.com/problems/frequency-of-the-most-frequent-element/)
模板框架
76. 最小覆盖子串
思路分析
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0;
int right = 0;
//valid表示窗口中满足need条件的字符个数
int valid = 0;
while (right < s.length()) {
// 开始滑动
}
代码实现
/**
* https://leetcode-cn.com/problems/minimum-window-substring/
*
* @author xiexu
* @create 2022-01-12 6:20 下午
*/
public class _76_最小覆盖子串 {
public String minWindow(String s, String t) {
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0;
int right = 0;
//valid表示窗口中满足need条件的字符个数
int valid = 0;
//记录最小覆盖子串的起始索引及长度
int start = 0;
int len = Integer.MAX_VALUE;
while (right < s.length()) {
//c是将移入窗口的字符
char c = s.charAt(right);
//右移窗口
right++;
//进行窗口内数据的一系列更新
if (need.containsKey(c)) {
window.put(c, window.getOrDefault(c, 0) + 1);
if (window.get(c).equals(need.get(c))) {
valid++;
}
}
//判断左侧窗口是否要收缩
while (valid == need.size()) {
//在这里更新最小覆盖子串
if (right - left < len) {
start = left;
len = right - left;
}
//d是将移出窗口的字符
char d = s.charAt(left);
//左移窗口
left++;
//进行窗口内数据的一系列更新
if (need.containsKey(d)) {
if (window.get(d).equals(need.get(d))) {
valid--;
}
window.put(d, window.get(d) - 1);
}
}
}
//返回最小覆盖子串
return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
}
}
567. 字符串的排列
思路分析
代码实现
/**
* https://leetcode-cn.com/problems/permutation-in-string/
*
* @author xiexu
* @create 2022-01-12 6:39 下午
*/
public class _567_字符串的排列 {
//判断s中是否存在t的排列
public boolean checkInclusion(String t, String s) {
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0;
int right = 0;
//valid表示窗口中满足need条件的字符个数
int valid = 0;
while (right < s.length()) {
char c = s.charAt(right);
right++;
//进行窗口内数据的一系列更新
if (need.containsKey(c)) {
window.put(c, window.getOrDefault(c, 0) + 1);
if (window.get(c).equals(need.get(c))) {
valid++;
}
}
//判断左侧窗口是否要收缩
while (right - left >= t.length()) {
//在这里判断是否找到了合法的子串
if (valid == need.size()) {
return true;
}
char d = s.charAt(left);
left++;
//进行窗口内数据的一系列更新
if (need.containsKey(d)) {
if (window.get(d).equals(need.get(d))) {
valid--;
}
window.put(d, window.get(d) - 1);
}
}
}
//未找到符合条件的子串
return false;
}
}
438. 找到字符串中所有字母异位词
思路分析
代码实现
/**
* https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/
*
* @author xiexu
* @create 2022-01-13 9:15 上午
*/
public class _438_找到字符串中所有字母异位词 {
//判断s中是否存在t的排列
public List<Integer> findAnagrams(String s, String t) {
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
//记录结果
ArrayList<Integer> list = new ArrayList<>();
int left = 0;
int right = 0;
//valid表示窗口中满足need条件的字符个数
int valid = 0;
while (right < s.length()) {
char c = s.charAt(right);
right++;
//进行窗口内数据的一系列更新
if (need.containsKey(c)) {
window.put(c, window.getOrDefault(c, 0) + 1);
if (window.get(c).equals(need.get(c))) {
valid++;
}
}
//判断左侧窗口是否要收缩
while (right - left >= t.length()) {
//当窗口符合条件时,把起始索引加入list
if (valid == need.size()) {
list.add(left);
}
char d = s.charAt(left);
left++;
//进行窗口内数据的一系列更新
if (need.containsKey(d)) {
if (window.get(d).equals(need.get(d))) {
valid--;
}
window.put(d, window.get(d) - 1);
}
}
}
return list;
}
}
3. 无重复字符的最长子串
思路分析
代码实现
/**
* https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
*
* @author xiexu
* @create 2022-01-13 9:25 上午
*/
public class _3_无重复字符的最长子串 {
public int lengthOfLongestSubstring(String s) {
HashMap<Character, Integer> window = new HashMap<>();
int left = 0;
int right = 0;
//记录结果
int res = 0;
while (right < s.length()) {
char c = s.charAt(right);
right++;
//进行窗口内数据的一系列更新
window.put(c, window.getOrDefault(c, 0) + 1);
//判断左侧窗口是否要收缩
while (window.get(c) > 1) {
char d = s.charAt(left);
left++;
//进行窗口内数据的一系列更新
window.put(d, window.get(d) - 1);
}
//在这里更新答案
res = Math.max(res, right - left);
}
return res;
}
}
643. 子数组最大平均数 I
思路分析
代码实现
/**
* https://leetcode-cn.com/problems/maximum-average-subarray-i/
*
* @author xiexu
* @create 2022-01-13 10:29 上午
*/
public class _643_子数组最大平均数_I {
public double findMaxAverage(int[] nums, int k) {
double ans = 0, sum = 0;
for (int i = 0; i < k; i++) {
sum += nums[i];
}
ans = sum / k;
for (int i = k; i < nums.length; i++) {
// int add = nums[i], del = nums[i - k];
//每往前滑动一次,需要删除一个和添加一个元素
sum = sum + nums[i] - nums[i - k];
ans = Math.max(ans, sum / k);
}
return ans;
}
}
187. 重复的DNA序列
代码实现
/**
* https://leetcode-cn.com/problems/repeated-dna-sequences/
*
* @author xiexu
* @create 2022-01-13 10:53 上午
*/
public class _187_重复的DNA序列 {
public List<String> findRepeatedDnaSequences(String s) {
ArrayList<String> ans = new ArrayList<>();
//记录每个子串的出现次数
HashMap<String, Integer> map = new HashMap<>();
for (int i = 0; i + 10 <= s.length(); i++) {
//一次性截取10个
String cur = s.substring(i, i + 10);
int cnt = map.getOrDefault(cur, 0);
if (cnt == 1) { //子串出现次数超过一次,就加入
ans.add(cur);
}
map.put(cur, cnt + 1);
}
return ans;
}
}
424. 替换后的最长重复字符
思路分析
本题可以先退化成考虑 K=0 的情况,此时题目就变成了求解字符串中最长连续子串长度问题了。
我们先可以通过这个特例先了解一下滑动窗口的求解过程
上图的求解过程展示中,窗口从左至右不断扩张/滑动,当窗口触达字符串末尾字符时,运算结束,窗口的宽度为最终结果。初始窗口的宽度为 1,我们不断的通过向当前窗口覆盖的子串后面追加一个字符看是否能满足我们的要求,如果满足窗口扩张,如果不满足,窗口向右滑动。
当 K>0 时,子串的条件变成了允许我们变换子串中的 K 个字符使其变成一个连续子串
那么这个题的关键点就是我们如何判断一个字符串改变 K 个字符,能够变成一个连续串
如果当前字符串中的出现次数最多的字母个数 + K 大于串长度,那么这个串就是满足条件的
我们维护一个数组 int[26]
来存储当前窗口中各个字母的出现次数,left 表示窗口的左边界,right 表示窗口右边界
- 窗口扩张:left 不变,right ++
- 窗口滑动:left ++,right ++
代码实现
/**
* https://leetcode-cn.com/problems/longest-repeating-character-replacement/
*
* @author xiexu
* @create 2022-01-13 11:04 上午
*/
public class _424_替换后的最长重复字符 {
public int characterReplacement(String s, int k) {
if (s == null) {
return 0;
}
int[] map = new int[26];
char[] chars = s.toCharArray();
int left = 0;
int right = 0;
//滑动窗口历史上在一个窗口内出现次数最多的字母的次数(仅限在当时的窗口内出现)
int historyCharMax = 0;
for (right = 0; right < chars.length; right++) {
int index = chars[right] - 'A';
map[index]++; //将右指针右移后对应的新元素添加到map中
historyCharMax = Math.max(historyCharMax, map[index]); //更新历史最大出现次数
//如果新加元素(即新加元素就是当下出现次数最多的字母)使得当下historyCharMax增加了1,则扩大窗口,以下if不执行
//否则,不扩窗,窗口滑动(右指针已经右移一位了,需要把左指针左移,执行if语句)
if (right - left + 1 - historyCharMax > k) {
map[chars[left] - 'A']--;
left++;
}
}
//即right - left最终窗口的长度 右指针right最终一定会移到chars.length即数组的末端
return chars.length - left;
}
}
也可以换另一种实现思路
/**
* https://leetcode-cn.com/problems/longest-repeating-character-replacement/
*
* @author xiexu
* @create 2022-01-13 11:04 上午
*/
public class _424_替换后的最长重复字符_另一种实现 {
public int characterReplacement(String s, int k) {
char[] cs = s.toCharArray();
int[] cnt = new int[26];
int ans = 0;
for (int l = 0, r = 0; r < s.length(); r++) {
int cur = cs[r] - 'A';
cnt[cur]++;
while (!check(cnt, k)) {
int del = cs[l] - 'A';
cnt[del]--;
l++;
}
ans = Math.max(ans, r - l + 1);
}
return ans;
}
public boolean check(int[] cnt, int k) {
int max = 0, sum = 0;
for (int i = 0; i < 26; i++) {
max = Math.max(max, cnt[i]);
sum += cnt[i];
}
return sum - max <= k;
}
}
480. 滑动窗口中位数
思路分析
代码实现
/**
* https://leetcode-cn.com/problems/sliding-window-median/
*
* @author xiexu
* @create 2022-01-13 1:34 下午
*/
public class _480_滑动窗口中位数_暴力解法 {
public double[] medianSlidingWindow(int[] nums, int k) {
int n = nums.length;
//计算结果数组的元素个数
int cnt = n - k + 1;
double[] ans = new double[cnt];
//每次窗口中的元素,单独拿出来放到数组中
int[] t = new int[k];
for (int left = 0, right = left + k - 1; right < n; left++, right++) {
for (int i = left; i <= right; i++) {
//把每一次窗口中的元素单独拿出来放到数组t中
t[i - left] = nums[i];
}
//对窗口中的元素进行排序
Arrays.sort(t);
ans[left] = (t[k / 2] / 2.0) + (t[(k - 1) / 2] / 2.0);
}
return ans;
}
}
594. 最长和谐子序列
思路分析
代码实现
/**
* https://leetcode-cn.com/problems/longest-harmonious-subsequence/
*
* @author xiexu
* @create 2022-01-14 3:57 下午
*/
public class _594_最长和谐子序列 {
public int findLHS(int[] nums) {
//先排序
Arrays.sort(nums);
int n = nums.length, ans = 0;
for (int left = 0, right = 0; right < n; right++) {
//如果条件不满足,滑动窗口
while (left < right && nums[right] - nums[left] > 1) {
left++;
}
//条件满足,记录满足条件的历史最大窗口
if (nums[right] - nums[left] == 1) {
ans = Math.max(ans, right - left + 1);
}
}
return ans;
}
}
992. K 个不同整数的子数组
思路分析
最长子数组长度
我们先求一个简单问题:求 A 中由 K 个不同整数组成的最长子数组的长度。
- 我们可以用以下滑动窗口模板解决
public int findSubstring(int[] nums) {
// 数组/字符串长度
int n = nums.length;
// 双指针,表示当前遍历的区间[left, right],闭区间
int left = 0, right = 0;
// 用于统计 子数组/子区间 是否有效
int[] count = new int[n + 1];
// 保存最大的满足题目要求的 子数组/子串 长度
int res = 0;
// 当右边的指针没有搜索到 数组/字符串 的结尾
while (right < n) {
// 增加当前右边指针的数字/字符的计数
count[nums[right]] += 1;
// while 区间[left, right]不符合题意:此时需要一直移动左指针,直至找到一个符合题意的区间
while (right - left > n) { //right - left > n 这个是模拟
// 移动左指针前需要从count中减少left位置字符的计数
count[nums[left]]--;
// 真正的移动左指针,注意不能跟上面一行代码写反
left++;
}
// 到 while 结束时,我们找到了一个符合题意要求的 子数组/子串
res = Math.max(res, right - left + 1);
// 移动右指针,去探索新的区间
right++;
}
return res;
}
滑动窗口中用到了左右两个指针,它们移动的思路是:以右指针作为驱动,拖着左指针向前走。右指针每次只移动一步,而左指针在内部 while 循环中每次可能移动多步。右指针是主动前移,探索未知的新区域;左指针是被迫移动,负责寻找满足题意的区间。
模板的整体思想是:
- 定义两个指针
left
和right
分别指向区间的开头和结尾,注意是闭区间;定义counter
用来统计该区间内的各个字符出现次数; - 第一重
while
循环是为了判断right
指针的位置是否超出了数组边界;当right
每次到了新位置,需要增加right
指针的计数; - 第二重
while
循环是让left
指针向右移动到[left, right]
区间符合题意的位置;当 left 每次移动前,需要减少 left 指针的计数; - 在第二重
while
循环之后,成功找到了一个符合题意的[left, right]
区间,题目要求最大的区间长度,因此更新res
为max(res, 当前区间的长度)
。 right
指针每次向右移动一步,开始探索新的区间。
求 A 中由 K 个不同整数组成的最长子数组的长度,我们写出以下代码:
public int atMostK(int[] nums, int k) {
int n = nums.length;
int left = 0, right = 0;
int[] counter = new int[n + 1];
int distinct = 0;
int res = 0;
while (right < n) {
if (counter[nums[right]] == 0) {
distinct += 1;
}
counter[nums[right]]++;
while (distinct > k) {
counter[nums[left]]--;
if (counter[nums[left]] == 0) {
distinct -= 1;
}
left += 1;
}
res = Math.max(res, right - left + 1);
right += 1;
}
return res;
}
这段代码的思想是:使用一个字典 counter
,保存区间内每个数字出现的次数,使用一个变量 distinct
表示该区间中不同的数字个数(distinct 等于 counter 的 key 的数量)。下面讲一下两个 if。
if counter[nums[right]] == 0: distinct += 1
,这是说,right
指向的位置不在counter中,是个新数字,所以distinct += 1
;if counter[nums[left]] == 0: distinct -= 1
,这是说,left
指针向右移动前,要把其原来指向的数字个数 - 1,当该数字的个数减到 0 了,则[left, right]
区间内的不同数字的个数减少了一个,所以distinct -= 1
- 其余代码和模板类似。
子数组个数
上面求的是 A 中由 K 个不同整数组成的最长子数组的长度,如果问 A 中由最多 K 个不同整数组成的子数组的个数,该怎么办呢?
答:只用把 res = Math.max(res, right - left + 1)
改成 res += right - left + 1
。
我们要求由最多 K
个不同整数组成的子数组的个数,那么对于长度 [left, right]
区间内的每个子数组都是满足要求的,res
要累加的其实是 符合条件的并且以 right
为右端点的子数组个数,这个数量就是right - left + 1
,即区间长度。例如 [2,4,3,5]
满足条件,要累加的其实就是 [2,4,3,5], [4,3,5], [3,5], [5]
四个子区间。
对于题目的例子:对于 nums = [1,2,1,2,3], K = 2
,我们运行上面的代码过程中,会得到的两个满足题意的子数组 [1, 2, 1, 2]
和[2, 3]
。
- 对于子数组
[1, 2, 1, 2]
,它的所有子数组都是满足题意的,共有 1 + 2 + 3 + 4 = 10 个子数组。- 以第一个
1
为右端点的满足题意的子数组为[1]
; - 以第一个
2
为右端点的满足题意的子数组为[1,2], [2]
; - 以第二个
1
为右端点的满足题意的子数组为[1,2,1],[2,1], [1]
; - 以第二个
2
为右端点的满足题意的子数组为[1,2,1,2], [2,1,2],[1,2], [2]
;
- 以第一个
- 对于子数组
[2, 3]
,它的所有子数组都满足题意,共有 3 个子数组。- 以
2
为右端点的满足题意的子数组,在上面已经统计过了,因此不要重复统计。 - 以
3
为右端点的满足题意的子数组为[2, 3], [3]
。
- 以
所以总的数组 A 有 12 个由 最多 2 个不同整数组成的子数组。
所以,当 right
到达一个新位置之后,把 left
调整到满足题意的位置,当前[left, right]
区间内符合条件的并且以 right
为右端点的子数组个数 为 right - left + 1
。当 right
指针把数组的每个位置遍历一遍,就得到了以每个位置作为区间右短点的子数组长度,累加得到的就是结果。
下面的动图能帮助理解:
K 个不同整数的子数组的个数
本题围绕一条公式:
恰好由 K 个不同整数的子数组的个数 = 最多由 K 个不同整数的子数组的个数 - 最多由 K - 1 个不同整数的子数组的个数
解释如下:
先来个通俗的:张三、李四、老王, 3 个人一起吃包子,他们最多能吃 10 个包子;如果有一天老王有事没来,现在只有张三、李四两个人吃包子,他们俩最多能吃 3 个包子。求老王的饭量是能吃几个包子?
代码实现
/**
* https://leetcode-cn.com/problems/subarrays-with-k-different-integers/
*
* @author xiexu
* @create 2022-01-14 4:31 下午
*/
public class _992_K_个不同整数的子数组 {
public int subarraysWithKDistinct(int[] nums, int k) {
return getMaxNum(nums, k) - getMaxNum(nums, k - 1);
}
/**
* 获取最多由 k 个不同整数的子数组数量
*
* @param nums 原始数组
* @param k k的数量
* @return
*/
public static int getMaxNum(int[] nums, int k) {
int n = nums.length;
int res = 0;
//count表示是对于不同数字的计数,在[left,right)的区间内,有count个不同的数字
int count = 0;
int left = 0;
int right = 0;
//定义hash数组,用arr中的数字来做为map中的下标
int[] map = new int[n + 1];
while (right < n) {
//如果arr[right]在map中作为下标对应数值为0,表示该数字目前只出现了1次,count加1
if (map[nums[right]] == 0) {
count++;
}
//如果数值不为0,表示该数字目前已经是多次出现,count不变,map[nums[right]]加1
map[nums[right]]++;
//如果count>K,表示不同的数字已经超过了预期,此时需要移动左指针
while (count > k) {
//开始计算左指针,计算完毕,左指针自动向右移动
//如果arr[left]在map中作为下标对应数值先减1之后的结果不为0,则表示还有多次出现
//如果数值为0,表示当前left下标对应的数字,已经从[left,right]区间中移出完毕,此时count--
if (--map[nums[left]] == 0) {
count--;
}
left++;
}
res += right - left + 1;
//右指针不断的向右移动,每次计算完一次,right自动加1
right++;
}
return res;
}
}
1423. 可获得的最大点数
代码实现
/**
* https://leetcode-cn.com/problems/maximum-points-you-can-obtain-from-cards/
*
* @author xiexu
* @create 2022-01-14 5:16 下午
*/
public class _1423_可获得的最大点数 {
// 一共 len 张卡牌
// 每次从头部或者尾部取一张卡牌,一共取K次,要求,取出的卡牌之和最大
// 等价于剩下的 len - K 张卡牌之和最小
// 并且剩下的len - K张卡牌必须是连续的
// 因此很容易想到滑动窗口
public int maxScore(int[] cardPoints, int k) {
int len = cardPoints.length;
int min = 0; //记录已知的 len - k 张卡牌之和的最小值
int now = 0; //记录当前的 len - k 张卡牌之和
int total = 0; //记录卡牌总和
int rest = len - k; //需要剩下的卡牌数量,即滑动窗口的大小
for (int i = 0; i < rest; i++) {
now += cardPoints[i]; //第0 到 第len-k 张卡牌之和
}
min = now;
total = now;
for (int i = rest; i < len; i++) {
now = now + cardPoints[i]; // 每次移动窗口都加上窗口右边的一个元素
now = now - cardPoints[i - rest]; // 同时减去左边的元素
min = Math.min(min, now); // 更新最小值
total = total + cardPoints[i]; // 记录卡牌总和
}
return total - min; // 卡牌总和 - 剩下卡牌的最小值 = 取走卡牌的最大值
}
}
1004. 最大连续1的个数 III
思路分析
示例
以 A= [1,1,1,0,0,0,1,1,1,1,0], K = 2
为例,下面的动图演示了滑动窗口的两个指针的移动情况。
代码实现
/**
* https://leetcode-cn.com/problems/max-consecutive-ones-iii/
*
* @author xiexu
* @create 2022-01-15 9:05 上午
*/
public class _1004_最大连续1的个数_III {
public int longestOnes(int[] nums, int k) {
int n = nums.length;
int res = 0;
int left = 0, right = 0;
//统计当前窗口内0的个数
int zeros = 0;
while (right < n) {
if (nums[right] == 0) {
zeros++;
}
while (zeros > k) {
if (nums[left] == 0) {
zeros--;
}
left++;
}
res = Math.max(res, right - left + 1);
right++;
}
return res;
}
}
1052. 爱生气的书店老板
思路分析
以题目示例 customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], X = 3
为例说明。下图中蓝色的是所有不生气时的顾客;滑动窗口内的浅红色是老板不生气时对应的顾客数,深红色是老板生气时对应的顾客数。
代码实现
/**
* https://leetcode-cn.com/problems/grumpy-bookstore-owner/
*
* @author xiexu
* @create 2022-01-15 9:20 上午
*/
public class _1052_爱生气的书店老板 {
public int maxSatisfied(int[] customers, int[] grumpy, int X) {
//统计出所有不生气时间内的顾客总数
int total = 0;
int length = customers.length;
for (int i = 0; i < length; i++) {
if (grumpy[i] == 0) {
total += customers[i];
}
}
//记录当前窗口下可以挽回的顾客数量
int increase = 0;
//最初窗口大小为X时可以挽回的顾客数量
for (int i = 0; i < X; i++) {
increase += customers[i] * grumpy[i];
}
//记录窗口中最大可以挽回的顾客数量
int maxIncrease = increase;
for (int i = X; i < length; i++) {
increase = increase - customers[i - X] * grumpy[i - X] + customers[i] * grumpy[i];
maxIncrease = Math.max(maxIncrease, increase);
}
//原来不生气时间满意的顾客总数+挽回的满意顾客数=最多的客户能够感到满意的数量
return total + maxIncrease;
}
}
1208. 尽可能使字符串相等
思路分析
这个题目比较难理解,我需要再解释一下。
于是题目变成了:已知一个数组 costs ,求:和不超过 maxCost 时最长的子数组的长度。
代码实现
/**
* https://leetcode-cn.com/problems/get-equal-substrings-within-budget/
*
* @author xiexu
* @create 2022-01-15 9:54 上午
*/
public class _1208_尽可能使字符串相等 {
public int equalSubstring(String s, String t, int maxCost) {
int left = 0, right = 0;
int sum = 0;
int res = 0;
while (right < s.length()) {
int num = Math.abs(s.charAt(right) - t.charAt(right));
sum += num;
while (sum > maxCost) {
sum -= Math.abs(s.charAt(left) - t.charAt(left));
left++;
}
res = Math.max(res, right - left + 1);
right++;
}
return res;
}
}
1438. 绝对差不超过限制的最长连续子数组
思路分析
代码实现
/**
* https://leetcode-cn.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/
*
* @author xiexu
* @create 2022-01-15 12:32 下午
*/
public class _1438_绝对差不超过限制的最长连续子数组 {
public int longestSubarray(int[] nums, int limit) {
TreeMap<Integer, Integer> map = new TreeMap<>();
int left = 0, right = 0;
int res = 0;
while (right < nums.length) {
map.put(nums[right], map.getOrDefault(nums[right], 0) + 1);
//lastKey():返回此TreeMap中存在的最后一个最高键元素值
//firstKey():返回Map中当前的第一个(最低)键
while (map.lastKey() - map.firstKey() > limit) {
map.put(nums[left], map.get(nums[left]) - 1);
if (map.get(nums[left]) == 0) {
map.remove(nums[left]);
}
left++;
}
res = Math.max(res, right - left + 1);
right++;
}
return res;
}
}
1838. 最高频元素的频数
思路分析
首先很容易想到,先对数组进行排序。
接下来,我们只需要找到某一段区间内,每个值与该区间内最后一个值相差的总和,不超过目标k的最大值。
举个🌰
数组:1,2,4,6
k:5
- 第一次在【1,2】范围内,如果要让1变成2,那么就加1即可。
- 第二次扩大到【1,2,4】范围内,如果要让1变成4,2变成4,那么一共需要增加5次
- 第三次扩大到【1,2,4,6】范围内,依次类推,每个值变为6,一共需要11次。(此时11次已经超过了5次的操作上限,那么就滑动窗口的区间)
接下来,我们只需要依次判断窗口内满足条件的最大值即可。
第一次区间内,需要加1(满足)
第二次区间内,需要加5(满足)
第三次区间内,需要加11(不满足)
由于超过了目标值5,所以窗口左侧,向右移动一位。
接下来,依此类推。。。
计算操作次数的公式:
count += (nums[right] - nums[right - 1]) * (right - left);
nums[right] - nums[right - 1]
表示窗口右区间的数与它前一个数的差值,这个差值也就是nums[right - 1]
变为nums[right]
的操作次数。(right - left)
表示这个区间里,有几个数需要被进行操作。
那么,这两项相乘即为区间内的操作次数。
举例说明
当nums[right] = 4
时,区间[0, 1]
内的操作次数 = nums[right - 1]
变为nums[right]
需要的次数,(即"1"变为"4"的次数)
当nums[right] = 8
时,区间[0, 2]内的操作次数 = nums[right - 1]
变为nums[right]
需要的次数乘区间长度,即两个"4"变为"8"的长度,此时nums[0]
经由上一次的操作已经变成了"4"。
那么总的操作次数,应当是每一次计算后的累加值。
代码实现
/**
* https://leetcode-cn.com/problems/frequency-of-the-most-frequent-element/
*
* @author xiexu
* @create 2022-01-15 1:13 下午
*/
public class _1838_最高频元素的频数 {
public int maxFrequency(int[] nums, int k) {
Arrays.sort(nums);
int ans = 1; //初始化最大频数为1
int left = 0, right = 0;
int count = 0; //操作次数
for (right = 1; right < nums.length; right++) {
count += (nums[right] - nums[right - 1]) * (right - left);
//如果超过目标值
while (count > k) {
//那么就减去区间内最左侧的值与最后一个值的差距
//然后再让区间左侧向右移动一位,相当于整个区间缩小了一位距离,在缩小的区间内再判断是否满足要求
count -= (nums[right] - nums[left]);
left++;
}
ans = Math.max(ans, right - left + 1);
}
return ans;
}
}