[寶寶也能看懂的活動篇][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

相關文章
相關標籤/搜索