【数据结构与算法】哈希表1:字母异位词 两数交集 快乐数 两数之和

news2024/11/15 19:29:11

文章目录

    • 今日任务
    • 1.哈希表理论基础
        • (1)哈希表
        • (2)哈希函数
        • (3)哈希碰撞
        • (4)链地址法(拉链法)
        • (5)线性探测法
        • (6)常见的三种哈希结构
        • (7)总结
    • 2.Leetcode242.有效的字母异位词
        • (1)题目
        • (2)思路
        • (3)代码实现
    • 3.Leetcode349. 两个数组的交集
        • (1)题目
        • (2)思路
        • (3)代码实现
    • 4.Leetcode202.快乐数
        • (1)题目
        • (2)思路
        • (3)代码实现
    • 5.Leetcode1.两数之和
        • (1)题目
        • (2)思路
        • (3)代码实现

今日任务

  • 哈希表理论基础

  • 242.有效的字母异位词

  • 349.两个数组的交集

  • 202.快乐数

  • 1.两数之和

1.哈希表理论基础

(1)哈希表

哈希表(Hash table,国内也有一些书籍翻译为散列表):是根据关键码的值而直接访问的数据结构。

最常见的哈希表例子就是数组。

哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素,如下图所示:

image-20230220102916613

那么哈希表一般适用于哪些场景呢?一般哈希表都是用来快速判断一个元素是否出现在集合里。

例如我们需要对指定商品信息进行查询,如果使用枚举的话,时间复杂度为O(n),但是如果我们选择使用哈希表,只需要O(1)就可以做到。

我们只需要初始化时将所有的商品名称存入哈希表,在查询的时候直接通过索引就可以知道该商品是否存在了。

这里将商品列表映射到哈希表上就涉及到哈希函数(Hash function)

(2)哈希函数

哈希函数,直接将商品的名称映射为哈希表上的索引,通过索引下标查询就可以知道该商品是否在售了。

哈希函数如下图所示,通过HashCode将名字转化为数值,一般HashCode是通过特定编码方式,可以将其他数据格式转化成不同的数值,这样就可以将商品名称映射到哈希表上的索引数字了。

image-20230220105717329

此时我们需要额外考虑一件事,如果通过hashCode得到的数值大于哈希表的大小,该怎么办?

为了保证映射出来的索引数值都落在哈希表上,我们会再一次对数值进行一个取模操作,这样我们就保证了商品名称就一定可以映射到哈希表上了。

此时由于哈希表本质上就是一个数组,如果商品的数量大于哈希表的大小该怎么办?哈希函数就算分的再均匀,也避免不了有几个商品名称同时映射到哈希表同一索引下标的位置。

这时候就需要引入哈希碰撞了。

(3)哈希碰撞

如下图所示,商品1和商品3都映射到索引1的位置上,这个现象称之为哈希碰撞

image-20230220112851251

对于哈希碰撞一般有两种解决方法:链地址法(拉链法)和线性探测法

(4)链地址法(拉链法)

这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。

由于商品1和商品3再索引2的位置发生了冲突,并且发生冲突的元素都被存储在链表中,这样我们就可以通过索引找到商品1和商品3了

image-20230220113841529

(5)线性探测法

使用线性探测法,一定要保证tableSize大于dataSize。我们需要依靠哈希表中的空位来解决碰撞问题。

例如索引1的位置已经存放了商品1的名称,那么当商品3再次进入索引1的位置就发生了冲突,当冲突发生后,就顺序查看表中的下一单元,直到找到一个空单元去存放商品3的名称。

image-20230220114854813

此外对于哈希碰撞的常用解决方法还有开放定址法、再哈希法、建立公共溢出区等等…

(6)常见的三种哈希结构

当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构:

  • 数组
  • set(集合)
  • map(映射)

数组在前面已经简单介绍了,此处不再赘述,我们看下set(集合):

set(集合)

在C++中,set和map分别提供以下三种数据结构,其底层优化以及优劣如下表所示:

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

map(映射)

映射底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
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)

(7)总结

当我们遇到这样一个场景:快速判断一个元素是否出现在集合里,就需要考虑哈希法。

但是哈希法的缺点也显而易见的:牺牲空间去换取时间

2.Leetcode242.有效的字母异位词

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/valid-anagram

(1)题目

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

注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

示例 1:

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

示例 2:

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

提示:

  • 1 <= s.length, t.length <= 5 * 104
  • s 和 t 仅包含小写字母

