预备知识:

1、最简单的哈希-----字符哈希
#include<stdio.h>
#include<string>
#include<stdlib.h>
using namespace std;
int main(){
int char_map[128] = {0};//ASCⅡ码从0-127,故用数组下标做映射,最大范围至128
string str="snaxjnjsxngdpq";
for(int i=0;i<str.length();i++){
char_map[str[i]]++;//统计字符串中各个字符的数量
}
for(int i=0;i<128;i++){
if(char_map[i]>0)
printf("[%c][%d]: %d\n",i,i,char_map[i]);//将字符对应的ASCⅡ码及其个数打印出来
}
system("pause");
return 0;
}

2、哈希表排序整数
#include<stdio.h>
#include<stdlib.h>
using namespace std;
int main(){
int random[10] = {3,456,78,43,124,789,908,78,3,124};
int hash_map[1000] = {0};//哈希表长度需要超过最大排序数
for(int i=0;i<10;i++){
hash_map[random[i]]++;//统计字符串中各个字符的数量
}
for(int i=0;i<1000;i++){
//从小到大查询hash表中是否出现该数字,若出现则全部打出,打印结果就为排序后de
for(int j = 0;j < hash_map[i];j++){
printf("%d\n",i);
}
}
system("pause");
return 0;
}

问题1:如何利用哈希表实现对任意字符的映射?

问题2:利用上述方式映射发生冲突怎么办?
用拉链法解决

#include<stdio.h>
#include<stdlib.h>
#include<vector>
using namespace std;
struct ListNode{
int val;
ListNode *next;
ListNode(int x):val(x),next(NULL){}
};
int hash_func(int key, int table_len){
return key%table_len;
}
void insert(ListNode *hash_table[],ListNode *node, int table_len){
int hash_key = hash_func(node->val,table_len);//将插入节点的值转换为hash关键值
node->next = hash_table[hash_key];//利用头插法将新的节点插入对应哈希值位置
hash_table[hash_key] = node;
}
bool search(ListNode *hash_table[],int value,int table_len){//查找value值
int hash_key = hash_func(value,table_len);
ListNode *head = hash_table[hash_key];
while(head){
if(head->val == value)
return true;
head=head->next;
}
return false;
}
int main(){
const int TABLE_LEN = 11;//!!!去质数冲突会比较小
ListNode *hash_table[TABLE_LEN]={0};
vector<ListNode*> hash_node_vec;
int test[8] = {14,17,404,109,9,6,7,81};//测试数组
for(int i=0;i<8;i++) //以此生成相应节点并将测试数组的值存进数组中
hash_node_vec.push_back(new ListNode(test[i]));
for(int i=0;i<hash_node_vec.size();i++)
insert(hash_table,hash_node_vec[i],TABLE_LEN);//将节点插入哈希表
printf("Hash table:\n");
for(int i=0;i<TABLE_LEN;i++){//打印哈希表
printf("[%d]",i);
ListNode* head=hash_table[i];
while(head){
printf("->%d",head->val);
head=head->next;
}
printf("\n");
}
printf("\n");
printf("Test search:\n");
for(int i=0;i<10;i++){//查找相应的值在不在表中
if(search(hash_table,i,TABLE_LEN))
printf("%d is in the hash table.\n",i);
else
printf("%d is not in the hash table.\n",i);
}
system("pause");
return 0;
}

一、最长回文串 LeetCode409
题目:
给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。
在构造过程中,请注意区分大小写。比如 “Aa” 不能当做一个回文字符串。
注意:
假设字符串的长度不会超过 1010。
示例 1:
输入:
“abccccdd”
输出:
7
解释:
我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
思路:

