【日常学习-算法:两数之和】

11 阅读4分钟

题目:leetcode.cn/problems/tw…

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

思路1:

一次for循环,查找 target - num[i] 差值是否在数组内 最先想到 查找差值的方法 indexOf,测试发现如果存在多个相同元素时不适用 解决:复制一份数组,把当前的元素替换成非数值

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
  const sliceArr = nums.slice(0);
  for (let i = 0; i < sliceArr.length; i+=1) {
    let cnum = sliceArr[i];
    sliceArr[i] = undefined;
    const index = sliceArr.indexOf(target-cnum)
    if (index >= 0) {
      return [i, index]
    }
  }
};

思路2:new Map 版本

看一眼排名第一的家伙怎么写的

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
  const map = new Map();
  for(let i = 0; i < nums.length; i++) {
      const complete = target - nums[i];
      if(map.has(complete)) {
          return [map.get(complete), i];
      }
      map.set(nums[i], i);
  }
  return null;
};

思路3:普通对象版本

卧槽原来用映射更快,那我改成普通对象是不是也是一样的效果

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
  const map = {};
  for(let i = 0; i < nums.length; i++) {
      const complete = target - nums[i];
      if(typeof map[complete] === 'number') {
          return [map[complete], i];
      }
      map[nums[i]] = i;
  }
  return null;
};

提交发现,比第一版快,但没有 Map 形式快,问下ai为啥?

对比普通对象版本 和 Map映射版本 为什么 Map 更快?

Qwen-AI: 代码分析与性能原因:

  1. const map = {}; (普通对象)

    • 类型检查与转换:当你使用 map[complete] 访问属性时,JavaScript 引擎需要处理属性名 complete。如果 complete 不是字符串,它会被强制转换成字符串。例如,map[1] 实际上会变成 map["1"]。此外,typeof map[complete] === 'number' 这种检查涉及类型推断,可能比直接检查存在性慢一些。
    • 原型链查找:普通对象有原型链。虽然在这个特定例子中影响可能不大(因为键是数字或数字字符串,不太可能与原型属性冲突),但在某些情况下,访问一个不存在的属性需要遍历原型链才能确定它真的不存在。
    • 优化:V8 等现代 JavaScript 引擎对对象属性访问做了大量优化,但对于用作哈希表的场景(频繁增删、键类型多样),这些优化可能不如专门为这种用途设计的 Map 高效。
  2. const map = new Map(); (Map 对象)

    • 专为键值对设计Map 是专门为了存储键值对而设计的数据结构。它明确知道它的用途是作为哈希表,因此引擎可以对其进行更针对性的优化。
    • 键类型Map 的键可以是任意 JavaScript 值(包括对象、函数等),而不仅仅是字符串或 Symbol。这意味着它不需要进行像对象那样的字符串转换和相关的类型处理开销。
    • 性能优化Map 的 setgethas 和 delete 操作在设计上就考虑了高性能,尤其是在频繁操作的场景下。浏览器引擎通常对 Map 的这些方法有更直接和高效的实现。
    • 无原型链干扰Map 实例没有用户可直接访问的原型链用于存储数据,因此不存在属性访问时需要遍历原型链的问题。 虽然两种方法在算法层面效率相同(O(n)),但 new Map() 的方式在底层实现和引擎优化上更适合用作频繁读写的哈希表。它避免了普通对象的一些潜在开销(如类型转换、原型链查找),并且其方法 (has, get, set) 是为哈希表操作专门优化的。因此,在需要高性能哈希表操作的场景下,Map 通常会比普通对象 {} 表现得更好。

总结:

虽然两种方法在算法层面效率相同(O(n)),但 new Map() 的方式在底层实现和引擎优化上更适合用作频繁读写的哈希表。它避免了普通对象的一些潜在开销(如类型转换、原型链查找),并且其方法 (has, get, set) 是为哈希表操作专门优化的。因此,在需要高性能哈希表操作的场景下,Map 通常会比普通对象 {} 表现得更好。

需要说明的是,实际性能差异可能因 JavaScript 引擎(如 V8, SpiderMonkey, JavaScriptCore)的不同版本和具体测试用例而有所变化,但普遍认为 Map 在这类场景下是更优的选择。

原来普通对象 版本 typeof map[complelet] 类型判断、取值都造成了性能开销,以后要在项目里多用Map 来解决这种查找逻辑操作

日期:2025-09-21 22:47