当前位置:   article > 正文

c++ 哈希表find_Leecode刷题---哈希表与字符串

哈希表find函数

哈希表,Hash table,也叫散列表,是根据关键字值(key)直接进行访问的数据结构,它通过把关键字值映射到表中一个位置(数组下标)来直接访问,以加快查找关键字值的速度,这个映射函数叫做哈希函数,存放记录的数组叫做哈希表

给定表M,存在函数f,对任意的关键字值key,带入函数后若能得到包含该关键字的表中地址,称表M为哈希(Hash)表,函数(key)为哈希函数。

预备知识1:最简单的哈希--字符哈希

统计字符型中的字符个数

  1. #include<stdio.h>
  2. #include<string>
  3. int main()
  4. {
  5. int char_map[128] = {0}; //ASCII码共有128个字符
  6. std::string str = "abcdefgaaxxy";
  7. for(int i = 0;i<str.length();i++)
  8. {
  9. char_map[str[i]]++;
  10. }
  11. for(int i = 0;i<128;i++)
  12. {
  13. if(char_map[i] > 0)
  14. {
  15. printf("[%c][%d]: %dn",i,i,char_map[i]);
  16. }
  17. }
  18. return 0;
  19. }

6c3d8b431e43f52e8c7e500db60e0946.png

预备知识2 :哈希表排序整数

  1. #include<stdio.h>
  2. #include<string>
  3. int main()
  4. {
  5. int random[10] = {999,1,444,7,20,9,1,3,7,7};
  6. int hash_map[1000]={0};
  7. for(int i =0;i<10;i++)
  8. {
  9. hash_map[random[i]]++;
  10. }
  11. for(int i = 0;i<1000;i++)
  12. {
  13. for(int j = 0;j<hash_map[i];j++)
  14. {
  15. printf("%dn",i);
  16. }
  17. }
  18. return 0;
  19. }

输出:

a874934c90c265ceaddebc999dc29989.png

思考:

1.当遇到负数,或非常大的整数,如何进行哈希

2当遇到字符串,如何进行哈希

3.当遇到其他无法直接映射的数据类型,如浮点数,数组,对象等等,如何进行哈希(映射)?

解决:利用哈希函数,将key(大整数,负数,浮点数)转换为整数再对表长取余,从而key转换为关键字被转换为哈希表的表长范围内的整数。

问题引入2,发生冲突:不同的整数或者字符串,由于哈希函数的选择,被映射到了同一个下标下;

  1. #include<stdio.h>
  2. #include<string>
  3. int int_func(int key,int table_len)
  4. {
  5. return key % table_len;
  6. }
  7. int string_func(std::string key,int table_len)
  8. {
  9. int sum = 0;
  10. for(int i = 0;i<key.length();i++)
  11. {
  12. sum += key[i];
  13. }
  14. return sum % table_len;
  15. }
  16. int main()
  17. {
  18. const int TABLE_LEN = 10;
  19. int hash_map[TABLE_LEN] = {0};
  20. hash_map[int_func(99999995,TABLE_LEN)]++;
  21. hash_map[int_func(5,TABLE_LEN)]++;
  22. hash_map[string_func("abc",TABLE_LEN)]++;
  23. hash_map[string_func("bac",TABLE_LEN)]++;
  24. for(int i =0;i<TABLE_LEN;i++)
  25. {
  26. printf("hash_map[%d] = %dn",i,hash_map[i]);
  27. }
  28. return 0;
  29. }

输出

1faf312bbaa73abc74eb499194c1dc90.png

预备知识:拉链法解决冲突,构造哈希表

将所有哈希表结果相同的结点连接在同一个单链表中。

若选定的哈希表长度为m,则可将哈希表定义一个长度为m的指针数组t[0,...m-1],指针数组中的每个指针指向哈希函数结果相同的单链表

插入value:

将元素value插入哈希表,若元素value的哈希函数值为hash_key,将value对应的节点以头插法的方法插入到t[hash_key]为头指针的单链表中

查找value:

