滑动窗口+前缀和打开新世界
之前因为学校的课程设计,想坚持力扣每日一题真是难上加难,每天都是看看题解草草了事。现在课设结束了,复盘一下之前遇到的几个开拓眼界的题目。
题目来源:1703. 得到连续 K 个 1 的最少相邻交换次数
https://leetcode.cn/problems/minimum-adjacent-swaps-for-k-consecutive-ones/
大神讲解:【多图】新手教程,一步步带你写,把Hard分解成Easy
https://leetcode.cn/problems/minimum-adjacent-swaps-for-k-consecutive-ones/solution/duo-tu-xin-shou-jiao-cheng-yi-bu-bu-dai-6bps4/
下面是我的学习笔记
这个题目一眼看去,应该是要用贪心,但是具体怎么做,还是没有思路,然后看了大神们的思路,兴致勃勃去实践,在我以为双指针(为什么用双指针,因为我个人感觉滑动窗口就是双指针的另一种形式)可以解决问题的时候,忽然发现大神们竟然用了滑动窗口+前缀和,看了好久终于顿悟了!
这道题目思路:
- 首先看题目是要找连续的1,第一想法肯定是想怎么将1移动到一起使得开销最少
- 改进一下,我们不妨反向思维一下,把0向两边移动,1是不是就到一起了,用zeros数组记录两个1之间存在多少个0
- 进而使用滑动窗口,滑动zeros数组计算总开销,注意如果移动zeros数组则是移动k-1个长度,比如:1010101,如果k是4的话,要把这4个1移动到一起,那么zeros窗口的长度就是3即k-1
- 然后就到了最精彩的部分:前缀和,下面就来详细讲解一下大神是怎么用前缀和解决问题的
偷一下大神的图~这是样例
然后计算第一个窗口的解
第一个窗口的开销(把0向两边移动,每个0路过几个1就乘几)为2*1 + 1*2 + 0*2 + 4*1
那么该怎么计算滑动窗口才能使复杂度为O(n)呢
我们要利用滑动窗口的特性,即:下一个窗口的解,可以由前一个窗口的解快速得到!
前缀和可以做到这一点
假设当前的窗口从 i 到 j ,那么上一个窗口就是从 i-1 到 j-1 。我们可以找到一个中点mid,它的cost是不变的,它左边的cost都减少了1,而右边的cost都增加了1。这就是本题的核心。
因为减少/增加的1需要与zeros中的值相乘,所以cost的变化可以通过求zeros上「区间和」来快速得到。
一句话点明整个题目!
然后区分一下窗口长度奇偶的情况
因此zeros数组从头滑动到尾就可以解决问题了,实现了O(n)时间复杂度,比双指针快了很多。
放一下大神的代码:
class Solution {
private:
vector<int> zeros;
vector<int> pre {0};
void GenerateZeros(const vector<int> &nums) {
int n = nums.size(), i = 0;
while (i < n && nums[i] == 0) i++;
while (i < n) {
int j = i+1;
while (j < n && nums[j] == 0) j++;
if (j < n) zeros.push_back(j-i-1);
i = j;
}
}
//计算前缀和,即前面提到的下一个窗口的解,可以由前一个窗口的解快速得到
void GeneratePresum(vector<int>& zeros) {
for (int i = 0; i < zeros.size(); i++) {
pre.push_back(pre.back() + zeros[i]);
}
}
//利用前缀和很容易得到你想要的区间和,进而决定加或者减
int GetRangeSum(int left, int right) {
return pre[right+1] - pre[left];
}
public:
int minMoves(vector<int>& nums, int k) {
GenerateZeros(nums); //第一步:生成zeros
int cost = 0; //第二步:计算第一个窗口的解
int left = 0, right = k-2;
for (int i = left; i <= right; i++) {
cost += zeros[i] * (min(i+1, right-i+1));
}
int minCost = cost;
GeneratePresum(zeros); //第三步:开始滑动窗口
int i = 1, j = i + k - 2;
for (; j < zeros.size(); i++, j++) {
int mid = (i + j) / 2;
cost -= GetRangeSum(i-1, mid-1);
cost += GetRangeSum(mid+k%2, j);
minCost = min(minCost, cost);
}
return minCost;
}
};
🍅