【算法专场】哈希表

目录

前言

哈希表

[1. 两数之和 - 力扣(LeetCode)](#1. 两数之和 - 力扣(LeetCode))

算法分析

算法代码

[面试题 01.02. 判定是否互为字符重排](#面试题 01.02. 判定是否互为字符重排)

​编辑算法分析

算法代码

[217. 存在重复元素](#217. 存在重复元素)

算法分析

算法代码

[219. 存在重复元素 II](#219. 存在重复元素 II)

算法分析

算法代码

解法二

算法代码

算法分析

算法代码

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

算法分析

算法代码


前言

当我们想要快速查找某个值是否存在,或者想要对数据进行去重的时候,我们有没有方法可以解决上面这些问题?我们可以用哈希表。

哈希表

哈希表(Hash Table)是一种通过哈希函数将键(key)映射到表中位置来访问记录的数据结构,它具有高效的数据查找、插入和删除能力

我们什么时候可以使用哈希表?

在快速查找、频繁统计、快速映射、去重的情景下都可以使用哈希表。

我们来通过算法题目来更好的了解哈希算法。

1. 两数之和 - 力扣(LeetCode)

算法分析

本道题要求我们从数组中找两个数,并且两数之和为target。这道题可以采用暴力枚举,时间复杂度为O(n^2),如果在大量数据的情况下,是会超时的,那么我们可以采用哈希算法来解决这道题。

我们可以在hash表中存储元素以及其下标的映射。在遍历数组的过程,每次判断(target-当前元素)是否在hash表中存在,如果不存在,那就将

当前元素以及其下标存到hash表中,反之,如果找到,那么就取出下标返回。

为什么不将所有元素都放进去hash表再进行判断?这样的话就无法解决元素重复的问题。假设目标值为2,那么现在有1,我们如果将1先放进了hash表,那么我们在遍历的时候,可以找到1,但实际上这两个1是同一个。而如果我们先判断hash表中是否存在(target-当前元素)的元素,则可以避免出现重复的情况。

算法代码

java 复制代码
/**
 * 解决方案:找出数组中两个数之和等于目标值的数对索引
 * 方法:使用哈希表实现
 * 时间复杂度:O(n),其中n是数组的长度
 * 空间复杂度:O(n),需要额外的空间来存储哈希表
 * 
 * @param nums 输入的整数数组
 * @param target 目标值,寻找两数之和等于此值
 * @return 返回一个长度为2的整数数组,包含满足条件的两个数的索引;如果不存在这样的数对,则返回{-1, -1}
 */
public int[] twoSum(int[] nums, int target) {
    // 利用哈希表来存储已经遍历过的数字及其索引,以便快速检查目标值与当前值的差值是否已经出现过
    HashMap<Integer,Integer> map=new HashMap<>();
    for(int i=0;i<nums.length;i++){
        // 检查当前数字与目标值的差值是否已经在哈希表中
        if(map.containsKey(target-nums[i])){
            // 如果差值存在,则找到了满足条件的两个数,返回它们的索引
            return new int[]{map.get(target-nums[i]),i};
        }else{
            // 如果差值不存在,将当前数字及其索引放入哈希表中,以供后续查找使用
            map.put(nums[i],i);
        }
    }
    // 如果遍历完整个数组后仍未找到满足条件的数对,返回{-1, -1}
    return new int[]{-1,-1};
}

时间复杂度为O(n),空间复杂度为O(n).

面试题 01.02. 判定是否互为字符重排

算法分析

本道题其实就是要判断两个字符串是否是一样,那么我们就可以两个hash表,来存储每次字符出现的次数,如果两个hash表对应的值都相等,那就说明两个字符串是一样的。

算法代码

java 复制代码
    /**
     * 检查两个字符串是否互为字符重排
     * 
     * @param s1 第一个字符串
     * @param s2 第二个字符串
     * @return 如果两个字符串互为字符重排,则返回true;否则返回false
     */
    public boolean CheckPermutation(String s1, String s2) {
        // 如果两个字符串长度不同,则它们不可能互为字符重排
        if (s1.length() != s2.length()) {
            return false;
        }
        // 如果任一字符串为空,则它们不可能互为字符重排
        if (s1.length() == 0 || s2.length() == 0) {
            return false;
        }
        // 创建一个哈希表来存储字符出现的次数,ASCII码共有128个字符,额外留一个位置给可能的空字符
        int hash[] = new int[129];
        
        // 遍历第一个字符串,统计每个字符出现的次数
        for (int i = 0; i < s1.length(); i++) {
            char ch = s1.charAt(i);
            hash[ch]++;
        }
        
        // 遍历第二个字符串,检查每个字符是否在哈希表中出现过,并减少相应字符的计数
        for (int i = 0; i < s2.length(); i++) {
            char ch = s2.charAt(i);
            // 如果字符在哈希表中的计数为0,则说明两个字符串的字符组成不同
            if (hash[ch] == 0) {
                return false;
            }
            hash[ch]--;
        }
        
        // 如果所有字符都能一一对应,且没有多余的字符,则两个字符串互为字符重排
        return true;
    }

217. 存在重复元素

算法分析

这道题就是要求在数组中有没有一个元素是出现超过两次的,我们可以用哈希算法来解决这道题,利用HashSet,如果set里面没有存在当前元素,就将当前元素存到set中;反之,如果有,则返回true。

算法代码

java 复制代码
  public boolean containsDuplicate(int[] nums) {
        HashSet<Integer> hashSet=new HashSet<>();
        for(int i=0;i<nums.length;i++){
            if(hashSet.contains(nums[i])){
                return true;
            }
            hashSet.add(nums[i]);
        }
        return false;
    }

219. 存在重复元素 II

算法分析

跟上一道题类似,不过这里还需要将下标进行存储,需要利用HashMap,在遍历的过程中,如果当前元素在hashmap中已经存在,那么就判断这两个元素之间的距离,如果小于k,则返回true,否则将当前元素以及其下标存储到hashmap中。

算法代码

java 复制代码
/**
     * 检查数组中是否存在两个相同的数字,且它们的索引之差的绝对值最大为k
     * 这个方法通过使用HashMap来跟踪数字及其最后出现的索引来实现
     * 如果发现重复数字,并且它们的索引之差不超过k,则返回true
     * 如果不满足这些条件,则在遍历完整个数组后返回false
     * 
     * @param nums 输入的整数数组
     * @param k 索引之差的绝对值的最大允许值
     * @return 如果找到至少一对索引之差的绝对值不超过k的相同数字,则返回true;否则返回false
     */
    public boolean containsNearbyDuplicate2(int[] nums, int k) {
        // 使用HashMap来存储数字及其对应的索引
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int i=0;i<nums.length;i++){
            // 检查当前数字是否已经在HashMap中存在
            if(map.containsKey(nums[i])){
                // 如果当前数字已存在,并且当前索引与之前索引之差不超过k,则返回true
                if(i-map.get(nums[i])<=k){
                    return true;
                }
            }
            // 更新或添加当前数字及其索引到HashMap中
            map.put(nums[i],i);
        }
        // 如果没有找到满足条件的数字对,则返回false
        return false;
    }

时间复杂度为O(n),空间复杂度为O(n)

解法二

对于这道题,我们还可以通过滑动窗口的思想来解决,题目要求在数组中找两个相同的元素,并且他们下标的距离要小于等于k,那么我们就可以通过维护一个长度为k的窗口。如果当窗口小于k的时候,那么我们就将元素添加到窗口中,如果窗口中存在和当前元素相同的元素,那么就直接返回true,反之,则将元素添加到窗口中,当元素的个数等于窗口的长度的时候,那就将左边第一个元素进行移除。这里我们借助HashSet来判断窗口中是否有重复的元素

算法代码

java 复制代码
  /**
     * 检查数组中是否存在重复的元素,且它们的索引之差的绝对值最大为k
     * 这个方法用于识别在一定索引范围内是否存在重复的数字
     *
     * @param nums 一个整数数组,其中可能包含重复元素
     * @param k 索引之差的绝对值的最大允许值
     * @return 如果找到至少一对索引之差的绝对值不超过k的重复元素,则返回true;否则返回false
     */
    public boolean containsNearbyDuplicate(int[] nums, int k) {
        // 使用HashSet存储当前窗口内的元素,以便快速检查重复
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < nums.length; i++) {
            // 当窗口大小超过k时,移除窗口最左侧的元素,以保持窗口大小不超过k
            if (i > k) {
                set.remove(nums[i - k - 1]);
            }
            // 尝试将当前元素添加到HashSet中如果添加失败,说明存在重复元素,且索引之差不超过k
            if (!set.add(nums[i])) {
                return true;
            }
        }
        // 遍历完成后,如果没有返回true,说明没有找到满足条件的重复元素
        return false;
    }

算法分析

这道题相对于前面两道题来说,要难上一些。但是以及依旧可以借鉴上面题目的思想,利用滑动窗口+有序集合

根据题意,假设任意位置为i,值为u。我们希望在下标范围为max(0,i-k),i)的范围内找到值范围在\[u-t,u+t范围内的数。如果我们每次遍历到任意位置的时候,都往前检查k个元素,这样时间负责度会达到O(nk),会超时,所以我们需要对检索后面k个元素进行优化,在java中,有一个TreeSet数据结构,能够帮助我们在有序集合内快速找到值小于等于u或者大于等于u的值。

算法代码

java 复制代码
/**
     * 检查数组中是否存在两个索引之差不超过indexDiff且值之差不超过valueDiff的元素
     * 
     * @param nums 包含整数的数组
     * @param indexDiff 索引之间的最大差值
     * @param valueDiff 值之间的最大差值
     * @return 如果找到满足条件的元素对,则返回true;否则返回false
     */
    public boolean containsNearbyAlmostDuplicate(int[] nums, int indexDiff, int valueDiff) {
        int n = nums.length;
        TreeSet<Integer> set = new TreeSet<>();
        for(int i=0;i<n;i++){
            int u=nums[i];
            //如果set不为空,从set中找到大于等于u的最小值(最接近u的数)
            Integer ceil = set.ceiling(u);
            //如果set不为空,且小于等于u的最大值(最接近u的数)
            Integer floor = set.floor(u);
            //检查是否存在满足条件的元素
            if(ceil!=null&&ceil-u<=valueDiff) return true;
            if(floor!=null&&u-floor<=valueDiff) return true;
            //将当前元素加入set
            set.add(u);
            //如果当前索引大于等于indexDiff,则从set中移除滑动窗口最左侧的元素
            if(i>=indexDiff) set.remove(nums[i-indexDiff]);
        }
        //遍历后仍未找到满足条件的元素,返回false
        return false;
    }

49. 字母异位词分组

算法分析

这道题要我们找字符串的字符个数都相同的字符串,并将这些字符串都放在同一个列表中,那么我们可以采用哈希算法,这里借助HashMap,键key为字符串,值为一个存储着字符串的列表。在遍历字符串数组的时候,我们每次将字符串进行排序,再判断hashmap是否存储当前字符串,如果存在,则将当前元素添加到列表中,反之,则创建一个列表,并将当前元素添加到列表中。最后再将map中的值存储放到ans中。

算法代码

java 复制代码
/**
 * 对字符串数组进行遍历,将其中的字符串转换为字符数组并排序,然后转换回字符串
 * 使用HashMap来存储这些转换后的字符串作为键,以及它们在原始数组中的出现情况作为值
 * 最后,将HashMap中的所有值收集到一个二维列表中并返回
 * 
 * @param strs 字符串数组,包含待处理的字符串
 * @return 返回一个二维列表,其中包含按照字母异位词分组的字符串列表
 */
public List<List<String>> groupAnagrams(String[] strs) {
    // 初始化答案列表
    List<List<String>> ans=new ArrayList<>();
    // 初始化哈希图,用于存储排序后的字符串作为键,以及它们的出现情况作为值
    HashMap<String,List<String>> map=new HashMap<>();
    // 遍历输入的字符串数组
    for(int i=0;i<strs.length;i++){
        // 获取当前遍历到的字符串
        String s=strs[i];
        // 将字符串转换为字符数组
        char[] chars=s.toCharArray();
        // 对字符数组进行排序
        Arrays.sort(chars);
        // 将排序后的字符数组转换回字符串
        s=new String(chars);
        // 如果排序后的字符串已经在哈希图中存在
        if(map.containsKey(s)){
            // 将当前字符串添加到该键对应的列表中
            map.get(s).add(strs[i]);
        }else{
            // 否则,创建一个新的字符串列表
            List<String> list=new ArrayList<>();
            // 将当前字符串添加到列表中
            list.add(strs[i]);
            // 将排序后的字符串和对应的字符串列表作为键值对存入哈希图
            map.put(s,list);
        }
    }
    // 遍历哈希图的所有键
    for(String key:map.keySet()){
        // 将每个键对应的值(字符串列表)添加到答案列表中
        ans.add(map.get(key));
    }
    // 返回答案列表
    return ans;
}

以上就是哈希算法篇的所有内容啦~

若有不足,欢迎指正~

相关推荐
闪电悠米43 分钟前
力扣hot100-41.缺失的第一个正数-原地哈希详解
数据结构·算法·哈希算法
WL学习笔记1 小时前
堆(数据结构)
数据结构·
wabs6665 小时前
关于图论【卡码网100.最大岛屿的面积的思考】
数据结构·算法·图论
yuannl107 小时前
图的最短路径Dijkstra
数据结构
zander2588 小时前
138. 随机链表的复制
数据结构·算法·链表
过期动态8 小时前
【LeetCode 热题 100】找到字符串中所有字母异位词
java·数据结构·算法·leetcode·职场和发展·rabbitmq
来一碗刘肉面8 小时前
栈在递归中的应用
数据结构·算法
皓月斯语8 小时前
程序设计语言的特点
开发语言·数据结构·c++
邪修king9 小时前
C++ 进阶终章:异常机制与智能指针全解 —— 从错误处理到 RAII 资源管理,打通现代 C++ 的核心命脉
android·数据结构·c++
Adios79417 小时前
设置交集大小至少为2
数据结构·算法·leetcode