1.1 灵神题单总结_滑动窗口与双指针

01_fixed_slide_win.md 定长滑动窗口刷题笔记(C语言完整版)

代码链接:https://gitee.com/hiwangGitee/0x3f-algorithm-c/tree/main/

通用模板核心

定长滑动窗口通用公式left = i - k + 1

通用执行流程:元素入窗统计 → 窗口满长更新答案 → 滑出左边界平移窗口

定长滑动窗口专题总结

通用核心公式left = i - k + 1,所有题目统一窗口逻辑,仅内部统计逻辑不同

题型分类

  1. 数值求和/最值:643、1052,适合数组区间统计;

  2. 字符计数最值:1456、2379,简单字符统计窗口;

  3. 频次匹配类:567、438,26字母桶匹配异位词;

  4. 字符串哈希查重:187,固定长度子串去重统计。

模板复用性:全部题目遵循「入窗统计 → 窗口满更新答案 → 出窗平移」流程,可直接作为刷题万能模板。

全局公共头文件

c 复制代码
#include <stdio.h>     // 标准输入输出,printf/scanf必备
#include <stdlib.h>    // malloc/free、exit()、rand()
#include <string.h>    // 字符串函数 strlen/strcpy/memset
#include <ctype.h>     // 字符判断 isdigit/isalpha
#include <math.h>      // 数学函数 sqrt/sin/cos
#include <time.h>      // 时间相关 time()
#include <stdbool.h>   // C99 布尔类型 bool true/false
#include <limits.h>
#include "uthash.h"

643 子数组最大平均数 I

题目链接:https://leetcode.cn/problems/maximum-average-subarray-i/

题目描述

给你一个由 n 个元素组成的整数数组 nums 和一个整数 k

请你找出平均数最大、且长度为 k 的连续子数组,输出该最大平均数。

答案误差小于10^-5 即判定正确。

示例

示例1 输入:nums = [1,12,-5,-6,50,3], k = 4输出:12.75 解释:最优子数组和 KaTeX parse error: Undefined control sequence: \- at position 3: 12\̲-̲5\-6\+50=51,平均数 51/4=127˙551/4=12\.7551/4=127˙5

示例2 输入:nums = [5], k = 1 输出:5.00000

核心考点

定长滑动窗口入门模板、增量维护窗口和、线性时间遍历优化

1. 公式含义

left = i - k + 1

当前右边界下标i,窗口固定长度k,窗口左边界下标 = 右边界 - 窗口长度 + 1

举例:i=3、k=4(样例 1 第 4 个元素),left=3-4+1=0,窗口范围0,3,正好 4 个元素,完全匹配定长窗口区间。

2. 分阶段作用

阶段 1:left < 0(窗口元素不足 k 个)

只累加元素、不做均值计算,跳过滑出逻辑,完成初始窗口填充;

阶段 2:left ≥ 0(窗口凑满 k 个元素)

立刻计算当前窗口平均数,对比更新最大值;随后剔除numsleft、窗口整体右移一格,开启下一轮迭代。

代码实现
c 复制代码
double findMaxAverage(int *nums, int numsSize, int k)
{
    double res = 0, result = INT_MIN;
    double cnt = 0;
    int num = 0;
    for (int i = 0; i < numsSize; i++) {
        cnt = cnt + nums[i];
        num = num + 1;
        // 当前右边界下标i,窗口固定长度k,窗口左边界下标 = 右边界 - 窗口长度 + 1
        int left = i - k + 1;
        if (left < 0) {
            continue;
        }
        res = cnt / num;
        if (res > result) {
            result = fmax(res, result);
        }
        cnt = cnt - nums[left];
        num = num - 1;
    }
    return result;
}

1456 定长子串中元音的最大数目

题目链接:https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/

题目描述

给定字符串 s 和整数k,找出 s 所有长度固定为 k 的连续子串里,包含元音字母(a、e、i、o、u)数量的最大值并返回。

输入输出示例

示例1 输入:s = "abciiidef", k = 3 输出:3 解释:子串 "iii" 包含3个元音字母,为全局最大值

示例2 输入:s = "aeiou", k = 2 输出:2 解释:任意长度为2的子串均包含2个元音字母

示例3 输入:s = "leetcode", k = 3 输出:2 解释:"lee"、"eet"、"ode" 这类子串元音数为当前最大的2

示例4 输入:s = "rhythms", k = 4 输出:0 解释:整串无元音字母,结果为0

