一、哈希表与字符串
字符操作API:
isalpha()用来判断一个字符是否为字母,如果是字符则返回非零,否则返回零。
isalnum()用来判断一个字符是否为数字或者字母,也就是说判断一个字符是否属于a~z||A~Z||0~9
islower()用来判断一个字符是否为小写字母,也就是是否属于a~z
isupper()和islower相反,用来判断一个字符是否为大写字母
tolower()将字符转换成小写
toupper()将字符转换成大写
1.最长回文串(409)
利用哈希表统计每一个字符的个数,当字符为偶数个数时,字符可以完全添加到回文中。当字符个数为奇数时,剔除一个可以全加入到回文中。最后从剩余的单个字符中拿出一个当中心。
英文字母ascII码:65~90号为26个大写英文字母,97~122号为26个小写英文字母
int longestPalindrome(string s) {
int char_map[128] = {0};//是以字符的ascII码做映射,所以要保证能存储到最大号的122——z
int max_len = 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){
max_len += char_map[i];
}
else{
max_len += char_map[i] - 1;
flag = 1;
}
}
//if(flag)return max_len+1;
return max_len+flag;
}
2.单词规律(290)
注意要点:1.使用空格来切分字符串
2.使用整数哈希表记录使用过的字母
3.建立字母与单词的哈希表
4.使用pos作为访问字母的指针
主要思路:每到一个空格得出当前单词,然后在哈希表中查找word:
若找到:(1)word和当前字母对应(2)word和字母不对应
若没找到:(1)当前字母已经占用 (2)当前字母没有被占用,则添加word和字母到哈希表。
补充判断要点:pos不能超过pattern长度 单词字符串和字母字符串长度不等两种情况
bool wordPattern(string pattern, string s) {
map<string, char> word_map;
int used[128] = {0};
string w = "";
int pos = 0;
s=s+" ";
for(int i=0;i<s.length();i++){
if(s[i]==' '){
if(pos==pattern.length()){
return false;
}
if(word_map.find(w)==word_map.end()){//未找到w
if(used[pattern[pos]]==1){//已占用
return false;
}
//未占用,添加进map
word_map[w] = pattern[pos];
used[pattern[pos]] = 1;
}
else{
if(word_map[w]!=pattern[pos]){
return false;
}
}
pos++;
w = "";
}
else{
w += s[i];
}
}
if(pos != pattern.length()){
return false;
}
return true;
}
3.字母异位词分组(49)
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/group-anagrams
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
字母异位词字母种类和数量是相同的,所以将字符 排序之后属于异位词的两个单词排序结果是相同的,此时就可以利用哈希表添加到排序后相同的对应的字符串数组中。
注意:哈希表的遍历方法,使用指针
std::map<string, vector<string>>::iterator it;
vector<vector<string>> groupAnagrams(vector<string>& strs) {
std::map<string, vector<string>>str_map;//建立map
vector<vector<string>>res;
for(int i=0;i<strs.size();i++){
string str = strs[i];
sort(str.begin(), str.end());
cout<<str<<endl;
if(str_map.find(str)!=str_map.end()){//找到了排序后的str
str_map[str].emplace_back(strs[i]);
}
else{
vector<string> vc = {strs[i]};//创建新的字符串向量
str_map[str] = vc;
}
}
std::map<string, vector<string>>::iterator it;
for(it=str_map.begin();it!=str_map.end();it++){
res.emplace_back((*it).second);
}
return res;
}
4.无重复字符的最长字串(3)
给定 “pwwkew” ,
子串是pww,wwk等很多个子串 是连在一起的
子序列是 pwk,pke等很多个子序列 ,但是子序列中的字符在字符串中不一定是连在一起的。
要点:(1)使用指针begin标识当前word的起始字符
(2)利用整数字符map统计当前字符是否已经出现过。
(3)result每次循环和word长度比较,较小则进行更新。
逻辑:循环遍历字符串,map中当前字符对应位置++,并判断当前字符是否已经出现过1)(map[s[i]]==1表明未出现过),添加到word中,更新result.。2)若出现过,剔除重复元素,移动begin位置,注意map中的值也要对应–。
//关键
while(map[s[i]]>1){
map[s[begin]]--;
begin++;
}
int lengthOfLongestSubstring(string s) {
int len = s.length();
if(len<2)return len;
unordered_map<char,int> search;
int maxVal = 1;
int begin = 0;
int pos = 1;
search[s[0]] = 0;
while(pos<len){
if(search.find(s[pos])!=search.end()&&search[s[pos]]>=begin){//注意这里,只有当最新的字符POS大于begin才对begin更新,要不然只更新哈希表字符POS "abcabcfgechb":例如当我的pos到最后一个b时,此时我的begin是已经到f了,如果不判断search[s[pos]]>=begin,begin又会跳回到了倒数第二个c的位置。造成了倒退重复。
begin = search[s[pos]] + 1;
search[s[pos]] = pos;
}
else{
search[s[pos]] = pos;
}
maxVal = max(pos-begin+1,maxVal);
cout<<maxVal<<endl;
pos +=1;
}
return maxVal;
}
int lengthOfLongestSubstring(string s) {
int map[128] = {0};
int res = 0;
int begin = 0;
string word = "";
for(int i=0;i<s.length();i++){
map[s[i]]++;
if(map[s[i]]==1){
word += s[i];
if(res<word.length()){
res = word.length();
}
}
else{
while(map[s[i]]>1){//关键
map[s[begin]]--;
begin++;
}
word = "";
for(int j=begin;j<=i;j++){
word += s[j];
}
}
}
return res;
}
5.重复的DNA序列
1)使用枚举法
将字符串中10个一组的子串都枚举出来,利用map图统计重复子串个数,最后将重复子串都push到结果中。
学习到的要点:string tem_str = s.substr(i, 10);
字符串中截取子串方法
vector<string> findRepeatedDnaSequences(string s) {
//枚举法
map<string,int>str_map;
vector<string>res;
for(int i=0;i<s.length()-10;i++){
string tem_str = s.substr(i, 10);
if(str_map.find(tem_str)!=str_map.end()){
str_map[tem_str]++;
}
else{
str_map[tem_str] = 1;
}
}
map<string,int>::iterator it;
for(it=str_map.begin();it!=str_map.end();it++){
if(it->second>1){
res.emplace_back(it->first);
}
}
return res;
}
6.电话号码字母组合(17)
使用unorder_map建表方式:
在查表时,如果map在传入时使用的const修饰,就不能直接采用重载运算符“[]”直接进行查找,只能使用.at()进行查找。
此题类似全部路径类型的题,注意solver函数内部分为两种情况,当index==digits.length()即遍历到末尾数字时候,就将当前path加入到res中,其他的就使用index驱动遍历digits进行查表,然后使用一个for循环遍历查找出来的每一个字符添加到path中,递归下一个(index+1),然后再回溯。
unordered_map<char,string> map1{
{'2',"abc"},
{'3',"def"},
{'4',"ghi"},
{'5',"jkl"},
{'6',"mno"},
{'7',"pqrs"},
{'8',"tuv"},
{'9',"wxyz"}
};
vector<string> letterCombinations(string digits) {
int len = digits.length();
int index = 0;
unordered_map<char,string> map1{
{'2',"abc"},
{'3',"def"},
{'4',"ghi"},
{'5',"jkl"},
{'6',"mno"},
{'7',"pqrs"},
{'8',"tuv"},
{'9',"wxyz"}
};
vector<string> res;
string path = "";
if(digits.length()==0)return res;
solver(map1,res,path,digits,index);
return res;
}
void solver(unordered_map<char,string>& map1,vector<string>& res,string& path,const string& digits,int index){
if(index == digits.length()){
res.emplace_back(path);
}
else{
string tem = map1[digits[index]];
for(const char item:tem){
path += item;
solver(map1,res,path,digits,index+1);
path.pop_back();
}
}
}
7.构建哈希表(705)
1.使用数组进行简单构建,注意数组的声明方式
/** Initialize your data structure here. */
MyHashSet() {
}
bool node[10000000] = {false};
void add(int key) {
node[key] = true;
}
void remove(int key) {
node[key] = false;
}
/** Returns true if this set contains the specified element */
bool contains(int key) {
return node[key];
}
2.使用除留余数法
/** Initialize your data structure here. */
vector<int>data;//定义是在prvate和public里定义
int len = 50000;
MyHashSet() {
data.assign(len, -1);//在构造函数中初始化变量
}
int compute(int key){
int pos = key* 33 % 50000;//乘上质数将空间扩展开,要不然挤在一起移动耗时,基础空间也给大一些,不容易冲突
while(data[pos]!=-1&&data[pos]!=key){//当不为空,且已经存在的值不为当前key值,pos向后挪动
pos = (pos + 1) % 50000;
}
return pos;
}
void add(int key) {
int pos = compute(key);
data[pos] = key;
}
void remove(int key) {
int pos = compute(key);
data[pos] = -1;
}
/** Returns true if this set contains the specified element */
bool contains(int key) {
return data[compute(key)]!=-1;
}
8.翻转字符串中的单词(151)
倒序记录,i记录当前指针位置,l记录当前单词的长度,每次对一个单词结束遍历就将其拼接到结果中。
string reverseWords(string s) {
int len = s.length();
int i = len - 1;
int l = 0;
string res = "";
while(i>=0){
while(i>=0&&s[i]==' '){
i--;
}
while(i>=0&&s[i]!=' '){
i--;
l++;
}
if(l>0){
res += s.substr(i+1, l) + " ";
l = 0;
}
}
res.pop_back();
return res;
}
9.压缩字符串(443)
int compress(vector<char>& chars) {
//使用三个指针,i记录遍历位置,c记录当前相同字符的数量,index记录重写之后的位置,与翻转字符串中的单词有异曲同工之妙
int i = 0;
int c = 0;
int index = 0;
int len = chars.size();
while(i<len){
char curr = chars[i];
while(i<len&&chars[i]==curr){
i++;
c++;
}
chars[index] = curr;
index++;
if(c>1){//当当前字符多于1时候
string num = to_string(c);
c = 0;
for(char ch:num){
chars[index] = ch;
index++;
}
}
else if(c==1){
c = 0;
}
}
return index;
}
10.字符串相乘(43)
乘积结果的最大长度为m+n。从后往前计算相乘,低位放置在i+j+1,高位放置在i+j。注意低位相加进位操作
string multiply(string num1, string num2) {
if(num1=="0"||num2=="0")return "0";
int len1 = num1.length();
int len2 = num2.length();
vector<int> vc(len1+len2, 0);//结果最长为len1+len2(最高位乘积大于10的时候)
for(int i=len1-1;i>=0;i--){
int x = num1[i] - '0';
for(int j=len2-1;j>=0;j--){
//j*i ---> 低位在i+j+1, 高位在i+j
int y = num2[j] - '0';
int res = x * y;
int d = res%10; //每一步乘积的低位
int g = res/10; //每一步乘积的高位
if(vc[i+j+1]+d>=10){ //低位相加大于10的情况,向高位进位
g += (vc[i+j+1] + d) / 10;
vc[i+j+1] = (vc[i+j+1] + d) % 10;
}
else if(vc[i+j+1]+d<10){
vc[i+j+1] = vc[i+j+1] + d;
}
vc[i+j] += g; //注意:是从后向前运算,所以不会出现高位相加大于10的情况
}
}
string res = "";
for(int i=0;i<vc.size();i++){
if(i==0&&vc[i]==0)continue; //注意排除先头0的情况
res += to_string(vc[i]);
}
return res;
}
11.最长公共前缀(14)
string longestCommonPrefix(vector<string>& strs) {
int len = strs.size();
if(len==0)return "";
if(len==1)return strs[0];
string str = strs[0];
for(int i=1;i<len;i++){//时间复杂度为O(MxN),空间复杂度为O(1)
if(str=="")return "";
int j = 0;
for(char ch : strs[i]){
if(ch==str[j]){
j++;
}
else{
break;
}
}
str = str.substr(0,j);
}
return str;
}
12.外观数列(38)
public:
//模拟方法,利用一个for循环计数遍历的字符串数
//然后利用一个while遍历描述当前字符串,描述结果又当做下一循环的被描述的字符串
//描述字符串的时候使用count计数
string countAndSay(int n) {
string str = "1";
if(n==1)return str;
int count = 1;
for(int j=1;j<n;j++){
int i = 0;
string path = "";
while(i<str.length()){
if(i<str.length()-1&&str[i]==str[i+1]){
count++;
i++;
}
else{
path = path + to_string(count) + str[i];
i++;
count = 1;
}
}
str = path;
}
return str;
}