算法 --- hash

哈希表简介

什么是 hash 表?hash表就是存储数据的容器

作用:快速查找某个元素

什么时候使用hash表?频繁查找某个数时,可以使用 hash 表

如何使用hash表?1.使用hash表容器;2.使用数组模拟简易hash表

什么时候使用数组模拟hash表?<1>. 字符串中的字符;<2>. 数据范围小


算法题目

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

题目分析

给定一个整数数组 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
  • 只会存在一个有效答案

算法原理

解法1:暴力枚举

第一种遍历的方式:先固定其中一个数,依次与后面的数相加,是否等于 target

第二种遍历的方式:先固定其中一个数,依次与前面的一个数相加,是否等于 target


代码实现

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

        return { };
    }
};

解法2:使用 hash 表

为什么暴力解法这么慢呢?因为固定一个数之后需要在前面找一个等于 target -- x 的数,如固定7,就需要在前面找到一个等于 target-x=9-7=2 的数。从前向后找太慢了,查找一个数可以使用hash 表。将固定的数的前面的数加入到 hash 表中,固定的数改变之前将该数加入到 hash 表中。hash 表需要结合第二种遍历的方法一起使用。

为什么使用第一种遍历方法,hash 表就不管用了呢?使用第二种遍历方法需要先将所有的元素加入到 hash 表中,之后固定一个数的时候,再去 hash 表中找 target -- x 的数。但是如果 nums 中存在元素2,而 target = 4呢?将所有的元素加入到 hash 表中,当固定的数为2时,在 hash 表中找为2的数,能否被找到,可以,但是满足题目要求吗(不能使用两次同一个下标的元素)?不满足。因此对于这样的情况需要判断一些边界情况,太麻烦了。


代码实现

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        // 创建一个hash表 存的是元素和对应元素的下标
        unordered_map<int, int>hash(nums.size());
        // 遍历nums数组
        for(int i = 0; i < nums.size(); i++)
        {
            int val = target - nums[i];
            // 如果存在 target-nums[i],则直接返回
            if(hash.count(val)) { return {hash[val], i}; }
            // 如果不存在,加入hash表
            hash[nums[i]] = i;
        }
        
        return {};
    }
};

题目2:面试题 01.02. 判定是否互为字符重排 - 力扣(LeetCode)

题目分析

给定两个由小写字母组成的字符串 s1s2,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。


题目示例

示例 1:

复制代码
输入: s1 = "abc", s2 = "bca"输出: true 

示例 2:

复制代码
输入: s1 = "abc", s2 = "bad"
输出: false

算法原理

如果一个字符串是另一个字符串重排列后的结果,它们会有一个相同的特点:字母出现的次数都相等。因此只需要统计字符串 s1 中每个字母出现的次数,s2 中每个字母出现的次数,在比较对应字母出现的次数是否相等。统计每个字母出现的次数,使用hash表。可以使用数组模拟实现 hash 表,为什么可以?因为题目说了字符串全是小写字母构成,数据范围小。

创建两个 hash 表,遍历两个字符串,分别将字符加入 hash 表,最后比较两个hash表是否相等。当然还可以在此基础进行优化,只使用一个 hash 表,遍历一个字符串,将该字符串中的字母加入到 hash 表中,其次遍历另一个字符串,将遍历到的字符在 hash 表中减去,如果最终 hash 表中的元素个数为0,说明满足题目要求。如果某个字符出现的次数为负数,那么要么这个字符是多余的,要么是 hash 表中没有的,也说明了不满足题目要求。

其次可以做一些小优化:如果一个字符串是另一个字符串重排列后的结果,那么这两个字符串的长度一定相等,若两个字符串的长度不相等,那么根本就不需要进行下一步操作。


代码实现

cpp 复制代码
class Solution {
public:
    bool CheckPermutation(string s1, string s2) {
        if(s1.length() != s2.length()) { return false; }

        // 使用数组模拟hash表
        int hash[26] = { 0 };
        // 加入hash表
        for(auto ch : s1) { hash[ch - 'a']++; }
        for(auto ch : s2)
        {
            hash[ch - 'a']--;
            // 如果出现负数,直接返回false
            if(hash[ch - 'a'] < 0) { return false; }
        }

        return true;
    }
};

题目3:217. 存在重复元素 - 力扣(LeetCode)

题目分析

给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false


题目示例

示例 1:

**输入:**nums = [1,2,3,1]

**输出:**true

解释:

元素 1 在下标 0 和 3 出现。

示例 2:

**输入:**nums = [1,2,3,4]

**输出:**false

解释:

所有元素都不同。

示例 3:

**输入:**nums = [1,1,1,3,3,4,3,2,4,2]

**输出:**true


算法原理

