哈希表①(快速的获取某个值是否存在)

哈希表

哈希表 用于存储键值对,并使我们能够快速的根据键获取值。哈希表的基础知识可以浏览这里-->哈希表

当我们需要快速的获取一个值是否存在时,哈希表是不二之选 ,此外哈希表的键是唯一的

本节主要是一些使用到哈希表的算法题目。

LeetCode-242.有效的字母异位词

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

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

示例:

text 复制代码
输入: s = "anagram", t = "nagaram"
输出: true

分析:我们只需要知道字符串s中每个字符出现的次数,然后遍历字符串t中每个字符,在s中出现就将出现次数减一。

看代码:

js 复制代码
var isAnagram = function (s, t) {
  const originMap = new Map();
  if (s.length !== t.length) return false; //长度不同则不可能是字母异位词
  for (let i = 0; i < s.length; i++) { //将s中每个字符及其出现次数存入map
    originMap.set(s[i], (originMap.get(s[i]) || 0) + 1);
  }

  for (let i = 0; i < s.length; i++) {
    //如果t中出现了所需字符,则将对应的map减一,表示抵消
    originMap.set(t[i], originMap.get(t[i]) - 1);
    if (originMap.get(t[i]) === 0) { //如果已经抵消完了就删除掉
      originMap.delete(t[i]);
    }
  }
  //此时,只要map为空,说明所有字符都被抵消,两个字符串拥有相同的字符
  if (originMap.size === 0) return true;
  return false;
};

这样做实际就是两个集合A,B(允许重复),判断A和B是否相等。

此外还有一个更简单的方法,就是将两个字符串变得有序,如果他们是异位词,那么他们将相等。

js 复制代码
var isAnagram = function (s, t) {
  return s.split('').sort().join('') === t.split('').sort().join('')
};

LeetCode-383.赎金信

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

  • 这道题相较于上一题,你会发现,两个字符串的关系发生了变化。
    • 本题实际上是两个集合A,B(允许重复),判断A是否是B的子集

那于上一题的代码很像,只需要记录目标集合(ransomNote)中字符出现次数,只要在magazine集合中拥有这些字符并次数大于等于即可。

js 复制代码
var canConstruct = function (ransomNote, magazine) {
  let targetMap = new Map();
  for (let i = 0; i < ransomNote.length; i++) { //ransomNote 中字符出现次数
    targetMap.set(ransomNote[i], ((targetMap.get(ransomNote[i]) || 0) + 1));
  }

  for (let i = 0; i < magazine.length; i++) {
    if (targetMap.has(magazine[i])) { //magazine中是否有我们需要的,有就抵消。
      targetMap.set(magazine[i], targetMap.get(magazine[i]) - 1);
      if (targetMap.get(magazine[i]) === 0) {
        targetMap.delete(magazine[i]);
      }
    }
  }

  if (targetMap.size === 0) return true;
  return false;
};

LeetCode-49.字母异位词分组

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

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

示例:

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

本题即是找到相同的字母异位词,我们可以利用两层循环判断出哪些字符串相互为异位词,这样的当然是可行的,不过你需要考虑去重,因为针对第一项的可能在遍历第二项时又出现,简化去重的方法就是用set/map存储。

这里我们利用前面提到的排序方法,将排序后的结果作为键,值则存储异位词数组

js 复制代码
var groupAnagrams = function (strs) {
  const map = new Map();
  for (let i = 0; i < strs.length; i++) {
    let sorted = strs[i].split('').sort().join(); //排序
    if (map.has(sorted)) {
      map.set(sorted, [...map.get(sorted), strs[i]]);
    } else { //没有对应的分组,创建分组
      map.set(sorted, [strs[i]]);
    }
  }
  return Array.from(map.values());
};

LeetCode-438.找到字符串中所有字母异位词

给定两个字符串 sp,找到 s中所有 p异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

示例 :

text 复制代码
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

你能否看出这是一个滑动窗口题 呢? 滑动窗口题目见双指针②--滑动窗口

对于滑动窗口,我们需要关心的是:

  • 何时扩大?扩大时需要做什么?
  • 何时缩小?缩小时需要做什么?
  1. 何时扩大?每次循环都需要扩大,直到遍历到尾部。扩大时我们需要知道当前进来的字符是不是需要的,如果是需要的我们记录一下。
  2. 何时缩小?当我们当前窗口中的长度大于等于p时,开始缩小。缩小时我们应该判断当前是否满足异位词并记录答案,然后清除扩大时带来的副作用。

引出了一个问题,如何判断当前是否满足异位词?

  • 如果与之前一样,每次去抵消map判断是否为异位词的话,那么窗口在移动过程中会由于map在判断一次后不能重新初始化--------为此我们需要重新维护一个map,存储当前窗口中的值。
  • 如果在滑动窗口while循环中再去判断是否是异位词,那么无疑增加了时间复杂度--------我们引入一个变量来记录当前窗口内满足异位的字符种类,这样就可以在每次找到时更新,缩小窗口时判断是否是异位词了。
  • 引入的这些新变量,都需要在对应扩大和缩小时进行更新。

可能还有点抽象,多读几遍,结合代码看看:

