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

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

题目详情

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

问题分析

字母异位词的关键特征是:

字符组成完全相同(字符种类及每个字符的出现次数相同)

字符顺序可以不同

因此,判断两个字符串是否为字母异位词,可以转化为判断它们字符频率分布是否相同。

核心思路:滑动窗口

由于我们要在字符串 s 中寻找与 p 长度相同的异位词子串,可以使用固定大小的滑动窗口来遍历 s,窗口大小即为 p 的长度。然后比较窗口内字符的频率分布与 p 的频率分布是否一致。

方法一:固定窗口比较法

最直接的思路是每次移动窗口后,都比较窗口内字符频率与 p 的字符频率。

java 复制代码
public List<Integer> findAnagrams(String s, String p) {
    List<Integer> result = new ArrayList<>();
    if (s.length() < p.length()) return result;
    
    int[] pCount = new int[26];
    int[] sCount = new int[26];
    
    // 统计p的字符频率,并初始化第一个窗口
    for (int i = 0; i < p.length(); i++) {
        pCount[p.charAt(i) - 'a']++;
        sCount[s.charAt(i) - 'a']++;
    }
    
    // 检查第一个窗口
    if (Arrays.equals(pCount, sCount)) {
        result.add(0);
    }
    
    // 滑动窗口:每次向右移动一位
    for (int i = 0; i < s.length() - p.length(); i++) {
        // 移除左边界字符
        sCount[s.charAt(i) - 'a']--;
        // 添加右边界字符
        sCount[s.charAt(i + p.length()) - 'a']++;
        
        // 检查当前窗口
        if (Arrays.equals(pCount, sCount)) {
            result.add(i + 1);
        }
    }
    
    return result;
}

复杂度分析:

时间复杂度:O(n × 26),其中 n 是字符串 s 的长度,每次比较数组需要 O(26) 时间

空间复杂度:O(26),使用固定大小的数组

方法二:计数差分法(优化版)

我们可以通过维护一个 count 变量来优化判断过程,避免每次都比较整个数组。

java 复制代码
public List<Integer> findAnagrams(String s, String p) {
    List<Integer> result = new ArrayList<>();
    if (s.length() < p.length()) return result;
    
    int[] count = new int[26];
    
    // 初始化p的字符统计
    for (char c : p.toCharArray()) {
        count[c - 'a']++;
    }
    
    int left = 0, right = 0;
    int needToMatch = p.length();
    
    while (right < s.length()) {
        // 右指针字符进入窗口
        char rightChar = s.charAt(right);
        if (count[rightChar - 'a'] > 0) {
            needToMatch--;
        }
        count[rightChar - 'a']--;
        right++;
        
        // 当窗口大小等于p长度时,检查结果
        if (right - left == p.length()) {
            if (needToMatch == 0) {
                result.add(left);
            }
            
            // 左指针字符移出窗口
            char leftChar = s.charAt(left);
            if (count[leftChar - 'a'] >= 0) {
                needToMatch++;
            }
            count[leftChar - 'a']++;
            left++;
        }
    }
    
    return result;
}

优化原理

count 数组记录当前窗口中字符与 p 中字符的差值

needToMatch 跟踪还需匹配的字符总数

当 needToMatch == 0 时,说明窗口内字符与 p 完全匹配

方法三:滑动窗口模板(通用解法)

这是一种更通用的滑动窗口解法,使用 valid 变量来跟踪已匹配的字符种类数。

java 复制代码
public List<Integer> findAnagrams(String s, String p) {
    List<Integer> result = new ArrayList<>();
    Map<Character, Integer> need = new HashMap<>();
    Map<Character, Integer> window = new HashMap<>();
    
    // 初始化need映射
    for (char c : p.toCharArray()) {
        need.put(c, need.getOrDefault(c, 0) + 1);
    }
    
    int left = 0, right = 0;
    int valid = 0;
    
    while (right < s.length()) {
        // 扩大右边界
        char rightChar = s.charAt(right);
        right++;
        
        if (need.containsKey(rightChar)) {
            window.put(rightChar, window.getOrDefault(rightChar, 0) + 1);
            if (window.get(rightChar).equals(need.get(rightChar))) {
                valid++;
            }
        }
        
        // 当窗口大小达到p长度时,收缩左边界
        while (right - left >= p.length()) {
            // 检查是否找到异位词
            if (valid == need.size()) {
                result.add(left);
            }
            
            char leftChar = s.charAt(left);
            left++;
            
            if (need.containsKey(leftChar)) {
                if (window.get(leftChar).equals(need.get(leftChar))) {
                    valid--;
                }
                window.put(leftChar, window.get(leftChar) - 1);
            }
        }
    }
    
    return result;
}

关键点与技巧

窗口大小固定:窗口大小始终为 p.length(),每次移动只需考虑进出的两个字符

字符频率统计:使用长度为26的数组统计小写字母频率,比HashMap更高效

优化判断:通过 count 或 valid 变量避免全数组比较,提升效率

边界处理:当 s.length() < p.length() 时直接返回空列表

性能对比

总结

滑动窗口是解决子串搜索问题的利器。对于字母异位词问题,关键是抓住字符频率一致性这一特征,通过维护窗口内字符频率来高效判断。

对于此题,推荐使用计数差分法,它在实现复杂度和性能之间取得了良好平衡。理解并掌握这种滑动窗口的优化技巧,对解决类似的字符串搜索问题大有裨益。

相关推荐
_Evan_Yao6 小时前
Redis 缓存穿透、击穿、雪崩,一次讲清!
java·spring·mybatis
梓豪csd6 小时前
消息订阅中心:谁该收到哪条通知
java·开发语言·人工智能·低代码·自动化·1024程序员节
秋饼6 小时前
Spring AI 2.1 Message Parts 深度实战:推理签名回放与跨轮记忆持久化的源码级落地
java·ai·技术分享·后端开发
thefool1122667 小时前
TCP 1
java·1024程序员节
企业数字化笔记9 小时前
视频人体姿态怎么做三维可视化?2D关键点、深度局限与技术选型
人工智能·算法·音视频
金銀銅鐵10 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(下)
java·后端·ai编程
?abc!10 小时前
(数组)581. 最短无序连续子数组
数据结构·算法·leetcode
AI人工智能+电脑小能手10 小时前
一次请求打几个下游,这个Java并行任务工具类6个方法按序聚合
java·线程池·并发编程·1024程序员节·批量处理·并行任务
paopaokaka_luck11 小时前
心理咨询管理系统(WebSocket实时沟通、基于用户的协同过滤、 ECharts多维分析、 预约业务流程、预约校验与定时处理)
java·vue.js·spring boot·mysql·spring·echarts·1024程序员节
2301_322414280412 小时前
springboot农产品交易平台的设计与实现08055-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·spring boot·redis·mysql·tomcat·maven·mybatis