目录
哈希表基础知识
哈希表定义
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));
/*4插入{类型1,类型2}*/
hash_map.insert({"zq",1});
//在哈希表中查找某关键字,没有找到
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;
}
};