js 复制代码
var findAnagrams = function (s, p) {
  let neededMap = new Map();//所需的字符以及出现的次数
  for (let i = 0; i < p.length; i++) {
    neededMap.set(p[i], (neededMap.get(p[i]) || 0) + 1);
  }
  let neededCount = neededMap.size;//所需字符的种类;

  let left = 0, right = 0;
  const res = [];
  let currentCount = 0; //窗口内满足条件的字符种类
  let currentMap = new Map(); //窗口内满足条件的字符
  while (right < s.length) {
    if (neededMap.has(s[right])) {//当前窗口右端点是所需字符
      currentMap.set(s[right], (currentMap.get(s[right]) || 0) + 1);
      //如果出现次数相同,那么当前已有种类加一
      if (currentMap.get(s[right]) === neededMap.get(s[right])) {
        currentCount++;
      }
    }

    //窗口大于等于p的长度时,判断是否是结果,然后逐步缩小
    while (right - left + 1 >= p.length) {
      if (currentCount === neededCount) {
        res.push(left);//记录结果
      }
      //缩小
      if (currentMap.has(s[left])) {
        //缩小前判断当前出去的字符是否会影响到所需字符种类,
        //窗口内出现的次数是需要的次数,那么缩小后当前的字符种类一定少1种,故减一
        if (currentMap.get(s[left]) === neededMap.get(s[left])) { 
          currentCount--;
        }
        //更新窗口内的字符出现次数
        currentMap.set(s[left], currentMap.get(s[left]) - 1);
        if (currentMap.get(s[left]) === 0) {
          currentMap.delete(s[left]);
        }
      }
      left++;
    }
    right++;
  }
  return res;
};

代码中的while缩小部分代码实际上可以改为if,因为窗口一旦到达目标长度,就会一个个扩大缩小,属于定长滑动窗口。

LeetCode-349.两个数组的交集

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

求两个数组的交集,不需要关心每个数字出现的次数,很快就能想到使用Set来存储了。 很简单,直接看代码:

js 复制代码
//使用filter会比较慢
var intersection = function (nums1, nums2) {
  const set = new Set(nums1);
  return Array.from(new Set(nums2.filter(num => set.has(num))));
};

//使用循环
var intersection = function (nums1, nums2) {
  const set = new Set(nums1);
  const res = new Set();
  for (let num of nums2) {
    if (set.has(num)) res.add(num);
  }
  return Array.from(res);
};

LeetCode-350.两个数组的交集 II

给你两个整数数组 nums1nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

类似于LeetCode-242.有效的字母异位词。这里无非是求有哪些数字。

js 复制代码
var intersect = function (nums1, nums2) {
  const originMap = new Map();
  const res = [];
  for (let i = 0; i < nums1.length; i++) {
    originMap.set(nums1[i], (originMap.get(nums1[i]) || 0) + 1);
  }

  for (let i = 0; i < nums2.length; i++) {
    if (originMap.has(nums2[i])) {
      res.push(nums2[i]);
      originMap.set(nums2[i], originMap.get(nums2[i]) - 1);
      if (originMap.get(nums2[i]) === 0) originMap.delete(nums2[i]);
    }
  }
  return res;
};

此外还可以使用排序加双指针解决此问题:

js 复制代码
var intersect = function (nums1, nums2) {
  nums1 = nums1.sort((a, b) => a - b);
  nums2 = nums2.sort((a, b) => a - b);

  let i = 0, j = 0;
  let res = [];
  while (i < nums1.length && j < nums2.length) {
    if (nums1[i] === nums2[j]) {
      res.push(nums1[i]);
      i++; j++;
    } else if (nums1[i] < nums2[j]) {
      i++;
    } else {
      j++;
    }
  }
  return res;
};

LeetCode-202.快乐数

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

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

分析:我们每次对n进行分解并平方求和,如果为1就返回true。题目告诉我们他可能会一直循环,那么我们就需要知道是否存在循环,想要知道是否存在循环就需要存储之前出现过的结果,为了能够快速判断是否已经出现过,就能想到使用哈希表存储,这里使用Set进行存储。

js 复制代码
function getSum(n) {
  let sum = 0;
  while (n) {
    let num = n % 10;
    n = Math.floor(n / 10);
    sum += num * num;
  }
  return sum;
}
var isHappy = function (n) {
  const computedSum = new Set();
  while (n !== 1) {
    if (computedSum.has(n)) return false;
    computedSum.add(n);
    n = getSum(n);
  }
  return true;
};

总结

  • 使用哈希表进行数据存储,能够让我们快速的获取某个值是否存在。
  • Set适合存储去重的数据
  • Map适合存储键值对,其中键是去重唯一的。
相关推荐
白榆maple8 分钟前
(蓝桥杯C/C++)——基础算法(下)
算法
阿伟来咯~11 分钟前
记录学习react的一些内容
javascript·学习·react.js
JSU_曾是此间年少13 分钟前
数据结构——线性表与链表
数据结构·c++·算法
吕彬-前端16 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱18 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai28 分钟前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨29 分钟前
在JS中, 0 == [0] 吗
开发语言·javascript
此生只爱蛋1 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
咕咕吖2 小时前
对称二叉树(力扣101)
算法·leetcode·职场和发展
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js