LeetCode 49: 字母异位词分组 Group Anagrams

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介: # LeetCode 49: 字母异位词分组 Group Anagrams ### 题目: 给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。 Given an array of strings, group anagrams together.

LeetCode 49: 字母异位词分组 Group Anagrams

题目:

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

Given an array of strings, group anagrams together.

示例:

输入: ["eat", "tea", "tan", "ate", "nat", "bat"],
输出:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]

说明:

  • 所有输入均为小写字母。
  • 不考虑答案输出的顺序。

Note:

  • All inputs will be in lowercase.
  • The order of your output does not matter.

解题思路:

​ 题目要求是 不管字母怎样排序只要字母相同都归为一类, 只要把所有单词的字母按一定规律排列好, 只要每个单词的字母按规律排好后组成的字符串相同, 则归为一类

排序字母解题:

​ 用哈希映射 {Key : Value} Key 为排好序的字符串, Value 为数组, 存储与 Key 字母相同的单词, 遍历每个单词并排序字母, 查找排序好的字符串是否存在于 Keys, 利用哈希映射可将查找操作时间复杂度降为 O(1)

其解题逻辑为(这里按字母升序排列):

输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
建立哈希映射 map = {}
遍历该字符串数组:

第一个单词: "eat" --> "aet"
"aet" 不存在于 map, 建立映射 {"aet" : [ "eat" ] }

第二个单词: "tea" --> "aet"
"aet" 存在于 map, 加入其 Values {"aet" : [ "eat" , "tea" ] }

第三个单词: "tan" --> "ant"
"ant" 不存在于 map, 建立映射  {"aet" : [ "eat" , "tea" ] ; "ant" : [ "tan" ] }

第四个单词: "ate" --> "aet"
"aet" 存在于 map, 加入其 Values {"aet" : [ "eat" , "tea" , "ate" ] ; "ant" : [ "tan" ] }

......

map = {"aet" : [ "eat" , "tea" , "ate" ] ; "ant" : [ "tan" , "nat"] ; "abt" : [ "bat" ] }

返回该哈希映射的 Values 组成的数组:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]

复杂度:

  • 时间复杂度:O(N*(K*logK)),其中 N 是 strs 的长度,而 K 是 strs 中字符串的最大长度。遍历每个字符串时复杂度为 O(N)。使用内置排序函数排序字符串中的字母的时间复杂度为 O(K*logK)。
  • 空间复杂度:O(N*K),存储在 map 中数据所占用的空间。

统计字频解题:

这种解题方法还可以再优化, 可以省略对字符串排序的操作。

仔细想想,一个单词最多由 26 个英文字母组成, 不就也可以建立一个哈希映射吗? 如:

对于单词 "aeat" :
建立哈希映射{ 'a' : 2 ; 'e' : 1; t : 1 }

key 为出现的单词, value 出现的频次。如果遍历每个 key 判断字母是否相等, 再判断出现次数是否相等, 这显然是更复杂了。

可以将每个字母出现的频次组成连续字符:

每个字母 a-z 出现频次: [2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0]
组成字符串: "20001000000000000001000000"

只需判断每个单词的字母频次字符串是否相同就可以了。

对于求词频还可以优化, 字母数量固定 26 个, 直接建立一个长度为 26 的数组, 其索引代表二十六个字母位, 遍历单词中的字母, 字母每出现一次, 数组中代表该字母的元素值加 1。

这样就避免了排序操作

排序字母解题:

Java:

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        if(strs.length==0) return new ArrayList<>();
        Map<String, List<String>> map = new HashMap<>();//建立映射关系
        for (String s : strs) {//遍历该字符串数组
            char[] chs = s.toCharArray();//转成字符
            Arrays.sort(chs);//排序字符串字母
            String key = String.valueOf(chs);//转成字符串
            if(!map.containsKey(key)) map.put(key, new ArrayList<>());//如果 key 不存在, 新建映射关系
            map.get(key).add(s);//加入其对应的 Value 所在的数组
        }
        return new ArrayList(map.values());//返回 Values 组成的数组
    }
}

Python:

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        ans = collections.defaultdict(list) # 建立映射关系
        for s in strs: # 遍历该字符串数组
            ans[tuple(sorted(s))].append(s) # sorted(s):排序字符串字母, 并加入其对应的 Value 所在的数组
        return ans.values() # 返回 Values 组成的数组

统计字频解题:

Java:

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        if (strs.length == 0) return new ArrayList<>();
        Map<String, List<String>> map = new HashMap<>();// 建立映射关系
        for (String s : strs) {//遍历该字符串数组
            int[] count = new int[26];//建立一个 26 字母的映射关系
            for (char c : s.toCharArray()) count[c - 'a']++;//遍历字字符串每个字母统计每个字母出现的频次
            String key = Arrays.toString(count);//转成字符串
            if (!map.containsKey(key)) map.put(key, new ArrayList<>());//如果 key 不存在, 新建映射关系
            map.get(key).add(s);//加入其对应的 Value 所在的数组
        }
        return new ArrayList(map.values());//返回 Values 组成的数组
    }
}

Python:

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        ans = collections.defaultdict(list)# 建立映射关系
        for s in strs: # 遍历该字符串数组
            count = [0] * 26 # 建立一个 26 字母的映射关系
            for c in s: # 遍历字字符串每个字母
                count[ord(c) - 97] += 1 # 每个字母出现的频次(元素值)加1
            ans[tuple(count)].append(s) # 加入其对应的 Value 所在的数组
        return ans.values() # 返回 Values 组成的数组

欢迎关注微信.公..众号: 爱写Bug

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
目录
相关文章
|
2月前
|
存储 算法
LeetCode第49题字母异位词分组
LeetCode第49题"字母异位词分组"的解题方法,通过将每个字符串的字符排序后作为键存储在HashMap中,有效地将所有字母异位词分组。
LeetCode第49题字母异位词分组
|
2月前
|
算法
LeetCode第17题电话号码的字母组合
该文章介绍了 LeetCode 第 17 题电话号码的字母组合的解法,通过分析得出可使用递归和回溯的思想解决,避免循环穷举的高循环次数,并给出了具体的编码实现,同时总结了该题较难理解,需要了解递归的本质,当嵌套循环层次多时可考虑递归。
LeetCode第17题电话号码的字母组合
|
4月前
|
算法 容器
【LeetCode刷题】滑动窗口解决问题:水果成篮、找到字符串中所有字母异位词
【LeetCode刷题】滑动窗口解决问题:水果成篮、找到字符串中所有字母异位词
|
4月前
|
存储
力扣经典150题第四十二题:字母异位词分组
力扣经典150题第四十二题:字母异位词分组
24 0
|
4月前
|
存储
力扣经典150题第四十一题:有效的字母异位词
力扣经典150题第四十一题:有效的字母异位词
20 0
|
4天前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
2月前
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
44 6
|
2月前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
82 2
|
4天前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
2月前
|
索引 Python
【Leetcode刷题Python】从列表list中创建一颗二叉树
本文介绍了如何使用Python递归函数从列表中创建二叉树,其中每个节点的左右子节点索引分别是当前节点索引的2倍加1和2倍加2。
38 7