class Solution {
public:
int longestPalindrome(string s) {
int char_map[128]={0};//哈希表
int char_length = 0;//回文串长度
int flag = 0;//有无中心字符
for(int i=0;i<s.length();i++){
char_map[s[i]]++;//统计每个字符出现的次数
}
for(int i=0;i<128;i++){
if(char_map[i]%2==0)
char_length +=char_map[i];
else{
char_length +=char_map[i]-1;
flag = 1;//有中心字符
}
}
return char_length+flag;
}
};
**二、单词模式 LeetCode290 **
题目:
给定一种 pattern(模式) 和一个字符串 str ,判断 str 是否遵循相同的模式。
这里的遵循指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应模式。
示例1:
输入: pattern = “abba”, str = “dog cat cat dog”
输出: true
示例 2:
输入:pattern = “abba”, str = “dog cat cat fish”
输出: false

class Solution {
public:
bool wordPattern(string pattern, string str) {
map<string, char> word_map;//单词到pattern的映射
char used[128] = {0};//已经被映射的pattern字符
string word;//被拆分出来的单词
int pos = 0;//当前指向的pattern字符
str.push_back(' ');//尾部输入一个空格,最后一个单词就不用特殊处理了
for(int i=0;i<str.length();i++){
if(str[i]==' '){//遇到空格则拆分出新单词了
if(pos==pattern.length()){//如果pattern已经没有字符与之对应了,返回false
return false;
}
if(word_map.find(word) == word_map.end()){
//如果该单词从未出现,但是对应字符已经被用返回false
if(used[pattern[pos]])
return false;
else{//如果对应字符没出现,则将其余相应单词对应,并且在used数组中记录
word_map[word] = pattern[pos];
used[pattern[pos]] = 1;
}
}
else{//之前已经出现了,但是字符串对应不上
if(word_map[word]!=pattern[pos])
return false;
}
word="";//处理完一个单词word置为空
pos++;//字符移向下一个位置
}
else //没有拆分出新单词
word += str[i];
}
if(pos!=pattern.length())//如果字符串已经遍历完了,pos还没到结尾就说明字符比单词多
return false;
return true;
}
};
三、字母异位词分组 LeetCode 49
题目:
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”],
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]
思路:
方法I,设计一个哈希表以内部进行排序后的各个单词为key,以字符串向量为value,存储各个字符数量相同的字符串

方法1思路

class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
vector<vector<string>> result;//用来存储结果
map<string,vector<string>> anagram;//排序后的各个单词为key,字符串向量为value,对应存储在anagram中
for(int i= 0;i< strs.size();i++){//遍历各个单词
string str = strs[i];//遍历各个单词
sort(str.begin(),str.end());//将str中的字符排序
if(anagram.find(str) == anagram.end()){//如果anagram中没有找到的话
vector<string> item;//设置一个空的字符向量,来存储这类字符
anagram[str] = item;//将key值与存储空间对应起来
}
anagram[str].push_back(strs[i]);//将字符串的值push进来
}
//建立一个与anagram同类型的迭代器,遍历输出结果
map<string,vector<string>>::iterator it;
for(it = anagram.begin();it!=anagram.end();it++){
result.push_back((*it).second);
}
return result;
}
};
方法2:

思路:

void change_to_vector(string &str,vector<int> &vec){
for(int i=0;i<26;i++)
vec.push_back(0);
for(int i=0;i<str.length();i++){//将str中字母个数进行统计
vec[str[i]-'a']++;
}
}
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
vector<vector<string>> result;//用来存储结果
map<vector<int>,vector<string>> anagram;//排序后的各个单词为key,字符串向量为value,对应存储在anagram中
for(int i= 0;i< strs.size();i++){//遍历各个单词
vector<int> vec;//用来存储单词中字母出现的数量
change_to_vector(strs[i],vec);
if(anagram.find(vec) == anagram.end()){//如果anagram中没有找同样数量的词
vector<string> item;//设置一个空的字符向量,来存储这类字符
anagram[vec] = item;//将key值与存储空间对应起来
}
anagram[vec].push_back(strs[i]);//将字符串的值push进来
}
//建立一个与anagram同类型的迭代器,遍历输出结果
map<vector<int>,vector<string>>::iterator it;
for(it = anagram.begin();it!=anagram.end();it++){
result.push_back((*it).second);
}
return result;
}
};
四、无重复字符的最长子串 LeetCode 3
题目:
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
思路:

