今天写了 存在重复元素 的两道题,附加原来写过的一道题"两数之和",初步学习到了哈希表/哈希集合的使用,后面会通过做相关的题目熟悉这种方法的。
一.存在重复元素 ★☆☆☆☆
题目
217. 存在重复元素 给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
思路1
先对数组进行排序,使得相等的元素相邻。随后遍历数组,只有有相邻的数字相等就满足题目要求,返回true;反之,循环结束没有返回true,就是没有元素相等,返回false。
代码
cpp
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
//先将数值进行排序
//遍历数组,如果前后元素相等直接返回true
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size()-1;i++){
if(nums[i]==nums[i+1]){
return true;
}
}
return false;
}
};
复杂度
时间复杂度:O(nlogn),其中 n 为数组的长度。需要对数组进行排序。
空间复杂度:O(logn),其中 n 为数组的长度。这里需要考虑递归调用栈的深度。
官方题解------哈希集合
核心思路:利用「哈希表 / 哈希集合」的「快速查找特性」,将判断 "元素是否已出现" 的时间从 O (n) 降到 O (1)。
代码
cpp
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_set<int> s;
for(int x:nums){
//s.find(x):返回指向 x 的迭代器;若没找到,返回 s.end()(集合末尾标记)
if(s.find(x) != s.end()){
//找到了,说明有重复元素,返回false
return true;
}
//没找到,将x插入哈希表中
s.insert(x);
}
return false;
}
};
复杂度
时间复杂度:O(n),其中 Nn为数组的长度。
空间复杂度:O(n),其中 Nn为数组的长度。
二.存在重复元素Ⅱ ★☆☆☆☆
题目
219. 存在重复元素 II 给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。
思路1------暴力法
利用两次循环,第一层固定一个元素 nums[i],第二层元素遍历 i 索引之后 len之前、<=(k-i)范围内的元素,有相等的数就返回true,没找到就移动 i,继续寻找。最后两层循环结束没有找到就返回false
代码
cpp
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
int len=nums.size();
//j设为i+k,然后向前移动
int i=0;
int j=1;
while(i<len-1){
while(j<=i+k && j<len){
//有相等元素返回true
if(nums[i]==nums[j]){
return true;
}
//不相等,向后移动j
j++;
}
//移动i,继续找和nums[i]相等的元素
i++;
j=i+1;
}
return false;
}
};
复杂度
时间复杂度:O(n×k)。外层循环次数 × 内层循环次数 = (len-1) × k ≈ n×k。
空间复杂度:O(n)。只有变量,没有哈希表和数组等动态分配的空间。
思路2------哈希表 ★★★★☆
用unordered_map<int, int>创建一个哈希表s,s中有两个元素key和value。key表示s中的元素,value表示key这个元素对应的"索引"。所以可以用curNum表示当前的数字nums[i],当s.find(curNum) != s.end()时,即哈希表中有curNum这个元素时,找出s[curNum]的值,即其对应的索引,用 i - s[curNum]与k比较,若<=k,就满足题目要求,输出true。反之在哈希表中不存在这个元素或者满足的元素的索引相差>k,需要将元素存入哈希表或者更新对应元素的索引,使得比较的索引始终时最大的,才能更好地满足 i - j <= k这个条件。
代码
cpp
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
int len=nums.size();
//创建一个哈希表,其中s[x]=y指的是x元素的索引为y
unorder_map<int,int> s;
for(int i=0;i<len;i++){
int curNum=nums[i];
if(s.find(curNum) != s.end()){
int k0=i-s[curNum[i]];
if(k0 <= k){
return true;
}
}
s[curNum[i]]=i;
}
return false;
}
};
时间复杂度
时间复杂度:O(n),其中 n 是数组 nums 的长度。遍历数组一次+哈希表的操作时间。
空间复杂度:O(n),其中 n 是数组 nums 的长度。哈希表 s 的空间消耗取决于存储的键值对数量。最坏情况:数组中所有元素都不重复,哈希表会存储 n 个键值对(每个元素对应一个索引),空间复杂度 O (n);最好情况:数组前两个元素就重复,哈希表仅存储 1 个键值对,空间复杂度 O (1)。算法的空间复杂度按「最坏情况」计算,因此是 O (n)。
三.两数之和 ★★☆☆☆☆
题目
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
思路
跟第二题类似的思路,利用哈希表。遍历数组,如果在哈希表中能找到元素和nums[i]之和,就将两数的索引记录下来,最后返回即可。如果没找到就将当前的数存入哈希表。
代码
cpp
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int len=nums.size();
unordered_map<int,int> s;
int res1=0,res2=0;
for(int i=0;i<len;i++){
int curNum=nums[i];
if(s.find(target-curNum) != s.end()){
res1=i;
res2=s[target - curNum];//另一个数的索引
break;//记得返回
}
s[curNum]=i;
}
return {res1,res2};
}
};
时间复杂度
时间复杂度:O(n)
空间复杂度:O(n)
总结------哈希表/哈希集合(来源于豆包)
1.定义
哈希表
一种存储键值对(key-value pair)的无序关联容器
- 每个元素由「键(key)」和「值(value)」两部分组成,键(key)是唯一的(不能重复),值(value)可重复且可存储任意类型数据;
- 底层通过哈希函数将「键(key)」映射到内存中的哈希桶索引,通过键可快速定位到对应的值,实现 "键→值" 的一对一关联映射。
哈希集合
一种存储唯一元素的无序容器
- 仅存储单个类型的元素(无键值对之分),元素本身具有唯一性(重复元素无法插入);
- 底层同样通过哈希函数将「元素本身」映射到哈希桶索引,核心作用是判断元素是否存在,以及实现元素去重
2.核心操作
哈希表
cpp
#include <unordered_map>
using namespace std;
unordered_map<int, int> map; // 定义:key=int,value=int
// 1. 插入/更新(最常用)
map[key] = value; // 若 key 不存在则插入,存在则覆盖旧 value
map.insert({key, value}); // 若 key 不存在则插入,存在则插入失败(不覆盖)
map.emplace(key, value); // 高效插入(C++11+),同 insert 逻辑(不覆盖)
// 2. 查找
if (map.find(key) != map.end()) { // 查找 key 是否存在
int val = map[key]; // 访问 value
// 或通过迭代器访问:auto it = map.find(key); int val = it->second;
}
// 3. 删除
map.erase(key); // 通过 key 删除键值对,成功返回 1,失败返回 0
// 4. 遍历
for (auto& pair : map) { // 范围 for 循环(推荐)
int key = pair.first;
int val = pair.second;
}
哈希集合
cpp
#include <unordered_set>
using namespace std;
unordered_set<int> set; // 定义:存储 int 类型元素
// 1. 插入
set.insert(val); // 若 val 不存在则插入,存在则插入失败
set.emplace(val); // 高效插入(C++11+),同 insert 逻辑
// 2. 查找
if (set.find(val) != set.end()) { // 判断 val 是否存在
// 存在逻辑
}
// 3. 删除
set.erase(val); // 通过元素值删除,成功返回 1,失败返回 0
// 4. 遍历
for (auto& val : set) { // 范围 for 循环(推荐)
// 访问元素 val
}
3.适用场景
