当前位置: 首页 > news >正文

哈希表 - 两数之和(Map) - JS

在 JavaScript 中,map 这个词确实可能引起混淆,因为它有两种不同的含义:

  1. 数组的 map() 方法:这是数组的一个高阶函数,用于转换数组元素
  2. Map 集合类型:这是 ES6 引入的一种新的集合类型,用于存储键值对

一、JavaScript 中数组里的 map 方法

map() 是 JavaScript 数组(Array)的一个内置方法,它创建一个新数组,其结果是该数组中的每个元素调用一次提供的函数后的返回值。

基本语法

const newArray = arr.map(callback(currentValue[, index[, array]]) {
  // 返回新数组的元素
}[, thisArg]);

参数说明

  • callback:生成新数组元素的函数,接收三个参数:
    • currentValue:当前正在处理的元素
    • index(可选):当前元素的索引
    • array(可选):调用map的数组
  • thisArg(可选):执行callback时使用的this

特点

  1. 不改变原数组map方法不会改变原数组,而是返回一个新数组
  2. 遍历范围:在第一次调用callback之前会确定数组的范围,之后添加到数组中的元素不会被callback访问到
  3. 跳过空位:如果数组是稀疏的(有空白元素),map会跳过这些空位

示例

基本使用

const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6]

使用索引参数

const numbers = [1, 2, 3];
const squaredWithIndex = numbers.map((num, index) => num * index);
console.log(squaredWithIndex); // [0, 2, 6]

处理对象数组

const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 35 }
];

const names = users.map(user => user.name);
console.log(names); // ['Alice', 'Bob', 'Charlie']

使用 thisArg

const obj = {
  multiplier: 10,
  calculate: function(arr) {
    return arr.map(function(num) {
      return num * this.multiplier;
    }, this);
  }
};

console.log(obj.calculate([1, 2, 3])); // [10, 20, 30]

