首页 > Java > java教程 > 正文

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

碧海醫心
发布: 2025-10-14 11:29:22
原创
425人浏览过

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

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

变位词分组问题概述

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

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

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

算法步骤详解

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

    • 创建一个 List<List<String>> 用于存放最终的分组结果。
    • 创建一个 Map<String, List<String>>,其中键是字符串的字符频率签名,值是属于该签名的字符串列表。
  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<String>,将当前字符串 str 添加进去,然后将这个新的列表以 inputStr 为键存入 outputMap。
  3. 收集结果:

    • 遍历完所有输入字符串后,哈希映射 outputMap 中的每个值(即 List<String>)都代表一个变位词分组。将 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<List<String>> groupAnagrams(String[] strs) {
        List<List<String>> output = new ArrayList<>();
        if (strs == null || strs.length == 0) {
            return output;
        }

        // 使用哈希映射存储频率签名到字符串列表的映射
        Map<String, List<String>> 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<String> 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<List<String>> result1 = solver.groupAnagrams(strs1);
        System.out.println("Result 1: " + result1); 
        // 预期输出可能类似:[[bat], [nat, tan], [eat, tea, ate]] (顺序可能不同)

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

        String[] strs3 = {"a"};
        List<List<String>> 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 次。

    简篇AI排版
    简篇AI排版

    AI排版工具,上传图文素材,秒出专业效果!

    简篇AI排版 554
    查看详情 简篇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)**。

总结

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

以上就是高效分组变位词:无需排序的哈希映射方法的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
热门推荐
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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