示例5 输入:s = "tryhard", k = 4 输出:1

核心考点

定长滑动窗口基础应用,用通用公式 left = i - k + 1 控制窗口边界,增量维护窗口内元音计数,线性遍历求解全局最大值

代码实现
c 复制代码
bool isVowel(char c)
{
    return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}
int maxVowels(char *s, int k)
{
    int len       = strlen(s);
    int windowCnt = 0;
    int maxCnt    = 0;
    for (int i = 0; i < len; i++) {
        if (isVowel(s[i])) {
            windowCnt++;
        }
        int left = i - k + 1;
        if (left < 0) {
            continue;
        }
        if (windowCnt > maxCnt) {
            maxCnt = windowCnt;
        }
        if (isVowel(s[left])) {
            windowCnt--;
        }
    }
    return maxCnt;
}

1052 爱生气的书店老板

题目链接:https://leetcode.cn/problems/grumpy-bookstore-owner/

题目描述

书店营业共 n 分钟:

  • customers[i]:第 i 分钟到店顾客数;

  • grumpy[i]=1:老板该分钟生气,顾客不满意;grumpy[i]=0:老板不生气,顾客满意;

老板可使用1次技能,连续 minutes 分钟强制不生气,原本生气时段的顾客也会满意。求全天最多能收获的满意顾客总数。

输入输出示例

示例1 输入:customers = 1,0,1,2,1,1,7,5,grumpy = 0,1,0,1,0,1,0,1,minutes = 3 输出:16 解释:技能作用于最后3分钟,原本不生气时段顾客照常满意,原本生气的3段顾客被额外挽救,累加得到最大值16

示例2 输入:customers = 1, grumpy = 0, minutes = 1 输出:1 解释:原本老板就不生气,技能不改变结果

解题思路拆解
  1. 基础满意值求和:先累加所有 grumpy[i]=0 位置的顾客,这部分不受技能影响;

  2. 定长滑窗求增量最大值:窗口长度固定为minutes,只统计窗口内grumpy[i]=1的顾客和(使用技能能额外挽救的人数),用left = i - minutes + 1经典定长窗口公式遍历;

  3. 最终答案 = 基础满意值 + 最大可挽救增量。

核心考点

定长滑动窗口求区间最值、拆分固定收益+窗口浮动增量的两步解题模型,线性时间遍历优化

代码实现
c 复制代码
int maxSatisfied(int *customers, int customersSize, int *grumpy, int grumpySize, int minutes)
{
    int max_s = 0;
    int base = 0, add_cnt = 0;
    for (int i = 0; i < customersSize; i++) {
        if (grumpy[i] == 0) {
            base += customers[i];
        }
    }
    // 选一段长度严格等于 minutes 的连续区间,
    // 区间内原本grumpy[i]=1(生气流失)的顾客,全部变成满意顾客
    for (int i = 0; i < customersSize; i++) {
        if (grumpy[i] == 1) {
            add_cnt += customers[i]; // 增益值,益值 = 窗口内原本生气(grumpy[i]=1)的顾客数
        }
        int left = i - minutes + 1; // 窗口左端点
        if (left < 0) { // 窗口长度不足 minutes
            continue;
        }
        max_s = fmax(max_s, add_cnt);
        if (grumpy[left] == 1) {
            add_cnt -=  customers[left];
        }
    }
    return base + max_s;
}

2379 得到 K 个黑块的最少涂色次数

题目链接:https://leetcode.cn/problems/minimum-recolors-to-get-k-consecutive-black-blocks/

题目描述

给定长度为n、仅由W(白色块)和B(黑色块)组成的字符串blocks,每次操作可把白色块涂成黑色块。要求字符串中至少出现一段连续k个黑色块,求需要的最少涂色次数。

输入输出示例

示例1 输入:blocks = "WBBWWBBWBW", k = 7 输出:3 解释:选取一段长度7的区间,将区间内3个白块涂黑即可达成目标,不存在操作次数更少的方案

示例2 输入:blocks = "WBWBBBW", k = 2 输出:0 解释:原字符串本身就存在连续2个黑块,无需涂色操作

解题思路拆解
  1. 问题等价转化:任意一段长度固定为k的连续区间,区间内白色块数量就是把这段全部涂黑需要的操作次数;题目求全局最小值;

  2. 采用定长滑动窗口遍历所有长度k的区间,用通用边界公式left = i - k + 1维护窗口;

  3. 右指针入窗统计白块数量,窗口满长后更新最小操作数,再滑出左边界元素完成平移。

