LeetCode 热题 100——哈希——两树之和
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]
求解
(1)双遍历
自己手搓真的是不优雅;并且复杂度为O(N^2)
关键点:nums[i] + nums[j] == target
var twoSum = function (nums, target) {for (var i = 0; i < nums.length; i++) {for (var j = i + 1; j < nums.length; j++) {if (nums[i] + nums[j] == target) {var arr_target = []arr_target[0] = iarr_target[1] = jreturn arr_target;// 语法也有点模糊了,可以直接返回:return [i, j];}}}
};
(2)使用Map
关键点: target - nums[j] = nums[i]
var twoSum = function(nums, target) {let map = new Map()for(let i = 0; i < nums.length; i++) {if (map.has(target - nums[i])) {return [i, map.get(target - nums[i])]} map.set(nums[i], i)}
};
补充知识点
Map
在 JavaScript 中,Map 是一种键值对集合(类似对象,但更灵活),用于存储和快速访问数据。它的核心特点和用法如下:
- 基本概念:Map 是 ES6 新增的内置对象,以键 - 值对的形式存储数据,其中键可以是任意类型(包括对象、函数、基本类型等),而普通对象的键只能是字符串或 Symbol。
- 常用方法
| 方法 | 功能描述 |
|---|---|
| new Map() | 创建一个空的 Map 实例。 |
| map.set(key, value) | 向 Map 中添加一个键值对,返回 Map 本身(可链式调用)。 |
| map.get(key) | 根据 key 获取对应的 value,若不存在则返回 undefined。 |
| map.has(key) | 判断 Map 中是否存在指定 key,返回布尔值(true/false)。 |
| map.delete(key) | 删除指定 key 对应的键值对,返回布尔值(成功删除为 true,否则 false)。 |
| map.clear() | 清空 Map 中所有键值对。 |
| map.size | 只读属性,返回 Map 中键值对的数量。 |
- 遍历方式
- Map 是可迭代对象,支持以下遍历方式:
- for…of 遍历:for (let [key, value] of map) { … }
- map.keys():遍历所有键。
- map.values():遍历所有值。
- map.entries():遍历所有键值对(默认遍历器,等价于 for…of 直接遍历 Map)。
- 与普通对象的区别
| 特性 | Map | 普通对象(Object) |
|---|---|---|
| 键的类型 | 任意类型(对象、函数、基本类型等) | 仅字符串或 Symbol |
| 键的顺序 | 严格按照插入顺序 | 键为字符串时顺序不确定(数字键会被排序) |
| 遍历便利性 | 原生支持迭代(for...of) | 需手动遍历(如 Object.keys()) |
| 大小获取 | 直接通过 map.size 获取 | 需手动计算(如 Object.keys(obj).length) |
- 代码示例(结合两数之和场景)
let map = new Map();
// 添加键值对
map.set(2, 0); // 键是2,值是下标0
map.set(7, 1); // 键是7,值是下标1// 查询键是否存在
console.log(map.has(2)); // true
console.log(map.has(9)); // false// 获取值
console.log(map.get(7)); // 1// 遍历
for (let [key, value] of map) {console.log(`键:${key},值:${value}`);
}
// 输出:
// 键:2,值:0
// 键:7,值:1
有关let和var
在 JavaScript 中,let 和 var 都是用于声明变量的关键字,但它们在作用域、变量提升、重复声明等方面有显著区别,主要差异如下:
-
作用域不同
-
var:声明的变量属于函数作用域(function scope)。即变量在声明它的整个函数内都可访问,若在函数外声明,则属于全局作用域。示例:
function test() {if (true) {var x = 10; // 函数作用域}console.log(x); // 10(可访问,因为在同一函数内) } test(); -
let:声明的变量属于块级作用域(block scope)。即变量只在当前代码块({} 包裹的区域,如 if、for、while 等)内有效,外部无法访问。示例:
function test() {if (true) {let x = 10; // 块级作用域(仅在 if 内有效)}console.log(x); // 报错:x is not defined(外部无法访问) } test();
-
-
变量提升(Hoisting)行为不同
-
var:会发生变量提升,即变量声明会被提升到作用域顶部,但赋值不会。示例:
console.log(a); // undefined(声明被提升,赋值未执行) var a = 20; -
let:不会发生变量提升,存在暂时性死区(Temporal Dead Zone, TDZ)。在 let 声明变量之前的区域,变量不可访问,访问会报错。示例:
console.log(b); // 报错:Cannot access 'b' before initialization let b = 30;
-
-
重复声明规则不同
-
var:允许在同一作用域内重复声明同一变量,后声明的会覆盖前一个。示例:
var c = 10; var c = 20; // 合法,覆盖前值 console.log(c); // 20 -
let:不允许在同一作用域内重复声明同一变量(包括与 var 或 function 声明的变量重名),否则会报错。示例:
let d = 10; let d = 20; // 报错:Identifier 'd' has already been declared
-
-
全局作用域中的表现不同
-
var:在全局作用域中声明的变量,会成为全局对象(如浏览器中的 window)的属性。示例:
var globalVar = 100; console.log(window.globalVar); // 100(浏览器环境) -
let:在全局作用域中声明的变量,不会成为全局对象的属性,仅在全局作用域中存在。示例:
let globalLet = 200; console.log(window.globalLet); // undefined(浏览器环境)
-
-
总结:
- 优先使用 let(或 const,用于声明常量),因为块级作用域更符合直觉,能减少变量污染和意外错误。
- 避免使用 var,其函数作用域和变量提升特性容易导致逻辑漏洞(如循环中的变量共享问题)。
补充const:
块级作用域,与 let 作用域规则完全一致;
声明的变量不可重新赋值(即 “常量”),声明时必须初始化,且后续不能修改其引用(注意:若值是对象 / 数组,其内部属性 / 元素可修改);(这点和let不一样,其他都一样)
不存在变量提升,存在暂时性死区(TDZ),声明前访问变量会报错;
不允许在同一作用域内重复声明同一变量(包括与 var、function 或自身重名),否则报错;
在全局作用域声明的变量不会成为全局对象的属性,仅在全局作用域中存在。
