<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ?LeetCode刷題實戰(zhàn)49:字母異位詞分組

          共 3306字,需瀏覽 7分鐘

           ·

          2020-09-26 14:02

          算法的重要性,我就不多說了吧,想去大廠,就必須要經(jīng)過基礎(chǔ)知識和業(yè)務(wù)邏輯面試+算法面試。所以,為了提高大家的算法能力,這個公眾號后續(xù)每天帶大家做一道算法題,題目就從LeetCode上面選 !

          今天和大家聊的問題叫做?字母異位詞分組,我們先來看題面:

          https://leetcode-cn.com/problems/group-anagrams/

          Given an array of strings strs, group the anagrams together. You can return the answer in any order.


          An Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.

          題意

          給定一個字符串?dāng)?shù)組,將字母異位詞組合在一起。字母異位詞指字母相同,但排列不同的字符串。

          舉個例子,比如給定的數(shù)組是[eat, ate, tea, tan, nat, bat]。其中eat,ate,tea這三個單詞用到的字母都是e,t和a各一個。tan和nat用到的都是a,n和t,最后剩下bat,所以分組結(jié)果就是:[eat, ate, tea],[tan, nat]和[bat]。

          樣例

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

          說明:

          所有輸入均為小寫字母。
          不考慮答案輸出的順序。


          解題

          https://www.cnblogs.com/techflow/p/12693828.html

          暴力

          我們依然從最簡單的思路開始想起,我們分組的依據(jù)是每一個字符串當(dāng)中用到的字母的情況。所以我們可以把每一個字符串當(dāng)中所有的元素拆解出來,放到一個dict當(dāng)中,然后我們用這個dict來作為分組的標(biāo)準(zhǔn),將dict相同的字符串放在同一組。
          比如eat我們把它變成{'e': 1, 'a': 1, 't': 1},由于一個字母可能出現(xiàn)多個,所以我們也要記錄出現(xiàn)的次數(shù)。但有一個問題是,dict是動態(tài)數(shù)據(jù),在Python當(dāng)中我們不能用它作為另一個dict的key。這個問題比較簡單的方法是我們寫一個方法將這個dict拼接成字符串,比如'e1a1t1'。我們用這個作為key。但是這又有了一個問題,dict當(dāng)中的key并不一定是有序的,所以我們需要對dict進行排序,可以看下下圖中的流程。
          也就是說我們需要實現(xiàn)一個函數(shù),它的輸入是字符串,輸出是這個字符串構(gòu)成的元素。
          def?splitStr(s):
          ????d?=?defaultdict(int)
          ????for?c?in?s:
          ????????d[c]?+=?1
          ????ret?=?''
          ????#?將dict中內(nèi)容排序
          ????for?k,v?in?sorted(d.items()):
          ????????ret?+=?(str(k)?+?str(v))
          ????return?ret
          到這里就簡單了,我們在外層再創(chuàng)建一個dict用來存儲分組后的結(jié)果即可,我們很容易就能寫出代碼:
          from?collections?import?defaultdict

          class?Solution:
          ????def?splitStr(self,?s):
          ????????d?=?defaultdict(int)
          ????????#?拆分字符串中元素
          ????????for?c?in?s:
          ????????????d[c]?+=?1
          ????????ret?=?''
          ????????#?將dict中內(nèi)容排序
          ????????for?k,v?in?sorted(d.items()):
          ????????????ret?+=?(str(k)?+?str(v))
          ????????return?ret
          ????
          ????def?groupAnagrams(self,?strs:?List[str])?->?List[List[str]]:
          ????????groups?=?defaultdict(list)
          ????????for?s?in?strs:
          ????????????#?拿到拆分之后的字符串作為key進行分組
          ????????????key?=?self.splitStr(s)
          ????????????groups[key].append(s)
          ????????????
          ????????ret?=?[]
          ????????for?_,?v?in?groups.items():
          ????????????ret.append(v)
          ????????return?ret
          ????????
          有些小伙伴可能意識到了一個問題,既然我們先轉(zhuǎn)化成dict之后后面還是要拼接成字符串,我們為什么不直接對字符串排序,將排序之后的結(jié)果作為key呢?構(gòu)成元素一樣的字符串,排序之后的結(jié)果必然是相同的。
          比如apple和pplae排序之后都是aelpp,這樣可行嗎?
          思路是OK的,但是提交并不能通過。原因也很簡單,三個字可以概括,就是復(fù)雜度。這樣做的復(fù)雜度非常大,因為字符串的長度并不是固定的,我們對它們一一排序需要大量的開銷。另外我們用排序之后的結(jié)果作為key,也會占用存儲資源。所以這不是一個好方法。

          hash

          接下來就到了我們的正主出場了,大家從標(biāo)題當(dāng)中應(yīng)該就已經(jīng)看出來了,這道題和hash算法有關(guān)。
          講道理,hash算法的名稱如雷貫耳,我們經(jīng)常聽到,但是很多人并不知道hash算法是干嘛的,或者我們究竟什么地方要用到它。大家聽得比較多的可能是hashMap。
          其實hash算法的內(nèi)容很簡單,可以簡單理解成映射。我們的輸入可以是任何內(nèi)容,可以是一個數(shù)字,也可以是個數(shù)組或者是一個對象,但是我們的輸出是一個固定若干個字節(jié)組成的信息。比如下圖當(dāng)中對4取模就是一個hash函數(shù),我們可以根據(jù)對4取模之后的結(jié)果將數(shù)歸類到不同的分桶當(dāng)中。
          我們可以按照我們的意愿讓一些數(shù)據(jù)分在同一個分桶當(dāng)中,我們也可以讓每一條數(shù)據(jù)hash之后的結(jié)果都盡量各不相同,這樣做實現(xiàn)的是信息的壓縮。比如我們將一個大小2MB的實例進行hash,得到了一個32位的字符串。相當(dāng)于我們用32位的字符串就可以代表原本2MB的內(nèi)容,這樣我們可以進行高效的查詢或者是其他操作。舉個例子,比如當(dāng)下的人臉識別模塊,就可以簡單理解成一個hash函數(shù)。攝像頭拍攝照片,算法將照片hash成一個id,再去數(shù)據(jù)庫當(dāng)中找到這個id對應(yīng)的個人信息,完成識別過程。
          在這道題當(dāng)中,我們希望設(shè)計一個hash函數(shù),它讀入一個字符串,根據(jù)字符串當(dāng)中的內(nèi)容進行hash,保證構(gòu)造相同的字符串hash得到的結(jié)果一致。我們就通過這個hash之后的結(jié)果來進行分桶,從本質(zhì)上來說,上面這一種做法也可以看成是一種hash方法。但是由于涉及到了排序,稍稍復(fù)雜了一些,并且最后返回的是一個字符串,從時間復(fù)雜度和空間復(fù)雜度上來看,都還有優(yōu)化的空間,下面我們就來看一個比較常用的hash算法。
          在這個算法當(dāng)中,我們會選擇一個質(zhì)數(shù)作為hash因子,這樣發(fā)生hash碰撞的概率會比較低。通過質(zhì)數(shù)的冪來構(gòu)造hash結(jié)果,我們來看代碼:
          def?hash(dict):
          ????ret?=?0
          ????prime?=?23
          ????#?k代表字符,v表示出現(xiàn)次數(shù)
          ????for?k,?v?in?dict:
          ????????ret?+=?v?*?pow(23,?ord(k)?-?ord('a'))
          ????????#?對2^32取模,控制返回值在32個bit內(nèi)
          ????????ret?%=?(1?<32)
          ????return?ret
          這里的ord是取ascii碼的運算,即將英文字母轉(zhuǎn)成數(shù)字。我們用某一個質(zhì)數(shù)不同的冪來表示不同的字母,再乘上字母出現(xiàn)的次數(shù)作為系數(shù)。最后將每個字母hash的值累加起來就得到了整個字符串的hash值,構(gòu)造相同的字符串的系數(shù)和冪都是一樣的,那么最后求和的結(jié)果顯然也會相等,這個結(jié)論沒有問題。但是反過來,hash值相等的字符串真的一樣嗎?
          其實我們想一下是可以想到反例的,比如說如果我們單個的字符a,它的hash值的結(jié)果是,單個b的hash值也很好算,是23。請問23個a的hash值是多少?算一下就知道,也是23。因為雖然我們用的冪不同,但是它們的底數(shù)是一樣的,我們可以用前面的系數(shù)來彌補指數(shù)的差。這種不同的對象hash結(jié)果一樣的情況叫做hash碰撞,這種是不符合我們預(yù)期的。但是可以肯定的是,大多數(shù)情況下hash碰撞幾乎是不可避免的。因為我們hash的目的就是為了用一個簡單的數(shù)字或者字符串代替原本復(fù)雜龐大的數(shù)據(jù),就好像拍照一樣,兩個不同的人,也可能拍出來看起來很像。
          我們在這個過程當(dāng)中存在大量的信息壓縮或者信息丟失,比如說我們用10個bit的數(shù)字代表了原本2000個bit的數(shù)據(jù)。不管我們用什么樣的策略,10個bit能表達的數(shù)據(jù)就是有限的。根據(jù)鴿籠原理,只要我們hash的數(shù)據(jù)足夠多,總有兩個不一樣的數(shù)據(jù)hash的結(jié)果碰撞。
          不過通過設(shè)計合適的因子和算法,可以降低或者控制出現(xiàn)碰撞的概率。比如這題當(dāng)中,我們選擇越大的素數(shù)越不容易出現(xiàn)碰撞,因為選擇的素數(shù)越大,構(gòu)成碰撞需要重復(fù)的字符就越多,這種可能性也就越低。
          最后,我們來看完整代碼:
          from?collections?import?defaultdict
          import?math


          class?Solution:
          ????def?splitStr(self,?s):
          ????????hashtable?=?[0?for?_?in?range(30)]
          ????????ret?=?0
          ????????for?c?in?s:
          ????????????hashtable[ord(c)?-?ord('a')]?+=?1
          ????????????
          ????????#?hash算法
          ????????for?i?in?range(26):
          ????????????ret?=?ret?*?23?+?hashtable[i]
          ????????????#?控制返回值在32個bit內(nèi)
          ????????????ret?%=?(1?<32)
          ????????return?ret
          ????
          ????def?groupAnagrams(self,?strs:?List[str])?->?List[List[str]]:
          ????????groups?=?defaultdict(list)
          ????????for?s?in?strs:
          ????????????key?=?self.splitStr(s)
          ????????????groups[key].append(s)
          ????????
          ????????????
          ????????ret?=?[]
          ????????for?_,?v?in?groups.items():
          ????????????ret.append(v)
          ????????return?ret
          ????????
          代碼里有一個細(xì)節(jié),我們剛才寫的是v * pow(23, k),為什么到這里我們換了一種寫法?
          因為Python當(dāng)中pow函數(shù)返回的是浮點數(shù),精度會有丟失,這樣會導(dǎo)致hash碰撞的概率大大增加,所以我們換了不適用pow函數(shù)的方法。
          好了,今天的文章就到這里,如果覺得有所收獲,請順手點個在看或者轉(zhuǎn)發(fā)吧,你們的支持是我最大的動力。


          上期推文:


          LeetCode1-20題匯總,速度收藏!
          LeetCode21-40題匯總,速度收藏!
          LeetCode刷題實戰(zhàn)40:組合總和 II
          LeetCode刷題實戰(zhàn)41:缺失的第一個正數(shù)
          LeetCode刷題實戰(zhàn)42:接雨水
          LeetCode刷題實戰(zhàn)43:字符串相乘
          LeetCode刷題實戰(zhàn)44:通配符匹配
          LeetCode刷題實戰(zhàn)45:跳躍游戲 II
          LeetCode刷題實戰(zhàn)46:全排列
          LeetCode刷題實戰(zhàn)47:全排列 II
          LeetCode刷題實戰(zhàn)48:旋轉(zhuǎn)圖像


          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美成人精品网站 | 中文字幕一区在线观看视频 | 日本亲子乱婬一级A片 | 99视频免费观看 | 日本色情视频免费 |