JavaScript哈希Map对象在算法题中的表现力【附大厂算法题】

415 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

前言

Map对象是ES6中引入的JavaScript语法,Map对象的在存储中从健的类别的多类型和性能等方面都要比Object对象要优雅。下面我们会介绍一下Map对象,然后通过哈希相关算法题来加强巩固知识。

Map 对象介绍

Map 对象和Object的区别

平时写代码的时候,如果要用JavaScript表示哈希对象或者说哈希表,我们这样子,

let hashTable = {} // 创建hashTable哈希对象,

哈哈,这不是就是普通的JavaScript对象吗? 是的。Object 和 Map 类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。因此(并且也没有其他内建的替代方式了)过去我们一直都把对象当成 Map 使用。

不过 Map 和 Object 有一些重要的区别,在下列情况中使用 Map 会是更好的选择:

场景MapObject
意外的键Map 默认情况不包含任何键。只包含显式插入的键。一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
键的类型一个 Map 的键可以是任意值,包括函数、对象或任意基本类型。一个 Object 的键必须是一个 String 或是 Symbol
键的顺序Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。虽然 Object 的键目前是有序的,但并不总是这样,而且这个顺序是复杂的。因此,最好不要依赖属性的顺序。
SizeMap 的键值对个数可以轻易地通过 size 属性获取。Object 的键值对个数只能手动计算
迭代Map 是 可迭代的 的,所以可以直接被迭代。对象可以实现迭代协议,或者你可以使用 Object.keys 或 Object.entries
性能在频繁增删键值对的场景下表现更好。在频繁添加和删除键值对的场景下未作出优化。

备注: 虽然从 ES5 开始可以用 Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。

自 ECMAScript 2015 规范以来,对象的属性被定义为是有序的;ECMAScript 2020 则额外定义了继承属性的顺序。参见 OrdinaryOwnPropertyKeys 和 EnumerateObjectProperties 抽象规范说明。但是,请注意没有可以迭代对象所有属性的机制,每一种机制只包含了属性的不同子集。(for-in 仅包含了以字符串为键的属性;Object.keys 仅包含了对象自身的、可枚举的、以字符串为键的属性;Object.getOwnPropertyNames 包含了所有以字符串为键的属性,即使是不可枚举的;Object.getOwnPropertySymbols 与前者类似,但其包含的是以 Symbol 为键的属性,等等。)

Map的实例属性和方法

size 属性

size和数组的length属性有点像,也是返回类似总个数,

let map = new Map();
map.set('tom', 18)
map.set('jack', 28)
console.log(map.size); // 2

使用时注意,size是属性,不要这样加括号size()

clear 方法

let map = new Map();
map.set('tom', 18)
map.set('jack', 28)
map.clear()
console.log(map.size); //0
  • 移除 Map 对象中所有的键值对。

delete方法

  • Map.prototype.delete(key)

  • 移除 Map 对象中指定的键值对,如果键值对存在,返回 true,否则返回 false。调用 delete 后再调用 Map.prototype.has(key) 将返回 false

let map = new Map();
map.set('tom', 18)
map.set('jack', 28)
console.log(map); // Map(2) { 'tom' => 18, 'jack' => 28 }
map.delete('tom')
console.log(map); // Map(1) { 'jack' => 28 }

get方法

let map = new Map();
map.set('tom', 18)
map.set('jack', 28)
console.log(map.get('tom')); // 18

has 方法

  • Map.prototype.has(key)

  • 返回一个布尔值,用来表明 Map 对象中是否存在与 key 关联的值。

let map = new Map();
map.set('tom', 18)
map.set('jack', 28)
console.log(map.has('tom')); // true

set方法

let map = new Map();
map.set('tom', 18)
map.set('jack', 28)
console.log(map); // Map(2) { 'tom' => 18, 'jack' => 28 }

keys 迭代方法

  • Map.prototype.keys()

  • 返回一个新的迭代对象,其中包含 Map 对象中所有的,并以插入 Map 对象的顺序排列。

let map = new Map();
map.set('tom', 18)
map.set('jack', 28)
console.log(map.keys()); // [Map Iterator] { 'tom', 'jack' }

values 迭代方法

  • Map.prototype.values()

  • 返回一个新的迭代对象,其中包含 Map 对象中所有的,并以插入 Map 对象的顺序排列。

let map = new Map();
map.set('tom', 18)
map.set('jack', 28)
console.log(map.values()); // [Map Iterator] { 18, 28 }

entries 迭代方法

  • Map.prototype.entries()

  • 返回一个新的迭代对象,其为一个包含 Map 对象中所有键值对的  [key, value] 数组,并以插入 Map 对象的顺序排列。