核心考点

定长滑动窗口计数求全局最小值,入门级定长窗口模板应用题

代码实现
c 复制代码
int minimumRecolors(char *blocks, int k)
{
    int len = strlen(blocks);
    int windowWhite = 0;
    int minOp = k;
    for (int i = 0; i < len; i++) {
        if (blocks[i] == 'W') {
            windowWhite++;
        }
        int left = i - k + 1;
        if (left < 0) {
            continue;
        }
        if (windowWhite < minOp) {
            minOp = windowWhite;
        }
        if (blocks[left] == 'W') {
            windowWhite--;
        }
    }
    return minOp;
}

567 字符串的排列

题目链接:https://leetcode.cn/problems/permutation-in-string/

题目描述

给定两个字符串 s1s2,判断 s2 是否包含 s1 的任意一种排列,若存在返回 true,反之返回 false

等价判定条件:s1 的某个排列能够作为 s2 的一段连续子串出现。

输入输出示例

示例1 输入:s1 = "ab",s2 = "eidbaooo" 输出:true 解释:s2 的子串 "ba" 是 s1 的排列形式之一

示例2 输入:s1 = "ab",s2 = "eidboaoo" 输出:false

核心考点

定长滑动窗口、字符频次统计比对;窗口长度固定为 s1 长度,通过对比窗口与目标字符串的字母频次,判断是否为排列子串

代码实现
c 复制代码
#define CHAR_TOTAL 26
// 比对两个长度26的频次数组是否完全相等
bool countEqual(int cntA[], int cntB[])
{
    for (int idx = 0; idx < CHAR_TOTAL; idx++) {
        if (cntA[idx] != cntB[idx]) {
            return false;
        }
    }
    return true;
}
bool checkInclusion(char *s1, char *s2)
{
    int targetCnt[CHAR_TOTAL] = {0};
    int windowCnt[CHAR_TOTAL] = {0};
    int k = strlen(s1);
    int s2Len = strlen(s2);
    // s2更短直接不可能包含s1排列子串
    if (k > s2Len) {
        return false;
    }
    // 统计s1目标频次
    for (int i = 0; i < k; i++) {
        int cIdx = s1[i] - 'a';
        targetCnt[cIdx]++;
    }
    for (int i = 0; i < s2Len; i++) {
        int cIdx = s2[i] - 'a';
        windowCnt[cIdx]++;
        int left = i - k + 1;
        if (left < 0) {
            continue;
        }
        // 当前窗口长度达标,比对频次
        if (countEqual(windowCnt, targetCnt)) {
            return true;
        }
        // 滑出左边界字符,窗口右移
        int leftCIdx = s2[left] - 'a';
        windowCnt[leftCIdx]--;
    }
    return false;
}

438 找到字符串中所有字母异位词

题目链接:https://leetcode.cn/problems/find-all-anagrams-in-a-string/

题目描述

给定两个字符串 sp,找出 s 中全部属于 p 字母异位词的连续子串,返回所有对应起始下标,结果顺序不作要求。字母异位词指字符种类、数量完全一致,排列顺序不同的字符串。

输入输出示例

示例1 输入:s = "cbaebabacd", p = "abc" 输出:0,6 解释:下标0子串cba、下标6子串bac都是abc的异位词

示例2 输入:s = "abab", p = "ab" 输出:0,1,2 解释:三处连续长度为2的子串均为目标异位词

解题思路拆解
  1. 统计模式串 p 的字符频次,作为匹配标准;

  2. p 的长度为固定窗口,在字符串 s 上滑动;

  3. 每次窗口满长后比对频次,完全一致则记录当前左边界下标;

  4. 窗口右移,剔除左侧出窗字符频次,继续遍历,收集所有合法异位词下标。

核心考点

定长滑动窗口 + 26字母频次比对、多答案收集模板,是567题的拓展升级版

