七、哈希表与字符串(小象)

目录

 

哈希表基础知识

哈希表定义

1、字符哈希

2、哈希表排序整数

3、拉链表解决冲突,构造哈希表

4、STL map中的常用操作

409、最长回文串

290、单词模式

49、字母异位词分组

3、无重复字符的最长子串(滑动窗口的机制)

187、重复的DNA序列

76、最小覆盖子串

哈希表基础知识

哈希表定义

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));

//在哈希表中查找某关键字,没有找到
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;
	}
};

猜你喜欢

转载自blog.csdn.net/qq_34269988/article/details/85256698