题目描述
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""]
输出: [[""]]
思路
对于相同的异位词分组,他们的元素都是相同的,只是元素排列顺序不同
那么可以通过创建一个映射,键为按字符编码顺序排列后的异位词,值为一个数组,存储具有相同元素的异位词
思路优化
可以通过一个字符数组来存储异位词的每个字母,通过这种方式也可以将字母排列不同的异位词进行分组
同时,可以省去排序过程的时间开销
复杂度分析
时间复杂度
- 假设:
- N:输入数组
strs的长度,即字符串的数量。 - K:每个字符串的平均长度。
- N:输入数组
- 主要操作分析:
- 遍历字符串数组:
- 使用
strs.forEach遍历所有字符串,时间复杂度为 O(N)。
- 使用
- 字符串分组:
- 使用排序分组的复杂度
- 对每个字符串
item进行split(''),生成字符数组,时间复杂度为 O(K)。 - 对字符数组进行
sort,时间复杂度为 O(K log K)。 - 使用
join('')将排序后的字符数组重新组合成字符串,时间复杂度为 O(K)。 - 整体排序操作的时间复杂度为 O(K log K)。
- 对每个字符串
- 使用字符计数作为键:
- 每次对字符串进行遍历,时间复杂度为O(K)
- 使用排序分组的复杂度
- Map 操作:
map.has(sorted)和map.get(sorted)的时间复杂度平均为 O(1)(假设良好的哈希函数)。map.set(...)的时间复杂度平均为 O(1)。[...]操作数组的时间复杂度为 O(1),因为只是创建一个新数组引用。
- 转换 Map 为数组:
Array.from(map.values())的时间复杂度为 O(N)。
- 遍历字符串数组:
- 总时间复杂度:
- 遍历 N 个字符串,避免了排序操作,字符串长度为K。
- 因此,总时间复杂度为 O(N * K)。
空间复杂度
- 主要空间占用:
- Map 存储:
- 最坏情况下,没有任何两个字符串是异位词,每个字符串对应一个唯一的键。
- Map 中存储了 N 个键,每个键的长度为 K(排序后的字符串)。
- Map 的空间复杂度为 O(N * K)。
- 中间变量:
- 排序:
sorted字符串占用 O(K) 空间。split('')生成的字符数组占用 O(K) 空间。sort操作生成的临时数组占用 O(K) 空间。- 因为这些是逐步处理的,中间空间为 O(K)。
- 字符数组:
- 创建长度为26的字母数组,常数空间,空间复杂度为O(1)
- 排序:
- 输出数组:
Array.from(map.values())返回一个包含所有字符串的二维数组,空间复杂度为 O(N * K)。
- Map 存储:
- 总空间复杂度:
- 主要的空间占用来自于 Map 和输出数组,总空间复杂度为 O(N * K)。
code
/**
* @param {string[]} strs
* @return {string[][]}
*/
var groupAnagrams = function (strs) {
const map = new Map();
strs.forEach(item => {
// 创建一个长度为26的数组,统计每个字母出现的次数
const charCount = Array(26).fill(0);
for (let char of item) {
charCount[char.charCodeAt(0) - 97]++;
}
// 将字符计数数组转换为字符串作为键
const key = charCount.join('#');
if (map.has(key)) {
map.get(key).push(item);
} else {
map.set(key, [item]);
}
});
return Array.from(map.values());
};