【算法学习】哈希表篇:哈希表的使用场景和使用方法

算法学习:

https://blog.csdn.net/2301_80220607/category_12922080.html?spm=1001.2014.3001.5482

前言:

在之前学习数据结构时我们就学习了哈希表的使用方法,这里我们主要是针对哈希表的做题方法进行讲解,都是leetcode上的经典题,各位可以自己做一遍再来看一下,主要抽取了几个经典的题

目录

[1. 哈希表的基础知识](#1. 哈希表的基础知识)

[1.1 哈希表是什么](#1.1 哈希表是什么)

[1.2 哈希表的作用](#1.2 哈希表的作用)

[1.3 什么时候用哈希表](#1.3 什么时候用哈希表)

[1.4 哈希表的应用场景](#1.4 哈希表的应用场景)

[2. 哈希表经典例题](#2. 哈希表经典例题)

[2.1 两数之和](#2.1 两数之和)

[2.2 存在重复元素||](#2.2 存在重复元素||)

[2.3 字母异位词分组](#2.3 字母异位词分组)

[3. 总结](#3. 总结)


1. 哈希表的基础知识

1.1 哈希表是什么

简单点来说哈希表就是一个存储数据的容器,但是能够对出现的数据进行标记

1.2 哈希表的作用

能够实现快速查找指定的数据,时间复杂度往往为O(1)

1.3 什么时候用哈希表

频繁的查找数据时

1.4 哈希表的应用场景

  1. 直接使用哈希表这个数据类型

  2. 在合适的场景使用数组来模拟哈希表

解释:

2、3:当我们需要频繁查找数据的时候我们就可以想到要用哈希表,哈希表的时间复杂度为o(1),空间复杂度为o(n)同时也要考虑是否可以用二分,二分时间复杂度为o(logn),也非常快,而且二分没有时间开销

4:除了之间使用函数提供给我们的哈希表外,我们也可以用数组模拟创建一个简易的哈希表,但这种只适用于元素少的情况,比如让我们统计字符串中各字符出现次数,我们就可以用数组 int hash[26]模拟一下

2. 哈希表经典例题

2.1 两数之和

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
  • 只会存在一个有效答案

算法原理:

解法一:暴力枚举

在这个暴力枚举中我们来考虑一种新的枚举思路

比如本题,按照之前的枚举策略,我们是要让当前位置(cur)与后面位置的数字依次相加,然后看是否有满足条件的数字

现在我们来看一种新的枚举思路,就是让所有位置的数字,与它前面位置的数字相加,然后判断是否有满足条件的两个数字这样的思路也能保证让所有的数两两相加,而且在处理一些细节上更有优势,尤其是本题,下面我们结合本题来感受一下

比如上面我们提到的这个队列,按照原来的思路,我们先来找一下2后面是否有合适的数字,使得2与其相加等于target,为了减小时间复杂度我们这里要用哈希表的方法,哈希表中存的值是数组中元素和对应的下标<nums[i,i>,但是如果我们是找当前元素后面的值时,我们就需要先提前将数组中所有数字及它们对应的下标先放入哈希表中,但这就会导致一些问题

比如这样一个数组和target值,如果我们按照原策略先将所有数字和对应下标放入哈希表中的话因为有两个3,在第一个3及它对应的下标放入哈希表中后,当放第二个3及它对应的下标时,就会把前一个存的3的下标给覆盖掉,所以我们还需要额外的操作对重复的数字的多个下标进行保存

但是如果我们采取的策略二,我们创建哈希表的时机就会发生一些变化,比如我们现在位置在2,我们是要从前面的位置找合适的数,前面没有数,所以哈希表中自然也没有,然后我们可以将2的值及它的下标放入哈希表中,同时让cur++,这样做的优化点是什么呢?

此时假如我们再遇到上面的情况(两个3),我们在第一个3时査找它前面是否有合适的数(前面的数己经放入哈希表了)时,会发现没有,然后我们把它放入哈希表中,然后到第二个3,我们发现哈希表中hash[target-3]存有一个下标,我们就找到满足条件的下标了

假如我们现在的target不是6,而是14,那我们在经过第二个3后,虽然会把哈希表中3对应的下标替换掉,但是仍不影响最终结果

思考一下:其实第二种策略比第一种策略的优化之处就是,策略二在替换一个数的坐标之前,是事先知道这个数字两两相加不满足条件的,所以保留一个与其它数字相加即可,而第一个数字则是在没考虑这种情况之前就只把重复数字只留下一个

代码实现:

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> hash;
        for(int i=0;i<nums.size();i++)
        {
            if(hash.count(target-nums[i])) return {hash[target-nums[i]],i};
            hash[nums[i]]=i;
        }
        return {-1,-1};
    }
};

2.2 存在重复元素||

219. 存在重复元素 II

给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 ij ,满足 nums[i] == nums[j]abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false

示例 1:

复制代码
输入:nums = [1,2,3,1], k = 3
输出:true

示例 2:

复制代码
输入:nums = [1,0,1,1], k = 1
输出:true

示例 3:

复制代码
输入:nums = [1,2,3,1,2,3], k = 2
输出:false

提示:

  • 1 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • 0 <= k <= 105

这道题就可以用上面的思路来解,挺简单的,可以练练手

代码实现:

cpp 复制代码
class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        unordered_map<int,int> hash;
        for(int i=0;i<nums.size();i++)
        {
            if(hash.count(nums[i])&&i-hash[nums[i]]<=k) return true;
            hash[nums[i]]=i;
        }
        return false;
    }
};

2.3 字母异位词分组

49. 字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

示例 1:

复制代码
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

示例 2:

复制代码
输入: strs = [""]

输出: [[""]]

示例 3:

复制代码
输入: strs = ["a"]

输出: [["a"]]

提示:

  • 1 <= strs.length <= 104
  • 0 <= strs[i].length <= 100
  • strs[i] 仅包含小写字母

算法原理:

解释:

这道题就很考验我们对哈希表的使用,我们在这里创建的哈希表类型是一个<string,vector<string>>类型的哈希表,我们要做的是把所有的字母异位词放在一起,那么我们首先就需要判断哪些词是字母异位词,在这里我们有一个很巧的方法去判断,我们可以把这些字符串排序,字母异位词排序后是相等的,就如左边那样("eat","tea"排序后都是"aet"),然后让排序后的字符串作为key值,字母异位词放在同一个key值后,最后再从哈希表中把这些字符串数组提取出来即可

代码实现:

cpp 复制代码
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string,vector<string>> hash;
        for(int i=0;i<strs.size();i++)
        {
            string tmp=strs[i];
            sort(strs[i].begin(),strs[i].end());
            hash[strs[i]].push_back(tmp);
        }
        vector<vector<string>> ret;
        for(auto& [x,y]:hash)
            ret.push_back(y);
        return ret;
    }
};

3. 总结

以上就是几个关于哈希表的经典例题,都不算难,哈希表在算法题里出现的比例还是非常高的,一般都是作为一种数据类型存在的,掌握还是很有必要的

本篇笔记:

感谢各位大佬观看,创作不易,还望各位大佬点赞支持!!

相关推荐
鱼嘻29 分钟前
数据结构------C语言经典题目(6)
linux·c语言·开发语言·数据结构·算法
千谦阙听1 小时前
数据结构篇:线性表的另一表达—链表之单链表(下篇)
c语言·数据结构·链表·visual studio
Theodore_10221 小时前
Python3(19)数据结构
大数据·开发语言·数据结构·python·网络爬虫
夏末秋也凉2 小时前
力扣-数组-238 除自身以外数组的乘积
数据结构·算法·leetcode
李匠20242 小时前
C++负载均衡远程调用学习之基础TCP服务
c++·学习
海码0073 小时前
【Hot 100】 148. 排序链表
数据结构·c++·链表·排序算法·hot100
蒙奇D索大3 小时前
【11408学习记录】英语书信通知写作模板大全:5个高分句式+使用场景解析,速存每日一句拆解练习!
笔记·学习·考研·改行学it
搏博3 小时前
模式识别的基本概念与理论体系
人工智能·深度学习·学习·算法·机器学习·数据挖掘
唐僧洗头爱飘柔95274 小时前
(Go Gin)Gin学习笔记(二):路由配置、基本路由、表单参数、上传单个文件、上传多个文件、浅扒路由原理
学习·golang·gin·路由参数·路由配置·web开发框架·路由组
我的golang之路果然有问题4 小时前
快速了解Go+微服务(概念和一个例子)
开发语言·笔记·后端·学习·微服务·golang