【滑动窗口与前缀和算法实战】:LeetCode560.438 高频题深度解析

🔥你好我是fengxin_rou这是我的个人主页 fengxin_rou的主页

❄️欢迎查看我的专栏我的专栏

《Java后端学习》《JAVASE基础》《JUC并发》《redis》《JVM虚拟机》《MYSQL》《黑马点评》《rabbitmq》《JavaWeb+AI的talis学习系统》《苍穹外卖》

目录

前言

一、滑动窗口算法:找到字符串中所有字母异位词

[1.1 问题背景与核心痛点](#1.1 问题背景与核心痛点)

[1.2 算法原理:字符计数 + 窗口滑动](#1.2 算法原理:字符计数 + 窗口滑动)

[1.3 完整 Java 代码实现](#1.3 完整 Java 代码实现)

​编辑

[1.4 关键细节与避坑指南](#1.4 关键细节与避坑指南)

[二、前缀和算法:统计和为 K 的子数组](#二、前缀和算法:统计和为 K 的子数组)

[2.1 问题背景与核心痛点](#2.1 问题背景与核心痛点)

[2.2 算法原理:前缀和定义 + 哈希表计数](#2.2 算法原理:前缀和定义 + 哈希表计数)

[2.3 完整 Java 代码实现](#2.3 完整 Java 代码实现)

​编辑

[2.4 关键细节与避坑指南](#2.4 关键细节与避坑指南)

三、两大算法对比与应用场景分析

[3.1 核心特性对比](#3.1 核心特性对比)

[3.2 实际应用场景](#3.2 实际应用场景)

四、性能优化与扩展思考

[4.1 滑动窗口优化](#4.1 滑动窗口优化)

[4.2 前缀和优化](#4.2 前缀和优化)

结语


前言

在算法面试与刷题中,字符串匹配与子数组求和是高频考点。本文将详解两道典型题:字母异位词匹配与子数组和统计,带你掌握滑动窗口与前缀和两大核心算法,高效解决这类问题。


一、滑动窗口算法:找到字符串中所有字母异位词

1.1 问题背景与核心痛点

LeetCode 438 题要求在字符串s中找到所有与字符串p的异位词匹配的子串起始索引。 异位词指由相同字母以不同顺序排列组成的字符串,传统暴力匹配会因重复统计字符而效率低下,滑动窗口是最优解。

1.2 算法原理:字符计数 + 窗口滑动

滑动窗口的核心思想是维护一个与p长度相同的窗口,通过字符计数数组统计窗口内字母出现次数,再与p的计数对比,实现 O (n) 时间复杂度匹配。

  1. 边界判断 :若s长度小于p,直接返回空列表,避免无效计算。
  2. 初始化计数 :用两个长度为 26 的数组,分别统计ps中首个窗口的字母出现次数。
  3. 初始窗口校验:对比两个计数数组,若相同则将索引 0 加入结果。
  4. 窗口滑动:每次移动窗口,减去移出字符的计数,增加移入字符的计数,再校验数组是否匹配。

1.3 完整 Java 代码实现

复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        int sLen = s.length(), pLen = p.length();
        // 边界条件:s长度小于p,不可能存在异位词
        if (sLen < pLen) {
            return new ArrayList<>();
        }

        List<Integer> ans = new ArrayList<>();
        // 统计s和p中字母出现次数(仅小写字母)
        int[] sCount = new int[26];
        int[] pCount = new int[26];

        // 初始化第一个窗口的计数
        for (int i = 0; i < pLen; ++i) {
            ++sCount[s.charAt(i) - 'a'];
            ++pCount[p.charAt(i) - 'a'];
        }

        // 检查初始窗口是否匹配
        if (Arrays.equals(sCount, pCount)) {
            ans.add(0);
        }

        // 滑动窗口:每次移动一位,更新计数并校验
        for (int i = 0; i < sLen - pLen; ++i) {
            // 移出窗口左侧字符
            --sCount[s.charAt(i) - 'a'];
            // 移入窗口右侧新字符
            ++sCount[s.charAt(i + pLen) - 'a'];

            // 校验当前窗口是否匹配异位词
            if (Arrays.equals(sCount, pCount)) {
                ans.add(i + 1);
            }
        }

        return ans;
    }
}

1.4 关键细节与避坑指南

  • 字符计数优化 :使用长度为 26 的数组代替哈希表,利用字母与'a'的偏移直接索引,效率更高。
  • 数组对比效率Arrays.equals直接对比两个计数数组,避免手动遍历,代码更简洁。
  • 窗口移动边界 :循环终止条件为sLen - pLen,确保窗口不会超出字符串s的范围。

二、前缀和算法:统计和为 K 的子数组

2.1 问题背景与核心痛点

LeetCode 560 题要求统计数组中连续子数组和等于k的个数。 数组中存在负数时,滑动窗口的单调性失效,无法直接使用;暴力枚举子数组则时间复杂度为 O (n²),效率低下,前缀和 + 哈希表是最优解。

2.2 算法原理:前缀和定义 + 哈希表计数

前缀和指数组前i个元素的和,定义preSum[i]为前i个元素的和,子数组[j, i]的和可表示为preSum[i+1] - preSum[j]。 要使子数组和为k,即preSum[i+1] - preSum[j] = k,等价于preSum[j] = preSum[i+1] - k。 用哈希表记录每个前缀和出现的次数,遍历前缀和时统计满足条件的preSum[j]数量即可。

2.3 完整 Java 代码实现

复制代码
import java.util.HashMap;

class Solution {
    public int subarraySum(int[] nums, int k) {
        // 前缀和数组,preSum[0] = 0,preSum[i]为前i个元素的和
        int[] preSum = new int[nums.length + 1];
        preSum[0] = 0;
        for (int i = 0; i < nums.length; i++) {
            preSum[i + 1] = preSum[i] + nums[i];
        }

        int ans = 0;
        // 哈希表:key为前缀和,value为该前缀和出现的次数
        HashMap<Integer, Integer> preSumCount = new HashMap<>();

        for (int sj : preSum) {
            // 寻找满足 si = sj - k 的前缀和次数
            int si = sj - k;
            if (preSumCount.containsKey(si)) {
                ans += preSumCount.get(si);
            }
            // 将当前前缀和加入哈希表,更新计数
            preSumCount.put(sj, preSumCount.getOrDefault(sj, 0) + 1);
        }

        return ans;
    }
}

2.4 关键细节与避坑指南

  • 前缀和初始化preSum[0] = 0是关键,处理从数组开头到当前位置的子数组和为k的情况。
  • 哈希表更新顺序 :先查询si = sj - k的次数,再将当前前缀和sj加入哈希表,避免重复统计。
  • 处理负数与 0:前缀和 + 哈希表天然支持数组中存在负数、0 的场景,无需额外处理。

三、两大算法对比与应用场景分析

3.1 核心特性对比

表格

算法 时间复杂度 空间复杂度 适用场景
滑动窗口 O(n) O (1)(固定长度窗口) 字符串匹配、定长子数组问题,元素为正数或有固定窗口长度
前缀和 + 哈希表 O(n) O(n) 不定长子数组和问题,含负数、0 的数组,无法用滑动窗口的场景

3.2 实际应用场景

  • 滑动窗口:可扩展到最长无重复子串、最小覆盖子串等字符串问题,也可用于定长滑动窗口的统计类问题。
  • 前缀和:广泛应用于数组区间和查询、子数组统计问题,结合哈希表可高效解决带条件的子数组计数。

四、性能优化与扩展思考

4.1 滑动窗口优化

在字母异位词问题中,可进一步优化:

  • 用一个变量记录窗口内与p字符计数不同的字母数量,避免每次对比数组,将常数时间进一步降低。
  • s中出现非小写字母时,可扩展计数数组大小,或用哈希表兼容所有字符。

4.2 前缀和优化

在子数组和问题中,可直接在遍历数组时计算前缀和并更新哈希表,无需额外创建前缀和数组,节省空间:

复制代码
public int subarraySum(int[] nums, int k) {
    HashMap<Integer, Integer> map = new HashMap<>();
    map.put(0, 1);
    int preSum = 0, ans = 0;
    for (int num : nums) {
        preSum += num;
        ans += map.getOrDefault(preSum - k, 0);
        map.put(preSum, map.getOrDefault(preSum, 0) + 1);
    }
    return ans;
}

结语

滑动窗口与前缀和是解决字符串匹配与子数组问题的核心算法。滑动窗口通过维护固定窗口实现高效字符统计,适用于定长匹配场景;前缀和 + 哈希表则解决了含负数的子数组和问题,是不定长区间问题的通用方案。

掌握这两种算法的核心原理与实现细节,能大幅提升刷题效率与面试竞争力。后续可进一步学习滑动窗口的可变长度实现、二维前缀和等扩展应用,深入理解算法思想的本质。

相关推荐
半夜修仙1 小时前
RabbitMQ入门概述
java·rabbitmq·java-rabbitmq
dusk_star1 小时前
go语言--笔记--接口
java·笔记·golang
Dillon Dong1 小时前
【风电控制】FPGA vs DSP 在ADC采样中的选择——从架构差异到工程实践
算法·变流器·风电控制·dfig
科研小白_1 小时前
【第九期:MATLAB点云处理基础】基于 Alpha Shapes 的边缘点提取
算法
The Sheep 20231 小时前
EFcore 查询数据
java·javascript
sali-tec1 小时前
C# 基于OpenCv的视觉工作流-章80-长短脚
图像处理·人工智能·opencv·算法·计算机视觉
han_hanker1 小时前
java8 stream 常用转换方法
java
星轨zb1 小时前
从通用到专属:文迹(WenJi)引入 RAG 向量库的技术复盘
java·spring·langchain4j
sul.i1 小时前
浅析·指针
算法