若元素value的哈希函数值为hash_key,遍历以t[hash_key]为头指针的单链表,查找链表各个节点的值域是否为value

df155d9969ab0a36f40a3d43edd5477a.png
  1. #include<stdio.h>
  2. struct ListNode
  3. {
  4. int val;
  5. ListNode *next;
  6. ListNode(int x):val(x),next(NULL) {}
  7. };
  8. int hash_func(int key,int table_len)
  9. {
  10. return key % table_len;
  11. }
  12. //使用头结点进行插入
  13. void insert(ListNode *hash_table[],ListNode *node, int table_len)
  14. {
  15. int hash_key = hash_func(node->val,table_len);
  16. node->next = hash_table[hash_key];
  17. hash_table[hash_key] = node;
  18. }
  19. bool search(ListNode *hash_table[],int value,int table_len)
  20. {
  21. int hash_key = hash_func(value,table_len);
  22. ListNode *head = hash_table[hash_key];
  23. while(head)
  24. {
  25. if(head->val==value)
  26. {
  27. return true;
  28. }
  29. head=head->next;
  30. }
  31. return false;
  32. }

测试一下

  1. int main()
  2. {
  3. const int TABLE_LEN = 11; //hash表长度尽量为质数;
  4. ListNode *hash_table[TABLE_LEN]={0};
  5. std::vector<ListNode *> hash_node_vec;
  6. int test[8]={1,1,4,9,20,30,150,500};
  7. for(int i = 0;i<8;i++)
  8. {
  9. hash_node_vec.push_back(new ListNode(test[i]));
  10. }
  11. for(int i = 0;i<hash_node_vec.size();i++)
  12. {
  13. insert(hash_table,hash_node_vec[i],TABLE_LEN);
  14. }
  15. printf("HashTable:n");
  16. for(int i = 0;i<TABLE_LEN;i++)
  17. {
  18. printf("[%d]:",i);
  19. ListNode *head = hash_table[i];
  20. while(head)
  21. {
  22. printf("->%d",head->val);
  23. head=head->next;
  24. }
  25. printf("n");
  26. }
  27. printf("n");
  28. printf("Test search:n");
  29. for(int i = 0;i<10;i++)
  30. {
  31. if(search(hash_table,i,TABLE_LEN))
  32. {
  33. printf("%d is in the hash table.n");
  34. }
  35. else
  36. {
  37. printf("%d is not in the hash table.n");
  38. }
  39. }
  40. return 0;
  41. }

734413b5ed9ef6b95905c3ad5d8d727b.png

预备知识3:哈希map与STL map

  1. #include<stdio.h>
  2. #include<string>
  3. #include<map>
  4. struct ListNode
  5. {
  6. std::string key;
  7. int val;
  8. ListNode *next;
  9. ListNode(int x):val(x),next(NULL) {}
  10. };
  11. int main()
  12. {
  13. std::map<std::string,int> hash_map;
  14. std::string str1 = "abc";
  15. std::string str2 = "aaa";
  16. std::string str3 = "xxxxx";
  17. hash_map[str1] = 1;
  18. hash_map[str2] = 2;
  19. hash_map[str3] = 100;
  20. if(hash_map.find(str1) != hash_map.end()) //c_str:返回当前字符串的首字符地址
  21. {
  22. printf("%s is in hash_map,value is %dn",str1.c_str(),hash_map[str1]);
  23. }
  24. std::map<std::string,int> :: iterator it;
  25. for(it = hash_map.begin();it != hash_map.end();it++)
  26. {
  27. printf("hash_map[%s] = %dn",it->first.c_str(),it->second);
  28. }
  29. }

输出:

b746e54d2985d9430ad757f3b58b89fa.png

leecode刷题

409 最长回文串

给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。

在构造过程中,请注意区分大小写。比如 "Aa" 不能当做一个回文字符串。

注意:

假设字符串的长度不会超过 1010。

示例 1:

输入:

"abccccdd"

输出:

7

解释:

我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。

来源:力扣(LeetCode)

链接:力扣

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思考:

1.查找出字符中所有字符的个数

2.回文字符串要分字符数量为偶数和奇数两种情况

偶数:

全部都会用上,头部放一个字符,尾部就对应放一个

奇数:丢掉一个字符,剩下的字符数为偶数个,按照字符数量为偶数的字符处理

随机选择一个字符当作中心字符

算法思路:

1.利用字符哈希方法,统计字符串中所有的字符数量;

2.设置最长回文串偶数字符长度max_length=0;

3.设置是否有中心点标记 flag = 0;

4.遍历每一个字符,字符数为count,若count为偶数,max_length += count;若count为奇数,max_length += count - 1, flag = 1;

5.最终最长回文子串长度:max_length + flag

  1. class Solution {
  2. public:
  3. int longestPalindrome(string s) {
  4. int char_map[128]={0};
  5. int max_length = 0;
  6. int flag = 0;
  7. for(int i=0;i<s.length();i++)
  8. {
  9. char_map[s[i]]++;
  10. }
  11. for(int i =0;i<128;i++)
  12. {
  13. if(char_map[i]%2==0)
  14. {
  15. max_length +=char_map[i];
  16. }
  17. else
  18. {
  19. max_length+=char_map[i]-1;
  20. flag =1;
  21. }
  22. }
  23. return max_length +flag;
  24. }
  25. };

290:词语模式

给定一种规律 pattern 和一个字符串 str ,判断 str 是否遵循相同的规律。

这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应规律。

示例1:

输入: pattern = "abba", str = "dog cat cat dog"

输出: true

示例 2:

输入:pattern = "abba", str = "dog cat cat fish"

输出: false

示例 3:

输入: pattern = "aaaa", str = "dog cat cat dog"

输出: false

示例 4:

输入: pattern = "abba", str = "dog dog dog dog"

输出: false

来源:力扣(LeetCode)

链接:力扣

分析:1,当拆解出一个单词时,若该单词已出现,则当前单词对应的pattern字符必为该单词曾经对应的pattern字符;

2.当拆解出一个单词时,若该单词未曾出现,则当前单词对应得到pattern字符也必须未曾出现。

3.单词的个数与pattern字符串中出现的字符数量相同

算法思路:

若pattern=“abb?”,string="dog cat dog *"

1.设置单词(字符串)到pattern字符中的映射(哈希);使用数组used[128]记录pattern字符串是否使用过;

2.遍历str,按照空格拆分单词,同时对应的向前移动指向pattern字符的指针,每拆分出一个单词,判断:

如果该单词从未出现在哈希表中:

如果当前pattern已经被使用,返回false;

否则将单词与所指向的pattern做映射;标记pattern已经被使用;

否则:

如果当前单词在哈希表中的映射字符不是当前指向的pattern字符,则返回false;

3.若单词个数与pattern字符个数不匹配,返回false

  1. class Solution {
  2. public:
  3. bool wordPattern(string pattern, string str) {
  4. std::map<std::string,char> word_map; //单词到字符的映射
  5. char used[128]={0};
  6. std::string word;//临时保存的单词;
  7. int pos = 0;//当前指向pattern的字符
  8. str.push_back(' ');
  9. for(int i =0;i<str.length();i++)
  10. {
  11. if(str[i]==' ') //拆分出一个字符
  12. {
  13. if( pos==pattern.length())
  14. {
  15. return false;
  16. }
  17. if(word_map.find(word) == word_map.end())
  18. {
  19. if(used[pattern[pos]])
  20. {
  21. return false;
  22. }
  23. word_map[word]=pattern[pos];
  24. used[pattern[pos]]=1;
  25. }
  26. else
  27. {
  28. if(word_map[word]!=pattern[pos])
  29. {
  30. return false;
  31. }
  32. }
  33. word="";
  34. pos++;
  35. }
  36. else
  37. {
  38. word+=str[i];
  39. }
  40. }
  41. if(pos!=pattern.length())
  42. {
  43. return false;
  44. }
  45. return true;
  46. }
  47. };

