七、雜湊表與字串(小象)
目錄
雜湊表基礎知識
雜湊表定義
1、字元雜湊
例題:統計一個字串中每個單詞出現的次數。
string str{ "abababcbebaqqwe" }; char char_map[128]{ 0 }; for (auto it : str) { char_map[it]++; } for (int i = 0; i < 128; i++) { if (char_map[i] > 0) { printf("%c[%d] occours %d times", i,i, char_map[i]); cout << endl; } }
2、雜湊表排序整數
例題:給定一串數字,通過雜湊表進行排序,雜湊表是遞增的,按順序輸出就行了,遇到一個數出現了k次,就輸出k遍。
int random_vec[10]{ 999,1,2,5,88,999,88,2,5,1 }; int int_hash[1000]{ 0 }; for (auto it : random_vec) { int_hash[it]++; } for (int i = 0; i < 1000; i++) { while(int_hash[i]) { cout << i<<endl; int_hash[i]--; } }
3、拉鍊表解決衝突,構造雜湊表
#include<iostream> #include<vector> using namespace std; struct ListNode { int val; ListNode *next; ListNode(int x):val(x){} }; int hash_fun(int key, int hash_len) { return key % hash_len; } void insert_hash_table(ListNode *hash_table[], ListNode *node, int table_len) { int hash_key = hash_fun(node->val, table_len); node->next = hash_table[hash_key]; hash_table[hash_key] = node; } bool search_hash_table(ListNode *hash_table[], int value, int table_len) { int hash_key = hash_fun(value, table_len); ListNode* head = hash_table[hash_key]; while (head) { if (head->val == value) { return true; } else { head = head->next; } } return false; } int main() { const int TABLE_LEN = 11; //TABLE_LEN取為質數,衝突會比其他數字少 ListNode * hash_table[TABLE_LEN] = { 0 }; vector<ListNode*> hash_node_vec; int test[] = { 1,1,4,9,20,30,150,500 }; for (int i = 0; i < 8; i++) { hash_node_vec.push_back(new ListNode(test[i])); } for (auto item : hash_node_vec) { insert_hash_table(hash_table, item, TABLE_LEN); } for (int i = 0; i < TABLE_LEN; i++) { printf("[%d]:", i); auto head = hash_table[i]; while(head){ printf("->%d", head->val); head = head->next; } cout << endl; } cout << "test search:" << endl; for (int i = 0; i < 10; i++) { if (search_hash_table(hash_table, i, TABLE_LEN)) { printf("%d is in the hash_table",i); } else { printf("%d is not in the hash_table", i); } cout << endl; } }
NOTE:構造數字雜湊表,用取餘運算的時候,把雜湊表的長度設定為質數,發生衝突的概率小一些。
如題中,const int TABLE_LEN = 11;
4、STL map中的常用操作
#include<map> ;//首先要包含map標頭檔案
map中存放的對應關係為 key -> value
map<string,int> hash_map; //定義雜湊表,要指出對映的兩種資料型別
/*1 以陣列的形式插入*/
hash_map[str1] = 1; //以“陣列中下標和內容對應的形式”來構造一組對應關係
/*2 用insert pair<型別1,型別2> 來插入*/
hash_map.insert(pair<string, int>("zwq",2));
/*3 用insert函式插入map<型別1,型別2>::value_type資料 */
hash_map.insert(map<string,int>::value_type ("zzw",2));
//在雜湊表中查詢某關鍵字,沒有找到
hash_map.find(str1) == hash_map.end()
//map 可以使用迭代器
auto it = hash_map.begin();
//常遇到的問題:
//1 如果要統計某個單詞出現的次數,一般都是通過查詢雜湊表,不存在就把他的值設定為1;存在再加一。
//其實這兩個操作都是完成了一個+1,可以直接寫成判斷存在與否,不存在,就建立一組對映關係,出現次數
//初始化為0,否則就++
例子:
if(hash_map.find(str1) == hash_map.end()){
hash_map[str1] = 0;
}
hash_map[str1]++;
409、最長迴文串
思考:首先要弄清楚:“迴文”的概念,即一個串或者數字,從左,從右開始看,他都是對稱的,那麼兩種情況,一種是整個串(數字)有偶數位,那麼前半部分和後半部分是對應相等;如果是整個串(數字)有奇數位,那麼除了正中間的一位,前半部分和後半部分是對應相等。
所以要構造一個最長的迴文串,只需要統計出每個字母出現的次數,出現偶數次的直接全部可以拿來用;出現奇數次的,要麼是1,直接做正中的數字,要麼大於一的奇數,減去1個,就又變成偶數,全部可以拿來用。
這裡把奇數的情況一起考慮:都直接減去1,但是flag置1,表明已經有中間位了,如果再出現一個奇數,它只能加k-1位了。(額....實在想不到怎麼說清楚這裡)
max_length += hash_table[i] - 1;
flag = 1;
//最後
max_length += flag;
程式碼:
class Solution {
public:
int longestPalindrome(string s) {
int hash_table[128] = {0};
int max_length = 0, flag = 0;
for (auto c : s) {
hash_table[c]++;
}
for (int i = 0; i < 128; i++) {
if (hash_table[i] % 2 == 0) {
max_length += hash_table[i];
}
else {
max_length += hash_table[i] - 1;
flag = 1;
}
}
return max_length + flag;
}
};
290、單詞模式
思考:在字串最後新增一個" "來作為分隔符,簡化操作。
程式碼:
class Solution {
public:
bool wordPattern(string pattern, string str) {
string word;
map<string, char> word_map;
int pos = 0;
char used[128] = { 0 };
str.push_back(' ');
for (auto character : str) {
if (character == ' ') { /*說明已經提取了一個新單詞*/
if (pos == pattern.length()) {
return false;
}
if (word_map.find(word) == word_map.end()) {
if (used[pattern[pos]]) {
return false;
}
else {
//警告C4553“ == ”: 運算子不起任何作用;是否是有意使用“ = ”的 ?
/*word_map[word] == pattern[pos];
used[pattern[pos]] == 1;*/
word_map[word] = pattern[pos];
used[pattern[pos]] = 1;
}
}
else {
if (word_map[word] != pattern[pos]) {
return false;
}
}
//否則表示這個單詞是對應是正確的 ,繼續下一個就行了
word = "";
pos++;
}
else {
word += character;
}
}
if (pos != pattern.length()) {
return false;
}
return true;
}
};
49、字母異位詞分組
思考:字母異位的詞,說明只是單詞當中的順序改變了,把每個單詞sort排序,記得要用一個temp來儲存,否則就改變了讀取到的單詞,沒辦法儲存到對應的value之中。
在操作中用map<string,vector<string>>,避免指標的頭插,更為快捷方便。
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
map<string, vector<string>> hash_table;
vector<vector<string>> result;
for (auto item : strs) {
string strTemp = item;
sort(item.begin(), item.end());//sort之後會改變 所以需要儲存下來
//如果沒找到則說明要建立一組對映
if (hash_table.find(item) == hash_table.end()) {
vector<string> temp;
hash_table[item] = temp;
}
/*else {
hash_table[item].push_back(strTemp);
}*/
/*無論有沒有都要插入到vector之中*/
hash_table[item].push_back(strTemp);
}
for (auto it = hash_table.begin(); it != hash_table.end(); it++) {
result.push_back(it->second);
}
return result;
}
};
方法2:
void change_str_to_nums(string & str, vector<int>& nums) {
nums.assign(26, 0);
for (int i = 0; i < str.size(); i++) {
nums[str[i] - 'a'] ++;//因為單詞中有的字母可能重複出現,所以要統計的是每個字元出現的次數
}
}
class Solution {
public:
std::vector<std::vector<std::string> > groupAnagrams(std::vector<std::string>& strs) {
map<vector<int>, vector<string>> hash_table;
vector<vector<string>> result;
for (auto item : strs) {
vector<int> nums;
change_str_to_nums(item, nums);
if (hash_table.find(nums) == hash_table.end()) {
vector<string> temp;
hash_table[nums] = temp;
}
hash_table[nums].push_back(item);
}
for (auto it = hash_table.begin(); it != hash_table.end(); it++) {
result.push_back(it->second);
}
return result;
}
};
3、無重複字元的最長子串(滑動視窗的機制)
程式碼:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int begin = 0, result = 0;
string word;
char c_nums[128] = { 0 };
for (int i = 0; i < s.size(); i++) {
c_nums[s[i]]++;
if (c_nums[s[i]] == 1) {
word += s[i];
if (result < word.size()) {
result = word.size();
}
}
else {
while (begin < i && c_nums[s[i]] > 1) {
c_nums[s[begin]]--;
begin++;
}
//word = " "; 錯誤的原因!要把word 變成空,而不是弄成" "!這樣的話中間有個空格
// " wk" -> [0] = ' ' [1] = w [2] = k
word = "";
for (int j = begin; j <= i; j++) {
word += s[j];
}
}
}
return result;
}
};
187、重複的DNA序列
方法一:建立hash_table,遍歷整個序列,所有的10個字母長的序列都提取出來,然後輸出出現次數超過1的。
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
vector<string> result;
map<string, int> hash_table;
for (int i = 0; i < s.size(); i++) {
string str = s.substr(i,10); //substr(from,length) 從某處開始 擷取length長度的子串
if (hash_table.find(str) == hash_table.end()) {
hash_table[str] = 0;
}
hash_table[str]++;
}
for (auto item : hash_table) {
if (item.second > 1) {
result.push_back(item.first);
}
}
return result;
}
};
方法二:主要考察 位運算。因為是DNA序列,所以可以對其進行編碼。
字串 ------轉------ 整數
字串 -->設定給定字元到整數的轉換陣列 -->轉換成整數
int chage_char_to_nums(string &str) {
int char_map[128]{ 0 };
char_map['A'] = 0;
char_map['C'] = 1;
char_map['G'] = 2;
char_map['T'] = 3;
int key = 0;
for (int i = 9; i >= 0;i--) {
//key = key << 2 + char_map[str[i]];
key = (key << 2) + char_map[str[i]];
/*C4554 “<<”: 檢查運算子優先順序可能存在的錯誤;使用圓括號闡明優先順序*/
//加法的優先順序比“<<”高,所以執行的順序是2 + char_map[str[i]] ,然後key = 0
//向左移動 2 + char_map[str[i]]位,肯定還是0了!
}
return key;
}
遇到了一個優先順序的問題:一開始怎麼輸出key都是等於0!原來是優先順序的問題!
整數 ------轉------ 字串
NOTE:若兩個十進位制的數相與,暗自進行了轉換,轉換成2進位制,然後相與,得出結果。
如:3&5 即 0000 0011& 0000 0101 = 00000001 因此,3&5的值得1。
string DNA_to_string(int DNA) {
const char char_map[]{ 'A','C','G','T' };
string str;
for (int i = 0; i < 10;i++) {
str += char_map[(DNA & 3)];
// DNA >> 2;
DNA = DNA >> 2;
}
return str;
}
疑問:!一開始輸出是錯的 ,全部變成"AAAAAAAAAA",後來發現是隻進行移位運算,沒有更改變數DNA的值。
然後發現在編譯器中 2* 3 ,2 >>3,這種操作在除錯的時候都直接跳過,額...不知道為啥。。看看以後怎麼解決吧!
程式碼:
int g_hash_map[1048576]{0};
string DNA_to_string(int DNA);
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
vector<string> result;
int char_map[128]{ 0 };
if (s.size() < 10) {
return result;
}
for (int i = 0; i < 1048576; i++) {
g_hash_map[i] = 0;
}
char_map['A'] = 0;
char_map['C'] = 1;
char_map['G'] = 2;
char_map['T'] = 3;
int key = 0;
for (int i = 9; i >= 0; i--) {
//!!!!key = key << 2 + char_map[s[i]]; 蠢笨的企鵝!都說了! = =
key = (key << 2) + char_map[s[i]];
}
g_hash_map[key] ++;
for (int i = 10; i < s.size(); i++) {
key = key >> 2;
key = key | (char_map[s[i]] << 18);
g_hash_map[key]++;
}
for (int i = 0; i < 1048576;i++) {
if (g_hash_map[i] > 1 ) {
//result.push_back(DNA_to_string(g_hash_map[i]));
/*真是找了一萬年!---> key -》 value*/
result.push_back(DNA_to_string(i));
}
}
return result;
}
};
string DNA_to_string(int DNA) {
const char char_map[]{ 'A','C','G','T' };
string str;
for (int i = 0; i < 10;i++) {
str += char_map[(DNA & 3)];
DNA = DNA >> 2;
}
return str;
}
76、最小覆蓋子串
class Solution {
public:
string minWindow(string s, string t) {
const int MAX_ARRAY_LEN = 128;
int map_t[MAX_ARRAY_LEN] = { 0 };
int map_s[MAX_ARRAY_LEN] = { 0 };
std::vector<int> vec_t;
for (int i = 0; i < t.length(); i++) {
map_t[t[i]]++;
}
for (int i = 0; i < MAX_ARRAY_LEN; i++) {
if (map_t[i] > 0) {
vec_t.push_back(i);
}
}
string result;
int begin = 0;
for (int i = 0; i < s.size();i ++) {
map_s[s[i]]++;
//i 指標向前移動,檢測begin移動與否。
while (begin < i) {
char begin_char = s[begin];
//兩種情況下移動 begin指標
//1 begin字元不是我們需要的字元
//2 begin字元雖然是需要 但是當前字串出現該字元超過一個了
if (map_t[begin_char] == 0) {
begin++;
}
//else if (map_s[begin_char] > 1)
else if (map_s[begin_char] > map_t[begin_char]){
map_s[begin_char]--;
begin++;
}
else {
break;
}
}
if(is_window_ok(map_s, map_t, vec_t)){
int length = i - begin + 1;
if (result == "" || length < result.size()) {
result = s.substr(begin, length);
}
}
}
return result;
}
private:
bool is_window_ok(int map_s[], int map_t[], std::vector<int> &vec_t) {
for (int i = 0; i < vec_t.size(); i++) {
if (map_s[vec_t[i]] < map_t[vec_t[i]]) {
return false;
}
}
return true;
}
};