class Solution {
public:
int lengthOfLongestSubstring(string s) {
int begin=0;//单词开始的字符
int i=0;//扫描字符串
int result =0;//不含重复字符的最长子串
int char_map[128]={0};//哈希表
string word ="";//用来存子串
for(;i<s.length();i++){
char_map[s[i]]++;//在表中记录相应字符出现的次数
if(char_map[s[i]]==1){
//无重复字符
word+=s[i];//更新子串长度
if(result<word.length())
//如果结果小于word长度更新result
result = word.length();
}
else{
//有重复字符就从begin开始删字符,直到没有重复字符,并且begin不能超过i
while(char_map[s[i]]>1 && begin<i){
char_map[s[begin]]--;
begin++;
}
//再重新更新word
word="";
for(int j=begin;j<=i;j++)//!!!不能忘了等号否则最后一个字母会记录不到
word+=s[j];
}
}
return result;
}
};
五、重复的DNA序列 LeetCode187
题目:
所有 DNA 由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。
编写一个函数来查找 DNA 分子中所有出现超过一次的10个字母长的序列(子串)。
示例:
输入: s = “AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT”
输出: [“AAAAACCCCC”, “CCCCCAAAAA”]
思路1:
因为子串的数量并不大,只有10,可以直接利用枚举的方式。
枚举DNA字符串中所有长度为10的子串,将其插入到哈希map中,记录子串数量;遍历哈希map,将所有子串存储到结果中。算法复杂度O(n).
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
map<string,int> word_map;//<子串,子串数量>的映射
vector<string> result;//满足条件的子串
for(int i=0;i<s.length();i++){
string word=s.substr(i,10);//让子串等于s的第i个到其后面的10个字符
if(word_map.find(word)!=word_map.end())//如果该子串出现过
word_map[word]++;
else //没出现过
word_map[word]=1;
}
map<string,int>::iterator it;
for(it=word_map.begin();it!=word_map.end();it++){//遍历哈希map
if(it->second>1)//如果子串出现超过1
result.push_back(it->first);
}
return result;
}
};
思路2:
计算机为大端存储
检测计算机为大端存储还是小端存储的方法:
short x;
char x0;
x=0x1122;
x0=((char*)&x)[0];
if(x0==0x11)
printf("da\n");
else
printf("xiao\n");
将长度为10的DNA序列进行整数编码:ACGT,分别用0123的二进制表示,故长度为10的DNA序列可以用20个比特位的整数所表示。