49.同字符词语分组

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。示例:

输入: ["eat", "tea", "tan", "ate", "nat", "bat"]

输出:[

["ate","eat","tea"],

["nat","tan"],

["bat"]

]

来源:力扣(LeetCode)

链接:力扣

思考:anagram分组,若某两个字符型,出现的各个字符串数相同,则他们应该为同一分组。

哈希表以内部进行排序的各个单词为key,以字符串向量(vetor<string>)为value,存储各个字符数量相同的字符串(anagram)

算法思路:

设置字符串到字符串向量的哈希表anagram,遍历字符串向量strs中的单词strs[i];

(1)设置临时遍历str = strs[i],对str进行排序;

(2)若str未出现在anagram中,设置str到一个空字符串向量的映射。

(3)将strs[i]添加至字符串向量anagram[str]中。

遍历哈希表anagram,将全部key对应的value push至最终结果中。

  1. class Solution {
  2. public:
  3. vector<vector<string>> groupAnagrams(vector<string>& strs) {
  4. std::map<std::string,std::vector<std::string>> anagram;
  5. std::vector<std::vector<std::string>> result;
  6. for(int i = 0; i<strs.size();i++)
  7. {
  8. std::string str = strs[i];
  9. std::sort(str.begin(),str.end());
  10. if(anagram.find(str) == anagram.end())
  11. {
  12. std::vector<std::string> item;
  13. anagram[str] = item;
  14. }
  15. anagram[str].push_back(strs[i]);
  16. }
  17. std::map<std::string,std::vector<std::string>> ::iterator it;
  18. for(it = anagram.begin();it != anagram.end(); it++)
  19. {
  20. result.push_back((*it).second);
  21. }
  22. return result;
  23. }
  24. };

方法二:

哈希表以26个字母的字符数量(一个长度为26的vector,统计单词中的各个字符的数量)为key,以字符串向量(vector<string>)为value,存储各个字符数量相同的字符串(anagram)

设置vector到字符串向量的哈希表anagram,遍历字符串向量strs中的单词strs[i]:

(1)统计strs[i]中的各个字符数量,存储至vec。

(2)若vec未出现在anagram中,设置vec到一个空字符串向量的映射。

(3)将str[i]添加至字符串向量anagram[vec]中。

遍历哈希表anagranm,将全部key对应的value,push至最终结果中。

  1. void change_to_vector(std::string &str,std::vector<int> &vec)
  2. {
  3. for(int i = 0;i<26;i++)
  4. {
  5. vec.push_back(0);
  6. }
  7. for(int i = 0; i<str.length();i++)
  8. {
  9. vec[str[i]-'a']++;
  10. }
  11. }
  12. class Solution {
  13. public:
  14. vector<vector<string>> groupAnagrams(vector<string>& strs) {
  15. std::map<std::vector<int>,std::vector<std::string>> anagram;
  16. std::vector<std::vector<std::string>> results;
  17. for(int i = 0; i<strs.size(); i++)
  18. {
  19. std::vector<int> vec;
  20. change_to_vector(strs[i],vec);
  21. if(anagram.find(vec) == anagram.end())
  22. {
  23. std::vector<std::string> item;
  24. anagram[vec] = item;
  25. }
  26. anagram[vec].push_back(strs[i]);
  27. }
  28. std::map<std::vector<int>,std::vector<std::string> > ::iterator it;
  29. for(it = anagram.begin();it!=anagram.end();it++)
  30. {
  31. results.push_back((*it).second);
  32. }
  33. return results;
  34. }
  35. };

3.无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"

输出: 3

解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: "bbbbb"

输出: 1

解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: "pwwkew"

输出: 3

解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。

请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

来源:力扣(LeetCode)

链接:力扣

双指针扫描字符串方法:

1.设置一个记录字符数量的字符哈希,char_map;

2.设置一个记录当前满足条件的最长子串变量word;

3.设置两个指针(记作指针i与指针begin)指向字符串第一个字符;

4.设置最长满足条件的子串的长度result;