let map = new Map();
map.set('tom', 18)
map.set('jack', 28)
console.log(map.entries()); // [Map Entries] { [ 'tom', 18 ], [ 'jack', 28 ] } 得到二维数组

算法题实战

以下四道题目来自牛客网,到时大厂常见算法题,使用的解答方式也都是巧妙的运用了哈希对象,存值取值的特点。下面一起来感受体会以下吧

题源链接

两数之和

题目描述 image.png 解析参考

  • 创建哈希对象
  • 遍历数组找到每一轮与number[i]相加等于target的值为num
  • 如果存在map中就返回num的下标值和当前下标i+1,因为下标从1开始所以要加一
  • 将当前number[i]值和下标保存到map中
function twoSum( numbers ,  target ) {
    let map = new Map() // 创建哈希对象
    for(let i = 0; i<numbers.length;i++ ){
      let num = target - numbers[i]  // 每一轮与number[i]相加等于target的值
      if(map.has(num)) return [map.get(num),i+1] // 如果存在map中就返回num的下标值和当前下标i+1,因为下标从1开始所以要加一
      else map.set(numbers[i], i+1) // 将当前number[i]值和下标保存到map中
    }
}

这题使用map对象的特性,帮我们存储了每个值和它的下标,然后我们通过判断满足num+number[i]=target的num是否在之前出现过,出现过就返回对应下标,不满足就继续遍历,因为下标要升序返回所以写[map.get(num),i+1]

数组中出现次数超过一半的数字

题目描述

image.png 解析参考

  • 创建哈希对象
  • 遍历数组判断对应数组的每一项是否在哈希对象中,map记录它们出现的次数
  • 返回出现次数超过一半的数组项
function MoreThanHalfNum_Solution(numbers)
{
    let len = numbers.length // 数组长度
    let half = len >> 1 // 数组长度的一半,位运算相当于 len/2
    let map = new Map() // 创建哈希对象
    for(let item of numbers){
        if(map.has(item)) map.set(item, map.get(item)+1) // 如果值在map中了,则将记录结果加一
        else map.set(item,1) // 如果不在map中,则记录其结果为1
        if(map.get(item) > half) return item  // 如果i出现次数超过half就返回
    }
}

不了解位运算可以点这里,超详细噢!

数组中只出现一次的两个数字

题目描述

image.png

解析参考

  • 创建map对象和res数组报错结果
  • 遍历数组用map记录数组每项出现的次方
  • 遍历map 对象,找出出现次数为一次的两个数字
  • 注意map.forEach((value,key)) 中value和key顺序不可以变,第一个参数表示value,第二个参数表示key。
/**
 * @param array int整型一维数组 
 * @return int整型一维数组
 */
function FindNumsAppearOnce( array ) {
    // write code here
    let len = array.length
    let map = new Map()
    let res = [] // 存储结果
    // 遍历数组用map记录数组每项出现的次方
    for(let i = 0; i <len; i++){
        if(map.has(array[i])){
            map.set(array[i],map.get(array[i])+1)
        }
        else map.set(array[i], 1)
    }
    // 遍历map 对象,找出出现次数为一次的两个数字
    // 注意map.forEach((value,key)) 中value和key顺序不可以变,第一个参数表示value,第二个参数表示key,
    map.forEach((value,key) => {
        if(value ==1) res.push(key)
    })
    // 返回升序返回结果
    return res.sort((a,b) => a-b)
}

缺失的第一个正整数

题目描述

image.png

解析参考

  • 创建map对象,遍历数组,将数组的值记录到map对象中
  • while循环找到最小正整数。
/**
 * @param nums int整型一维数组 
 * @return int整型
 */
function minNumberDisappeared( nums ) {
    let map = new Map() // 创建map对象
    let n = nums.length // 数组长度
    // 遍历nums数组,将出现过的数记录在map对象中
    for(let i = 0; i<n; i++){
        if(!map.has(nums[i])) map.set(nums[i], 1)
    }
    let res = 1 // 最小正整数为1
    // 从1 往上加,没有连续的值就是最小正整数了,如果都是连续 的值,那结果就是map对象中的最大数加一了
    while(map.has(res)){
       res++
    }
    return res
}

总结

Map对象是ES6中引入的JavaScript语法,Map对象的在存储中从健的类别的多类型和性能等方面都要比Object对象要优雅。介绍了Map对象,然后通过哈希相关算法题来加强巩固知识。通过上面算法题的练习我们发现使用map对象可以高效的查出数组中的一些特定条件内容。

看完三件事!!!

graph TD
点赞 --> 关注-->评论