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

点此看全部题解 LeetCode必刷100题:一份来自面试官的算法地图(题解持续更新中)

生活中的算法

还记得小时候玩的"找朋友"游戏吗?每个人都有一个字母牌,需要找到拥有相同字母组合的伙伴。比如,拿着"ate"的同学要找到拿着"eat"或"tea"的同学。这其实就是在寻找字母异位词!

在实际应用中,字母异位词的检测有着广泛的用途。比如在密码学中检测可能的密文变体,或在文本分析中找出词语的不同排列组合。

问题描述

LeetCode第438题"找到字符串中所有字母异位词"是这样描述的:给定两个字符串 s 和 p,找到 s 中所有 p 的异位词的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词指由相同字母重排列形成的字符串(包括相同的字符串)。

例如:

  • 输入: s = “cbaebabacd”, p = “abc”
  • 输出: [0,6]
  • 解释: 起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。起始索引等于 6 的子串是 “bac”, 它也是 “abc” 的异位词。

最直观的解法:排序比较法

最容易想到的方法是:取出s中长度等于p的每个子串,将子串和p分别排序后比较是否相等。如果相等,就找到了一个异位词。

让我们用一个例子来模拟这个过程:

s = "cbae", p = "abc"

检查所有长度为3的子串:
"cba" -> 排序后:"abc" = "abc"(p排序后) ✓
"bae" -> 排序后:"abe" ≠ "abc"(p排序后) ✗

这种思路可以用Java代码这样实现:

public List<Integer> findAnagrams(String s, String p) {
List<Integer> result = new ArrayList<>();
if (s.length() < p.length()) return result; // 将p排序
char[] sortedP = p.toCharArray();
Arrays.sort(sortedP); // 检查每个可能的子串
for (int i = 0; i <= s.length() - p.length(); i++) {
// 取出子串并排序
char[] window = s.substring(i, i + p.length()).toCharArray();
Arrays.sort(window); // 比较排序后的字符串
if (Arrays.equals(window, sortedP)) {
result.add(i);
}
} return result;
}

优化解法:滑动窗口 + 字符计数法

仔细思考会发现,我们其实不需要对字符串排序。只要两个字符串包含相同的字符,且每个字符出现的次数相同,它们就是异位词。这启发我们使用字符计数的方法。

结合滑动窗口技巧,我们可以在一次遍历中完成所有比较。

算法原理

  1. 使用数组记录p中每个字符的出现次数
  2. 维护一个大小等于p长度的滑动窗口
  3. 对窗口中的字符计数,与p的字符计数比较
  4. 当找到匹配时记录起始索引

算法步骤(伪代码)

  1. 初始化两个大小为26的数组,分别记录p和窗口中字符出现次数
  2. 先统计p中字符出现次数
  3. 使用滑动窗口遍历s:
    • 右边加入新字符,更新计数
    • 当窗口大小超过p长度时,左边移除字符,更新计数
    • 比较两个计数数组是否相等

示例运行

让我们用s = “cbae”, p = "abc"模拟这个过程:

初始状态:
p的字符计数:[a:1, b:1, c:1]
窗口字符计数:[] 1. 添加'c':
窗口计数:[c:1] 2. 添加'b':
窗口计数:[b:1, c:1] 3. 添加'a':
窗口计数:[a:1, b:1, c:1]
比较:相等 ✓
记录索引0 4. 添加'e',移除'c':
窗口计数:[a:1, b:1, e:1]
比较:不等 ✗

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[] windowCount = new int[26]; // 统计p中字符出现次数
for (char c : p.toCharArray()) {
pCount[c - 'a']++;
} // 初始化窗口
for (int i = 0; i < p.length(); i++) {
windowCount[s.charAt(i) - 'a']++;
} // 如果初始窗口就是异位词
if (Arrays.equals(pCount, windowCount)) {
result.add(0);
} // 滑动窗口
for (int i = p.length(); i < s.length(); i++) {
// 移除窗口最左边的字符
windowCount[s.charAt(i - p.length()) - 'a']--;
// 添加新的字符
windowCount[s.charAt(i) - 'a']++; // 比较是否是异位词
if (Arrays.equals(pCount, windowCount)) {
result.add(i - p.length() + 1);
}
} return result;
}

解法比较

让我们比较这两种解法:

排序比较法:

  • 时间复杂度:O(n·k·log k),其中k是p的长度
  • 空间复杂度:O(k)
  • 优点:直观易懂
  • 缺点:需要频繁排序,效率较低

滑动窗口 + 字符计数法:

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)(因为数组大小固定为26)
  • 优点:一次遍历就能得到结果,无需排序
  • 缺点:需要额外空间存储字符计数

题目模式总结

这道题体现了几个重要的算法思想:

  1. 滑动窗口:用于高效处理子串问题
  2. 计数统计:用计数替代排序来判断字符组成是否相同
  3. 空间换时间:使用额外空间来优化时间复杂度

这种解题模式在很多字符串问题中都有应用,比如:

  • 无重复字符的最长子串
  • 最小覆盖子串
  • 字符串的排列

解决此类问题的通用思路是:

  1. 考虑能否用计数方法代替排序
  2. 思考是否可以用滑动窗口优化
  3. 注意窗口更新时的计数维护
  4. 考虑边界条件的处理