进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

(2)思路

前面我们讲了数组其实就是一个简单的哈希表,在本题中,我们可以定义一个数组,来记录字符串s中出现的字符次数。

由于都是字母,对应的也就是26个字符,所以这里我们设置的数组长度为26即可,并且初始化为0.

例如,我们对字符串s = “aee”, t = '“eae”,我们观察动画:

242.有效的字母异位词

我们定义一个record的数组来记录字符串s里所有字符出现的次数。

需要将字符映射到数组也就是哈希表的下标上,字符a映射为下标0,字符z映射为下标25。

在遍历字符串s的时候,只需要将s[i] = 'a’所在的元素作+1操作即可;同时在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再作-1操作;最后再检查一下,record数组如果有的元素不为0,那么就说明字符t和字符s一定不互为字母异位词,return false.

最后如果record数组所有元素都为0,则说明字符s和字符t是字母异位词,return true。

时间复杂度为O(n),空间上因为定义的是一个常量大小的辅助数组,所以空间复杂度为O(1)

(3)代码实现

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;
    }
};

3.Leetcode349. 两个数组的交集

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/intersection-of-two-arrays

(1)题目

给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

提示:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

(2)思路

在这道题目中,需要我们掌握哈希数据结构:unordered_set,如下图所示:

image-20230220175039323

题目中特别声明:输出结果的每个元素一定是唯一的,也就是说输出的结果不用对重复出现的元素输出,同时可以不考虑输出结果的顺序。

之所以这里不使用数组,是因为题目限制了数组的大小,并且如果哈希值比较少、特别分散、跨度大,使用数组就会造成空间的极大浪费。

所以结合std::unordered_set的无序性,查询效率和增删效率都是O(1)的情况下,果断使用unordered_set

image-20230220180154535

(3)代码实现

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
        unordered_set<int> nums_set(nums1.begin(), nums1.end());// 定义哈希表存放结果
        for (int num : nums2) {
            // 发现nums2的元素 在nums_set里又出现过
            if (nums_set.find(num) != nums_set.end()) { // 在nums1中查找num(nums2)
                result_set.insert(num);// 如果发现与nums(nums2)的元素,向result_set插入该元素
            }
        }
        return vector<int>(result_set.begin(), result_set.end());
    }
};

当然这道题也可以使用数组的方式进行求解:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
        int hash[1005] = {0}; // 默认数值为0
        for (int num : nums1) { // nums1中出现的字母在hash数组中做记录
            hash[num] = 1;
        }
        for (int num : nums2) { // nums2中出现话,result记录
            if (hash[num] == 1) {
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(), result_set.end());
    }
};

4.Leetcode202.快乐数

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/happy-number

(1)题目

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

**「快乐数」 **定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。

  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。

  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:

输入:n = 2
输出:false

提示:

  • 1 <= n <= 231 - 1

(2)思路

根据题目所给出的提示:重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。

简单解释下这句话,那么我们是不是可以理解为如果存在循环的数的话,那么这是不是就说明这个数不是开心数?

那么对于判断是否存在重复出现的数,我们选择使用哈希法,如果重复了的话就返回false,否则一直找到sum = 1为止。

(3)代码实现

class Solution {
public:
    // 取数值各个位上的单数平方之和
    int getSum(int n) {
        int sum = 0;
        while (n) {
            sum += (n % 10) * (n % 10); // n每位数的平方和
            n /= 10;
        }
        return sum;
    }
    bool isHappy(int n) {
        unordered_set<int> set;
        while(1) {
            int sum = getSum(n);
            if (sum == 1) {
                return true;
            }
            // 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
            if (set.find(sum) != set.end()) {
                return false;
            } else {
                set.insert(sum); // 记录第一次出现的数
            }
            n = sum;
        }
    }
};

5.Leetcode1.两数之和

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/two-sum

(1)题目

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

提示:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在一个有效答案

进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?

(2)思路

根据提示:只存在一个有效答案。所以我们这里可以选择unordered_map

接下来我们明确两点:

  • map用来做什么
  • map中key和value分别表示什么

拿target = 9举例子:map的目的是用来存取我们访问过的元素,当我们遍历数组的时候,需要我们记录之前遍历过哪些元素和对应的下标,首先先选定一个值(比如2),通过map查询是否存在与之满足条件的符合 因子(只能是7),此时如果在map中索引到该值,那么就得出我们想要的结果了;如果没有则继续选定下一个值,再去寻找与之相对应的符合因子。

