目录
- 一、有效的字母异位词
- 1.1 赎金信
- 1.2 字符异位词分组
- 1.3 找到字符串中所有字母异位词
- 二、两个数组的交集
- 2.1 两个数组的交集 II
- 三、快乐数
- 四、两数之和
- 五、四数相加 II
- 六、三数之和
- 七、四数之和
哈希解决问题一般有三种数据结构供选择:
- 数组
- map(映射)
- set(集合)
一、有效的字母异位词
Leetcode 242
数组就是一个简单的哈希表,使用数组比使用 map
或者 unordered_map
更省时间和空间,因为后者需要创建哈希函数以及维护哈希表 or 红黑树。
class Solution {
public:
bool isAnagram(string s, string t) {
int record[30] = {0};
for (char c: s)
record[c - 'a'] ++ ;
for (char c: t)
record[c - 'a'] -- ;
for (int x: record)
if (x) return false;
return true;
}
};
1.1 赎金信
Leetcode 383
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int record[30] = {0};
for (char c: magazine)
record[c - 'a'] ++ ;
for (char c: ransomNote) {
if (!record[c - 'a'])
return false;
record[c - 'a'] -- ;
}
return true;
}
};
1.2 字符异位词分组
Leetcode 49
核心思路:找一个中间状态,让相同字符异位词均能映射到上面。这里将每个字符按照其 ASCII
码的大小排序后,所有相同的字符处在一个组内部即可。
move()
的作用:本来需要将整个字符串copy
过去,使用move
之后只需要把字符串的首地址赋值过去,可以减少一次拷贝操作,提高效率。
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> dict;
for (auto &str: strs) {
string key = str;
sort(key.begin(), key.end());
dict[key].push_back(move(str));
}
vector<vector<string>> res;
for (auto it = dict.begin(); it != dict.end(); it ++ )
res.push_back(move(it->second));
return res;
}
};
时间复杂度: N N N 表示字符串个数, L L L 表示字符串平均长度。对于每个字符串,哈希表和 v e c t o r vector vector 的插入操作时间复杂度为 O ( 1 ) O(1) O(1),排序时间时间复杂度为 L l o g ( L ) Llog(L) Llog(L),所以总共时间复杂度为 O ( N L l o g L ) O(NLlogL) O(NLlogL)。
1.3 找到字符串中所有字母异位词
Leetcode 438
使用一个哈希表来维护字符,先将字符串 p
中所有字符添加进哈希表中,然后使用一个滑动窗口来遍历字符串 s
,字符进入窗口相应元素数目减一,出窗口加一。当元素数目为零的时候,表示该元素匹配。
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
unordered_map<char, int> cnt;
for (auto c: p) cnt[c] ++ ;
int tot = cnt.size(); // 记录种类数目
vector<int> res;
for (int l = 0, r = 0, satisfy = 0; r < s.size(); r ++ ) { // satisfy指当前窗口内部满足要求的种类数
if ( -- cnt[s[r]] == 0) satisfy ++ ;
if (r - l + 1 > p.size()) {
if (cnt[s[l]] == 0) satisfy -- ;
cnt[s[l ++ ]] ++ ;
}
if (satisfy == tot) res.push_back(l);
}
return res;
}
};
二、两个数组的交集
Leetcode 349
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set;
unordered_set<int> nums_set(nums1.begin(), nums1.end());
for (int x: nums2)
if (nums_set.find(x) != nums_set.end())
result_set.insert(x);
return vector<int>(result_set.begin(), result_set.end());
}
};
2.1 两个数组的交集 II
Leetcode 350
方法一:哈希表(适用于进阶问题二)
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
if (nums1.size() > nums2.size()) return intersect(nums2, nums1);
unordered_map<int, int> cnt; // 节省空间,只保存较小长度的数组
for (int x: nums1) cnt[x] ++ ;
vector<int> res;
for (int x: nums2)
if (cnt.count(x)) {
res.push_back(x);
cnt[x] -- ;
if (!cnt[x]) cnt.erase(x);
}
return res;
}
};
- 时间复杂度: O ( m + n ) O(m + n) O(m+n)
- 空间复杂度: O ( m i n ( m + n ) ) O(min(m +n)) O(min(m+n))
方法二:双指针(适用于进阶问题一)
先对数组排序,然后使用两个指针分别指向两个数组开始,如果两个指针指向元素不相等,将数值较小的指针右移。如果两个指针指向元素相同,则将该元素加入结果中,并同时右移两个指针。当至少有一个指针超出数组范围结束。
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
vector<int> res;
for (int i = 0, j = 0; i < nums1.size() && j < nums2.size();) {
if (nums1[i] == nums2[j]) {
res.push_back(nums1[i]);
i ++ , j ++ ;
} else if (nums1[i] < nums2[j]) i ++ ;
else j ++ ;
}
return res;
}
};
- 时间复杂度: O ( m l o g m + n l o g n ) O(m\ log\ m + n\ log\ n) O(m log m+n log n)
- 空间复杂度: O ( m i n ( m , n ) ) O(min(m,n)) O(min(m,n))
三、快乐数
Leetcode 202
提示:如果出现过的数字再重复出现就进入循环了
注意,最终结果会存在三种可能:
- 最终得到 1 1 1
- 最终进入循环
- 值会越来越大,接近无穷大
对于第三种情况,考虑每一位数的最大数字的下一个数(即每一位数平方之和)是多少。
几位数 | 最大数字 | 下一个数 |
---|---|---|
1 | 9 | 81 |
2 | 99 | 162 |
3 | 999 | 243 |
4 | 9999 | 324 |
13 | 9999999999999 | 1053 |
在三位数的情况下,其各位数的平方和不可能大于 243 243 243,要么被困在 243 243 243 以下的循环内,要么跌到 1 1 1。所以对于四位数或者四位以上的数字,其在每一步(各位数平方和)之后都会丢失一位,直到降到 3 3 3 位为止。所以可以发现第三种出现无穷大的情况使不可能的。
class Solution {
public:
int getSum(int n) {
int sum = 0;
while (n) {
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> st;
while (true) {
int sum = getSum(n);
if (sum == 1) return true;
if (st.find(sum) != st.end()) return false;
else st.insert(sum);
n = sum;
}
}
};
四、两数之和
Leetcode 1
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> mp;
for (int i = 0; i < nums.size(); i ++ ) {
auto it = mp.find(target - nums[i]);
if (it != mp.end()) return {it->second, i};
mp[nums[i]] = i;
}
return {};
}
};
五、四数相加 II
Leetcode 454
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> mp; // 记录数组1与数组2元素 (和,出现次数)
for (int a: nums1)
for (int b: nums2)
mp[a + b] ++ ;
int count = 0;
for (int c: nums3)
for (int d: nums4)
if (mp.find(0 - (c + d)) != mp.end())
count += mp[0 - (c + d)];
return count;
}
};
六、三数之和
Leetcode 15
这个题与上一题的区别在于,这个的结果不能重复。
如果使用哈希表,这个题目不好判重,会非常麻烦,细节很多。所以这里使用双指针的思路。
class Solution {
public:
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 (i && nums[i] == nums[i - 1]) continue;
if (nums[i] > 0) break;
for (int l = i + 1, r = nums.size() - 1; l < r; ) // 双指针
if (l < r && nums[l] + nums[r] + nums[i] > 0) r -- ;
else if (l < r && nums[l] + nums[r] + nums[i] < 0) l ++ ;
else {
res.push_back({nums[i], nums[l], nums[r]});
while (l < r && nums[l] == nums[l + 1]) l ++ ;
while (l < r && nums[r] == nums[r - 1]) r -- ;
l ++ , r -- ;
}
}
return res;
}
};
七、四数之和
Leetcode 18
这个题目和上一个题目类似,结果依然使不能重复的,唯一区别在于多了一层 for
循环。
注意四个数相加可能会溢出
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
vector<vector<int>> res;
for (int i = 0; i < nums.size(); i ++ ) {
if (i && nums[i - 1] == nums[i]) continue;
if (nums[i] >= 0 && nums[i] > target) break;
for (int j = i + 1; j < nums.size(); j ++ ) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
if (nums[i] + nums[j] >= 0 && nums[i] + nums[j] > target) break;
for (int l = j + 1, r = nums.size() - 1; l < r; ) // 双指针
if ((long long) nums[l] + nums[r] + nums[i] + nums[j] > target) r -- ;
else if ((long long) nums[l] + nums[r] + nums[i] + nums[j] < target) l ++ ;
else {
res.push_back({nums[i], nums[j], nums[l], nums[r]});
// 去重
while (l < r && nums[l] == nums[l + 1]) l ++ ;
while (l < r && nums[r] == nums[r - 1]) r -- ;
// 找到答案,同时更新
l ++ , r -- ;
}
}
}
return res;
}
};