哈希表与字符串

本文介绍了使用哈希表解决最长回文串、单词模式匹配、字母异位词分组、无重复字符子串、重复DNA序列和最小覆盖子串等LeetCode题目,通过哈希表的高效特性来优化算法解决方案。
部署运行你感兴趣的模型镜像

预备知识:
在这里插入图片描述
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;
    }
};

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值