[宝宝也能看懂的活动篇][30-Day LeetCoding Challenge] 第六天

Hi 你们好,我是张小猪。欢迎来到『宝宝也能看懂』系列特别篇 - 30-Day LeetCoding Challenge。git

这是一个 leetcode 官方的小活动。能够在官网看到,从 4 月 1 号开始,天天官方会选出一道题,在 24 小时内完成便可得到一点小奖励。虽然奖励彷佛也没什么用,不过做为一个官方的打卡活动,小猪仍是来打一下卡吧,正好做为天天下班回家后的娱乐。github

这里是 4 月 6 号的题,也是题目列表中的第 49 题 -- 『字母异位词分组』shell

题目描述

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

示例:数组

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

说明:性能

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

官方难度

MEDIUM优化

解决思路

因为题目不限制返回数据的顺序,因此咱们只须要作字符串内容的区分便可。而这个区分,笼统的来讲,就是找到一个格式化或者是序列化的方式,这个方式只关心不一样字母出现的次数,并不关心它们的位置。spa

直接方案

基于上面的思路,咱们能够轻松的想到一个最基础的方式,即统计单词中字母出现的频次。这种方式实现起来很是轻松,可是性能实在是不敢恭维。code

在遍历每个单词的时候,咱们能够统计其中不一样字母出现的次数,并将统计结果作互相比较,从而作到将不一样类型的单词归类输出。具体代码以下:blog

const wordHash = word => {
  const map = new Map();
  for (let i = 0; i < word.length; ++i) {
    const char = word[i];
    map.set(char, map.has(char) ? map.get(char) + 1 : 1);
  }
  return map;
};
const compareHash = (map1, map2) => {
  if (map1.size !== map2.size) return false;
  for (let [char, count] of map1.entries()) {
    if (!map2.has(char) || map2.get(char) !== count) return false;
  }
  return true;
};
const groupAnagrams = strs => {
  const ret = [];
  const hashMap = new Map();
  for (const word of strs) {
    const hash = wordHash(word);
    let exist = false;
    for (let [map, idx] of hashMap.entries()) {
      if (!compareHash(hash, map)) continue;
      exist = true;
      ret[idx].push(word);
      break;
    }
    !exist && hashMap.set(hash, ret.push([word]) - 1);
  }
  return ret;
};

优化

上面思路里,处理过程的逻辑咱们是比较难优化的,不过这个序列化的方式咱们可让它变得更快一些。

首先,因为只有小写字母,因此咱们能够基于字母顺序对单词内的字符进行排序,这样就能够完成单词的归类啦。具体代码以下:

const groupAnagrams = strs => {
  const hashMap = new Map();
  for (const word of strs) {
    const serializeWord = word.split("").sort().join("");
    hashMap.has(serializeWord)
      ? hashMap.get(serializeWord).push(word)
      : hashMap.set(serializeWord, [word]);
  }
  return [...hashMap.values()];
};

再优化

再看上面的过程,其中字符串的排序过程仍是比较耗时的。因此咱们能够尝试再换一下特征提取方式。

咱们能够申明一个长度为 26 的数组,基于字母 charCode 为下标来记录每一个字母出现的次数,而后把这个数组转换为一个字符串做为序列化的结果。这样便可完成单词的归类啦。具体代码以下:

const groupAnagrams = strs => {
  const BASE_CODE = 97;
  const hashMap = new Map();
  for (const word of strs) {
    const countList = new Int8Array(26);
    for (let j = 0; j < word.length; ++j) {
      ++countList[word.charCodeAt(j) - BASE_CODE];
    }
    const serializeWord = countList.join("");
    hashMap.has(serializeWord)
      ? hashMap.get(serializeWord).push(word)
      : hashMap.set(serializeWord, [word]);
  }
  return [...hashMap.values()];
};

再再优化

在上面的转化过程当中,咱们仍是每次都须要借助额外的数组,以及把数组转化为字符串。此次咱们尝试一个单纯的数字计算,从而再优化这部分的性能。

咱们能够利用质数的特殊性质来参与计算,从而实现只用一个数字就能完成特征标识的任务。不过这里有一个前提,即计算的结果不会超出数字的表示范围。具体代码以下:

const groupAnagrams = strs => {
  const BASE_CODE = 97;
  const CHAR_VAL_MAP = [2, 3, 5, 7, 11 ,13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101];
  const hashMap = new Map();
  for (const word of strs) {
    let val = 1;
    for (let j = 0; j < word.length; ++j) {
      val *= CHAR_VAL_MAP[word.charCodeAt(j) - BASE_CODE];
    }
    hashMap.has(val)
      ? hashMap.get(val).push(word)
      : hashMap.set(val, [word]);
  }
  return [...hashMap.values()];
};

总结

终于出现不是 EASY 难度的题了,可喜可贺!

这道题的核心逻辑并不复杂,很容易 AC,只是在具体的序列化部分咱们能够有一些优化空间,从而提高性能。小猪给出了我本身的优化思路,但愿能帮到有须要的小伙伴。

若是以为不错的话,记得『三连』哦。小猪爱大家哟~

相关连接

qrcode_green.jpeg

相关文章
相关标签/搜索