与 forEach 的区别

  • map 返回一个新数组,而 forEach 不返回值(返回 undefined
  • map 适用于需要基于原数组创建新数组的场景,forEach 适用于仅需要遍历数组执行操作的场景

注意事项

  1. 如果回调函数没有返回值,新数组将填充 undefined
  2. 对于稀疏数组,空元素会被跳过,但新数组仍会保持相同的长度
  3. 对于大型数组,map 可能会比手动循环慢一些,因为需要创建新数组

map 是函数式编程中常用的方法,可以使代码更简洁、更易读。

二、Map集合类型

Map 是 ES6 (ECMAScript 2015) 引入的一种新的集合类型,用于存储键值对(key-value pairs)。它类似于对象(Object),但有以下几个重要区别:

基本特性

  1. 键的类型:Map 的键可以是任意值(包括对象、函数等),而普通对象的键只能是字符串或 Symbol
  2. 顺序保证:Map 会记住键的原始插入顺序
  3. 大小获取:可以直接通过 .size 属性获取 Map 中的元素数量
  4. 性能优化:在频繁增删键值对的场景下表现更好

基本用法

创建 Map

// 创建一个空 Map
const map = new Map();

// 通过二维数组初始化
const map2 = new Map([
  ['name', 'Alice'],
  ['age', 25],
  [1, 'number one']
]);

最常用的三个方法

1.map.set(key, value)

作用:向 Map 中添加或更新一个键值对。
参数

  • key:可以是任意类型(数字、字符串、对象等)。
  • value:任意值(数字、字符串、数组、对象等)。

示例

const map = new Map();
map.set('name', 'Alice');  // 添加键值对 'name' → 'Alice'
map.set(123, { age: 25 }); // 添加键值对 123 → { age: 25 }
map.set('name', 'Bob');    // 更新 'name' 的值为 'Bob'

特点

  • 如果 key 已存在,set() 会覆盖旧值。
  • 键可以是任意类型(普通对象的键只能是字符串或 Symbol)。

2. map.get(key)

作用:获取指定 key 对应的 value
参数

  • key:要查找的键。

返回值

  • 如果 key 存在,返回对应的 value
  • 如果 key 不存在,返回 undefined

const map = new Map();
map.set('name', 'Alice');

console.log(map.get('name')); // 输出 'Alice'
console.log(map.get('age'));  // 输出 undefined

用途

  • 在 "两数之和" 问题中,map.get(complement) 用于快速获取补数的索引。

3. map.has(key)

作用:检查 Map 中是否存在指定的 key
参数

  • key:要检查的键。

返回值

  • true(如果 key 存在)。
  • false(如果 key 不存在)。
const map = new Map();
map.set('name', 'Alice');

console.log(map.has('name')); // 输出 true
console.log(map.has('age'));  // 输出 false

用途

  • 在 "两数之和" 问题中,map.has(complement) 用于快速判断补数是否存在。

三者的关系

方法用途返回值时间复杂度
set()添加/更新键值对返回整个 Map(可链式调用)O(1)
get()获取指定 key 的 valuevalue 或 undefinedO(1)
has()检查 key 是否存在true 或 falseO(1)

    添加/获取元素

    const map = new Map();
    
    // 添加元素
    map.set('name', 'Alice');
    map.set(1, 'number one');
    map.set({}, 'object key');
    
    // 获取元素
    console.log(map.get('name')); // 'Alice'
    console.log(map.get(1));      // 'number one'

    检查键是否存在

    console.log(map.has('name')); // true
    console.log(map.has('nonexistent')); // false

    删除元素

    map.delete('name'); // 删除指定键
    map.clear(); // 清空整个 Map

    获取大小

    console.log(map.size); // 返回 Map 中键值对的数量

    遍历 Map

    Map 提供了多种遍历方式:

    const map = new Map([
      ['name', 'Alice'],
      ['age', 25],
      ['job', 'developer']
    ]);
    
    // 1. for...of 遍历
    for (const [key, value] of map) {
      console.log(key, value);
    }
    
    // 2. forEach 方法
    map.forEach((value, key) => {
      console.log(key, value);
    });
    
    // 3. 获取键、值或条目的迭代器
    console.log([...map.keys()]);    // ['name', 'age', 'job']
    console.log([...map.values()]);  // ['Alice', 25, 'developer']
    console.log([...map.entries()]); // 同直接迭代 Map

    Map 与 Object 的比较

    特性MapObject
    键的类型任意值字符串或 Symbol
    顺序按插入顺序不保证顺序
    大小通过 .size 获取需要手动计算
    默认属性有原型链属性
    性能频繁增删时更优一般情况更优
    序列化不能直接 JSON.stringify可以直接序列化

    使用场景

    1. 需要非字符串键时

      const objKey = {};
      const map = new Map();
      map.set(objKey, 'value for object');
    2. 需要保持插入顺序时

      const map = new Map();
      map.set('a', 1);
      map.set('b', 2);
      map.set('c', 3);
      // 遍历时会保持 a → b → c 的顺序
    3. 频繁增删键值对时

      // Map 在频繁增删时性能更好
      for (let i = 0; i < 1000; i++) {
        map.set(i, i * 2);
        if (i % 2 === 0) map.delete(i / 2);
      }
    4. 需要避免属性冲突时

      // 不会意外访问到原型上的属性
      const map = new Map();
      map.set('toString', 'custom toString');
      // 不会调用 Object.prototype.toString

    注意事项

    1. Map 的键比较是基于 "SameValueZero" 算法:

      • NaN 被视为等于 NaN(尽管 NaN !== NaN
      • -0 和 +0 被视为相等
      • 其他值使用严格相等比较 (===)
    2. 对象作为键时,只有引用相同才会被视为同一个键:

      const obj1 = {};
      const obj2 = {};
      map.set(obj1, 'value1');
      map.set(obj2, 'value2');
      console.log(map.get(obj1)); // 'value1'
      console.log(map.get(obj2)); // 'value2'
    3. Map 不能被直接序列化为 JSON,需要先转换为数组:

      const map = new Map([['a', 1], ['b', 2]]);
      const jsonStr = JSON.stringify([...map]);

    Map 为 JavaScript 提供了一种更灵活、更强大的键值存储机制,特别适合需要复杂键或需要保持插入顺序的场景。

    三、两数之和

    1. 两数之和

     

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

    方法一:暴力枚举法(不推荐)

    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function (nums, target) {
        let arr = [];
        for (let i = 0; i < nums.length; i++) {
            for (let j = i + 1; j < nums.length; j++) {
                if (nums[i]+nums[j] === target) {
                    arr.push(i);
                    arr.push(j);
                }
            }
        }
        return arr
    };

    方法二:哈希表法(推荐)

    map()设置为当前数组,索引:map.set(nums[i], i);

    has检查值。get检查索引。

    var twoSum = function(nums, target) {
        // 1. 创建一个空的哈希表(Map)来存储数字和它们的索引
        const map = new Map();
        
        // 2. 遍历数组中的每个数字
        for (let i = 0; i < nums.length; i++) {
            // 3. 计算当前数字所需的"补数"(即target - 当前数字)
            const complement = target - nums[i];
            
            // 4. 检查这个补数是否已经在哈希表中存在
            if (map.has(complement)) {
                // 5. 如果存在,返回补数的索引和当前数字的索引
                return [map.get(complement), i];
            }
            
            // 6. 如果不存在,把当前数字和它的索引存入哈希表
            map.set(nums[i], i);
        }
        
        // 7. 如果没有找到符合条件的两个数,返回空数组
        return [];
    };

    步骤解析

    1. map.has(complement):检查需要的补数是否之前出现过。
    2. map.get(complement):如果补数存在,获取它的索引。
    3. map.set(nums[i], i):将当前数字和索引存入 Map

    也可以用数组 通过includes判断是否有这个数,通过indexOf返回下表

    var twoSum = function(nums, target) {
        const arr = new Array();
        for (let i = 0; i < nums.length; i++) {
            const complement = target - nums[i];
            if (arr.includes(complement)) {
                return [arr.indexOf(complement), i];
            }
            arr.push(nums[i])
        }
        return [];
    };

    但是 问题依然存在

    • includes() 仍然是 ​O(n),整体时间复杂度 ​O(n²)
    • 需要额外维护 indexMap,不如直接用 Map 简洁高效。

    为什么推荐 Map 而不是数组?

    操作数组 (Array)哈希表 (Map)优势
    检查存在includes() (O(n))has() (O(1))Map 快 100 倍+
    获取索引indexOf() (O(n))get() (O(1))Map 直接存储索引

    数组 vs Map 的对比

    操作数组 (Array)Map问题
    检查值是否存在arr.includes(x)(O(n) 遍历)map.has(x)(O(1) 哈希查找)数组慢
    获取值的索引arr.indexOf(x)(O(n) 遍历)map.get(x)(O(1) 直接拿索引)数组慢
    存储数据arr.push(x)(只能存值,无索引映射)map.set(num, index)(存值+索引)

    数组无法高效存索引

    处理重复值需要额外逻辑天然支持Map 更简洁

    方法三:排序+双指针法(特定场景适用)

    function twoSum(nums, target) {
        const sorted = nums.map((num, index) => ({ num, index }))
                           .sort((a, b) => a.num - b.num);
        
        let left = 0, right = sorted.length - 1;
        while (left < right) {
            const sum = sorted[left].num + sorted[right].num;
            if (sum === target) {
                return [sorted[left].index, sorted[right].index].sort();
            } else if (sum < target) {
                left++;
            } else {
                right--;
            }
        }
        return [];
    }

    四、四数相加

    454. 四数相加 II

     

    给你四个整数数组 nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

    • 0 <= i, j, k, l < n
    • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

    示例 1:

    输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
    输出:2
    解释:
    两个元组如下:
    1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
    2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
    

    示例 2:

    输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
    输出:1
    

      提示:

    • n == nums1.length
    • n == nums2.length
    • n == nums3.length
    • n == nums4.length
    • 1 <= n <= 200
    • -228 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 228
    /**
     * @param {number[]} nums1
     * @param {number[]} nums2
     * @param {number[]} nums3
     * @param {number[]} nums4
     * @return {number}
     */
    var fourSumCount = function(nums1, nums2, nums3, nums4) {
        const twoSumMap = new Map();
        let count = 0;
        // 统计nums1和nums2数组元素之和
        for(let n1 of nums1){
            for(let n2 of nums2){
                const sum=n1+n2;
                twoSumMap.set(sum, (twoSumMap.get(sum) || 0) + 1)
            }
        }
        // 找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来
        for(const n3 of nums3) {
            for(const n4 of nums4) {
                const sum = n3 + n4;
                count += (twoSumMap.get(0 - sum) || 0)
            }
        }
        return count;
    };

     

    相关文章:

  1. OpenBMC:BmcWeb 处理http请求2 查找路由对象
  2. 0102-web架构网站搭建-基础入门-网络安全
  3. 我的世界1.20.1进阶模组开发教程——升级模板与文字格式
  4. Nginx 配置 HTTPS 与 WSS 完整指南
  5. 亚马逊新卖家破局指南:从0到1搭建可持续出单模型
  6. Linux内核编程
  7. 关于CodeJava的学习笔记——11
  8. 贪心算法(13)(java)合并区间
  9. vscode 使用vue3
  10. Linux内核设计——(一)进程管理
  11. 2025年汽车加气站操作工备考题库
  12. 基于超分辨率与YOLO的多尺度红外小目标检测方法YOLO-MST论文解读
  13. OpenCV 图形API(3)高层次设计概览
  14. 变量(Variable)
  15. 详解VAE损失函数
  16. 从零开始学Rust:所有权(Ownership)机制精要
  17. Android版本更新服务通知下载实现
  18. C++编程指南31 - 除非绝对必要,否则不要使用无锁编程
  19. BERT与Transformer到底选哪个-上部
  20. 福建省公共数据授权运营实践案例详解(运营机制及模式、运营单位、运营平台、场景案例等)
  21. 中石化网站是哪个公司做的/百度广告屏蔽
  22. 肇东市建设局网站/bt磁力兔子引擎
  23. iis做的网站模板/seo怎么去优化
  24. 如何做销售直播网站/新品上市怎么推广词
  25. 黑龙江城乡建设厅网站/商品推广
  26. 建网上商城的第三方网站哪个好/新闻头条最新消息30字