LeetCode体操-5 | 哈希表理论基础、242.有效的字母异位词、349. 两个数组的交集、202. 快乐数、1. 两数之和

49 阅读6分钟

哈希表理论基础

简介

哈希表/散列表(Hash table),数组就是一张哈希表,哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素,一般哈希表都是用来快速判断一个元素是否出现集合里。

  • 哈希表的内部实现原理:

    • 哈希表通常由数组实现,每个数组元素称为桶(bucket)。
    • 每个桶可以存储一个键值对,或者一个链表/红黑树(用于解决冲突)。
    • 通过哈希函数将键映射到数组索引,实现快速查找。
  • 哈希函数:

    • 作用:将键转换为数组索引。
    • 特点:相同的输入总是产生相同的输出;不同的输入应尽可能产生不同的输出。
    • 实现:常见方法包括除留余数法、平方取中法、折叠法等。
  • 哈希碰撞:

    • 定义:不同的键通过哈希函数得到相同的数组索引。
    • 解决方法:
      • 链地址法:在冲突位置维护一个链表,存储所有哈希到该位置的元素。
      • 开放寻址法:寻找下一个空位置存储冲突元素(线性探测、二次探测、双重哈希等)。
  • 常见数据结构对比

数据结构特点访问方式操作时间复杂度
数组连续内存空间通过索引直接访问元素查找、插入、删除时间复杂度 O(n)
Set基于哈希表实现存储唯一值,无重复查找、插入、删除时间复杂度平均 O(1)
Map基于哈希表实现存储键值对查找、插入、删除时间复杂度平均 O(1)
  • 哈希法应用场景
    • 当需要快速判断一个元素是否出现在集合中时

    • 需要 O(1) 时间复杂度的查找、插入、删除操作时

    • 处理需要去重的问题时

    • 需要统计元素出现频率时

242.有效的字母异位词

题目

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

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

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

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

解题思路

  1. 涉及到查询字符是否出现以及出现次数,考虑用哈希表
  2. 因为小写字母数量有限且映射为 ASCII 表即可转为数字形式对应数组key,即每个字符使用 charCodeAt 映射为数字并减去'a'在 ASCII 表上对应的数字即可将字符串上的字符信息映射到数组中。
  3. 需要将S和T两个字符串的内容分别映射为数组,再进行数组的比较,需要两次for循环,产生两个数组。
  4. 因为字符串映射到数组的过程高度一致,可以合并为映射到同个数组中进行数量增减操作,可将两字符串上字符的差异信息汇聚到一个数组上,最后根据该数组数据判断结果即可。
  5. 如果S和T字符的长度相等,则可以在单次for循环中同时完成两字符串到数组的转换过程,因此可以先对比两字符串长度是否相等,之后再进行for循环
  6. 最后查看记录两字符串字符信息的数组是否全部元素值都为0,如是则返回true,否则返回false

代码实现

function isAnagram(s: string, t: string): boolean {
  if (s.length !== t.length) return false;
  const infoList = Array(26).fill(0);
  const privt = "a".charCodeAt(0);
  for (let i = 0; i < s.length; i++) {
    infoList[s.charCodeAt(i) - privt]++;
    infoList[t.charCodeAt(i) - privt]--;
  }
  return infoList.every((i) => i === 0);
}

349. 两个数组的交集

题目

给定两个数组,编写一个函数来计算它们的交集。

349. 两个数组的交集

说明:  输出结果中的每个元素一定是唯一的。 我们可以不考虑输出结果的顺序。

解题思路

为避免双重for循环暴力解法,可以采用空间换时间的方式,将一个入参数组转为哈希表set,遍历另一个数组,过程中当遍历的值存在与set中时则加入到存放结果的哈希表resSet中,最后在将结果哈希表resSet转换为数组并返回即可

代码实现

function intersection(nums1: number[], nums2: number[]): number[] {
    const set = new Set<number>(nums1);
    const resSet = new Set<number>();

    for (let val of nums2) {
        if (set.has(val)) {
            resSet.add(val)
        }
    }
    return Array.from(resSet)
};

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

解题思路

  1. 建一个函数来计算数字每位平方和
  2. 使用哈希集合来检测循环。
  3. 不断计算平方和,直到结果为1或出现循环。

image.png

代码实现

function isHappy(n: number): boolean {
    // 创建一个Set来存储已经出现过的数字,用于检测循环
    const seen = new Set<number>();

    // 当n不为1且没有出现循环时,继续计算
    while (n !== 1 && !seen.has(n)) {
        seen.add(n);
        n = getSum(n);
    }

    // 如果最终n为1,则是快乐数
    return n === 1;
}

// 辅助函数:计算数字每位的平方和
function getSum(num: number): number {
    let sum = 0;
    while (num > 0) {
        const digit = num % 10;
        sum += digit * digit;
        num = Math.floor(num / 10);
    }
    return sum;
}

1. 两数之和

题目

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例: 给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]

解题思路

因为需要判断一个元素是否出现过,所以应选用哈希法;因为需要存储键和值,所以使用map结构;因为想要查询的对象是值(计算和是否等于目标值),所以key存放元素值,value存放该元素的下标

代码实现

function twoSum(nums: number[], target: number): number[] {
    const map: Map<number, number> = new Map();
    for (let i = 0; i < nums.length; i++) {
        const index = map.get(target - nums[i]);
        if (index !== undefined) {
            return [index, i];
        }
        map.set(nums[i], i);
    }
    return [];
}