使用《两数之和》题目的思想:固定一个数,在这个数的前面找是否出现相同的元素。

解法:hash 表

cpp 复制代码
class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        // 创建hash表,由于不需要存下标,因此使用set
        unordered_set<int> hash;

        // 遍历nums数组
        for(int i = 0; i < nums.size(); i++)
        {
            // 如果hash表中存在nums[i],直接返回true
            if(hash.count(nums[i])) { return true; }
            // 如果hash表中不存在nums[i],加入hash表
            hash.insert(nums[i]);
        }

        // 出循环了,说明不存在重复的元素,返回false
        return false;
    }
};

题目4:219. 存在重复元素 II - 力扣(LeetCode)

题目分析

给你一个整数数组 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

算法原理

这道题与《存在重复元素I》解法相似,只不过在判断是否存在重复元素时,需要判断两个重复元素的下标之差是否小于等于k。因此创建的 hash 表中需要存两个元素,元素和元素对应的下标。

固定一个数,看该数前面是否存在相同的元素,如果存在,还需比较这两个相同的数的下标查是否等于 k,如果小于相等,返回 true。如果不相等,加入 hash 表中,这样会将相同的元素给覆盖掉,如此还能找到最终结果吗?当然可以!!


代码实现

cpp 复制代码
class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        // 创建hash表
        unordered_map<int, int> hash;

        // 遍历nums数组
        for(int i = 0; i < nums.size(); i++)
        {
            int val = nums[i];
            // 如果hash表中存在等于val的元素,且对应元素的下标相减小于等于k,直接返回true
            if(hash.count(val) && i - hash[val] <= k) { return true;}
            // 如果hash表中不存在等于val的元素,或者对应元素的下标相减不小于等于k,加入到hash表中
            hash[val] = i;
        }

        return false;
    }
};

题目5:49. 字母异位词分组 - 力扣(LeetCode)

题目分析

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


题目示例

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]

输出:[["bat"],["nat","tan"],["ate","eat","tea"]]

解释:

  • 在 strs 中没有字符串可以通过重新排列来形成 "bat"
  • 字符串 "nat""tan" 是字母异位词,因为它们可以重新排列以形成彼此。
  • 字符串 "ate""eat""tea" 是字母异位词,因为它们可以重新排列以形成彼此。

示例 2:

输入: strs = [""]

输出:[[""]]

示例 3:

输入: strs = ["a"]

输出:[["a"]]
提示:

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

算法原理

解法:hash表

首先需要判断两个字符是否是字母异位词,如果使用 hash 表来统计两个字符串的字母出现的次数是否相等,会有点麻烦。可以使用排序,两个字符串排序之后如果相等,那么这两个字符串就是字母异位词。接下来考虑如何分组?如何将字母异位词分成一组?这就体现出泛型编程的强大之处了,hash的参数类型为 string和 vector<string>

最终遍历一遍 hash 表,将 hash 表中的 value 值提取出来。


代码实现

cpp 复制代码
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        // 创建一个hash表
        unordered_map<string, vector<string>> hash;

        // 根据字符串排好序的结果进行分组
        for(string& str : strs)
        {
            // 将字符串排好序,不要对str操作
            string tmp = str;
            sort(tmp.begin(), tmp.end());
            // 若排序后字符串相等,则进入同一个字符串数组
            // hash[tmp]的类型是vector<string>,可以调用push_back函数
            hash[tmp].push_back(str);
        }
        // 返回值
        vector<vector<string>> ret;
        // 遍历hash表
        for(auto& [k, v] : hash) { ret.push_back(v); }

        return ret;
    }
};

知识回顾

深入浅出哈希表-CSDN博客

深入理解STL关联容器:map/multimap与set/multiset全解析-CSDN博客

相关推荐
ywwwwwwv4 小时前
力扣139
算法·leetcode·职场和发展
黛色正浓4 小时前
leetCode-热题100-哈希合集(JavaScript)
javascript·leetcode·哈希算法
smj2302_796826524 小时前
解决leetcode第3777题使子字符串变交替的最少删除次数
python·算法·leetcode
fei_sun4 小时前
【数据结构】2025年真题
数据结构
我在人间贩卖青春4 小时前
线性表之队列
数据结构·队列
Tisfy4 小时前
LeetCode 2110.股票平滑下跌阶段的数目:数学(一次遍历)
数学·算法·leetcode·题解
1024小神4 小时前
swift中 列表、字典、集合、元祖 常用的方法
数据结构·算法·swift
ULTRA??4 小时前
Informed RRT*实现椭圆启发式采样
c++·算法
Swizard4 小时前
告别样本不平衡噩梦:Focal Loss 让你的模型学会“划重点”
算法·ai·训练