面试题库

整理各类技术面试题目,包含解题思路和参考答案。共收录 6 道题目。

简单
算法#数组#哈希表

两数之和

题目描述

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

解题思路

使用哈希表存储已遍历的元素及其索引。遍历数组时,检查 target - nums[i] 是否在哈希表中,如果存在则返回两个索引;否则将当前元素加入哈希表。时间复杂度 O(n),空间复杂度 O(n)。

参考代码

function twoSum(nums: number[], target: number): number[] {
  const map = new Map<number, number>();
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];
    if (map.has(complement)) {
      return [map.get(complement)!, i];
    }
    map.set(nums[i], i);
  }
  return [];
}
来源:LeetCode #1
中等
前端#JavaScript#Promise

实现一个 Promise.all

题目描述

请实现一个 Promise.all 方法,接收一个 Promise 数组,返回一个新的 Promise。当所有 Promise 都成功时,返回所有结果的数组;当任一 Promise 失败时,返回第一个失败的原因。

解题思路

创建一个结果数组和计数器,遍历所有 Promise,使用 Promise.resolve 包装确保兼容非 Promise 值。成功时将结果存入对应位置并增加计数,当计数等于总数时 resolve 结果数组。任一失败则直接 reject。

参考代码

function promiseAll<T>(promises: Promise<T>[]): Promise<T[]> {
  return new Promise((resolve, reject) => {
    const results: T[] = [];
    let completed = 0;
    
    if (promises.length === 0) {
      resolve(results);
      return;
    }
    
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          results[index] = value;
          completed++;
          if (completed === promises.length) {
            resolve(results);
          }
        })
        .catch(reject);
    });
  });
}
来源:字节跳动面试题
中等
算法#设计#哈希表

LRU 缓存机制

题目描述

设计和实现一个 LRU (最近最少使用) 缓存机制。实现 LRUCache 类:get(key) 如果关键字存在于缓存中,则返回关键字的值,否则返回 -1;put(key, value) 如果关键字已经存在,则变更其数据值,如果关键字不存在则插入该组数据。当缓存容量达到上限时,应该在写入新数据之前删除最久未使用的数据值。

解题思路

使用哈希表 + 双向链表实现。哈希表提供 O(1) 的查找,双向链表维护访问顺序。每次 get/put 操作都将对应节点移到链表头部,当容量满时删除链表尾部节点。JavaScript 中可以用 Map 保持插入顺序来简化实现。

参考代码

class LRUCache {
  private capacity: number;
  private cache: Map<number, number>;
  
  constructor(capacity: number) {
    this.capacity = capacity;
    this.cache = new Map();
  }
  
  get(key: number): number {
    if (!this.cache.has(key)) return -1;
    const value = this.cache.get(key)!;
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }
  
  put(key: number, value: number): void {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }
}
来源:LeetCode #146
中等
前端#React#虚拟DOM

解释 React 的虚拟 DOM 原理

题目描述

请解释 React 虚拟 DOM 的工作原理,以及它如何提高性能?Diff 算法是如何工作的?

解题思路

虚拟 DOM 是真实 DOM 的 JavaScript 对象表示。当状态变化时,React 创建新的虚拟 DOM 树,通过 Diff 算法与旧树比较,计算出最小变更集,然后批量更新真实 DOM。Diff 算法采用三个策略优化:1) 只比较同层节点;2) 不同类型节点直接替换;3) 通过 key 属性标识同一节点。这避免了频繁的 DOM 操作,提升了性能。

来源:腾讯面试题
困难
后端#MySQL#索引

数据库索引原理

题目描述

请解释 MySQL 中 B+ 树索引的原理。为什么选择 B+ 树而不是二叉树或 B 树?聚簇索引和非聚簇索引有什么区别?

解题思路

B+ 树是多路平衡搜索树,特点:1) 非叶子节点只存索引,叶子节点存数据,减少 IO 次数;2) 叶子节点形成有序链表,支持范围查询;3) 树高度低(通常3-4层),查询效率稳定。相比二叉树,B+ 树更矮胖,减少磁盘 IO;相比 B 树,B+ 树叶子节点链表更适合范围查询。聚簇索引的叶子节点存储完整数据行(InnoDB 主键),非聚簇索引叶子节点存储主键值,需要回表查询。

来源:阿里面试题
中等
前端#JavaScript#深拷贝

实现深拷贝

题目描述

请实现一个深拷贝函数,能够正确处理各种数据类型,包括对象、数组、Date、RegExp、循环引用等。

解题思路

使用递归遍历对象的所有属性。需要处理的情况:1) 基本类型直接返回;2) Date 和 RegExp 特殊处理;3) 使用 WeakMap 缓存已拷贝对象解决循环引用;4) 遍历对象/数组的所有属性递归拷贝。

参考代码

function deepClone<T>(obj: T, cache = new WeakMap()): T {
  // 基本类型
  if (obj === null || typeof obj !== 'object') return obj;
  
  // 处理循环引用
  if (cache.has(obj as object)) return cache.get(obj as object);
  
  // 处理 Date
  if (obj instanceof Date) return new Date(obj.getTime()) as T;
  
  // 处理 RegExp
  if (obj instanceof RegExp) return new RegExp(obj) as T;
  
  // 处理数组和对象
  const clone = Array.isArray(obj) ? [] : {} as T;
  cache.set(obj as object, clone);
  
  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      (clone as any)[key] = deepClone((obj as any)[key], cache);
    }
  }
  
  return clone;
}
来源:美团面试题