代码实现
c 复制代码
#define NUM_26 26
bool countEqual(int targetCnt[], int windowCnt[]) {
    for (int idx = 0; idx < NUM_26; idx++) {
        if (targetCnt[idx] != windowCnt[idx]) {
            return false;
        }
    }
    return true;
}
int *findAnagrams(char *s, char *p, int *returnSize) {
    int sLen = strlen(s);
    int pLen = strlen(p);
    *returnSize = 0;
    if (pLen > sLen) {
        return NULL;
    }
    int targetCnt[NUM_26] = {0};
    int windowCnt[NUM_26] = {0};
    for (int i = 0; i < pLen; i++) {
        int c = p[i] - 'a';
        targetCnt[c]++;
    }
    int *resArr = (int *)malloc(sizeof(int) * sLen);
    int k = pLen;
    for (int i = 0; i < sLen; i++) {
        int c = s[i] - 'a';
        windowCnt[c]++;
        int left = i - k + 1;
        if (left < 0) {
            continue;
        }
        if (countEqual(targetCnt, windowCnt)) {
            resArr[(*returnSize)++] = left;
        }
        int leftC = s[left] - 'a';
        windowCnt[leftC]--;
    }
    return resArr;
}

187 重复的DNA序列

题目链接:https://leetcode.cn/problems/repeated-dna-sequences/

题目描述

DNA 序列由 A、C、G、T 四种字符组成,给定一个字符串 s,返回所有长度为 10 且出现次数大于等于 2 的子串,返回结果无需有序。

输入输出示例

示例1 输入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT" 输出:"AAAAACCCCC","CCCCCAAAAA"

示例2 输入:s = "AAAAAAAAAAAAA" 输出:"AAAAAAAAAA"

解题思路拆解
  1. 固定窗口长度为 10,遍历截取所有连续子串;

  2. 借助 uthash 哈希表统计每个子串出现频次;

  3. 仅收录首次达到两次的子串,避免重复答案;

  4. 遍历结束释放哈希内存,避免内存泄漏。

核心考点

定长滑动窗口截取子串、uthash 字符串哈希频次统计、重复数据去重收集

代码实现
c 复制代码
#define NUM_10 10
#define NUM_11 (NUM_10 + 1)
typedef struct {
    char key_val[NUM_11];
    int cnt;
    UT_hash_handle hh;
} Node;
Node *hash_table = NULL;
char **findRepeatedDnaSequences(char *s, int *returnSize)
{
    *returnSize = 0;
    int sLen = strlen(s);
    if (sLen < NUM_10) {
        return NULL;
    }
    char **resArr = (char **)malloc(sizeof(char *) * (sLen - NUM_10 + 1));
    char sub[NUM_11];
    for (int i = 0; i < sLen; i++) {
        int left = i - NUM_10 + 1;
        if (left < 0) {
            continue;
        }
        strncpy(sub, s + left, NUM_10);
        sub[NUM_10] = '\0';
        Node *node = NULL;
        HASH_FIND(hh, hash_table, sub, NUM_11, node);
        if (node != NULL) {
            node->cnt += 1;
            // 仅首次重复时收录答案
            if (node->cnt == 2) {
                resArr[*returnSize] = (char *)malloc(NUM_11);
                strcpy(resArr[*returnSize], sub);
                (*returnSize)++;
            }
        } else {
            Node *newNode = (Node*)malloc(sizeof(Node));
            strcpy(newNode->key_val, sub);
            newNode->cnt = 1;
            HASH_ADD(hh, hash_table, key_val, NUM_11, newNode);
        }
    }
    // 释放哈希表内存
    Node *cur, *tmp;
    HASH_ITER(hh, hash_table, cur, tmp) {
        HASH_DEL(hash_table, cur);
        free(cur);
    }
    return resArr;
}
相关推荐
Navigator_Z4 小时前
LeetCode //C - 1187. Make Array Strictly Increasing
c语言·算法·leetcode
旖旎夜光5 小时前
LeetCode 397:整数替换(贪心问题) —— 题解
数据结构·c++·算法·leetcode·贪心算法
wabs6666 小时前
关于哈希表【力扣1.两数之和的思考】
数据结构·算法·leetcode·散列表·图论
星轨初途7 小时前
LeetCode 热题 100——day7 接雨水
数据结构·c++·笔记·算法·leetcode·职场和发展
青 春 记 忆14 小时前
LeetCode 21. 合并两个有序链表|Python 解法详解
python·算法·leetcode·链表
Forever Nore21 小时前
LeetCode 1 两数之和
算法·leetcode·职场和发展
To_OC21 小时前
LC 3 无重复字符的最长子串:从入门滑动窗口到优化写法,再也不怕面试官追问
javascript·算法·leetcode
青 春 记 忆1 天前
LeetCode 53. 最大子数组和|Python 解法详解
python·算法·leetcode
退休倒计时1 天前
【每日一题】LeetCode 215. 数组中的第K个最大元素 TypeScript
算法·leetcode·typescript