5.i指针向后逐个扫描字符串中的字符,在这个过程中,使用char_map记录字符数量

如果word中没有出现过该字符:对word尾部添加字符并检查result是否需要更新;

否则:begin指针向前移动,更新char_map中的字符数量,直到字符s[i]的数量为i;更新word,将word赋值为begin与i之间的子串。

在整个过程中使用begin与i维护一个窗口,该窗口中的子串满足题目条件(无重复的字符),窗口线性向前滑动,整体时间复杂度为Q(n)。

  1. class Solution {
  2. public:
  3. int lengthOfLongestSubstring(string s) {
  4. int begin = 0;
  5. int result = 0;
  6. std::string word = "";
  7. int char_map[128] = {0};
  8. for(int i = 0;i<s.length();i++)
  9. {
  10. char_map[s[i]]++;
  11. if(char_map[s[i]] == 1)
  12. {
  13. word+=s[i];
  14. if(result < word.length())
  15. {
  16. result = word.length();
  17. }
  18. }
  19. else
  20. {
  21. while(begin <i && char_map[s[i]]>1)
  22. {
  23. char_map[s[begin]]--;
  24. begin++;
  25. }
  26. word = "";
  27. for(int j = begin; j<=i; j++)
  28. {
  29. word +=s[j];
  30. }
  31. }
  32. }
  33. return result;
  34. }
  35. };

187,重复的DNA序列

所有 DNA 都由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。

编写一个函数来查找 DNA 分子中所有出现超过一次的 10 个字母长的序列(子串)。

示例:

输入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"

输出:["AAAAACCCCC", "CCCCCAAAAA"]

来源:力扣(LeetCode)

链接:力扣

算法思路:

枚举法
枚举DNA字符串的所有长度为10的子串,将其插入到哈希Map中,并记录子串数量;遍历哈希map,将所有出现超过1次的子串存储到结果中,算法复杂度O(n)。

  1. class Solution {
  2. public:
  3. vector<string> findRepeatedDnaSequences(string s) {
  4. std::map<std::string,int> word_map;
  5. std::vector<std::string> result;
  6. for(int i = 0;i<s.length();i++)
  7. {
  8. std::string word = s.substr(i,10); 获得字符串s中从第i位开始的长度为10的字符串
  9. if(word_map.find(word)!=word_map.end())
  10. {
  11. word_map[word]+=1;
  12. }
  13. else
  14. {
  15. word_map[word] =1;
  16. }
  17. }
  18. std::map<std::string,int>::iterator it;
  19. for(it = word_map.begin();it!=word_map.end();it++)
  20. {
  21. if(it->second>1){
  22. result.push_back(it->first);
  23. }
  24. }
  25. return result;
  26. }
  27. };

方法二:将长度为10的DNA序列进行整数编码;

['A','C','G','T']4个字符串分别用[0,1,2,3](二进制形式00,01,10,11)所表示,故长度为10的DNA序列可以用20个比特位的整数所表示

1.设置全局整数哈希int g_hash_map[1048576];(2^20=1048576),表示所有的长度为10的DNA序列。

2,将DNA字符串的前10个字符使用左移位运算转换为整数key,g_hash_map[key]++。

3.从DNA的第11个字符开始,按顺序遍历各个字符,遇到1个字符即将key右移2位(去掉最低位),并且将新的DNA字符s[i]转换为整数后,或到最高位(第19,20位),g_hash_map[key]++。

4.遍历哈希表g_hash_map,若g_hash_map[i]>1,将i从低位到高位转换为10个字符的DNA序列,push至结果数组

76.最小窗口子串

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。

示例:

输入: S = "ADOBECODEBANC", T = "ABC"

输出: "BANC"

说明:

如果 S 中不存这样的子串,则返回空字符串 ""。

如果 S 中存在这样的子串,我们保证它是唯一的答案。

来源:力扣(LeetCode)

链接:力扣

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思考:枚举法:枚举S的所有子字符串,检查字符串中是否包含字符串T=“ABC”

