LeetCode 11:盛最多水的容器 TypeScript 笔记
1. 实现代码
function maxArea(height: number[]): number {
const n = height.length;
let left = 0;
let right = n - 1;
let bestArea = 0;
while (left < right) {
const leftHeight = height[left];
const rightHeight = height[right];
bestArea = Math.max(
bestArea,
Math.min(leftHeight, rightHeight) * (right - left)
);
if (leftHeight <= rightHeight) {
left++;
} else {
right--;
}
}
return bestArea;
}时间复杂度为 O(n),额外空间复杂度为 O(1)。高度相等时,移动任意一侧都可以。
2. 笔记
2.1 多变量声明
一个 let 声明可以包含多个变量,但每个变量都需要分别写自己的初始化表达式,逗号分隔的是完整的变量声明项。
const n = 10;
let left = 0, right = n - 1;以下写法只初始化了 right:
let left, right = 0;left 没有初始化表达式,执行声明后其值为 undefined;right 被初始化为 0。
工程中通常更倾向于每行声明一个变量,并在声明时初始化:
const n = 10;
let left = 0;
let right = n - 1;这种写法更便于阅读、修改,也方便为各个变量添加注释。
2.2 JavaScript 标准库 API 与内置对象
Math、数组和字符串的方法等,通常称为 JavaScript 标准库 API。更具体地说,Math 是标准内置对象,max、min 是它的方法。
TypeScript 沿用 JavaScript 的这些运行时能力,并提供相应的类型检查。调用方式取决于 API 所属的位置:
Math.max(3, 8); // 内置对象的方法
nums.push(5); // 数组实例的方法
parseInt("42", 10); // 全局函数max、min 没有作为全局函数提供,所以需要写 Math.max(...)、Math.min(...)。Math 可以直接使用,无需导入,也无需通过 new 创建。
2.3 常见的 Math 方法
| 写法 | 作用 | 结果 |
|---|---|---|
Math.max(3, 8, 5) | 最大值 | 8 |
Math.min(3, 8, 5) | 最小值 | 3 |
Math.abs(-3) | 绝对值 | 3 |
Math.floor(2.8) | 向下取整 | 2 |
Math.ceil(2.2) | 向上取整 | 3 |
Math.trunc(-2.8) | 截去小数部分,向零取整 | -2 |
Math.round(2.6) | 取最近的整数,恰好居中时取较大的整数 | 3 |
Math.floor 与 Math.trunc 对负数的处理不同:
Math.floor(-2.8); // -3
Math.trunc(-2.8); // -2Math.max、Math.min 可以接收多个数字。已有数组时,可以使用展开语法将数组元素作为各个参数传入:
const nums = [3, 8, 5];
Math.max(...nums); // 8,相当于 Math.max(3, 8, 5)
Math.min(...nums); // 3,相当于 Math.min(3, 8, 5)2.4 写题时常见的其他标准库 API
2.4.1 数组 Array<T>
| 操作 | 常见写法 | 返回值与注意事项 |
|---|---|---|
| 创建空数组 | const nums: number[] = [] | 元素类型为 number |
| 创建并填充 | new Array<number>(n).fill(0) | 返回长度为 n、全部填为 0 的数组;单独 new Array(n) 不会填零 |
| 按下标生成 | Array.from({ length: n }, (_, i) => i) | 返回 [0, 1, ..., n - 1] |
| 创建二维数组 | Array.from({ length: rows }, () => new Array<number>(cols).fill(0)) | 每行是独立数组;用 .fill(同一个数组) 会让各行共享引用 |
| 长度 | nums.length | 元素个数;是属性,不加 () |
| 读取、修改元素 | nums[i]、nums[i] = value | 下标从 0 开始;越界读取为 undefined;修改会影响原数组 |
| 尾部追加 | nums.push(value) | 修改原数组,返回新长度;可一次追加多个元素 |
| 尾部删除 | nums.pop() | 修改原数组,返回末尾元素;空数组返回 undefined |
| 头部插入 | nums.unshift(value) | 修改原数组,返回新长度;通常需要移动已有元素,耗时 O(n) |
| 头部删除 | nums.shift() | 修改原数组,返回首元素;空数组返回 undefined;通常耗时 O(n) |
| 截取 | nums.slice(start, end) | 返回新数组,包含 start、不包含 end;原数组不变,复制是浅复制 |
| 浅复制 | [...nums]、nums.slice() | 返回新数组;如果元素是对象,仍然共享那些对象 |
| 删除、插入、替换 | nums.splice(start, deleteCount, ...items) | 修改原数组,返回被删除元素组成的数组;中间增删通常耗时 O(n) |
| 查找值 | nums.indexOf(value) | 返回首次出现的下标,找不到返回 -1;通常耗时 O(n) |
| 判断包含 | nums.includes(value) | 返回 boolean;通常耗时 O(n) |
| 按条件查找 | nums.find(x => x > 0)、nums.findIndex(x => x > 0) | 分别返回首个匹配元素或其下标;找不到时分别为 undefined、-1 |
| 数字排序 | nums.sort((a, b) => a - b) | 原地升序,返回原数组;降序使用 b - a;默认排序按字符串顺序比较 |
| 反转 | nums.reverse() | 原地反转,返回原数组 |
| 映射 | nums.map(x => x * 2) | 返回映射后的新数组 |
| 筛选 | nums.filter(x => x > 0) | 返回满足条件的元素组成的新数组 |
| 累积 | nums.reduce((sum, x) => sum + x, 0) | 示例返回总和;0 是累积初值,空数组时也能返回结果 |
| 条件判断 | nums.some(x => x > 0)、nums.every(x => x > 0) | 返回是否存在匹配元素、是否全部匹配;空数组分别返回 false、true |
| 连接为字符串 | nums.join(",") | 返回字符串,原数组不变 |
| 遍历 | for (const x of nums) { ... } | 依次取得元素,可用 break、continue |
| 同时遍历下标与元素 | for (const [i, x] of nums.entries()) { ... } | 每轮取得 [下标, 元素] |
| 清空 | nums.length = 0 | 修改原数组,其他共享该数组的引用也能看到清空结果 |
2.4.2 栈:使用数组实现
| 操作 | 常见写法 | 返回值与注意事项 |
|---|---|---|
| 创建 | const stack: number[] = [] | JS/TS 标准库没有独立的 Stack 类;用数组尾部作为栈顶 |
| 入栈 | stack.push(value) | 修改数组,返回新长度;通常均摊 O(1) |
| 出栈 | stack.pop() | 返回并删除栈顶;空栈返回 undefined;通常 O(1) |
| 查看栈顶 | stack[stack.length - 1] | 不删除元素;空栈得到 undefined |
| 元素个数 | stack.length | 当前栈大小 |
| 判空 | stack.length === 0 | 返回 boolean |
2.4.3 队列:使用数组与队头下标实现
| 操作 | 常见写法 | 返回值与注意事项 |
|---|---|---|
| 创建 | const queue: number[] = []; let head = 0 | JS/TS 标准库没有独立的 Queue 类;用 head 表示当前队头 |
| 入队 | queue.push(value) | 追加到队尾;通常均摊 O(1) |
| 出队 | const value = queue[head++] | 先确认 head < queue.length;读取队头并推进下标,避免移动剩余元素 |
| 查看队头 | queue[head] | 不出队;空队列得到 undefined |
| 有效元素个数 | queue.length - head | 已出队元素仍保留在底层数组中,不能只用 .length 表示队列大小 |
| 判空 | head === queue.length | 在每次出队前判空、避免越界推进下标的前提下使用 |
| 处理所有入队元素 | while (head < queue.length) { const value = queue[head++]; ... } | 循环内部可以继续 push(),适合 BFS 等场景 |
| 简单出队方式 | queue.shift() | 另一种实现方式:不用 head,直接删除首元素;通常耗时 O(n),大量出队时不优先采用;不要与队头下标方案混用 |
| 重置 | queue.length = 0; head = 0 | 清空底层数组并重置队头;仅推进 head 不会释放数组保存的已出队元素引用 |
2.4.4 哈希映射 Map<K, V>
| 操作 | 常见写法 | 返回值与注意事项 |
|---|---|---|
| 创建 | const counts = new Map<string, number>() | 保存键与值的对应关系 |
| 从键值对创建 | new Map<string, number>([["a", 1], ["b", 2]]) | 返回包含初始条目的 Map |
| 元素个数 | counts.size | 键值对数量;是属性,不加 () |
| 插入、更新 | counts.set(key, value) | 修改 Map,返回 Map 自身;已有键会覆盖原值 |
| 读取 | counts.get(key) | 返回对应值,找不到返回 undefined;不是 counts[key] |
| 判断键存在 | counts.has(key) | 返回 boolean;即使允许保存 undefined,也能区分键是否存在 |
| 计数加一 | counts.set(key, (counts.get(key) ?? 0) + 1) | 不存在时按 0 处理,再将新次数写回 |
| 删除 | counts.delete(key) | 修改 Map;删除成功返回 true,键不存在返回 false |
| 清空 | counts.clear() | 删除全部条目,返回 undefined |
| 遍历键值对 | for (const [key, value] of counts) { ... } | 按键的插入顺序遍历;也可使用 counts.entries() |
| 遍历键、值 | counts.keys()、counts.values() | 返回可用于 for...of 的迭代器 |
| 转成数组 | Array.from(counts.keys())、Array.from(counts.values()) | 返回键数组、值数组;Array.from(counts) 返回键值对数组 |
| 键的比较 | counts.set(objectKey, value) | 对象键按身份区分,不按内容比较;另一个内容相同的对象不是同一个键 |
| 查找性能 | get()、has()、set()、delete() | 标准要求平均访问耗时低于线性;具体复杂度取决于引擎实现 |
2.4.5 哈希集合 Set<T>
| 操作 | 常见写法 | 返回值与注意事项 |
|---|---|---|
| 创建 | const seen = new Set<number>() | 保存不重复的值 |
| 从数组创建、去重 | new Set(nums) | 返回集合,重复值只保留一份 |
| 元素个数 | seen.size | 不重复的元素数量;是属性,不加 () |
| 添加 | seen.add(value) | 修改 Set,返回 Set 自身;重复添加不会增加大小 |
| 判断存在 | seen.has(value) | 返回 boolean |
| 删除 | seen.delete(value) | 修改 Set;删除成功返回 true,值不存在返回 false |
| 清空 | seen.clear() | 删除全部元素,返回 undefined |
| 遍历 | for (const value of seen) { ... } | 按插入顺序遍历;没有按位置读取的 seen[i] |
| 转成数组 | Array.from(seen)、[...seen] | 返回包含集合元素的新数组;[...new Set(nums)] 可用于数组去重 |
| 元素的比较 | seen.add(objectValue) | 对象按身份区分,内容相同的不同对象仍可同时存在;Map、Set 都将 NaN 视为与自身相等,+0、-0 视为相等 |
| 查找性能 | has()、add()、delete() | 标准要求平均访问耗时低于线性;具体复杂度取决于引擎实现 |
2.4.6 字符串 string
| 操作 | 常见写法 | 返回值与注意事项 |
|---|---|---|
| 长度 | text.length | UTF-16 编码单元数量;对英文字母、数字可直接按常见字符计数理解 |
| 按下标读取 | text[i] | 返回该位置的字符串,越界为 undefined;不能用下标修改字符串 |
| 读取编码 | text.charCodeAt(i) | 返回 UTF-16 编码单元值;例如 c.charCodeAt(0) - "a".charCodeAt(0) 计算小写字母下标 |
| 截取 | text.slice(start, end) | 包含 start、不包含 end,返回字符串 |
| 判断包含 | text.includes(part) | 返回 boolean |
| 查找位置 | text.indexOf(part) | 首次出现的位置,找不到返回 -1 |
| 判断前缀、后缀 | text.startsWith(prefix)、text.endsWith(suffix) | 返回 boolean |
| 拆分 | text.split(",") | 按分隔符返回字符串数组;英文字母场景可用 text.split("") 拆成字符数组 |
| 拼接 | a + b | 返回拼接后的字符串;字符串不可变,text += part 会给变量重新赋值 |
| 去除两端空白 | text.trim() | 返回处理后的字符串 |
| 大小写转换 | text.toLowerCase()、text.toUpperCase() | 返回转换后的字符串 |
| 替换 | text.replace("a", "b")、text.replaceAll("a", "b") | 字符串模式下分别替换首个、全部匹配,返回处理后的字符串 |
| 遍历 | for (const c of text) { ... } | 按 Unicode 码点遍历;并不总与 .length、下标访问采用相同单位 |
2.4.7 数字检查与类型转换
| 操作 | 常见写法 | 返回值与注意事项 |
|---|---|---|
| 转成数字 | Number("42") | 返回数字 42;转换失败通常为 NaN,空字符串转为 0 |
| 转成字符串 | String(42) | 返回字符串 "42" |
| 解析整数 | parseInt("101", 2) | 按指定进制解析,示例为 5;可以只解析有效前缀,如 parseInt("12px", 10) 为 12 |
| 判断整数 | Number.isInteger(value) | 返回 boolean,不先把字符串转成数字 |
| 判断有限数值 | Number.isFinite(value) | 排除 NaN、正负无穷,也不做类型转换 |
| 判断 NaN | Number.isNaN(value) | 判断值是否为 NaN;不能用 value === NaN 判断 |
2.4.8 其他常见算法数据结构
| 数据结构 | 标准库支持与常见表示 | 使用要点 |
|---|---|---|
| 双端队列 Deque | 无独立内置类;数组可组合 push()、pop()、unshift()、shift() | 数组头部增删通常为 O(n);单调队列等大量操作场景可自行实现环形缓冲区等结构 |
| 堆、优先队列 | 无标准内置类;通常用数组自行实现二叉堆,或使用题目平台明确提供的类 | 没有通用标准的 PriorityQueue API,需按实现或平台文档使用 |
| 链表 | 无通用内置链表类;通常由题目提供 ListNode,或自行定义节点 | 通过 node.val、node.next 访问,具体字段和类型以定义为准 |
| 二叉树 | 无通用内置二叉树类;通常由题目提供 TreeNode | 通过 node.val、node.left、node.right 访问;注意空节点 |
| 图的邻接表 | 编号连续时可用 number[][],任意键时可用 Map<number, number[]> | 每个节点对应一个邻居数组,使用数组或 Map 的 API 构建、遍历 |
| 并查集 | 无内置类;常用 parent: number[]、size: number[] 等数组表示 | find、union 需要自行实现,不是标准库函数 |
学习 API 时,重点确认参数是什么、返回什么、是否修改原对象。例如,slice() 返回新数组,sort() 修改原数组,这会直接影响程序行为。