《面试算法 LeetCode 刷题班》——7. 哈希表与字符串

本文内容是基于小象学院——林沐 《面试算法 LeetCode 刷题班》,后期仍将对相关内容进行不定期更新!

7. 哈希表与字符串

文章目录

      • 7. 哈希表与字符串
        • LeetCode 409 最长回文串(E)
        • LeetCode 290 词语模式(E)
        • LeetCode 49 同字符词语分组(M)
        • LeetCode 3 无重复字符的最长字串(M)
        • LeetCode 187 重复的 DNA 序列(M)
        • LeetCode 76 最小窗口子串(H)

哈希表: 也叫散列表,根据key直接进行访问得数据结构,比较快速。

问题1:当遇上 负数或非常大的整数,字符串,及浮点数,数组,对象等,如何进行哈希映射?

需要利用哈希函数,将关键字值转换为整数再对表长取余,从而关键字值被转换成哈希表的表长范围内的整数。

问题2: 采用上述方法,但不排除地址冲突(不同元素对应同一个地址)的问题,如何解决?

这就采用拉链法,构造哈希表来解决冲突,可以将哈希表设置为单个的指针数组,每一个指针指向的是哈希函数的结果相同的单链表. 插入时采用头插法,搜索时依次遍历.

实现:

struct  ListNode
{
	int val;
	ListNode *next;
	ListNode (int x) : val(x),next(nullptr) {}
};


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);
	node->next = hash_table[hash_key];
	hash_table[hash_key] = node;
}

bool search(ListNode *hash_table[], int value, int table_len) {
	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;
}

还可以同 STL 中的 map 包含在 中

LeetCode 409 最长回文串(E)

给定一个字符串,求该字符串可以生成的最长回文字符串长度.

观察回文字符串的特点,字符串长度为奇数时有中心字符,除了中心字符外,或者字符串长度为偶数长度时,其他字符只要头部出现尾部就要对应出现.

本题思路:

  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;

解决方法:

class Solution {
public:
int longestPalindrome(string s) {
int char_map[128] = { 0 };
		int max_length = 0;
		int flag = 0;
		for (int i = 0; i < s.length(); i++)
		{
			char_map[s[i]] += 1;
		}

		for (int i = 0; i < 128; i++)
		{
			if (char_map[i] % 2 == 0) {
				max_length += char_map[i];
			}
			else
			{
				max_length += char_map[i] - 1;
				flag = 1;
			}
		}
		return max_length + flag;
	}
};

LeetCode 290 词语模式(E)

已知字符串pattern 与 字符串str, 确认str是否与 pattern匹配,两者均只包含小写字母,以空格间隔. pattern = “abba”, str = “dog cat cat dog” 则为 true ,反之则为 false.

本题思路:

  1. 设置单个 单词字符 的映射(哈希), 使用数组used[128]记录 pattern 字符是否使用
  2. 遍历所有单词,同时对应的向前移动指向 pattern 字符的指针,每拆分一个单词,做逻辑判断 。
  3. 若单词个数与 pattern 字符个数不匹配,返回 false 。

解决方法:

class Solution {
public:
	bool wordPattern(string pattern, string str) {
		map word_map;
		char used[128] = { 0 };
		string word;
		int pos = 0;
		str.push_back(' ');

		for (int i = 0; i < str.length(); i++)
		{
			if (str[i] == ' ')
			{
				if (pos == pattern.length())
				{
					return false;
				}

				if (word_map.find(word) == word_map.end())
				{
					if (used[pattern[pos]]) {  // 单词未出现,字符出现
						return false;
					}
					word_map[word] = pattern[pos];
					used[pattern[pos]] = 1;
				}
				else
				{
					if (word_map[word] != pattern[pos])
					{
						return false; // 单词出现,字符未出现
					}
				}
				word = "";  // 完成一个单词的插入和查询后,清空word
				pos++; // 指向pattern字符的pos 指针前移
			}
			else {
				word += str[i]; // 读取字符
			}
		}
		if (pos != pattern.length())
		{
			return false; // 有多余的pattern字符
		}
		return true;
	}
};

LeetCode 49 同字符词语分组(M)

已知一组字符串,将所有由颠倒字母顺序构成的字放到一起输出。

Example:

Input: ["eat", "tea", "tan", "ate", "nat", "bat"],
Output:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]

本题思路1:

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

  1. 设置临时变量 str=strs[i],对str进行排序
  2. 若str未出现在anagram中,设置str到一个空字符串向量的映射
  3. 将strs[i]添加到字符串向量 anagram[str]中

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

解决方法:

class Solution {
public:
	vector> groupAnagrams(vector& strs) {

		map> anagram;
		vector> result;
		for (int i = 0; i < strs.size(); i++)
		{
			string str = strs[i];
			sort(str.begin(), str.end());
			if (anagram.find(str) == anagram.end())
			{
				vector item;
				anagram[str] = item;
			}
			anagram[str].push_back(strs[i]);
		}

		map>::iterator it;
		for  (it = anagram.begin();it != anagram.end() ; it++)
		{
			result.push_back((*it).second);
		}
		return result;
	}
};