所以在map中的存储结构为:{key:数据元素, value:数组元素对应的下标}

image-20230220210132750

image-20230220211643116

(3)代码实现

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++) {
            // 遍历当前元素,并在map中寻找是否有匹配的key
            auto iter = map.find(target - nums[i]); 
            if(iter != map.end()) {
                return {iter->second, i};
            }
            // 如果没找到匹配对,就把访问过的元素和下标加入到map中
            map.insert(pair<int, int>(nums[i], i)); 
        }
        return {};
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/359333.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python采集双色球数据,做数据分析,让我自己实现自己的富豪梦

来唠点嗑&#xff1f; 咳咳&#xff0c;最近是咋的了&#xff0c;某站掀起了一股双色球热潮&#xff1f;一般我自己的账号上&#xff0c;是很少看到关于python这些内容的&#xff0c;都是小姐姐和热梗&#xff0c;或者其他搞笑视频 由于&#x1f4b4;的吸引力…手不自觉的就点…

《系统架构设计》-03-软件结构体系和架构风格

文章目录1. 软件结构体系1.1 抽象&#xff08;Abstract&#xff09;1.1.1 抽象的应用1.1.2 不同层次的抽象1.2 组件&#xff08;Component&#xff09;1.2.1 定义1.2.2 切入点1.3 组织过程资产&#xff08;Organizational Process Assets&#xff09;1.3.1 定义1.3.2 作用1.4 体…

springboot整合Chat Generative Pre-trained Transformer

什么是Chat Generative Pre-trained Transformer Chat Generative Pre-trained Transformer&#xff0c;是以人工智能驱动的聊天机器人程序 &#xff0c;已经更新多个版本&#xff0c;很多大厂也都在接入其API。 整合难度 难度一颗星&#xff0c;基本上就是给官方API发请求&am…

特征工程:特征构造以及时间序列特征构造

数据和特征决定了机器学习的上限&#xff0c;而模型和算法只是逼近这个上限而已。由此可见&#xff0c;特征工程在机器学习中占有相当重要的地位。在实际应用当中&#xff0c;可以说特征工程是机器学习成功的关键。 那特征工程是什么&#xff1f; 特征工程是利用数据领域的相关…

UI自动化测试之设计框架

目的 相信做过测试的同学都听说过自动化测试&#xff0c;而UI自动化无论何时对测试来说都是比较吸引人的存在。 相较于接口自动化来说它可以最大程度的模拟真实用户的日常操作与特定业务场景的模拟&#xff0c;那么存在即合理&#xff0c;自动化UI测试自然也是广大测试同学职…

身为大学生,你不会还不知道有这些学生福利吧!!!!

本文介绍的是利用学生身份可以享受到的相关学生优惠权益&#xff0c;但也希望各位享受权利的同时不要忘记自己的义务&#xff0c;不要售卖、转手自己的学生优惠资格&#xff0c;使得其他同学无法受益。 前言 高考已经过去&#xff0c;我们也将迎来不同于以往的大学生活&#x…

磁盘结构

一.盘片 盘片是一个圆形坚硬的表面&#xff0c;通过引入磁性变化来永久存储数据&#xff0c;这些盘片通常由一些硬质材料&#xff08;如铝&#xff09;制成&#xff0c;然后涂上薄薄的磁性层&#xff0c;即使驱动器断电&#xff0c;驱动器也能持久存储数据位。每个盘片有两面&a…

袋鼠云高教行业数字化转型方案,推进数字化技术和学校教育教学深度融合

在当前的数字化转型浪潮下&#xff0c;“基础设施、配套设备、应用探索”的数字校园1.0阶段即将步入尾声、亦或已经完结&#xff0c;不同地区和类型的高校通过各类信息化系统和基础设施已经初步实现了业务数字化&#xff0c;整个数字校园的信息基础设施底座已有一定基础、信息时…

TCP编程之网卡信息获取和域名解析

TCP编程之网卡信息获取和域名解析 1.TCP/IP简介 TCP/IP协议源于1969年&#xff0c;是针对Internet开发的一种体系结构和协议标准&#xff0c;目的在于解决异种计算机网络的通信问题。使得网络在互联时能为用户提供一种通用、一致的通信服务。是Internet采用的协议标准。   …

三菱PLC的MC协议配置说明

