哈希表

哈希表

242. 有效的字母异位词(Easy)

用哈希表来统计两个字符串的每个字符数量。因为有26个字母,所以可以直接用大小为26的数组来记录。而不需要用哈希表容器。

1
2
3
4
5
6
7
8
9
10
11
12
bool isAnagram(string s, string t) 
{
int charCnt[26]{};

for (char c: s) charCnt[c - 'a']++;
for (char c: t) charCnt[c - 'a']--;

for (int i: charCnt)
if (i != 0) return false;

return true;
}
1
2
3
4
5
6
7
8
9
10
bool isAnagram(string s, string t)
{
unordered_map<char, int> hashS, hashT;

for (char c: s) hashS[c]++;

for (char c: t) hashT[c]++;

return hashS == hashT;
}

49. 字母异位词分组(Medium)

哈希+排序

因为异位词的字母相同,排序后得到的值也是一样的,可以用它作为哈希表的键值。

哈希表的键值为排序后的字符串,值为异位词组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vector<vector<string>> groupAnagrams(vector<string>& strs)
{
unordered_map<string, vector<string>> groups;
vector<vector<string>> res;

for (const string& str: strs)
{
string key = str;
sort(key.begin(), key.end());
groups[key].push_back(str);
}

for (pair<string, vector<string>> p: groups)
res.push_back(p.second);

return res;
}

哈希+计数

因为题目只有26个字母,所以可以用大小为26的数组来计数,把计数的数组作为哈希的键值。不过哈希默认不支持数组作为键值,所以得额外设计哈希函数。

438. 找到字符串中所有字母异位词(Medium)

类似于76. 最小覆盖子串,利用滑动窗口+哈希的方式去寻找覆盖子串。不同之处在于,这道题是寻找异位词,子串中不能包含多余的其他字符。所以需要额外排除有多余字符的子串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
vector<int> findAnagrams(string s, string p)
{
int sHash[26] = {}, pHash[26] = {};
vector<int> res;

for (char c: p) pHash[c - 'a']++;

int len = 0;
for (int l = 0, r = 0; r < s.length(); r++)
{
sHash[s[r] - 'a']++;

if (sHash[s[r] - 'a'] <= pHash[s[r] - 'a']) len++;

// 找到最小覆盖子串
// 这里用的数组哈希,所以得加上边界判断,防止下标越界
while (l < s.length() && sHash[s[l] - 'a'] > pHash[s[l] - 'a'])
sHash[s[l++] - 'a']--;

// 排除有非p中字符的子串
while (r - l + 1 > p.length())
{
if (pHash[s[l] - 'a'] > 0)
len--;

sHash[s[l++] - 'a']--;
}

if (len == p.length())
res.push_back(l);
}

return res;
}

349. 两个数组的交集(Easy)

直接使用Set容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vector<int> intersection_set(vector<int>& nums1, vector<int>& nums2)
{
unordered_set<int> numSet(nums1.begin(), nums1.end());
unordered_set<int> interSet;

for (int num: nums1) numSet.insert(num);

for (int num: nums2)
{
if (numSet.find(num) != numSet.end())
interSet.insert(num);
}

vector<int> res(interSet.begin(), interSet.end());
return res;
}

使用哈希表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 哈希容器
vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
{
unordered_map<int, int> numCnt1, numCnt2;
vector<int> interSet;

for (int num: nums1) numCnt1[num]++;
for (int num: nums2) numCnt2[num]++;

for (pair<int, int> p: numCnt1)
{
if (numCnt2[p.first] > 0)
interSet.push_back(p.first);
}

return interSet;
}

// 哈希数组
vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
{
int hash1[1005] = {}, hash2[1005] = {};
vector<int> interSet;

for (int num: nums1) hash1[num]++;
for (int num: nums2) hash2[num]++;

for (int i = 0; i < 1000; i++)
{
if (hash1[i] > 0 && hash2[i] > 0)
interSet.push_back(i);
}

return interSet;
}

202. 快乐数(Easy)

无限循环的条件是,得到的sum值在之前出现过。可以用Set容器来查重。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int sumOfSqure(int n)
{
int sum = 0;

while (n != 0)
{
sum += (n % 10) * (n % 10);
n /= 10;
}

return sum;
}

bool isHappy(int n)
{
unordered_set<int> numSet;

while (n != 1)
{
if (numSet.find(n) != numSet.end())
return false;

numSet.insert(n);
n = sumOfSqure(n);
}

return true;
}

1. 两数之和(Easy)

这个题容易想到的方法就是暴力求解,但是时间复杂有\(O(n^2)\)

题目的思路其实可以从找两个数加起来等于目标值,变成:遍历一个数组,判断数组中是否存在

