Day2--滑动窗口与双指针--2090. 半径为 k 的子数组平均值,2379. 得到 K 个黑块的最少涂色次数,2841. 几乎唯一子数组的最大和
Day2–滑动窗口与双指针–2090. 半径为 k 的子数组平均值,2379. 得到 K 个黑块的最少涂色次数,2841. 几乎唯一子数组的最大和
今天要训练的题目类型是:【定长滑动窗口】,题单来自@灵艾山茶府。
2090. 半径为 k 的子数组平均值
思路【我】:
- 先让数组默认填充-1,这样不用自己填充
- 直接让k变成我们熟知的窗口大小,在填avg的时候,再求回窗口的中心
- 注意元素大小,sum要用long类型
- 定长滑动窗口三部曲:入–更新–出。
class Solution {public int[] getAverages(int[] nums, int k) {// 直接让k变成我们熟知的窗口大小k = 2 * k + 1;int n = nums.length;// 结果集先填充-1int[] avg = new int[n];Arrays.fill(avg, -1);// 剪枝:如果n小于窗口,返回if (n < k) {return avg;}// 踩坑:没注意元素大小,累计和已经超过int上限了,要longlong sum = 0;// 开始滑动窗口for (int i = 0; i < n; i++) {// 1,右指针(i)进窗sum += nums[i];// 没到窗口大小,跳过if (i < k - 1) {continue;}// 2,更新结果集。这里要用i-k/2,找回中心位置// 因为sum是long,所以要强行转为intavg[i - k / 2] = (int) (sum / k);// 3,左指针(i-k=1)退出窗口sum -= nums[i - k + 1];}return avg;}
}
思路:
写法二:用window变量
class Solution {public int[] getAverages(int[] nums, int k) {int n = nums.length;int window = 2 * k + 1;// 结果集先填充-1int[] avg = new int[n];Arrays.fill(avg, -1);// 剪枝:如果n小于窗口,返回if (n < window) {return avg;}long sum = 0;// 开始滑动窗口for (int i = 0; i < n; i++) {// 1,右指针(i)进窗sum += nums[i];// 没到窗口大小,跳过if (i < window - 1) {continue;}// 索引在窗口中心位置avg[i - k] = (int) (sum / window);// 3,左指针(i-window+1)退出窗口sum -= nums[i - window + 1];}return avg;}
}
2379. 得到 K 个黑块的最少涂色次数
思路【我】:
定长滑动窗口模板题。
定长滑动窗口三部曲:入–更新–出。
class Solution {public int minimumRecolors(String blocks, int k) {char[] ch = blocks.toCharArray();int n = ch.length;// 初始化int minCount = Integer.MAX_VALUE;int count = 0;// 开始滑动for (int i = 0; i < n; i++) {// 1,右指针(i)入窗if (ch[i] == 'W') {count++;}// 没到窗口大小,继续if (i < k - 1) {continue;}// 2,更新if (count < minCount) {minCount = count;}// 3,左指针(i-k+1)出窗if (ch[i - k + 1] == 'W') {count--;}}return minCount;}
}
2841. 几乎唯一子数组的最大和
思路【我】:
- 在传统滑动窗口的基础上,多添加一个mapmap<元素,出现次数>来统计窗口内元素的出现次数,map.size()就是不同种元素的个数。
- 注意返回值是long类型的
- 定长滑动窗口三部曲:入–更新–出。
- 入:右指针(i)入栈,map统计<元素,出现次数>
- 更新,要先满足不同种元素个数大于等于m
- 左指针出窗,map对应要减少该元素的出现次数,如果为0,要remove掉
class Solution {public long maxSum(List<Integer> nums, int m, int k) {// 先确定是几乎唯一子数组,再求最大和。int[] arr = nums.stream().mapToInt(Integer::intValue).toArray();int n = arr.length;// 使用map<元素,出现次数>来统计窗口内,元素的出现次数,map.size()就是不同种元素的个数Map<Integer, Integer> map = new HashMap<>();// 注意要返回的是long啊,又踩坑了long sum = 0;long maxSum = 0;// 开始滑动窗口for (int i = 0; i < n; i++) {// 右指针(i)入栈,map统计<元素,出现次数>sum += arr[i];map.put(arr[i], map.getOrDefault(arr[i], 0) + 1);// 如果还不到窗口大小,继续if (i < k - 1) {continue;}// 2,更新,要先满足不同种元素个数大于等于mif (sum > maxSum && map.size() >= m) {maxSum = sum;}// 3,左指针出窗,map对应要减少该元素的出现次数,如果为0,要remove掉int out = arr[i - k + 1];sum -= out;if (map.get(out) == 1) {map.remove(out);} else {map.put(out, map.get(out) - 1);}}return maxSum;}
}
map.put(arr[i], map.getOrDefault(arr[i], 0) + 1);
可以写成map.merge(arr[i], 1, Integer::sum);
其中前者需要两次哈希操作,一次获取值,一次更新值。后者只需要一次哈希操作。
因此,
merge
在理论上会略快一点,尤其是在哈希表较大或哈希冲突较多的场景下,减少一次哈希操作的优势会更明显。