三菱PLC的MC协议配置说明先说一下弱智的踩坑记录详细配置过程1、三菱Q02H CPUQJ71E71-100以太网模块设置MC协议1.1 PLC编程线连接与编程线驱动安装1.2 PLC通讯测试1.3 PLC MC协议设置1.4 PLC断点重启1.5 网络调试助手测试2、三菱Q03UDE CPU内置以太网设置MC协议2.1 PLC编程线连…

决策树算法和CART决策树算法详细介绍及其原理详解

相关文章 K近邻算法和KD树详细介绍及其原理详解朴素贝叶斯算法和拉普拉斯平滑详细介绍及其原理详解决策树算法和CART决策树算法详细介绍及其原理详解 文章目录相关文章前言一、决策树算法二、CART决策树算法2.1 基尼系数2.2 CART决策树算法总结前言 今天给大家带来的主要内容包…

虹科分享 | 网络流量监控 | 你的数据能告诉你什么:解读网络可见性的4种数据类型

要了解网络性能问题的原因&#xff0c;可见性是关键。而这四种数据类型&#xff08;流、数据包、SNMP和API&#xff09;都在增强网络可见性方面发挥着重要作用。 流 流是通过网络发送的数据的摘要。流类型不同&#xff0c;可以包括NetFlow, sFlow, jFlow和IPFIX。不同的流类型…

SPF动物实验室设计,SPF动物实验室装修SICOLAB

SPF&#xff08;特殊病原体自由&#xff09;动物实验室规划设计SICOLAB&#xff08;一&#xff09;设计原则为了建造一座SPF&#xff08;特殊病原体自由&#xff09;动物实验室&#xff0c;需要采取以下步骤&#xff1a;&#xff08;1&#xff09;选址&#xff1a;选择远离其他…

lombok注解@Data使用在继承类上时出现警告解决方案

lombok为我们提供了Data注解&#xff0c;帮助我们省略了Setter,Getter,ToString等注解&#xff0c;一般对于普通的实体类使用该注解&#xff0c;不会出现什么问题&#xff0c;但是当我们把这个注解&#xff0c;使用在派生类上&#xff0c;就出现了一个警告1 情景再现父类:Data …

SESAM 安装教程

SESAM &#xff08;Super Element Structure Analysis Module&#xff09;是由挪威船级社&#xff08;DNV-GL&#xff09;开发的一款有限元分析&#xff08;FEA&#xff09;系统&#xff0c;它以 GeniE、HydroD 和 DeepC 等模块为核心&#xff0c;主要用于海工结构的强度评估、…

leaflet 绘制两个多边形的交集、差集、并集(083)

第083个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中如何获取两个多边形的交集、差集、并集。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共148行)安装插件相关API参考:专栏目标示例效果 配…

Python日期时间模块

Python 提供了 日期和时间模块用来处理日期和时间&#xff0c;还可以用于格式化日期和时间等常见功能。 时间间隔是以秒为单位的浮点小数。每个时间戳都以自从 1970 年 1 月 1 日午夜&#xff08;历元&#xff09;经过了多长时间来表示。 一、time模块使用 Time 模块包含了大…

使用ribbon实现负载均衡

1.新建两个provider&#xff1a;springcloud-provider-dept-8002 2. 配置跟8001一样 整合 Ribbon 由上述可知&#xff0c;Ribbon 是需要集成在消费端的 所以在消费端 &#xff1a; springcloud-03-consumer-dept-8082 进行修改 在 POM 文件中添加 Ribbon、Eureka 依赖 <!--…

docker swarm 集群服务编排部署指南(docker stack)

Docker Swarm 集群管理 概述 Docker Swarm 是 Docker 的集群管理工具。它将 Docker 主机池转变为单个虚拟 Docker 主机&#xff0c;使得容器可以组成跨主机的子网网络。Docker Swarm 提供了标准的 Docker API&#xff0c;所有任何已经与 Docker 守护程序通信的工具都可以使用…

【Java|golang】2347. 最好的扑克手牌---桶排序

给你一个整数数组 ranks 和一个字符数组 suit 。你有 5 张扑克牌&#xff0c;第 i 张牌大小为 ranks[i] &#xff0c;花色为 suits[i] 。 下述是从好到坏你可能持有的 手牌类型 &#xff1a; “Flush”&#xff1a;同花&#xff0c;五张相同花色的扑克牌。 “Three of a Kind…