0

0

高效分组变位词:无需排序的哈希映射方法

碧海醫心

碧海醫心

发布时间:2025-10-14 11:29:22

|

470人浏览过

|

来源于php中文网

原创

高效分组变位词:无需排序的哈希映射方法

本文详细介绍了在leetcode中无需对字符串进行排序,通过字符频率统计实现高效分组变位词(anagrams)的算法。文章将深入解析核心思路、代码实现细节,并对该方法的关键步骤和时间复杂度进行专业分析,帮助读者理解如何利用哈希映射优化变位词分组问题。

变位词分组问题概述

变位词(Anagrams)是指由相同字母但顺序不同的字符串组成的词语,例如 "eat"、"tea" 和 "ate" 互为变位词。在编程中,一个常见的问题是将一组字符串按照它们是否为变位词进行分组。传统方法通常涉及对每个字符串进行排序,然后将排序后的字符串作为哈希表的键。然而,本文将探讨一种无需排序,通过字符频率统计实现的高效方法。

基于字符频率统计的解决方案

该解决方案的核心思想是为每个字符串创建一个唯一的“签名”,这个签名能够代表其包含的字符种类和数量,而与字符的排列顺序无关。所有互为变位词的字符串,都将拥有相同的签名。我们将利用哈希映射(HashMap)来存储这些签名作为键,并将对应的变位词列表作为值。

算法步骤详解

  1. 初始化结果列表和哈希映射:

    • 创建一个 List> 用于存放最终的分组结果。
    • 创建一个 Map>,其中键是字符串的字符频率签名,值是属于该签名的字符串列表。
  2. 遍历输入字符串数组

    • 对于数组中的每一个字符串 str: a. 创建字符频率数组: 初始化一个长度为 26 的整型数组 int[] input。这个数组的每个索引代表一个英文字母('a' 到 'z'),对应的值表示该字母在当前字符串中出现的次数。 b. 填充频率数组: 遍历当前字符串 str 的每一个字符。对于每个字符 c,通过 c - 'a' 计算其在数组中的索引,并将对应位置的值加一。例如,如果字符是 'e',则 input['e' - 'a'] 的值会增加。 c. 生成字符串签名: 将这个 int[26] 频率数组转换为一个字符串表示。Java 的 Arrays.toString(int[]) 方法可以方便地完成此操作,它会生成形如 "[1, 0, 0, 0, 1, 0, ..., 1, 0]" 的字符串。这个字符串就是当前字符串的唯一签名。
      • 关键理解点: 无论 "eat"、"tea" 还是 "ate",它们在经过字符频率统计后,都会得到相同的频率数组(例如,'a' 出现 1 次,'e' 出现 1 次,'t' 出现 1 次,其他字母出现 0 次)。因此,Arrays.toString() 转换后得到的字符串签名也将完全相同。这使得我们能够将互为变位词的字符串映射到同一个键下。 d. 更新哈希映射:
      • 检查哈希映射 outputMap 中是否已存在以 inputStr(即频率数组的字符串签名)为键的条目。
      • 如果存在,说明之前已经处理过与当前字符串互为变位词的字符串,直接将当前字符串 str 添加到该键对应的列表中。
      • 如果不存在,说明这是第一次遇到这种字符组合,创建一个新的 ArrayList,将当前字符串 str 添加进去,然后将这个新的列表以 inputStr 为键存入 outputMap。
  3. 收集结果:

    • 遍历完所有输入字符串后,哈希映射 outputMap 中的每个值(即 List)都代表一个变位词分组。将 outputMap.values() 中的所有列表添加到最终的结果列表 output 中。

示例代码

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class GroupAnagrams {

    public List> groupAnagrams(String[] strs) {
        List> output = new ArrayList<>();
        if (strs == null || strs.length == 0) {
            return output;
        }

        // 使用哈希映射存储频率签名到字符串列表的映射
        Map> outputMap = new HashMap<>();

        // 遍历所有输入字符串
        for (String str : strs) {
            // 1. 创建字符频率数组
            int[] charCounts = new int[26]; // 对应 'a' 到 'z'

            // 2. 填充频率数组
            for (char c : str.toCharArray()) {
                charCounts[c - 'a']++;
            }

            // 3. 生成字符串签名(将频率数组转换为字符串)
            String signature = Arrays.toString(charCounts);

            // 4. 更新哈希映射
            // 如果签名已存在,则将当前字符串添加到对应的列表中
            // 否则,创建一个新列表并存入哈希映射
            outputMap.computeIfAbsent(signature, k -> new ArrayList<>()).add(str);

            // 等价于以下传统写法:
            // if (outputMap.containsKey(signature)) {
            //     outputMap.get(signature).add(str);
            // } else {
            //     List newList = new ArrayList<>();
            //     newList.add(str);
            //     outputMap.put(signature, newList);
            // }
        }

        // 5. 收集结果
        output.addAll(outputMap.values());
        return output;
    }

    public static void main(String[] args) {
        GroupAnagrams solver = new GroupAnagrams();
        String[] strs1 = {"eat", "tea", "tan", "ate", "nat", "bat"};
        List> result1 = solver.groupAnagrams(strs1);
        System.out.println("Result 1: " + result1); 
        // 预期输出可能类似:[[bat], [nat, tan], [eat, tea, ate]] (顺序可能不同)

        String[] strs2 = {""};
        List> result2 = solver.groupAnagrams(strs2);
        System.out.println("Result 2: " + result2); // 预期输出:[[]] 或 [[ ]]

        String[] strs3 = {"a"};
        List> result3 = solver.groupAnagrams(strs3);
        System.out.println("Result 3: " + result3); // 预期输出:[[a]]
    }
}

