哈希表(Hash Table)也叫散列表,通过关键码的值直接访问的数据结构,查询效率高;
index = hashFunction(name)
hashFunction = hashCode(name) % tableSize
需要做映射的数据大于哈希表,会出现哈希碰撞,解决方法:1.发生冲突元素存在链表2.线性探测:既表的长度大于数据长度,利用空位解决碰撞问题。
常见三种哈希结构:
数组,集合,映射
1.异位词
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
slist = list(s)
tlist = list(t)
nums = [0]*26
for i in slist:
nums[ord(i)-97] += 1
for j in tlist:
nums[ord(j)-97] -= 1
nums = [abs(x) for x in nums]
return False if (sum(nums))!=0 else True
C++版本代码同理,但是需要注意的是直接通过字符相减即可 ,这是因为在 C++ 中,字符类型本质上是一个整数类型,字符的 ASCII 值可以用来计算索引。
class Solution {
public:
bool isAnagram(string s, string t) {
int record[26] = {0};
for (int i = 0; i < s.size(); i++) {
// 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
record[s[i] - 'a']++;
}
for (int i = 0; i < t.size(); i++) {
record[t[i] - 'a']--;
}
for (int i = 0; i < 26; i++) {
if (record[i] != 0) {
// record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return false;
}
}
// record数组所有元素都为零0,说明字符串s和t是字母异位词
return true;
}
};
2.两个数组交集
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1 = set(nums1)
nums2 = set(nums2)
result = list(nums1 & nums2)
return result
C++的话,要考虑的是使用哪一种表,std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。
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 num:nums2){
if(nums_set.find(num)!=nums_set.end()){
result_set.insert(num);
}
}
return vector<int>(result_set.begin(),result_set.end());
}
};
nums_set.find(num)
:
find
是unordered_set
类的一个成员函数,用于查找集合中是否存在某个特定的元素。- 如果集合中包含
num
,则find
函数会返回一个指向num
的迭代器。 - 如果集合中不包含
num
,则find
函数会返回一个指向集合末尾(即nums_set.end()
)的迭代器。
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> result;
while(1){
int sum = getSum(n);
if(sum==1){
return true;
}
if(result.find(sum)!=result.end()){
return false;
}
else{
result.insert(sum);
}
n = sum;
}
}
};
思路一样,但是注意C++中/可以判断两边类型,都是整形最后得到的也是
python中用//表示整除,/表示除
class Solution:
def isHappy(self, n: int) -> bool:
record = set()
while(1):
n = self.getSum1(n)
if(n==1):
return True
if n in record:
return False
else:
record.add(n)
def getSum1(self, n:int)->int:
sum1 = 0
while(n):
sum1 += (n%10)**2
n = n // 10
return sum1
4.两数之和
这到底也是查询一个元素是否出现,可以使用hash,由于需要返回出现的元素值和索引,也就是需要元素值和索引对应起来,根据三种类型的hash,map是最符合的,将元素值作为key,然后将索引作为value。遍历数组中的值,通过target-nums[i],然后再去map中找,如果存在就返回索引。
值得注意的一点是,不要在一开始就把所有的数据和对应索引值放到字典中去,否则会造成相同的数据,但是最后索引只有一个。每次都去比较之前的数据,如果是两个重复的值相加而得到的数据的话,不会造成覆盖。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
map1 = {}
for id,i in enumerate(nums):
if(target-i in map1):
return [id, map1[target-i]]
map1[i] = id
return []
sol = Solution()
nums = [3,3]
target =6
print(sol.twoSum(nums,target))
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::unordered_map<int,int> map;
for(int i =0;i<nums.size();i++){
auto iter = map.find(target-nums[i]);
if(iter!=map.end()){
return {iter->second, i};
}
map.insert(pair<int,int>(nums[i],i));
}
return {};
}
};
auto iter = map.find(target - nums[i]);
:
auto
是一种类型推导,iter
作为一个迭代器,它的具体类型由map.find()
返回的类型决定。对于unordered_map<int, int>
,iter
的类型是std::unordered_map<int, int>::iterator
。
return {iter->second, i};
:
iter->second
表示找到的目标元素在map
中对应的值,即数组中之前的元素的索引。iter->second
通过箭头运算符->
访问unordered_map
元素的second
成员。unordered_map
中的元素是键值对(pair
),其中first
是键,second
是值。- 该行代码返回的是一个包含两个整数的
vector
,这两个整数分别是两个符合条件的数组元素的索引。
map.insert(pair<int, int>(nums[i], i));
:
- 这行代码将当前数组元素及其索引作为键值对插入到
map
中。
5.四数相加
实际上就是先将两两记录成一个map,然后两两相加,将问题简化。
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int> map1;
for(int i=0;i<nums1.size();i++){
for(int j=0;j<nums2.size();j++){
map1[nums1[i]+nums2[j]]++;
}
}
int count = 0;
for(int i=0;i<nums3.size();i++){
for(int j=0;j<nums4.size();j++){
if(map1.find(0-(nums3[i]+nums4[j]))!=map1.end()){
count += map1[0-(nums3[i]+nums4[j])];
}
}
}
return count;
}
};
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
map1 = dict()
for i in nums1:
for j in nums2:
if i+j in map1:
map1[i+j] += 1
else:
map1[i+j] = 1
count=0
for i in nums3:
for j in nums4:
if (0-(i+j)) in map1:
count += map1[-(i+j)]
return count
5.赎金信
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
record = [0]*26
ran = list(ransomNote)
mag = list(magazine)
for i in mag:
record[ord(i)-ord('a')] += 1
for j in ran:
record[ord(j)-ord('a')] -= 1
for i in record:
if(i<0):
return False
return True
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int record[26] = {0};
if(ransomNote.size()>magazine.size()){
return false;
}
for(int i=0;i<magazine.length();i++){
record[magazine[i]-'a']++;
}
for(int j=0;j<ransomNote.size();j++){
record[ransomNote[j]-'a']--;
if(record[ransomNote[j]-'a']<0){
return false;
}
}
return true;
}
6.三数之和:开始梦碎
比较疑惑的一点就是在去重,对于a的去重,考虑的是i-1,对于-1-12,这种情况如果i对应第二个-1,那么left一定在i的右边,想要得到一个重复的元组,唯一的可能就是right--到第一个-1,但是后面给出的条件已经是right>left,所以不可能实现,那么也就不可能出现重复的情况.
仔细想想,他只是实现了对i去重的功能,这就是仅仅针对i不能重复。如果前面一个和当前相同,说明这个i值已经用过了,后面一旦能够出现使三数和为0的,那么这两个就是重复的,所以要判断前一个是否相同,不同则是i第一次使用这个值,否则就是第二次。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++){
if(nums[i]>0){
return result;
}
if(i>0 && nums[i]==nums[i-1]){
continue;
}
int left = i+1;
int right = nums.size()-1;
while(right>left){
if(nums[i]+nums[left]+nums[right]>0) right--;
else if(nums[i]+nums[left]+nums[right] <0) left++;
else{
result.push_back(vector<int>{nums[i],nums[left],nums[right]});
while(right>left && nums[right]==nums[right-1]) right--;
while(right>left && nums[left]==nums[left+1]) left++;
right--;
left++;
}
}
}
return result;
}
};
今天清醒了一点,三数之和的主要做法用的是双指针,先固定i作为一个循环,然后left和right分别从i后面和数组末尾开始收缩。由于一开始已经做了排序操作,所以需要判断第一个数是否>0,然后开始接下来的操作,注意的是在结果进行去重,那么首先就是在里面对a,b,c这三个元素进行去重,先对a,昨天已经理解了,就是因为第一个使用这个a,那么后面的left和right指向的值的和就能够满足三者之和为0,此时如果下一个还是和a一样的值,很明显就会产生重复,那么现在就只需要取这个值第一次出现的即可。对于b,c既left和right 同理。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
for i in range(len(nums)):
if nums[i] > 0:
return result
if i > 0 and nums[i]==nums[i-1]:
continue
left = i+1
right = len(nums) - 1
while right > left:
sum_ = nums[i] + nums[left] + nums[right]
if sum_<0:
left+=1
elif sum_>0:
right-=1
else:
result.append([nums[i],nums[left],nums[right]])
while right > left and nums[right] == nums[right-1]:
right -= 1
while right > left and nums[left] == nums[left+1]:
left += 1
right -=1
left +=1
return result
7.四数之和
相比于三数之和,差别在于外面多加了一层循环,这层循环里面是三数之和。但是对于剪枝还是有一点细微差别。由于此时目标值是target,不再是0.所以对排序后第一个值的判断不同了,不仅需要大于target还需要大于0,才能跳出,之后对于去重还是和三数之和一样判断这个数是不是第一次出现,是否和前面相同了
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
sort(nums.begin(),nums.end());
for(int k=0;k<nums.size();k++){
if(nums[k]>target && nums[k]>=0){
break;
}
if(k>0 && nums[k]==nums[k-1]){
continue;
}
for(int i=k+1;i<nums.size();i++){
if(nums[k]+nums[i]>target && nums[k]+nums[i]>=0){
break;
}
if(i>k+1 && nums[i]==nums[i-1]){
continue;
}
int left = i + 1;
int right = nums.size()-1;
while(right>left){
if((long) nums[k] + nums[i] + nums[left] + nums[right] > target){
right--;
}
else if((long) nums[k] + nums[i] + nums[left] + nums[right] < target){
left++;
}
else{
result.push_back(vector<int>{nums[k],nums[i],nums[left],nums[right]});
while(right>left && nums[right]==nums[right-1]) right--;
while(right>left && nums[left]==nums[left+1]) left++;
right--;
left++;
}
}
}
}
return result;
}
};
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
result = []
nums.sort()
for i in range(len(nums)):
if(nums[i]>0 and nums[i]>target):
break
if(i>0 and nums[i]==nums[i-1]):
continue
for j in range(i+1,len(nums)):
if(nums[i]+nums[j]>0 and nums[i]+nums[j]>target):
break
if(j>i+1 and nums[j]==nums[j-1]):
continue
left = j+1
right = len(nums)-1
while(left<right):
if nums[i]+nums[j]+nums[left]+nums[right] < target: left += 1
elif nums[i]+nums[j]+nums[left]+nums[right] > target: right -= 1
else:
result.append([nums[i],nums[j],nums[left],nums[right]])
while left<right and nums[right]==nums[right-1]: right-=1
while left<right and nums[left]==nums[left+1]: left+=1
right-=1
left+=1
return result