一、哈希表理论基础
二、LeetCode 242. 有效的字母异位词
题目链接:242. 有效的字母异位词
文章讲解:代码随想录 (programmercarl.com)
视频讲解:
1、排序
代码
class Solution {
public:
bool isAnagram(string s, string t) {
// 检查两个字符串的长度是否相等,如果不相等,直接返回false,因为长度不同的字符串无法成为字母异位词
if (s.size() != t.size()) {
return false;
}
// 对字符串s和t进行排序,使得其中的字符按照字母顺序排列
sort(s.begin(), s.end());
sort(t.begin(), t.end());
// 比较排序后的两个字符串是否相等,如果相等,说明它们包含的字符种类和数量完全一样,只是字符的顺序不同,因此是字母异位词
return s == t;
}
};
复杂度分析
时间复杂度:O(nlogn),其中 n 为 s 的长度。排序的时间复杂度为 O(nlogn),比较两个字符串是否相等时间复杂度为 O(n),因此总体时间复杂度为 O(nlogn+n)=O(nlogn)。
空间复杂度:O(logn)。排序需要 O(logn) 的空间复杂度。
2、哈希表
代码
class Solution {
public:
bool isAnagram(string s, string t) {
// 检查两个字符串的长度是否相等,如果不相等,直接返回false,因为长度不同的字符串无法成为字母异位词
if (s.size() != t.size()) {
return false;
}
// 用于记录每个字母出现的次数
int record[26] = {0};
// 遍历字符串,每出现一次,对应位置的值加1
for (int i = 0; i < s.size(); ++i) {
record[s[i] - 'a']++;
}
// 遍历字符串,每出现一次,对应位置的值减1
// 如果在减1的过程中,对应位置的值小于0,说明字符串s和t不是字母异位词,直接返回false
for (int i = 0; i < t.size(); ++i) {
record[t[i] - 'a']--;
if (record[t[i] - 'a'] < 0) {
return false;
}
}
// 如果以上所有字符都成功完成了计数和减1操作,没有产生负值,说明字符串s和t是字母异位词,返回true
return true;
}
};
复杂度分析
时间复杂度:O(n),其中 n 为 s 的长度。
空间复杂度:O(S),其中 S 为字符集大小,此处 S=26。
三、LeetCode 349. 两个数组的交集
题目链接:349. 两个数组的交集
文章讲解:代码随想录 (programmercarl.com)
视频讲解:
1、使用set
代码
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
// 声明一个无序集合result_set,用于存储两个向量的交集元素
unordered_set<int> result_set;
// 将nums1向量转换为一个无序集合nums_set,这样可以更快地进行查找操作。其中使用begin()和end()函数来获取向量的起始和结束迭代器,以便将它们复制到nums_set中
unordered_set<int> nums_set(nums1.begin(), nums1.end());
// 遍历nums2中的每个元素,设其值为num
for (int num : nums2) {
// 检查num是否在nums_set中存在,如果存在则返回一个迭代器,否则返回end(),即指向nums_set的“末尾”(实际上是无效位置)。find()函数在哈希表中查找给定的值,如果不存在则返回end()
if (nums_set.find(num) != nums_set.end()) {
// 如果num存在于nums_set中,则将其添加到result_set中
result_set.insert(num);
}
}
// 返回result_set中的所有元素,作为一个新的整数向量。使用vector的构造函数,它接受两个迭代器(即result_set的开始和结束迭代器),并将它们复制到新的vector中
return vector<int>(result_set.begin(), result_set.end());
}
};
复杂度分析
- 时间复杂度: O(m + n)
- 空间复杂度: O(n)
2、使用数组
代码
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
// 声明一个无序集合result_set,用于存储两个向量的交集元素
unordered_set<int> result_set;
// 声明一个名为hash的数组,用于存储nums1中的元素及其对应的标记(存在或不存在)
int hash[1000] = {0};
// 遍历nums1中的每个元素,将元素的值作为索引,将该索引对应的标记设为1,表示该元素存在于nums1中
for (int num : nums1) {
hash[num] = 1;
}
// 遍历nums2中的每个元素,检查该元素是否存在于hash中,如果存在(即hash中对应索引的标记为1),则将该元素插入到result_set中
for (int num : nums2) {
if(hash[num] == 1) {
result_set.insert(num);
}
}
// 返回result_set中的所有元素,作为一个新的整数向量
return vector<int>(result_set.begin(), result_set.end());
}
};
复杂度分析
- 时间复杂度: O(m + n)
- 空间复杂度: O(n)
四、LeetCode 202. 快乐数
题目链接:202. 快乐数
文章讲解:代码随想录 (programmercarl.com)
视频讲解:
1、哈希法
代码
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) {
// 创建一个无序集合set,用于存储平方和,以便检查是否有重复
unordered_set<int> set;
int sum = 0;
// 无限循环,直到sum等于1或set中已有sum时停止
while (1) {
// 计算n的平方和
sum = getSum(n);
// 如果平方和sum等于1,说明是快乐数,返回true
if (sum == 1) {
return true;
}
// 如果set中已有sum,说明不是快乐数,返回false
if (set.find(sum) != set.end()) {
return false;
} else {
// 将新的平方和sum加入set中,并对n进行更新,为下一次循环做准备
set.insert(sum);
n = sum;
}
}
}
};
复杂度分析
- 时间复杂度: O(logn)
- 空间复杂度: O(logn)
2、双指针法
代码
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) {
// 计算n的平方和,赋值给slow,slow可以看作是n的移动轨迹或者说是“速度慢的点”
int slow = getSum(n);
// 计算slow的平方和,赋值给fast,fast可以看作是“速度快的点”或者说是指向slow的“指针”
int fast = getSum(getSum(n));
// 当fast不等于1且slow不等于fast时,执行下面的循环
while (fast != 1 && slow != fast) {
// slow的值加1,即求slow的平方和,相当于让slow这个“点”移动到新的位置
slow = getSum(slow);
// fast的值加1,即求fast的平方和,相当于让fast这个“指针”指向新的位置
fast = getSum(getSum(fast));
}
// 返回fast是否等于1的结果,即判断是否循环到最后得到1,如果是则返回true(真),否则返回false(假)
return fast == 1;
}
};
复杂度分析
- 时间复杂度:O(logn)
- 空间复杂度:O(1)
五、LeetCode 1.两数之和
题目链接:1. 两数之和
文章讲解:代码随想录 (programmercarl.com)
视频讲解:
1、暴力法
思路
枚举数组中所有的不同的两个下标的组合,逐个检查它们所对应的数的和是否等于target
代码
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int n = nums.size();
// 遍历数组nums,第一个循环变量i从0开始到n-2
for (int i = 0; i < n - 1; ++i) {
// 第二个循环变量j从i+1开始到n-1
for (int j = i + 1; j < n; ++j) {
// 检查nums[i]和nums[j]的和是否等于目标值target
if (nums[i] + nums[j] == target) {
// 如果和等于目标值,则返回一个包含i和j的向量
return {i, j};
}
}
}
// 如果在数组中找不到两个数的和等于目标值,则返回一个空向量
return {};
}
};
复杂度分析
时间复杂度:O(N^2),其中 N 是数组中的元素数量。最坏情况下数组中任意两个数都要被匹配一次。
空间复杂度:O(1)。
2、哈希表
思路
暴力法的时间复杂度较高的原因是寻找 target - x 的时间复杂度过高。因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找出它的索引。
使用哈希表,可以将寻找 target - x 的时间复杂度降低到从 O(N) 降低到 O(1)。
创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。
代码
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hashtable;
for (int i = 0; i < nums.size(); ++i) {
// 尝试在哈希表中查找键为target - nums[i]的元素,返回迭代器iter
auto iter = hashtable.find(target - nums[i]);
// 如果迭代器iter不等于hashtable的末尾(意味着找到了键为target - nums[i]的元素)
if (iter != hashtable.end()) {
// 返回一个包含iter->second和i的向量,即找到了两个数,它们的和等于目标值target
return {iter->second, i};
}
// 如果在哈希表中没有找到键为target - nums[i]的元素,则将nums[i]及其索引i添加到哈希表中
hashtable[nums[i]] = i;
}
// 如果遍历完整个数组nums都找不到两个数的和等于目标值target,则返回一个空向量
return {};
}
};
复杂度分析
时间复杂度:O(N)。
空间复杂度:O(N)。