代码随想录算法训练营第5天|哈希表part01

哈希表part01

  • 哈希表理论基础
  • 242.有效的字母异位词
  • 349. 两个数组的交集
  • 202. 快乐数
  • 1. 两数之和

哈希表理论基础

建议:大家要了解哈希表的内部实现原理,哈希函数,哈希碰撞,以及常见哈希表的区别,数组,set 和map。

哈希函数通过hashCode把索引转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把数据映射为哈希表上的索引数字了。哈希碰撞就是对应的多个数据要放在一个位置上。

保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,这样我们就保证了学生姓名一定可以映射到哈希表上了。如果取模相同的值,就要进行拉链法和线性探测法。拉链法是取模的数值后面用一个链表存储一系列数据。线性探测法就是在取模冲突后,放在后面的空间中

什么时候想到用哈希法,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。 这句话很重要,大家在做哈希表题目都要思考这句话。

数组就是一张哈希表,一般哈希表都是用来快速判断一个元素是否出现集合里。要枚举的话时间复杂度是O(n),但如果使用哈希表的话, 只需要O(1)就可以做到。

集合底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
std::set红黑树有序O(log n)O(log n)
std::multiset红黑树有序O(logn)O(logn)
std::unordered_set哈希表无序O(1)O(1)
映射底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
std::map红黑树key有序key不可重复key不可修改O(logn)O(logn)
std::multimap红黑树key有序key可重复key不可修改O(log n)O(log n)
std::unordered_map哈希表key无序key不可重复key不可修改O(1)O(1)

无序map的底层实现是哈希表,key值无序不可重复,也不可修改,牺牲了顺序,换来了删改查的效率,基于红黑树的,有序的,查改删的效率低点,multi的特点就是key有序,数值可以重复还算比较好记。

文章讲解:代码随想录

242.有效的字母异位词

力扣题目链接(opens new window)

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

示例 1: 输入: s = "anagram", t = "nagaram" 输出: true

示例 2: 输入: s = "rat", t = "car" 输出: false

说明: 你可以假设字符串只包含小写字母。

建议: 这道题目,大家可以感受到 数组 用来做哈希表 给我们带来的遍历之处。

class Solution {
public:
    bool isAnagram(string s, string t) {
int record[26]={0};
 for (int i = 0; i < s.size(); i++) {
            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) {  
                return false;
            }
        }
    return true;
    }
};

不得不说数组也是哈希表,确实思路很清晰,最开始想得是两个数组,一个记录字符串t,一个记录s,最后比较,但是发现如果记录t,然后数组加,记录s,数组减。很好的思路呢,我们只是进行比较结果,不需要知道各个字符多少。

题目链接/文章讲解/视频讲解: 代码随想录

349. 两个数组的交集

建议:本题就开始考虑 什么时候用set 什么时候用数组,本题其实是使用set的好题,但是后来力扣改了题目描述和 测试用例,添加了 0 <= nums1[i], nums2[i] <= 1000 条件,所以使用数组也可以了,不过建议大家忽略这个条件。 尝试去使用set。

set确实挺好用的,

题目链接/文章讲解/视频讲解:代码随想录

定义一个数组:定义 set 时直接初始化它,使用大括号 {} 包围已知元素。
插入元素:使用 `insert()` 函数向Set中添加元素。例如 mySet.insert(10);` 可以将数字 10 添加到Set中。
删除元素:使用 `erase()` 函数可以从Set中删除元素。例如 `mySet.erase(10);` 会删除Set中值为10的元素。
使用 `find()` 函数可以检查Set中是否存在某个元素。例如 `set<int>::iterator it = mySet.find(10);` 如果找到该元素,它会返回一个指向该元素的迭代器,否则返回 end() 迭代器。
访问元素:通过迭代器可以访问Set中的元素。例如,使用 `for` 循环和迭代器可以遍历并访问Set中的每个元素。
获取Set大小:使用 `size()` 函数可以获取Set中当前存储的元素数量,例如 `int setSize = mySet.size();。
清空Set:使用 `clear()` 函数可以清空整个Set,移除所有元素[^1^][^4^]。
判断Set是否为空:使用 `empty()` 函数可以检查Set是否为空,如果没有任何元素则返回 true。
获取最大可能的元素个数:使用 `max_size()` 函数可以返回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());

    }
};

