hash table, 也叫散列表,把关键值key映射为数组下标进行访问,映射函数叫做哈希/散列函数,数组叫做哈希/散列表。
// hash table
// record times of char in string
#include
#include
int main()
{
std::string str = "abcdefaaaxxy";
int charMap[128] = {};
for (int i = 0, nLen = str.length(); i < nLen; ++i)
{
++charMap[str[i]];
}
for (int i = 0; i < 128; ++i)
{
if (charMap[i])
{
printf("[%d(%c)]: %d times\n", i, i, charMap[i]);
}
}
return 0;
}
使用数组下标对正整数排序。哈希表长度需要超过最大待排序数字。
时间复杂度O(表长+n)
#include
int main()
{
int aRand[10] = {999, 1, 333, 7, 20, 9, 1, 3, 7, 7};
int hashMap[1000] = {};
for (int i = 0; i < 10; ++i)
{
++hashMap[aRand[i]];
}
for (int i = 0; i < 1000; ++i)
{
for (int j = 0; j < hashMap[i]; ++j)
{
// 有几个就打印几个
printf("%d\n", i);
}
}
return 0;
}
答:转换为整数再对数组长度取余。
答:冲突本质是数组不够长,可以将哈希表定义为链表数组,头插法添加元素。
#include
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(NULL){}
};
int hash(int key, int nTableLen)
{
return key % nTableLen;
}
bool insert(ListNode *hashTable[], int nTableLen, ListNode *pNode)
{
if (!hashTable || !pNode) return false;
int key = hash(pNode->val, nTableLen);
pNode->next = hashTable[key];
hashTable[key] = pNode;
return true;
}
bool search(ListNode* hashTable[], int nTableLen, int val)
{
if (!hashTable) return false;
int key = hash(val, nTableLen);
ListNode* pNode = hashTable[key];
while (pNode)
{
if (pNode->val == val) return true;
pNode = pNode->next;
}
return false;
}
int main()
{
const int nTableLen = 11;
int aNodes[8] = { 1, 1, 4, 9, 20, 30, 150, 500 };
ListNode* hashTable[nTableLen] = {};
for (int i = 0; i < 11; ++i)
{
insert(hashTable, nTableLen, new ListNode(aNodes[i]));
}
for (int i = 0; i < 11; ++i)
{
printf("[%d]: ", i);
ListNode* pNode = hashTable[i];
while (pNode)
{
printf("->%d", pNode->val);
pNode = pNode->next;
}
printf("\n");
}
for (int i = 0; i < 10; ++i)
{
if (search(hashTable, nTableLen, i))
{
printf("%d in hashTable\n", i);
}
else
{
printf("%d not in hashTable\n", i);
}
}
return 0;
}
/*
[0]: ->11
[1]: ->1->1
[2]:
[3]:
[4]: ->4
[5]: ->500
[6]:
[7]: ->150
[8]: ->30
[9]: ->20->9
[10]:
0 not in hashTable
1 in hashTable
2 not in hashTable
3 not in hashTable
4 in hashTable
5 not in hashTable
6 not in hashTable
7 not in hashTable
8 not in hashTable
9 in hashTable
*/
给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。
在构造过程中,请注意区分大小写。比如 “Aa” 不能当做一个回文字符串。
注意:
假设字符串的长度不会超过 1010。
示例 1:
输入:
"abccccdd"
输出:
7
解释:我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
思路:
class Solution {
public:
int longestPalindrome(string s) {
const int nTableLen = 128;
int hashMap[nTableLen] = {};
int nLen = s.length();
int nMid = 0;
int nRes = 0;
for(int i = 0; i < nLen; ++i)
{
++hashMap[s[i]];
}
for(int i = 0; i < nTableLen; ++i)
{
if(hashMap[i] % 2)
{
// odd
nRes += hashMap[i] - 1;
nMid = 1;
}
else
{
nRes += hashMap[i];
}
}
return nRes + nMid;
}
};
给定一种规律 pattern 和一个字符串 str ,判断 str 是否遵循相同的规律。
这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应规律。
示例1:
输入: pattern = "abba", str = "dog cat cat dog"
输出: true
示例 2:
输入:pattern = "abba", str = "dog cat cat fish"
输出: false
示例 3:
输入: pattern = "aaaa", str = "dog cat cat dog"
输出: false
示例 4:
输入: pattern = "abba", str = "dog dog dog dog"
输出: false
说明:你可以假设 pattern 只包含小写字母, str 包含了由单个空格分隔的小写字母。
思路,解析出新的单词,若已出现,则对应一个字符,否则映射为新的字符。最终字符数和单词数还要相等。
class Solution {
public:
bool wordPattern(string pattern, string str) {
std::map<string, char> mapWord;
std::string word;
bool bUsed[128] = {};
int nLenStr = str.length();
int nLenPat = pattern.length();
int nPosPat = 0;
for(int i = 0; i <= nLenStr; ++i)
{
// new word
if ( str[i] == ' ' || str[i] == '\0')
{
// pattern end?
if ( nPosPat == nLenPat ) return false;
// word not mapped
if ( mapWord.find(word) == mapWord.end())
{
if ( bUsed[pattern[nPosPat]])
{
return false;
}
mapWord[word] = pattern[nPosPat];
bUsed[pattern[nPosPat]] = true;
}
// word mapped
else if(mapWord[word] != pattern[nPosPat])
{
return false;
}
word = "";
++nPosPat;
}
else
{
word += str[i];
}
}
if ( nLenPat != nPosPat)
// 有多余pattern
return false;
else
return true;
}
};
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
说明:所有输入均为小写字母。不考虑答案输出的顺序。
关键是如何把anagram(异位词)映射到一起。
可以把排好序的单词作为key。
using namespace std;
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
map<string, vector<string>> mapAnagrams;
vector<vector<string>> vecRes;
int nSize = strs.size();
for(int i = 0 ; i < nSize; ++i)
{
// strs is a reference
// so use a tmp var
string strKey = strs[i];
sort(strKey.begin(), strKey.end());
// strTmp is a new key
if(mapAnagrams.find(strKey) == mapAnagrams.end())
{
// needn't to alloc
vector<string> vecItem;
mapAnagrams[strKey] = vecItem;
}
mapAnagrams[strKey].push_back(strs[i]);
}
for(map<string, vector<string>>::iterator it = mapAnagrams.begin();
it != mapAnagrams.end();
++it)
{
vecRes.push_back((*it).second);
}
return vecRes;
}
};
用vector存储各个字母出现次数,作为key。也就是用另一个哈希表作为key。标准的用空间换时间。
class Solution {
public:
void generateVector(string &str, vector<int> &vec)
{
int nLen = str.length();
for(int i = 0; i < nLen; ++i)
{
++vec[str[i] - 'a'];
}
}
vector<vector<string>> groupAnagrams(vector<string>& strs) {
map<vector<int>, vector<string>> mapAnagrams;
vector<vector<string>> vecRes;
int nSize = strs.size();
for(int i = 0 ; i < nSize; ++i)
{
vector<int> vecKey(26, 0);
generateVector(strs[i], vecKey);
if(mapAnagrams.find(vecKey) == mapAnagrams.end())
{
vector<string> vecItem;
mapAnagrams[vecKey] = vecItem;
}
mapAnagrams[vecKey].push_back(strs[i]);
}
for(map<vector<int>, vector<string>>::iterator it = mapAnagrams.begin();
it != mapAnagrams.end();
++it)
{
vecRes.push_back((*it).second);
}
return vecRes;
}
};
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
枚举法复杂度是n方,现在思考O(n)
的方法,也就是扫描一遍。
枚举法的缺陷,一个是会重复扫描已重复的字符,再一个是会枚举长度短于已知解的子串。
思路:
这个过程,双指针维护了一个窗口,并线性滑动(看着有点熟~)。
双指针常常用来维护一个需要满足一定条件的窗口。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int mapChar[128] = {};
int begin = 0;
int nRes = 0;
string str;
for(int i = 0, nLen = s.length(); i < nLen; ++i)
{
++mapChar[s[i]];
// char not in str
if(mapChar[s[i]] == 1)
{
str += s[i];
if(nRes < str.length())
{
nRes = str.length();
}
}
// char in str
else
{
// 设重复字符第一次出现在索引x处
// 则要更新begin-x区间的map值
while( begin < i && mapChar[s[i]] > 1)
{
--mapChar[s[begin]];
++begin;
}
str.assign(s, begin, i - begin + 1);
}
}
return nRes;
}
};
参考labuladong大神的框架:
int left = 0, right = 0;
while (right < s.size()) {`
// 增大窗口
window.add(s[right]);
right++;
while (window needs shrink) {
// 缩小窗口
window.remove(s[left]);
left++;
}
}
所有 DNA 都由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。
编写一个函数来查找目标子串,目标子串的长度为 10,且在 DNA 字符串 s 中出现次数超过一次。
示例:
输入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
输出:["AAAAACCCCC", "CCCCCAAAAA"]
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
map<string, int> hashMap;
vector<string> vecRes;
int nSize = s.length();
for ( int i = 0 ; i < nSize; ++i)
{
string str = s.substr(i, 10);
if(hashMap.find(str) != hashMap.end())
{
++hashMap[str];
}
else
{
hashMap[str] = 1;
}
}
for(map<string, int>::iterator it = hashMap.begin();
it != hashMap.end();
++it)
{
if(it->second > 1)
{
vecRes.push_back(it->first);
}
}
return vecRes;
}
};
每个字符AGCT
四种情况,2 bit表示,10个字符则是20bit,可作为key值作为数组下标。
// too large, so define global var.
int g_hashMap[1048576] = {0};
class Solution {
public:
Solution()
{
}
string key2DNA(int key)
{
static char acDNA[4] = {'A', 'C', 'G', 'T'};
string strRes;
for(int i = 0; i < 10; ++i)
{
strRes += acDNA[key & 3];
key = key >> 2;
}
return strRes;
}
vector<string> findRepeatedDnaSequences(string s) {
vector<string> vecRes;
int nLen = s.length();
if (nLen < 10) return vecRes;
map<char, int> mapChar;
mapChar['A'] = 0;
mapChar['C'] = 1;
mapChar['G'] = 2;
mapChar['T'] = 3;
// reset per call
memset(g_hashMap, 0, sizeof(int) * 1048576);
int key = 0;
// 先处理前10个字符
for(int i = 9 ; i >= 0; --i)
{
key = (key << 2) + mapChar[s[i]];
}
g_hashMap[key] = 1;
for(int i = 10 ; i < nLen; ++i)
{
// 前9个字符
key = key >> 2;
// s[i]放在高位
key = key | mapChar[s[i]] << 18;
++g_hashMap[key];
}
for(int i = 0; i < 1048576; ++i)
{
if(g_hashMap[i] > 1)
vecRes.push_back(key2DNA(i));
}
return vecRes;
}
};
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:
本题双指针扫描过程:
class Solution {
public:
bool checkWindow(int mapS[], int mapT[], vector<char> setCharInT)
{
vector<char>::iterator it;
for(it = setCharInT.begin(); it != setCharInT.end(); ++it)
{
if(mapS[*it] < mapT[*it])
{
return false;
}
}
return true;
}
string minWindow(string s, string t) {
const int MAP_SIZE = 128;
int mapS[MAP_SIZE] = {};
int mapT[MAP_SIZE] = {};
vector<char> setCharInT;
// 先处理T
int nLenT = t.length();
for(int i = 0; i < nLenT; ++i)
{
++mapT[t[i]];
}
for(int i = 0 ; i < MAP_SIZE; ++i)
{
if(mapT[i] > 0)
{
setCharInT.push_back(i);
}
}
// scan s
int begin = 0;
int nLenS = s.length();
string strRes;
for(int i = 0 ; i < nLenS; ++i)
{
++mapS[s[i]];
// scan window
while(begin < i)
{
char cBegin = s[begin];
// if s[begin] not in t
if(mapT[cBegin] == 0)
{
++begin;
}
// if s[begin] in t
else if (mapS[cBegin] > mapT[cBegin])
{
--mapS[cBegin];
++begin;
}
else
{
break;
}
}
// check current result
if(checkWindow(mapS, mapT, setCharInT))
{
int nNewLen = i - begin + 1;
if (strRes == "" || strRes.length() > nNewLen)
{
strRes = s.substr(begin, nNewLen);
}
}
}
return strRes;
}
};
需要用mapS、mapT记录字符数量。本来是想用set,但超时了。
来自labuladong大神的解法。
unordered_map
经常用来代替map,几种键值对容器抽空还得整理对比下。