1. 程式人生 > >七、雜湊表與字串(小象)

七、雜湊表與字串(小象)

目錄

 

雜湊表基礎知識

雜湊表定義

1、字元雜湊

2、雜湊表排序整數

3、拉鍊表解決衝突,構造雜湊表

4、STL map中的常用操作

409、最長迴文串

290、單詞模式

49、字母異位詞分組

3、無重複字元的最長子串(滑動視窗的機制)

187、重複的DNA序列

76、最小覆蓋子串

雜湊表基礎知識

雜湊表定義

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;
	}
};