本题思路2:

将字符串映射到26个字母的字符数量为 key , 以字符串向量为 value ,剩余过程同上。

解决方法:

void change_to_vector(string &str, vector &vec) {
	for (int i = 0; i < 26; i++)
	{
		vec.push_back(0);
	}

	for (int i = 0; i < str.length(); i++)
	{
		vec[str[i] - 'a']++;
	}
}

class Solution {
public:
	vector> groupAnagrams(vector& strs) {

		map, vector> anagram;
		vector> result;
		for (int i = 0; i < strs.size(); i++)
		{
			vector vec;
			change_to_vector(strs[i], vec);
			if (anagram.find(vec) == anagram.end())
			{
				vector item;
				anagram[vec] = item;
			}
			anagram[vec].push_back(strs[i]);
		}

		map, vector>::iterator it;
		for  (it = anagram.begin();it != anagram.end() ; it++)
		{
			result.push_back((*it).second);
		}
		return result;
	}
};

LeetCode 3 无重复字符的最长字串(M)

本题思路:

  1. 设置一个记录字符数量的字符哈希, char_map
  2. 设置一个记录当前满足条件的最长子串变量 word;
  3. 设置两个指针( i 和 j )指向字符串的第一个字符
  4. 设置最长满足条件的字串的长度 result
  5. i 指针向后逐个扫描字符串的字符,使用 char_map 记录字符数量,如果word中没有出现该字符,对word尾部添加字符并检查result是否需要更新;否则 j 向前移动,更新 char_map 的字符数量,直到字符s[i]的数量为1;更新 word ,将 word 赋值为 j 和 i 之间的字串。

解决方法:

class Solution {
public:
	int lengthOfLongestSubstring(string s) {
		int j = 0;
		int result = 0;
		string word = "";
		int char_map[128] = { 0 };
		for (int i = 0; i < s.length(); i++)
		{
			char_map[s[i]]++;
			if (char_map[s[i]] == 1)
			{
				word.push_back(s[i]);
				if (result < word.length())
				{
					result = word.length();
				}
			}
			else
			{
				while (j1)
				{
					char_map[s[j]]--;
					j++;
				}
			}
			word = "";
			for (int k = j; k <= i; k++)
			{
				word += s[k];
			}
		}
		return result;
	}
};

LeetCode 187 重复的 DNA 序列(M)

将 DNA 序列看作只包含[‘A’,‘C’,‘G’,‘T’] 4个字符的字符串,给一个DNA字符串,找到所有长度为10的,且超过1次的子串。

本题思路1:

依次取出给定的字符串中所有长度为 10 的字符串,然后使用 map 来统计字串出现个数,然后输出,复杂度为 O(n)

解决方法:

class Solution {
public:
	vector findRepeatedDnaSequences(string s) {
		vector result;
		map word_map;

		for (int i = 0; i < s.length(); i++)
		{
			string word = s.substr(i, 10);
			if (word_map.find(word) != word_map.end())
			{
				word_map[word]++;
			}
			else
			{
				word_map[word] = 1;
			}
		}
		map::iterator it;
		for (it = word_map.begin();  it !=word_map.end(); it++)
		{
			if ( (*it).second > 1 ) {
				result.push_back(it->first);
			}
		}
		return result;
	}
};

本题思路2:

????

LeetCode 76 最小窗口子串(H)

已知字符串S与字符串T,求S在的最小窗口(区间),使得这个区间中包含了字符串T中的所有字符。

Example:

Input: S = "ADOBECODEBANC", T = "ABC"
Output: "BANC"

本题思路1:

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

2.设置两个指针( i 和 begin)指向字符第一个字符

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

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

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

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

4.指针i 每向前扫描一个字符,即检查一下是否可以更新最终结果

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

解决方法:

class Solution {
public:

	bool is_window_ok(int map_s[], int map_t[] , vector &vec_t) {
		for (int i = 0; i < vec_t.size(); i++) {
			if (map_s[vec_t[i]] < map_t[vec_t[i]]) // 遍历 t 中字符
			{
				return false; // 若s出现该字符的数量小于 t 中出现的数量
			}
		} 
		return true;
	}

	string minWindow(string s, string t) {
		const int MAX_ARRAY_LEN = 128;
		int map_s[MAX_ARRAY_LEN] = { 0 }; //记录 s 字符串各字符个数
		int map_t[MAX_ARRAY_LEN] = { 0 }; //记录 t 字符串各字符个数

		vector 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);  // 遍历,将字符串t中出现的字符存储到 vec_t中
			}
		}

		int window_begin = 0;
		string result;

		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)
				{
					window_begin++;
				}
				else if (map_s[begin_ch] > map_t[begin_ch]) {
					map_s[begin_ch]--;
					window_begin++;
				}
				else
				{
					break;
				}
			}
			if (is_window_ok(map_s,map_t,vec_t))
			{
				int new_window_len = i - window_begin + 1;
				if (result =="" || result.length() > new_window_len)
				{
					result = s.substr(window_begin, new_window_len);
				}
			}
		}
		return result;
	}
};

你可能感兴趣的:(C++,数据结构,leetcode)