预备知识:

  1. #include<stdio.h>
  2. #include<vector>
  3. #include<string>
  4. bool is_window_ok(int map_s[],int map_t[],std::vector<int> &vec_t)
  5. {
  6. for(int i = 0; i<vec_t.size();i++)
  7. {
  8. if(map_s[vec_t[i]] < map_t[vec_t[i]])
  9. {
  10. return false;
  11. }
  12. }
  13. return true;
  14. }
  15. int main()
  16. {
  17. std::string s="ADOBECODEBANC";
  18. std::string t = "ABCDAB";
  19. const int MAX_ARRAY_LEN = 128;
  20. int map_t[MAX_ARRAY_LEN]={0};
  21. int map_s[MAX_ARRAY_LEN]={0};
  22. std::vector<int> vec_t;
  23. for(int i = 0;i<s.length();i++)
  24. {
  25. map_s[s[i]]++;
  26. }
  27. for(int i = 0;i<t.length();i++)
  28. {
  29. map_t[t[i]]++;
  30. }
  31. for(int i = 0;i<MAX_ARRAY_LEN;i++)
  32. {
  33. if(map_t[i]>0)
  34. {
  35. vec_t.push_back(i);
  36. }
  37. }
  38. }

1.设置两个字符哈希数组,map_s与map_t,map_s代表当前处理窗口区间中的字符数量,map_t代表子串T的字符数量

2.设置两个指针(记作指针i与指针begin)指向字符串第一个字符;

3.i指针向后逐个扫描字符串中的字符,在这个过程中,循环检查begin指针是否可以向前移动:

如果当前begin指向的字符T中没出现,直接前移begin;

如果begin指向的字符T中出现了,但是当前区间窗口中的该字符数量足够,向前移动begin,并更新map_s;

否则不能移动begin,跳出检查。

4.指针i每向前扫描一个字符,即检查一下是否可以更新最终结果(找到最小的包含T中各个字符的窗口)。

在整个过程中使用begin与i维护一个窗口,该窗口中的子串满足题目条件,窗口线性向前滑动,整体时间复杂度为O(n)

  1. class Solution {
  2. private:
  3. bool is_window_ok(int map_s[],int map_t[],std::vector<int> &vec_t)
  4. {
  5. for(int i = 0; i<vec_t.size();i++)
  6. {
  7. if(map_s[vec_t[i]] < map_t[vec_t[i]])
  8. {
  9. return false;
  10. }
  11. }
  12. return true;
  13. }
  14. public:
  15. string minWindow(string s, string t) {
  16. const int MAX_ARRAY_LEN = 128;
  17. int map_t[MAX_ARRAY_LEN]={0};
  18. int map_s[MAX_ARRAY_LEN]={0};
  19. std::vector<int> vec_t;
  20. for(int i = 0;i<t.length();i++)
  21. {
  22. map_t[t[i]]++;
  23. }
  24. for(int i = 0;i<MAX_ARRAY_LEN;i++)
  25. {
  26. if(map_t[i]>0)
  27. {
  28. vec_t.push_back(i);
  29. }
  30. }
  31. int window_begin = 0;
  32. std::string result;
  33. for(int i =0;i<s.length();i++)
  34. {
  35. map_s[s[i]]++;
  36. while(window_begin<i)
  37. {
  38. char begin_ch = s[window_begin];
  39. if(map_t[begin_ch]==0)
  40. {
  41. window_begin++;
  42. }
  43. else if(map_s[begin_ch]>map_t[begin_ch])
  44. {
  45. //更新map_s
  46. map_s[begin_ch]--;
  47. window_begin++;
  48. }
  49. else{
  50. break;
  51. }
  52. }
  53. if(is_window_ok(map_s,map_t,vec_t))
  54. {
  55. int new_window_len = i - window_begin+1;
  56. if(result==""||result.length()>new_window_len )
  57. result = s.substr(window_begin,new_window_len);
  58. }
  59. }
  60. return result;
  61. }
  62. };
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/227422
推荐阅读
相关标签
  

闽ICP备14008679号