小结

通过这道题,我们不仅学会了如何找到字符串中的所有异位词,更重要的是掌握了字符统计和滑动窗口相结合的技巧。从排序比较到字符计数,我们看到了如何通过巧妙的思路来提升算法效率。

记住,在处理字符串相关的问题时,排序并不总是最好的选择。有时候,简单的计数可能会带来意想不到的效率提升!


作者:忍者算法
公众号:忍者算法

从找朋友到找变位词:一道趣味字符串问题的深入解析|LeetCode 438 找到字符串中所有字母异位词的更多相关文章

  1. [LeetCode]438. 找到字符串中所有字母异位词、76. 最小覆盖子串(滑动窗口解决子串问题系列)

    题目438. 找到字符串中所有字母异位词 给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引. 说明: 字母异位词指字母相同,但排列不同的字符 ...

  2. [Swift]LeetCode438. 找到字符串中所有字母异位词 | Find All Anagrams in a String

    Given a string s and a non-empty string p, find all the start indices of p's anagrams in s. Strings ...

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

    给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引. 字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100. 说明: ...

  4. *438. Find All Anagrams in a String 找到字符串中所有字母异位词

    1. 原始题目 给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引. 字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 201 ...

  5. Leetcode438.Find All Anagrams in a String找到字符串中所有字母异位词

    给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引. 字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100. 说明: ...

  6. Java实现 LeetCode 438 找到字符串中所有字母异位词

    438. 找到字符串中所有字母异位词 给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引. 字符串只包含小写英文字母,并且字符串 s 和 p ...

  7. C#版 - Leetcode49 - 字母异位词分组 - 题解

    C#版 - Leetcode49 - 字母异位词分组 - 题解 Leetcode49.Group Anagrams 在线提交: https://leetcode.com/problems/group- ...

  8. [Swift]LeetCode49. 字母异位词分组 | Group Anagrams

    Given an array of strings, group anagrams together. Example: Input: ["eat", "tea" ...

  9. [Swift]LeetCode242. 有效的字母异位词 | Valid Anagram

    Given two strings s and t , write a function to determine if t is an anagram of s. Example 1: Input: ...

  10. 有效的字母异位词的golang实现

    给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词. 输入: s = "anagram", t = "nagaram" 输出: ...

随机推荐

  1. Maven多模块项目 eclipse热部署 Maven项目实现 tomcat热部署

    Maven 多模块项目在eclipse下面热部署,即你可以体验下无论你修改整个项目里面的任何模块的代码,都不需要用maven打包就可以看到效果, 1.首先准备好创建一个maven多项目的代码,准备好一 ...

  2. 项目部署工具之walle

    最近部署walle进行线上项目的上线发布,安装中遇到的问题,在此记录 walle(http://www.walle-web.io) git地址:https://github.com/meolu/wal ...

  3. vagrant搭建开发环境

    一:我们为什么需要用这玩意 我们在开发中经常会面临的问题:环境不一致,有人用Mac有人用Windos还有几个用linux的,而我们的服务器都是linux. 在我本地是可以的啊,我测了都,没有问题啊,然 ...

  4. .net core想到哪写道哪之asp.net core的机密

    我们往往需要在项目里使用一些机密数据,比如数据库的密码,再比如一些密钥.这些东西一般来说我们都会放到配置文件里. 但是这些东西是跟自己的账号相关的,我们在一些多人合作的项目中,尤其是开源项目肯定不能直 ...

  5. linux模拟HID USB设备及wireshark USB抓包配置

    目录 1. 内核配置 2. 设备配置 附 wireshark USB抓包配置 笔者开发USB设备时的一些记录 1. 内核配置 内核启用USB Gadget,使用fs配置usb device信息. De ...

  6. WxPython跨平台开发框架之参数配置管理界面的设计和实现

    我曾经在2014年在随笔<Winform开发框架之参数配置管理功能实现-基于SettingsProvider.net的构建>介绍过基于.NET开发的参数配置管理界面,本篇随笔基于类似的效果 ...

  7. PM2部署DotNet应用程序

    pm2简介 PM2是一个Node.js的进程管理工具,可以帮助开发者简化Node.js应用的部署和运维.它提供了进程守护.负载均衡.日志管理等功能,可以监控应用程序的运行状态,并在发生意外情况时自动重 ...

  8. 鸿蒙UI布局实战 —— 个人中心页面开发

    1.前言 接下里我们将开启"鸿蒙UI布局系列"的学习,第一站:学习线性布局(Row/Column)+ 弹性布局(Flex) 在展开学习前,先上一个实战demo--开发一个个人中心页 ...

  9. 企业IT基础资源管理的“帮帮团”上线啦——源启云原生基础设施管理平台

    为助力企业提升基础资源一体化管理和交付效率,以更先进的基础设施管理方式来满足现代企业业务持续扩展和复杂化的需要,中电金信运用基础设施即代码(Infrastructure as Code,简称IaC)技 ...

  10. 执行docker-compose up -d时出现ERROR: Failed to Setup IP tables: Unable to enable SKIP DNAT rule

    原因是因为防火墙关闭之后需要重启docker服务. 执行: service docker restart 即可.