target-nums[i] 。这样问题就转换为了利用哈希表的方法来查找,时间复杂度为\(O(n)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 精炼版
vector<int> twoSum_compact(vector<int>& nums, int target)
{
unordered_map<int, int> numHash;

for (int i = 0; i < nums.size(); i++)
{
if (numHash.find(target - nums[i]) != numHash.end())
return {i, numHash[target - nums[i]]};

numHash[nums[i]] = i;
}

return {};
}

// 不那么精简的版本
vector<int> twoSum(vector<int>& nums, int target)
{
unordered_multimap<int, int> numHash;
vector<int> res(2);

for (int i = 0; i < nums.size(); i++)
numHash.insert(pair<int, int>(nums[i], i));

for (int& num : nums)
{
res[0] = numHash.find(num)->second;
numHash.erase(numHash.find(num));
if (numHash.find(target - num) != numHash.end())
{
res[1] = numHash.find(target - num)->second;
break;
}
}

return res;
}

454. 四数相加 II(Medium)

分成两个部分,先计算1,2数组组合结果存入哈希表,然后利用哈希表来从3,4中判断是否有符合条件的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 只用一个哈希表
int fourSumCount_saveMemory(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4)
{
int cnt = 0;
unordered_map<int, int> hash12;

for (int num1: nums1)
for (int num2: nums2)
hash12[num1 + num2]++;

for (int num3: nums3)
for (int num4: nums4)
cnt += hash12[0 - num3 - num4];

return cnt;
}

// 用两个哈希表
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4)
{
int cnt = 0;
unordered_map<int, int> hash12, hash34;

for (int num1: nums1)
for (int num2: nums2)
hash12[num1 + num2]++;

for (int num3: nums3)
for (int num4: nums4)
hash34[num3 + num4]++;

for (auto iter: hash12)
{
if (hash34.find(0 - iter.first) != hash34.end())
cnt += iter.second * hash34[0 - iter.first];
}

return cnt;
}

15. 三数之和(Medium)

因为题目要求不能出现重复的组合,所以要涉及到元组三个数的去重问题。如果使用哈希表来解题,会不方便剪枝,最后时间和空间消耗都会非常大。最好的办法是使用双指针。

排序+双指针

先对数组排序。然后使用两个大循环,第一个循环负责遍历第一个数,第二个while循环使用左右双指针。因为是从小到大排序,所以如果三个数相加:

  • < 0:收缩左指针;

  • > 0:收缩右指针;

  • = 0:将当前的三个数添加到返回值;

排序后的数组可以非常便利的去重。对于三元组每个数,去重的方法也不同。

  • 第一个数:如果上一个数和当前的数相同,说明上一个数的情况已经讨论过了,可以跳过

  • 左指针:如果下一个数和当前相同则收缩。

  • 右指针:上一个数和当前相同则收缩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
vector<vector<int>> threeSum(vector<int>& nums)
{
vector<vector<int>> res;

sort(nums.begin(), nums.end());

for (int i = 0; i < nums.size(); i++)
{
if (nums[i] > 0) return res;

if (i > 0 && nums[i] == nums[i - 1]) continue;

int l = i + 1, r = nums.size() - 1;

while (l < r)
{
if (nums[i] + nums[l] + nums[r] > 0)
r--;
else if (nums[i] + nums[l] + nums[r] < 0)
l++;
else
{
res.push_back({nums[i], nums[l], nums[r]});

while (l < r && nums[r] == nums[r - 1]) r--;
while (l < r && nums[l] == nums[l + 1]) l++;

l++; r--;
}
}
}

return res;
}

排序+哈希

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
vector<vector<int>> threeSum_hash(vector<int>& nums)
{
vector<vector<int>> res;

sort(nums.begin(), nums.end());

for (int i = 0; i < nums.size(); i++)
{
if (nums[i] > 0) return res;

if (i > 0 && nums[i] == nums[i - 1]) continue;

unordered_set<int> numSet;
for (int j = i + 1; j < nums.size(); j++)
{
// 这里判断j的前两个数是为了防止跳过如 [-4,2,2] 这样的元组
if (j > i + 2 && nums[j] == nums[j - 1] && nums[j] == nums[j - 2])
continue;

int tmp = 0 - nums[i] - nums[j];

if (numSet.find(tmp) != numSet.end())
{
res.push_back({nums[i], nums[j], tmp});
numSet.erase(tmp);
}
else
numSet.insert(nums[j]);
}
}

return res;
}

18. 四数之和(Medium)

类似 15. 三数之和 的思路,先使用一个循环确定第一个数,然后嵌套三数之和的循环。需要注意的是,题目数据范围非常大,四数相加int 可能会溢出,需要用long

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
vector<vector<int>> fourSum(vector<int>& nums, int target)
{
vector<vector<int>> res;

sort(nums.begin(), nums.end());

for (int i = 0; i < nums.size(); i++)
{
if (i > 0 && nums[i] == nums[i - 1]) continue;

for (int j = i + 1; j < nums.size(); j++)
{
if (j > i + 1 && nums[j] == nums[j - 1])
continue;

long t = (long)target - nums[i] - nums[j];
int l = j + 1, r = nums.size() - 1;
while (l < r)
{
if (nums[l] + nums[r] < t)
l++;
else if (nums[l] + nums[r] > t)
r--;
else
{
res.push_back({nums[i], nums[j], nums[l], nums[r]});

while (l < r && nums[l + 1] == nums[l]) l++;
while (l < r && nums[r - 1] == nums[r]) r--;

l++; r--;
}
}
}
}

return res;
}

哈希表
http://blog.ashechol.top/posts/850f2080.html
作者
Ashechol
发布于
2023年2月6日
许可协议