大端存储
using namespace std;
int g_hash_map[1048576] = {0};
string change_int_to_DNA(int DNA){
static const char DNA_CHAR[]={'A','C','G','T'};
string str;
for(int i = 0; i < 10;i++){
str += DNA_CHAR[DNA&3];//长度为10的片段,冲整型转换成字符
DNA = DNA>>2;
}
return str;
}
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s){
vector<string> result;
if(s.length() < 10){
return result;
}
for(int i=0; i < 1048576;i++){//每次调用时更新全局哈希数组
g_hash_map[i] = 0;
}
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--){//将DNA字符串前十个字符转化成相应的关键字
key = (key<<2)+char_map[s[i]];
}
g_hash_map[key] = 1;//并记录出现的字符
for(int i=10; i < s.length();i++){//遍历后面的字符,将其放进高位,并在哈希数组中记录
key = key>>2;
key = key|(char_map[s[i]]<<18);
g_hash_map[key]++;
}
for(int i=0;i<1048576;i++){//扫描哈希数组,出现次数大于1的放进结果中
if(g_hash_map[i]>1)
result.push_back(change_int_to_DNA(i));
}
return result;
}
};
小端存储
#include <stdio.h>
#include <vector>
#include <string>
#include <stdlib.h>
#include <iostream>
using namespace std;
int g_hash_map[1048576] = {0};
string change_int_to_DNA(int DNA){
static const char DNA_CHAR[]={'A','C','G','T'};
string str;
for(int i = 9; i >= 0;i--){
str += DNA_CHAR[(DNA&(3<<(2*i)))>>(2*i)];//长度为10的片段,冲整型转换成字符
//DNA = DNA>>2;
}
return str;
}
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s){
vector<string> result;
if(s.length() < 10){
return result;
}
for(int i=0; i < 1048576;i++){//每次调用时更新全局哈希数组
g_hash_map[i] = 0;
}
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 = 0;i<=9;i++){//将DNA字符串前十个字符转化成相应的关键字
key = (key<<2)+char_map[s[i]];
}
g_hash_map[key] = 1;//并记录出现的字符
for(int i=10; i < s.length();i++){//遍历后面的字符,将其放进高位,并在哈希数组中记录
key = (key<<2)&(~(3<<20));
key = key + char_map[s[i]];
//key = key|char_map[s[i]];
g_hash_map[key]++;
}
for(int i=0;i<1048576;i++){//扫描哈希数组,出现次数大于1的放进结果中
if(g_hash_map[i] > 1)
result.push_back(change_int_to_DNA(i));
}
return result;
}
};
void main()
{
string s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT";
Solution solve;
vector<string> res=solve.findRepeatedDnaSequences(s);
for(int i=0;i < res.size();i++)
cout<<res[i]<<'\n';//printf("%s\n",result[i].c_str());
cout<<endl;
system("pause");
}
!!!只要知道字符存储的方式是大端就行了,计算机的大小端只会影响最后的输出顺序
六、最小覆盖子串 LeetCode76
题目:
给定一个字符串 S 和一个字符串 T,请在 S 中找出包含 T 所有字母的最小子串。
示例:
输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”
思路:

class Solution {
public:
string minWindow(string s, string t) {
const int MAX_ARRAY_LEN = 128;// 数组下标
int map_t[MAX_ARRAY_LEN] = {0};//存储t中各字符出现次数
int map_s[MAX_ARRAY_LEN] = {0};//存储当前窗口各字符出现次数
vector<int> vec_t;//记录t中字符
int window_begin = 0;//最小窗口起始指针
string result;//最小窗口对应的字符串
for(int i=0;i < t.length();i++)//记录t中字符出现次数
map_t[t[i]]++;
for(int i=0;i < MAX_ARRAY_LEN;i++){//遍历将t中出现字符存入vec_t中
if(map_t[i])
vec_t.push_back(i);
}
for(int i=0;i < s.length();i++){
map_s[s[i]]++;//记录当前窗口出现的字符
while(window_begin <i ){//窗头指针不能超过尾指针
char begin_ch = s[window_begin];//存储窗头指针指向的字符
if(map_t[begin_ch]==0)//如果该字符在t中没有则窗头指针直接后移
window_begin++;
else if(map_s[begin_ch]>map_t[begin_ch]){//如果当前窗口begin_ch字符比较多 则后移窗头指针
map_s[begin_ch]--;
window_begin++;
}
else
break;//如果不是以上两种情况则跳出循环
}
if(is_window_ok(map_s,map_t,vec_t)){//检查当前的窗口是否包含字符串t
int new_window_len = i- window_begin+1;//计算当前字符串长度
if(result==""||result.length()>new_window_len)//如果result为空或者长度大于新窗口的长度则更新
result = s.substr(window_begin,new_window_len);
}
}
return result;
}
private:
bool is_window_ok(int map_s[],int map_t[],vector<int> &vec_t){
//map_s表示当前窗口字符数量,map_t表示t中字符数量,vec_存储t中所有字符
for(int i=0;i<vec_t.size();i++){
if(map_s[vec_t[i]] < map_t[vec_t[i]])//如果s出现该字符数量少于t则返回false
return false;
}
return true;
}
};
本文介绍了使用哈希表解决最长回文串、单词模式匹配、字母异位词分组、无重复字符子串、重复DNA序列和最小覆盖子串等LeetCode题目,通过哈希表的高效特性来优化算法解决方案。
550

被折叠的 条评论
为什么被折叠?



