【每日算法】統計「詞頻相同」字符的幾種方式(打表技巧)|Python 主題月

本文正在參加「Python主題月」,詳情查看 活動連接html

題目描述

這是 LeetCode 上的 面試題 10.02. 變位詞組 ,難度爲 中等git

Tag : 「哈希表」、「排序」、「計數」、「數學」、「打表」github

編寫一種方法,對字符串數組進行排序,將全部變位詞組合在一塊兒。變位詞是指字母相同,但排列不一樣的字符串。面試

注意:本題相對原題稍做修改數組

示例:markdown

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

輸出:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]
複製代碼

說明:app

  • 全部輸入均爲小寫字母。
  • 不考慮答案輸出的順序。

模擬 + 排序

一個樸素的想法是根據題意進行模擬,對每一個字符串進行排序做爲 key,從而實現相同的「變位詞」對應同一個 key,使用哈希表進行統計便可。oop

Java 代碼:post

class Solution {
    public List<List<String>> groupAnagrams(String[] ss) {
        List<List<String>> ans = new ArrayList<>();
        Map<String, List<String>> map = new HashMap<>();
        for (String s : ss) {
            char[] cs = s.toCharArray();
            Arrays.sort(cs);
            String key = String.valueOf(cs);
            List<String> list = map.getOrDefault(key, new ArrayList<>());
            list.add(s);
            map.put(key, list);
        }
        for (String key : map.keySet()) ans.add(map.get(key));
        return ans;
    }
}
複製代碼

Python 3 代碼:ui

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        ans = []
        hashmap = defaultdict(list)
        for s in strs:
            key = "".join(sorted(s))
            hashmap[key].append(s)
        for val in hashmap.values():
            ans.append(val)
        return ans
複製代碼
  • 時間複雜度: O ( i = 0 n 1 s s [ i ] . l e n g t h log s s [ i ] . l e n g t h ) O(\sum_{i = 0}^{n - 1}ss[i].length * \log{ss[i].length})
  • 空間複雜度: O ( i = 0 n 1 s s [ i ] ) O(\sum_{i = 0}^{n - 1}ss[i])

模擬 + 計數

方法一沒法作到線性,主要是存在對字符串進行排序的環節。

事實上,咱們能夠利用字符集大小有限做爲切入點(只包含小寫字母),使用一個大小爲 26 26 的數組進行計數,而後對計數後的數組統計值進行拼接,做爲哈希表的 key,從而實現線性複雜度。

Java 代碼:

class Solution {
    public List<List<String>> groupAnagrams(String[] ss) {
        List<List<String>> ans = new ArrayList<>();
        Map<String, List<String>> map = new HashMap<>();
        for (String s : ss) {
            int[] cnts = new int[26];
            for (char c : s.toCharArray()) cnts[c - 'a']++;
            StringBuilder sb = new StringBuilder();
            for (int i : cnts) sb.append(i + "_");
            String key = sb.toString();
            List<String> list = map.getOrDefault(key, new ArrayList<>());
            list.add(s);
            map.put(key, list);
        }
        for (String key : map.keySet()) ans.add(map.get(key));
        return ans;
    }
}
複製代碼

Python 3 代碼:

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        ans = []
        hashmap = defaultdict(list)
        for s in strs:
            cnts = [0] * 26
            for c in s:
                cnts[ord(c) - ord('a')] += 1
            key = "_".join(map(str, cnts))
            hashmap[key].append(s)
        for val in hashmap.values():
            ans.append(val)
        return ans
複製代碼
  • 時間複雜度:令 n n 爲數組大小, C C 爲字符集大小,固定爲 26 26 。總體複雜度爲 O ( i = 0 n 1 s s [ i ] . l e n g t h + n C ) O(\sum_{i = 0}^{n - 1}ss[i].length + n * C)
  • 空間複雜度: O ( i = 0 n 1 s s [ i ] ) O(\sum_{i = 0}^{n - 1}ss[i])

質數分解惟一性

事實上,咱們還能使用「質數分解惟一性」性質,使用質數乘積代指某個「變位詞」。

具體的,咱們能夠先使用 static 代碼塊(確保只會發生一次)打表最小的 26 26 個質數(任意 26 26 個均可以,使用小的,乘積溢出風險低一點),這 26 26 個質數分別對應了 26 26 個字母。

對於一個「變位詞」而言,其對應的質數乘積必然相同。

Java 代碼:

class Solution {
    static int[] nums = new int[26]; 
    static {
        for (int i = 2, idx = 0; idx != 26; i++) {
            boolean ok = true;
            for (int j = 2; j <= i / j; j++) {
                if (i % j == 0) {
                    ok = false;
                    break;
                } 
            }
            if (ok) nums[idx++] = i;
        }
    }
    public List<List<String>> groupAnagrams(String[] ss) {
        List<List<String>> ans = new ArrayList<>();
        Map<Integer, List<String>> map = new HashMap<>();
        for (String s : ss) {
            int cur = 1;
            for (char c : s.toCharArray()) {
                cur *= nums[c - 'a'];
            }
            List<String> list = map.getOrDefault(cur, new ArrayList<>());
            list.add(s);
            map.put(cur, list);
        }
        for (int key : map.keySet()) ans.add(map.get(key));
        return ans;
    }
}
複製代碼

Python 3 代碼:

class Solution:
    nums = [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]

    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        ans = []
        hashmap = defaultdict(list)
        for s in strs:
            cur = 1
            for c in s:
                cur *= self.nums[ord(c) - 97]
            hashmap[cur].append(s)
        return list(hashmap.values())
複製代碼
  • 時間複雜度: O ( i = 0 n 1 s s [ i ] . l e n g t h ) O(\sum_{i = 0}^{n - 1}ss[i].length)
  • 空間複雜度: O ( i = 0 n 1 s s [ i ] ) O(\sum_{i = 0}^{n - 1}ss[i])

溢出說明

使用 long 仍然存在溢出風險,但使用「長度不受限制」的高精度哈希值實現是不現實的。

哈希值必須是有限值域內,纔有意義。

換句話說,若是使用高精度的哈希值的話,咱們是沒法直接將兩個哈希值進行異或判斷結果是否爲 0 0 來得出哈希值是否相同的結論,而是須要使用 O ( L e n ) O(Len) 的複雜度來斷定哈希值是否相同。

所以,針對存在的哈希衝突問題,要麼是解決衝突;要是使用與「字符串哈希」相似的作法,不處理溢出(至關於模 2 64 2^{64} ),但這樣會存在溢出次數不同的值對應的哈希值相同的問題,只能說是一種指望衝突不發生的作法。

最後

這是咱們「刷穿 LeetCode」系列文章的第 No.面試題 10.02 篇,系列開始於 2021/01/01,截止於起始日 LeetCode 上共有 1916 道題目,部分是有鎖題,咱們將先把全部不帶鎖的題目刷完。

在這個系列文章裏面,除了講解解題思路之外,還會盡量給出最爲簡潔的代碼。若是涉及通解還會相應的代碼模板。

爲了方便各位同窗可以電腦上進行調試和提交代碼,我創建了相關的倉庫:github.com/SharingSour…

在倉庫地址裏,你能夠看到系列文章的題解連接、系列文章的相應代碼、LeetCode 原題連接和其餘優選題解。

相關文章
相關標籤/搜索