- 综述
- 模板
- 例题
- 1176. 健身计划评估">【求和】1176. 健身计划评估
- 1456. 定长子串中元音的最大数目">【计数】1456. 定长子串中元音的最大数目
- 643. 子数组最大平均数 I">【平均数】643. 子数组最大平均数 I
- 1052. 爱生气的书店老板">【有条件计算】1052. 爱生气的书店老板
- 1423. 可获得的最大点数">【左右两端】1423. 可获得的最大点数
- 438. 找到字符串中所有字母异位词">【排列】438. 找到字符串中所有字母异位词
- 567. 字符串的排列">【排列】567. 字符串的排列
- 1151. 最少交换次数来组合所有的 1">【交换/中等】1151. 最少交换次数来组合所有的 1
综述
通常分为两段式,先计算一个定长窗口,然后再向右依次滑动窗口,每滑动一次窗口,更新一次结果集。
模板
写法一:用两个指针l, r
let l = 0, r = 0, ans = 结果集
while(r < k) {
计算窗口
r++
}
while( r < |len|) {
滑动窗口,加入新的r元素,去除旧的l元素,r++,l++;
更新结果集
}
return ans;
写法二:只使用一个主指针r, 由于是定长,所以可以直接计算l的位置。
let r = 0, ans = 结果集
while(r < k) {
计算窗口
r++
}
while( r < |len|) {
滑动窗口,加入新的r元素,去除旧的l元素,r++,l = r - k;
更新结果集
}
return ans;
例题
【求和】1176. 健身计划评估
难度简单17收藏分享切换为英文接收动态反馈
你的好友是一位健身爱好者。前段日子,他给自己制定了一份健身计划。现在想请你帮他评估一下这份计划是否合理。
他会有一份计划消耗的卡路里表,其中 calories[i]
给出了你的这位好友在第 i
天需要消耗的卡路里总量。
为了更好地评估这份计划,对于卡路里表中的每一天,你都需要计算他 「这一天以及之后的连续几天」 (共 k
天)内消耗的总卡路里 T:
- 如果
T < lower
,那么这份计划相对糟糕,并失去 1 分; - 如果
T > upper
,那么这份计划相对优秀,并获得 1 分; - 否则,这份计划普普通通,分值不做变动。
请返回统计完所有 calories.length
天后得到的总分作为评估结果。
注意:总分可能是负数。
/**
* @param {number[]} calories
* @param {number} k
* @param {number} lower
* @param {number} upper
* @return {number}
*/
var dietPlanPerformance = function(calories, k, lower, upper) {
let t = 0, r = 0, ans = 0;
while (r < calories.length && r < k) {
t += calories[r++];
}
const update = (t) => {
if (t > upper) {
return 1;
}
if (t < lower) {
return -1;
}
return 0;
};
ans += update(t);
while (r < calories.length) {
t += calories[r];
t -= calories[r - k];
ans += update(t);
r++;
}
return ans;
};
【计数】1456. 定长子串中元音的最大数目
/**
* @param {string} s
* @param {number} k
* @return {number}
*/
var maxVowels = function(s, k) {
const dict = new Set(['a', 'e', 'i', 'o', 'u']);
let t = 0;
for (let i = 0; i < k; i++) {
t += dict.has(s.charAt(i)) ? 1 : 0;
}
let ans = t;
for (let i = k; i < s.length; i++) {
t += dict.has(s.charAt(i)) ? 1 : 0;
t -= dict.has(s.charAt(i - k)) ? 1 : 0;
ans = Math.max(ans, t);
}
return ans;
};
【平均数】643. 子数组最大平均数 I
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var findMaxAverage = function(nums, k) {
let ans = 0, t = 0;
for (let i = 0; i< k; i++) {
t+= nums[i];
}
ans = t;
for (let i = k; i < nums.length; i++) {
t = t + nums[i] - nums[i-k];
ans = Math.max(ans, t);
}
return ans/k;
};
【有条件计算】1052. 爱生气的书店老板
// 写法一:定长式简单写法,不死脑细胞
/**
* @param {number[]} customers
* @param {number[]} grumpy
* @param {number} minutes
* @return {number}
*/
var maxSatisfied = function(customers, grumpy, minutes) {
let ans = 0, t = 0, base = 0;
for (let i = 0; i < minutes; i++) {
t += customers[i] * grumpy[i];
base += customers[i] * (1 - grumpy[i]);
}
ans = t;
for (let i = minutes; i < customers.length; i++) {
base += customers[i] * (1 - grumpy[i]);
t += customers[i] * grumpy[i];
t -= customers[i - minutes] * grumpy[i-minutes]
ans = Math.max(ans, t);
}
return base + ans;
};
/**
* @param {number[]} customers
* @param {number[]} grumpy
* @param {number} minutes
* @return {number}
*/
var maxSatisfied = function(customers, grumpy, minutes) {
let sum = 0, start = 0, base = 0, maxDiff = 0;
// 求的结果为最多满意的客户,由于可以作弊变脸色,所以结果可以分成两个部分,一个是不受变脸色影响的结果,这个部分的特点是grumpy[i] 为0;
// 受影响是grumpy[i] === 1的部分,而minutes为窗口大小,故只需要计算grumpy[i] === 1 时的customers在窗口中的最大窗口值。
for (let i = 0; i < customers.length; i++) {
// 不受影响的部分的前缀和计算
base += customers[i] * ( 1 - grumpy[i]);
// 受影响的部分的前缀和计算
sum += customers[i] * grumpy[i];
// 计算影响因子的大小,求最大值。最大值一般产生在窗口扩展时
maxDiff = Math.max(maxDiff, sum);
// 窗口大小满足,为固定窗口,所以不用循环,只判断一次。
if (i - start + 1 === minutes) {
// 窗口右移,计算区间和
sum -= customers[start] * grumpy[start];
// 如果需要计算最小值,可以在在次计算。发生在窗口收缩
// 窗口右移
start++;
}
}
// 两部分之和。
return base + maxDiff;
};
// 写法三:不定长的方式来写。
/**
* @param {number[]} customers
* @param {number[]} grumpy
* @param {number} minutes
* @return {number}
*/
var maxSatisfied = function(customers, grumpy, minutes) {
let l = 0, r = 0, ans = 0, t = 0, base = 0;
while (r < customers.length) {
base += customers[r] * (1 - grumpy[r]);
t += customers[r] * grumpy[r];
r++;
while (r - l > minutes) {
t -= customers[l] * grumpy[l];
l++;
}
ans = Math.max(ans, t);
}
return base + ans;
};
【左右两端】1423. 可获得的最大点数
/**
* @param {number[]} cardPoints
* @param {number} k
* @return {number}
*/
var maxScore = function(cardPoints, k) {
let ans = 0, t = 0, len = cardPoints.length;
for (let i = len - k ; i <len; i++) {
t += cardPoints[i];
}
ans = t;
for (let i = 0; i < k; i++) {
t += cardPoints[i] - cardPoints[len - k + i];
ans = Math.max(ans, t);
}
return ans;
};
【排列】438. 找到字符串中所有字母异位词
/**
* @param {string} s
* @param {string} p
* @return {number[]}
*/
var findAnagrams = function(s, p) {
let ct = new Array(26).fill(0), ans=[], base = 'a'.charCodeAt(0);
const t = new Array(26).fill(0);
for (let r = 0; r < p.length; r++) {
ct[p.charCodeAt(r) - base]++;
t[s.charCodeAt(r) - base]++;
}
const isValidWindow = () => {
for (let i = 0; i < ct.length; i++) {
if (ct[i] === 0) continue;
if (ct[i] !== t[i]) return false;
}
return true;
};
const updateAns = (pos) => {
if (isValidWindow()) {
ans.push(pos);
}
};
updateAns(0);
for (let r = p.length; r < s.length; r++) {
t[s.charCodeAt(r) - base]++;
t[s.charCodeAt(r - p.length) - base]--;
updateAns(r - p.length + 1);
}
return ans;
};
【排列】567. 字符串的排列
这题可以看作是上面的题目 438的简化,只需要判断是否有,而不用全记录, 直接基于上题的模板套路修改下结果更新。
/**
* @param {string} s1
* @param {string} s2
* @return {boolean}
*/
var checkInclusion = function(s1, s2) {
let s = s2, p = s1;
let ct = new Array(26).fill(0), base = 'a'.charCodeAt(0);
const t = new Array(26).fill(0);
for (let r = 0; r < p.length; r++) {
ct[p.charCodeAt(r) - base]++;
t[s.charCodeAt(r) - base]++;
}
const isValidWindow = () => {
for (let i = 0; i < ct.length; i++) {
if (ct[i] === 0) continue;
if (ct[i] !== t[i]) return false;
}
return true;
};
if (isValidWindow()) {
return true;
}
for (let r = p.length; r < s.length; r++) {
t[s.charCodeAt(r) - base]++;
t[s.charCodeAt(r - p.length) - base]--;
if (isValidWindow()) {
return true;
}
}
return false;
};
【交换/中等】1151. 最少交换次数来组合所有的 1
给出一个二进制数组 data,你需要通过交换位置,将数组中 任何位置 上的 1 组合到一起,并返回所有可能中所需 最少的交换次数。
思路:
目标是,是将所有的11集中在一起,这个时候的窗口长度为真个串中1的个数len,在这种情况下,要使交换次数最少,即为其中的0的数量最少。
于是将原问题转换为求长度为len的窗口中,0最少的窗口,而答案即为窗口内0的数量。
由于只有0、1两种数字,个数的计算等价与求和,故计算窗口的sum值,最后再以窗口长度-sum即可。
/**
* @param {number[]} data
* @return {number}
*/
var minSwaps = function(data) {
// 最少交换,存在一个长度为 |1的个数| 的窗口,计数其中的1的个数为最大,反过来这个区间中的0的个数即为最少交换次数。
let t = data.filter(p => p === 1).length;
let r = 0, s = 0, ans = 0;
while (r < t) {
s += data[r++];
}
ans = s;
while (r < data.length) {
s += data[r];
s -= data[r-t];
ans = Math.max(ans, s);
r++;
}
return t - ans;
};