目錄
哈希表(Hash Table)是根據關鍵碼值(key, value)而直接進行訪問的數據結構。當我們從哈希表中查找需要的數據時,理想情況是不經過任何比較,一次存取便能得到所查記錄,那就必須在記錄的存儲位置和它的關鍵字之間建立一個確定的對應關系f,使每個關鍵字和結構中唯一的存儲位置對應。
可以把哈希表理解為一個數組,每個索引對應一個存儲位置,哈希表的索引並不像普通數組的索引那樣,從0到length-1.而是關鍵字(key)本身通過哈希函數得到。
舉例說明:
將26個小寫字母存儲到數組 int a[26]中。
a對應a[0], b對應a[1],c對應a[2],....以此類推
哈希函數
上述例子中,關鍵字(小寫字母)是如何得到自己對應索引的位置的呢
關鍵字的ASCII值減去a的ASCII值。
之前說過,關鍵字通過哈希函數得到索引,所以,f(ch)就是哈希函數。
- 問題分析
要統計第一個只出現一次的字符,可以利用哈希表,遍歷字符串兩次
第一次:從頭到尾遍歷整個字符串,並統計每個字符出現的次數
第二次:再遍歷字符串,在哈希表中找到首個數量為1的字符,並返回
為了實現哈希表,我們可以利用數組來進行實現。由於字符串中都是小寫字母,因此我們申請一個長度為26的字符(一共有26個小寫字母)。為了實現哈希表,我們將26個小寫字母存儲到數組word中,a對應a[0],b對應a[1],c對應a[2],...以此類推。
此時word[26]就是一個哈希表。但是小寫字母如何得到自己索引呢?由於每個小寫字母都有自己對應的ASCII碼值,用每個小寫字母的ASCII碼值減去97即a的ASCII碼值,就可以得到小寫字母的索引位置。數組的元素存儲每個小寫字母出現的次數。
1 class Solution { 2 public: 3 char firstUniqChar(string s) { 4 if(s.empty()) 5 return ' '; 6 int n=s.size(); 7 vector<int> word(26); 8 for(int i=0;i<n;++i) 9 word[s[i]-'a']++; 10 for(int i=0;i<n;++i) 11 { 12 if(word[s[i]-'a']==1) 13 return s[i]; 14 } 15 return ' '; 16 } 17 };
- 問題分析
由於字符串全部由大寫字母和小寫字母組成。小寫字母的ASCII碼范圍為65-90,大寫字母的ASCII碼范圍為97-122,因此我們可以設置一個長度為65的數組即可。
我們采用哈希表,將字符串中每個字符和其ASCII碼值相對應,每個字母如何找到其索引呢?每個字母的字符值減去65就是其索引值。
- 代碼參考
1 class Solution { 2 public: 3 int FirstNotRepeatingChar(string str) { 4 if(str.empty()) 5 return -1; 6 vector<int> word(65); 7 for(int i=0;i<str.size();++i) 8 word[(int)str[i]-65]++; 9 for(int i=0;i<str.size();++i) 10 { 11 if(word[(int)str[i]-65]==1) 12 return i; 13 } 14 return -1; 15 } 16 };
- 問題分析
要找到數組中的重復的數字,可以采用以下算法
對於數字范圍在0-n-1范圍內的長度為n的數組,如果沒有重復的數字,則數組中每個位置有且僅有一個元素
如果有重復數字,則有些位置沒有元素,有些位置有多個元素。
找到數組中重復數字的算法如下:
從頭到尾掃描整個數組,設下標為i的位置元素值為m
如果m==i,則繼續掃描下一個元素
如果m!=i,首先判斷下標值為m的位置其元素值和下標值是否相同
如果元素值和下標值相同,則找到一個重復元素
否則,交換他們
- 代碼參考
1 class Solution { 2 public: 3 int findRepeatNumber(vector<int>& nums) { 4 /* 5 如果0-n-1范圍內長度為n的數組中沒有重復的元素,數組中每一個位置有且僅有一個元素 6 如果有重復的元素,則有些位置是空的,有些位置存儲了多個元素 7 因此找到數組中重復的數字算法如下: 8 從頭到尾掃描整個數組 9 如果下標為i的位置元素為m,如果m==i,則繼續掃描下一個元素 10 如果下標為i的位置的元素m!=i,則看下標為m的位置元素值和下標值是否相同 11 如果相同,則找到一個重復元素 12 否則,交換m和i 13 */ 14 if(nums.empty()) 15 return 0; 16 //從頭到尾掃描整個數組 17 for(int i=0;i<nums.size();++i) 18 { 19 //判斷下標為i的元素值和下標值是否相同 20 //如果相同,則直接掃描下一個元素 21 //如果不同,則做下一步判斷 22 while(nums[i]!=i) 23 { 24 //判斷下標為m的位置元素值和下標值是否相同 25 //如果相同,則找到一個重復元素 26 if(nums[nums[i]]==nums[i]) 27 { 28 return nums[i]; 29 } 30 else 31 { 32 int temp=nums[i]; 33 nums[i]=nums[temp]; 34 nums[temp]=temp; 35 } 36 } 37 } 38 return 0; 39 } 40 };
- 問題分析
這道題我們可以使用滑動窗口+雙指針+哈希表來實現
題目中要求答案必須是子串的長度,意味着子串內的字符在原生字符串中一定是連續的。因此我們可以將答案看做源字符串的一個滑動窗口,並維護窗口內不能有重復字符,同時更新窗口的最大值。我們可以使用哈希表記錄每個字符的下一個索引,然后盡量向右移動尾指針來擴展窗口,並更新窗口的最大長度,如果尾指針指向的元素重復,則將頭指針直接移動到窗口中重復元素的右側。
算法:
1. tail指針向末尾方向移動
2. 如果尾指針指向的元素存在於哈希表中:
head指針跳躍到重復字符的下一位
3. 更新哈希表的窗口長度。
定義一個哈希表(k, v), 其中key為字符,value為字符位置+1,+1表示從字符位置后一個才開始不重復
我們定義不重復子串的開始位置為start,結束位置為end
隨着end不斷遍歷向后,會遇到與[start, end]區間內字符相同的情況,此時將字符作為key值,獲取其value值,並更新start,此時[start, end]區間內不存在重復字符
無論是否更新start,都會更新其哈希表和結果ans
- 代碼參考
1 class Solution { 2 public: 3 int lengthOfLongestSubstring(string s) { 4 if(s.empty()) 5 return 0; 6 unordered_map<char,int> map;//利用哈希表,key存儲字符,value存儲字符的位置+1 7 /* 8 定義不重復字符子串的開始位置為start,結束位置為end 9 end向后遍歷,若end指向的字符在哈希表中出現過,則將此字符作為key值,獲取其value值,並更新start,此時[start,end]區間內不存在重復字符 10 */ 11 int ans=0; 12 int start=0; 13 for(int end=0;end<s.size();++end) 14 { 15 //如果此字符在哈希表中出現過,將此字符作為key值,獲取其value值,並更新start 16 if(map.count(s[end])!=0) 17 { 18 //更新start 19 start=max(map[s[end]],start); 20 } 21 //無論是否出現,都將目前的字符作為key值,獲取其value值 22 map[s[end]]=end+1; 23 //更新ans 24 ans=max(end-start+1,ans); 25 } 26 return ans; 27 } 28 };
- 問題分析
這道題是需要我們統計數組中前k個高頻元素。算法如下
首先統計數組中每個元素出現的次數,用哈希表進行存儲元素和出現次數,key表示數組元素,value表示數組出現的次數
由於需要找到錢k個高頻元素,因此需要對數組中出現的次數進行排序,我們可以用快速排序或者最小堆來解決這個問題,在這里主要介紹最小堆
創建一個最小堆,如果最小堆中元素的個數小於k,則直接將其插入最小堆中
如果最小堆中的元素大於k,則比較堆頂元素與當前出現次數大小
如果堆頂元素更大,說明至少有k個數字出現的次數比當前值大,將當前值舍去
如果堆頂元素更小,則將當前值插入堆中
為了更簡便的寫代碼,也可以這樣構造一個大小為k的最小堆
將來的元素通通插入最小堆,若最小堆尺寸大於k,則直接將堆頂元素出棧,否則不作出反應
- 代碼參考
1 class Solution { 2 public: 3 class mycomparison{ 4 public: 5 bool operator()(const pair<int,int> &lhs,const pair<int,int> &rhs) 6 { 7 return lhs.second>rhs.second; 8 } 9 }; 10 vector<int> topKFrequent(vector<int>& nums, int k) { 11 vector<int> result(k); 12 if(nums.empty()) 13 return result; 14 //從頭到尾統計數組中每個數字出現的次數 15 unordered_map<int,int> map; 16 for(int i=0;i<nums.size();++i) 17 map[nums[i]]++; 18 //建立一個最小堆 19 //每個新來的元素都插入最小堆,若最小堆元素個數超過k,則將堆頂元素刪除 20 priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparison> pri_que; 21 for(unordered_map<int,int>::iterator it=map.begin();it!=map.end();++it) 22 { 23 pri_que.push(*it); 24 if(pri_que.size()>k) 25 pri_que.pop(); 26 } 27 //建立好最大k個元素的最小堆后,由於最小堆是最小元素在堆頂,因此倒序打印 28 for(int i=k-1;i>=0;--i) 29 { 30 result[i]=pri_que.top().first; 31 pri_que.pop(); 32 } 33 return result; 34 } 35 };
- 問題分析(解法一)
解法一:位運算
首先要了解異或運算:
一個元素與其本身異或結果為0
如果整數數組中,一個元素出現次數兩次,則其異或結果為0.
如果整數數組中,只有一個元素出現了一次,其余元素都出現了兩次,則從頭到尾異或整個數組,結果為那個只出現一次的數組
- 代碼參考
1 class Solution { 2 public: 3 /* 4 對於一個整數數組來說,如果一個元素出現了兩次,則異或結果為0 5 如果一個整數數組除了一個元素出現一次,其余每個元素均出現兩次,則從頭到尾異或整個數組,結果為只出現一次的元素 6 */ 7 int singleNumber(vector<int>& nums) { 8 if(nums.empty()) 9 return 0; 10 int sumOR=0; 11 for(int i=0;i<nums.size();++i) 12 { 13 sumOR^=nums[i]; 14 } 15 return sumOR; 16 } 17 };
- 問題分析(解法二)
也可以直接使用哈希表來實現
從頭到尾遍歷整個數組,並統計數組中每個元素出現的次數
從頭到尾遍歷整個哈希表,如果元素出現的次數為1,則直接將這個元素返回
- 代碼參考
1 class Solution { 2 public: 3 int singleNumber(vector<int>& nums) { 4 /* 5 還可以使用哈希表,兩次掃描 6 第一次掃描: 7 如果數組中元素第一次出現,則將元素插入哈希表 8 如果數組中元素第二次出現,則將元素從哈希表中移除 9 第二次掃描哈希表 10 最后剩下的數組就是只出現一次的數字 11 */ 12 if(nums.empty()) 13 return 0; 14 unordered_map<int,int> m; 15 int result; 16 for(int i=0;i<nums.size();++i) 17 { 18 m[nums[i]]++; 19 } 20 for(auto i=m.begin();i!=m.end();++i) 21 { 22 if(i->second==1) 23 result=i->first; 24 } 25 return result; 26 } 27 };
- 問題分析
利用哈希表實現
從頭到尾遍歷整個數組,統計數組中每個元素出現的次數,如果某個元素出現次數大於1,則直接返回true,否則繼續統計。
- 代碼參考
1 class Solution { 2 public: 3 bool containsDuplicate(vector<int>& nums) { 4 if(nums.empty()) 5 return false; 6 //統計整數數組中每個元素出現的次數 7 unordered_map<int,int> map; 8 for(auto c:nums) 9 { 10 map[c]++; 11 if(map[c]>1) 12 return true; 13 } 14 return false; 15 } 16 };
- 問題分析
遍歷一個哈希表
維護一個哈希表,里面始終最多包含k個元素,當出現重復值時則說明在k距離內存在重復元素。
每次遍歷一個元素則將其加入哈希表,如果哈希表的大小大於k,則移除最前面的數字。
- 代碼參考
1 class Solution { 2 public: 3 bool containsNearbyDuplicate(vector<int>& nums, int k) { 4 if(nums.empty()) 5 return false; 6 unordered_set<int> set; 7 for(int i=0;i<nums.size();++i) 8 { 9 if(set.count(nums[i])) 10 return true; 11 //每次遍歷一個元素則將其加入哈希表中 12 set.insert(nums[i]); 13 //如果哈希表大小大於k,則移除最前面的數字 14 if(set.size()>k) 15 set.erase(nums[i-k]); 16 } 17 return false; 18 } 19 };
- 問題分析
要判斷字符串是否是回文排列,可以采用以下算法
首先通過統計字符串中每個字符出現的次數,用哈希表存儲起來
然后統計哈希表中出現的次數,回文排列最多允許出現一個奇數字符,若超過奇數字符,則判斷出不是回文排列
- 代碼參考
1 class Solution { 2 public: 3 bool canPermutePalindrome(string s) { 4 if(s.empty()) 5 return true; 6 int odd_number=0; 7 //首先統計字符串中每個字符出現的次數 8 unordered_map<char,int> ch_map; 9 for(int i=0;i<s.size();++i) 10 { 11 ch_map[s[i]]++; 12 } 13 for(auto c:ch_map) 14 { 15 if(c.second%2!=0) 16 odd_number+=1; 17 } 18 if(odd_number>1) 19 return false; 20 else 21 return true; 22 } 23 };
- 問題分析
使用哈希表來進行實現,由於哈希查找的時間復雜度為O(1),所以可以利用哈希表來降低時間復雜度
從頭到尾遍歷整個數組nums,i為當前下標,對於數組nums中的每個元素,都要判斷哈希表map中是否存在temp=target-nums[i]的key值
如果存在,則找到兩個值
如果不存在,則將當前的(nums[i], i)存入哈希表中,繼續遍歷直到找到位置
在進行判斷哈希表中是否存在temp的key值時,也要注意判斷條件,防止利用同個元素
在存儲時,為了防止處理下標為0的情況,將哈希表對應下標+1
- 代碼參考
1 class Solution { 2 public: 3 vector<int> twoSum(vector<int>& nums, int target) { 4 vector<int> B; 5 if(nums.empty()) 6 return B; 7 unordered_map<int,int> map; 8 //遍歷整個數組nums,i為當前下標,每個nums[i]都判斷map中是否存在temp=target-nums[i] 9 for(int i=0;i<nums.size();++i) 10 { 11 int temp=target-nums[i]; 12 //如果哈希表中存在key值為temp的元素,則找到這兩個數,直接將下標返回 13 if(map.count(temp)!=0&&map[target-nums[i]]!=i+1) 14 { 15 //防止利用同個元素 16 B.push_back(i); 17 B.push_back(map[target-nums[i]]-1); 18 break; 19 } 20 //如果不存在,則將(nums[i],i)存入哈希表中 21 map[nums[i]]=i+1; 22 } 23 return B; 24 } 25 };
- 問題分析
這是一道經典的題型,凡是和“變位詞”,“字母順序打亂”相關的題目,都考慮字母出現的次數
我們既要統計字母表中字符出現的次數,又要統計單詞中字母出現的次數,如果單詞中字母出現的次數都小於或等於字母表中字母出現的次數,則這個單詞可以由字母表拼寫出來,否則不能被字母表拼寫出來。
對於這種拼寫單詞的問題,我們可以設置一個長度為26的數組,數組中的下標為字符的ASCII碼-a的ASCII碼。數組的元素值為字符出現的次數。
我們遍歷這個單詞數組,如果單詞數組中的單詞中字符出現的次數小於或等於字母表中字符出現的次數,則這個單詞可以由字母表拼寫出來,否則不能。
- 代碼參考
1 class Solution { 2 public: 3 int countCharacters(vector<string>& words, string chars) { 4 if(chars.empty()||words.empty()) 5 return 0; 6 //首先統計字母表中每個字符出現的次數 7 vector<int> chars_count=count(chars); 8 int res=0; 9 //統計words中每個元素中每個字符出現的次數 10 for(string& word:words) 11 { 12 vector<int> word_count=count(word); 13 if(iscontains(chars_count,word_count)) 14 res+=word.length(); 15 } 16 return res; 17 } 18 /*設置兩個函數,一個函數判斷字母表中字符出現的次數是否覆蓋單詞中字符出現的次數 19 如果字母表中出現的次數大於單詞中字符出現的次數,則直接遍歷下一個字符 20 如果字母表中出現的次數小於單詞中出現的次數,則返回結果錯誤。 21 */ 22 bool iscontains(vector<int>&chars_count,vector<int> &word_count) 23 { 24 for(int i=0;i<26;++i) 25 { 26 if(chars_count[i]<word_count[i]) 27 return false; 28 } 29 return true; 30 } 31 /* 32 第二個函數,統計每個單詞或者字符表中每個字符出現的次數,對於類似這種字母問題,直接用長度為26的數組,可以節省時間和空間,即統計26個字符出現的次數 33 設置一個長度為26的數組,數組下標為字符的ASCII碼減去字符的ASCII碼,數組的元素為每個字符出現的次數 34 */ 35 vector<int> count(string &words) 36 { 37 vector<int> word(26,0); 38 for(auto c:words) 39 { 40 word[c-'a']++; 41 } 42 return word; 43 } 44 };
- 問題分析
由於字符串中都是小寫字母,因此我們可以利用長度為26的數組來實現哈希表
若兩個字符串是字母異位詞,則這兩個字符串長度一樣,每個字符出現的次數是相同的。
因此要判斷字符串t是否是字符串s的字母異位詞,我們可以采用如下算法
首先判斷兩個字符串是否相等,不相等則返回false
若相等,則利用長度為26的數組來實現哈希表,初始化數組中元素為0,並遍歷字符串s和t
字符串s負責在對應位置+1
字符串t負責在對應位置-1
如果最終數組中所有元素值為0,則這兩個字符串是字母異位詞。
- 代碼參考
1 class Solution { 2 public: 3 bool isAnagram(string s, string t) { 4 //字母異位詞,即兩個字符串中字符個數是相等的,只是可能在不同的位置 5 //首先判斷兩個字符串長度是否相同,若字符串長度不同,則直接返回false 6 if(s.size()!=t.size()) 7 return false; 8 //由於都是小寫字母組成,因此用長度為26的數組即可 9 //首先將數組中每個元素值初始化為0 10 vector<int> word(26, 0); 11 //分別遍歷字符串s和字符串t,字符串s則+1,字符串t則-1 12 for(int i=0;i<s.size();++i) 13 { 14 word[(int)s[i]-'a']++; 15 word[(int)t[i]-'a']--; 16 } 17 for(int i=0;i<26;++i) 18 { 19 if(word[i]!=0) 20 return false; 21 } 22 return true; 23 } 24 };
- 問題分析
要找到字母異位詞分組,可以采用以下算法
首先遍歷strs,對每個string進行排序,字母異位詞排序結果相同
同時,字母異位詞在map中的key值也是一樣的,在map中添加相應的vector
- 代碼參考
1 class Solution { 2 public: 3 vector<vector<string>> groupAnagrams(vector<string>& strs) { 4 vector<vector<string>> B; 5 if(strs.empty()) 6 return B; 7 unordered_map<string,vector<string>> word; 8 //對字符串數組中的每個字符串進行排序,字母異位詞排序之后是同一個單詞 9 for(auto c:strs) 10 { 11 //字母異位詞排序之后是一樣的,即unordered_map中的key值也一樣 12 string temp=c; 13 sort(temp.begin(),temp.end()); 14 word[temp].push_back(c); 15 } 16 for(auto c:word) 17 { 18 B.push_back(c.second); 19 } 20 return B; 21 } 22 };
- 問題分析
什么
- 代碼參考
1 class Solution { 2 public: 3 bool isIsomorphic(string s, string t) { 4 if(s.size()==0&&t.size()==0) 5 return true; 6 if(s.size()!=t.size()) 7 return false; 8 unordered_map<char,int> shash; 9 unordered_map<char,int> thsah; 10 for(int i=0;i<s.size();++i) 11 { 12 char ss=s[i]; 13 char tt=t[i]; 14 if(shash.count(ss)) 15 { 16 if(shash[ss]!=tt) 17 return false; 18 } 19 else if(thsah.count(tt)) 20 { 21 if(thsah[tt]!=ss) 22 return false; 23 } 24 else 25 { 26 shash[ss]=tt; 27 thsah[tt]=ss; 28 } 29 } 30 return true; 31 } 32 };
- 問題分析
要判斷擁有的石頭中有多少寶石,首先需要統計擁有的石頭中總的石頭中各類石頭的數量
然后判斷寶石的數量相加
- 代碼參考
1 class Solution { 2 public: 3 int numJewelsInStones(string J, string S) { 4 //由於J和S中的字符都是字母且區分大小寫,因此我們可以用數組來進行實現 5 //小寫字母ASCII碼為65-90,大寫字母ASCII碼值為97-122,設置長度為65的數組來存儲 6 if(J.empty()||S.empty()) 7 return 0; 8 int total_count=0; 9 //首先統計你擁有的石頭個數 10 vector<int> stone(65,0); 11 for(int i=0;i<S.size();++i) 12 { 13 stone[(int)S[i]-65]++; 14 } 15 for(int i=0;i<J.size();++i) 16 { 17 total_count+=stone[(int)J[i]-65]; 18 } 19 return total_count; 20 } 21 };
- 問題分析
最直觀的方法是迭代並檢查第一個數組nums1中的每個值也存在於num2中,如果存在,則將值添加到輸出。
這道題是可以用unordered_set實現的,unordered_set和unordered_map底層是用哈希表實現的
set和map底層是用紅黑樹實現的
使用unordered_set實現。判斷第二個數組中的每個值也存在於nums2中,如果存在,則將值添加到輸出
- 代碼參考
1 class Solution { 2 public: 3 vector<int> intersection(vector<int>& nums1, vector<int>& nums2) { 4 /* 5 這道題可以使用unordered_set實現,unordered_map和unordered_set底層實現是哈希表 6 map和set底層實現是紅黑樹,是有序的 7 這道題使用unordered_set實現,其是不包含重復元素的,因此首先轉換成unordered_set實現去重 8 然后判斷unordered_set中是否存在nums2中的元素,如果存在,則找到一個 9 */ 10 unordered_set<int> num1_set(nums1.begin(),nums1.end()); 11 unordered_set<int> ans; 12 for(auto num:nums2) 13 { 14 if(num1_set.count(num)==1) 15 ans.insert(num); 16 } 17 return vector<int>(ans.begin(),ans.end()); 18 19 } 20 };
- 問題分析
這道題和兩個數組的交集1的差別是:兩個數組的交集1中結果是唯一的,因此可以借助unordered_set來實現。
這道題的思路是類似的,但是可能有重復的交集,因此采用哈希表來存儲每個元素及其出現的次數。
由於同一個數字在兩個數組中可能出現多次,因此需要用哈希表存儲每個數字出現的次數。對於一個數字,其在交集中出現的次數等於該數字在兩個數組中出現次數的最小值。
算法如下
首先遍歷第一個數組,並在哈希表中記錄第一個數組中的每個數字及其對應出現次數。
然后遍歷第二個數組,對於第二個數組中的每個數字,如果在哈希表中存在這個數字,則將該數字添加到答案,並較少哈希表中該數字出現的次數
- 代碼參考
1 class Solution { 2 public: 3 vector<int> intersect(vector<int>& nums1, vector<int>& nums2) { 4 //假設第二個數組更大 5 if(nums1.size()>nums2.size()) 6 return intersect(nums2,nums1); 7 vector<int> result; 8 if(nums1.empty()||nums2.empty()) 9 return result; 10 unordered_map<int,int> num1_map; 11 //首先遍歷第一個數組,在哈希表中記錄第一個數組中的每個數字及對應出現的次數 12 for(auto c:nums1) 13 { 14 num1_map[c]++; 15 } 16 //遍歷第二個數組,對第二個數組中的每個數字,如果在哈希表中存在這個數字,則將該數字添加到答案,並減少哈希表中該數字出現的次數 17 for(auto c:nums2) 18 { 19 if(num1_map.count(c)) 20 { 21 result.push_back(c); 22 --num1_map[c]; 23 if(num1_map[c]==0) 24 num1_map.erase(c); 25 } 26 } 27 return result; 28 } 29 };