注意事项

  • 字符集限制: 此方法假设输入字符串只包含小写英文字母。如果包含大写字母、数字或其他字符,需要调整频率数组的大小(例如 256 来覆盖所有 ASCII 字符)以及字符到索引的映射方式。
  • Arrays.toString() 的开销: 尽管 Arrays.toString() 需要遍历整个数组,但由于频率数组的大小固定为 26,这个操作的时间复杂度是常数级的 O(1)。
  • 哈希冲突: HashMap 在内部处理哈希冲突,其平均操作时间复杂度为 O(1)。

时间复杂度分析

我们来详细分析上述算法的时间复杂度。 假设输入字符串数组的长度为 m (即 strs.length),平均每个字符串的长度为 n。

  1. 外层循环: 算法包含一个主循环,它会遍历 strs 数组中的每一个字符串,共执行 m 次。

    LibLibAI
    LibLibAI

    国内领先的AI创意平台,以海量模型、低门槛操作与“创作-分享-商业化”生态,让小白与专业创作者都能高效实现图文乃至视频创意表达。

    下载
  2. 内层操作(针对每个字符串):

    • 创建并填充 charCounts 数组: 对于每个字符串,我们需要遍历其所有字符来统计频率。这个操作的时间复杂度是 O(n),其中 n 是当前字符串的长度。
    • 调用 Arrays.toString(charCounts): 这个方法会将长度为 26 的 charCounts 数组转换为字符串。由于数组长度是固定的常数(26),此操作的时间复杂度可以视为 O(1)。
    • 哈希映射操作: outputMap.computeIfAbsent()(或 containsKey()、get()、put())这些哈希映射操作在平均情况下具有 O(1) 的时间复杂度。
  3. 总时间复杂度计算:

    • 外层循环执行 m 次。
    • 在每次外层循环中,我们执行一个 O(n) 的操作(填充频率数组)和几个 O(1) 的操作(Arrays.toString 和哈希映射操作)。
    • 因此,总的时间复杂度是 m * (O(n) + O(1) + O(1)),简化后为 O(m * n)。
  4. 最终结果收集: output.addAll(outputMap.values()) 操作会将所有列表添加到最终结果中。这个操作的复杂度取决于所有列表的总长度,最坏情况下是 O(m * n),但通常可以视为 O(m) 因为它只是收集指针。不过,它不会超过 O(m * n)。

综合来看,该算法的*时间复杂度为 O(m n)**。

总结

通过利用字符频率数组作为哈希映射的键,我们能够高效地解决变位词分组问题,而无需对每个字符串进行耗时的排序操作。这种方法的核心在于将字符串的“结构”抽象为一个固定长度的数字序列,并将其转换为字符串作为唯一的标识符。这种思路在处理需要基于内容而非顺序进行分类的问题时非常有用。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

443

2023.08.02

mysql标识符无效错误怎么解决
mysql标识符无效错误怎么解决

mysql标识符无效错误的解决办法:1、检查标识符是否被其他表或数据库使用;2、检查标识符是否包含特殊字符;3、使用引号包裹标识符;4、使用反引号包裹标识符;5、检查MySQL的配置文件等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

183

2023.12.04

Python标识符有哪些
Python标识符有哪些

Python标识符有变量标识符、函数标识符、类标识符、模块标识符、下划线开头的标识符、双下划线开头、双下划线结尾的标识符、整型标识符、浮点型标识符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

286

2024.02.23

java标识符合集
java标识符合集

本专题整合了java标识符相关内容,想了解更多详细内容,请阅读下面的文章。

258

2025.06.11

c++标识符介绍
c++标识符介绍

本专题整合了c++标识符相关内容,阅读专题下面的文章了解更多详细内容。

124

2025.08.07

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1500

2023.10.24

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

73

2026.01.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.9万人学习

C# 教程
C# 教程

共94课时 | 7.8万人学习

Java 教程
Java 教程

共578课时 | 52.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号