语言还是比较简单的,主要还是学习一些关于set的用法,在这题中,不涉及排序的问题,所以还是很适合unordered_set的增改查的时间利用率比较高。

nums_set(nums1.begin(), nums1.end());这句话可以用来定义一个基于vector的set;

for (int num : nums2)可以用来遍历一个数组vector;

使用 `find()` 函数可以检查Set中是否存在某个元素。例如 `set<int>::iterator it = mySet.find(10);` 如果找到该元素,它会返回一个指向该元素的迭代器,否则返回 end() 迭代器。

202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为  1,那么这个数就是快乐数。

如果 n 是快乐数就返回 True ;不是,则返回 False 。

示例:

输入:19
输出:true
解释:
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1

建议:这道题目也是set的应用,其实和上一题差不多,就是 套在快乐数一个壳子

如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false

题目链接/文章讲解:代码随想录

class Solution {
public:
int getSum(int n){
    int sum=0;
    while(n!=0){
    sum+=(n%10)*(n%10);
    n/=10;
    }
    return sum;
}
    bool isHappy(int n) {
        std::unordered_set<int> set1;
         int sum=n;
            while(sum!=1){
            if(set1.find(sum)!=set1.end())
             return false;
              set1.insert(sum);
              sum=getSum(sum);
        }
         return true;
    }
};

看着非常难得一题,但是细细想来没想到这么简单,主要就是考察数组的使用。最重要的是一直判断就行了,我还在想如果是一个很大的数,时间复杂度会很高,但是仔细想想,在一次次循环过程中,每位的平方最大只有九九八十一。即便有几十位长的数,一次循环后,最多也只有几十*81<几万,变为五位数了。while只要不断循环,实际上,最后循环的次数并不很大。就回到一个范围,然后陷入死循环,用set判断是否循环,就完成任务了呢。

1. 两数之和

建议:本题虽然是 力扣第一题,但是还是挺难的,也是代码随想录中数组,set之后,使用map解决哈希问题的第一题。

建议大家先看视频讲解,然后尝试自己写代码,在看文章讲解,加深印象。

这题如果使用暴力法,也挺简单的,但是这题主要是学习下如何使用map

题目链接/文章讲解/视频讲解:代码随想录

首先我再强调一下 什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。核心就是查找

本题呢,我就需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是是否出现在这个集合。那么我们就应该想到使用哈希法了。

因为本题,我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。所以要考虑自己究竟是查的是什么,查的究竟是key还是value;原本最开始的时候想得是用set来解决题目,思路和上一题有点类似。但是考虑到数组不能有键值key,确实不能采用。

再来看一下使用数组和set来做哈希法的局限。

  • 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
  • set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。

再学习下map的用法中的常用操作

定义一个确定的map:std::map<int, std::string> myMap = { {1, "One"}, {2, "Two"}, {3, "Three"}, {4, "Four"}, {5, "Five"} };
插入元素:
   std::map<int, std::string> m;
   m.insert({1, "One"});  // 插入键值对 {1, "One"}
   m[2] = "Two";          // 通过下标运算符插入或更新键值对 {2, "Two"}
   ```

查找元素:
   auto it = map.find(1);  // 查找键为 1 的元素
   if (it != map.end()) {
       std::cout << "Found: " << it->second << std::endl;
   }

it->first和it->second分别是map的key和value;所以这题在写的时候要注意一定是value值。

删除元素
   m.erase(1);  // 删除键为 1 的元素
遍历元素
   for (const auto &pair : m) {
       std::cout << pair.first << ": " << pair.second << std::endl;
   }

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    std::unordered_map<int, int> map1;
    for(int i=0;i<nums.size();i++)
    if (map1.find(target-nums[i])!=map1.end())
    return {map1.find(target-nums[i])->second,i};
    else{
        map1[nums[i]] = i;
        }
    return {};
    }
};

本题还是比较简单的,先定义一个unordered_map,存放以数值为key,以下标为value,一个个的遍历查找,没查到和为目标值的就导入进去map,查到了就返回结果下标。另外要注意下数组的空集直接{}即可。

代码随想录里的代码是直接插在入,map.insert()函数需要传入一个键值对对象作为参数,而不是两个单独的参数。所以最开始写的时候总是报错。 这么写代码map.insert(pair<int, int>(nums[i], i)); 或者直接更简单的写法:map.insert({nums[i], i});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值