2495 字
12 分钟
Algorithm
2026-02-18
2026-03-16
无标签
统计加载中...

Algorithm#

1. Promise 与异步并发控制#

Q1: 实现一个 Promise.all

Promise.myAll = function (promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) return reject(new TypeError('Argument must be an array'));
let result = [];
let count = 0;
if (promises.length === 0) return resolve(result);
promises.forEach((p, index) => {
Promise.resolve(p).then(res => {
result[index] = res; // 保证返回顺序
count++;
if (count === promises.length) resolve(result);
}).catch(reject); // 任一失败则整体失败
});
});
};

Q2: 实现一个 Promise.allSettled

Promise.myAllSettled = function (promises) {
return Promise.myAll(promises.map(p =>
Promise.resolve(p)
.then(value => ({ status: 'fulfilled', value }))
.catch(reason => ({ status: 'rejected', reason }))
));
};

Q3: 实现一个异步任务的调度器 & Q4: 用 JS 实现一个并发控制器

(这两题本质是同一种实现,这里提供一个最经典的并发控制器类)

class Scheduler {
constructor(limit) {
this.limit = limit; // 最大并发数
this.running = 0; // 当前执行数
this.queue = []; // 等待队列
}
add(taskCreator) {
return new Promise((resolve, reject) => {
this.queue.push({ taskCreator, resolve, reject });
this.run();
});
}
run() {
if (this.running < this.limit && this.queue.length > 0) {
this.running++;
const { taskCreator, resolve, reject } = this.queue.shift();
taskCreator()
.then(resolve)
.catch(reject)
.finally(() => {
this.running--;
this.run(); // 递归触发下一个
});
}
}
}

2. 数据结构#

Q5: 实现一个 LRUCache

class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map(); // Map 维持了插入的顺序
}
get(key) {
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, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
// 淘汰最老的:Map.keys().next().value 是第一个插入的键
this.cache.delete(this.cache.keys().next().value);
}
this.cache.set(key, value);
}
}

Q6: 实现一个深拷贝函数(兼容的数据结构越多越好,需要解决循环依赖情况)

function deepClone(target, map = new WeakMap()) {
if(target === null || typeof target !== 'object') return target
if(target instanceof Date) return new Date(target)
if(target instanceof RegExp) return new RegExp(target)
if(target instanceof Map) {
const cloneMap = new Map()
map.set(target, cloneMap)
target.forEach((value, key) => {
cloneMap.set(deepClone(key, map), deepClone(value, map))
});
return cloneMap
}
if(target instanceof Set) {
const cloneSet = new Set()
map.set(target, cloneSet)
target.forEach((value, key) => {
cloneSet.add(deepClone(value, map))
});
return cloneSet
}
if(map.has(target)) return map.get(target)
const cloneTarget = Array.isArray(target) ? [] : {}
map.set(target, cloneTarget)
Reflect.ownKeys(target).forEach(k => {
cloneTarget[k] = deepClone(target[k], map)
});
return cloneTarget
}

Q7: 如何判断一个单向链表是否存在环?(追问) 有没有别的方式?

// 方式 1:快慢指针 (空间复杂度 O(1),最佳实践)
function hasCycle(head) {
let slow = head, fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) return true;
}
return false;
}
// 方式 2:Set 缓存法 (空间复杂度 O(N),容易想到)
function hasCycleWithSet(head) {
const visited = new Set();
while (head) {
if (visited.has(head)) return true;
visited.add(head);
head = head.next;
}
return false;
}

3. 数组/字符串与算法核心题#

Q8: 算法题:进阶版两数之和。给定一个包含重复元素的数组 nums 和一个目标值 target,找出数组中和为 target 的所有不重复的组合。

function twoSumUnique(nums, target) {
nums.sort((a, b) => a - b); // 必须先排序
const res = [];
let left = 0,
right = nums.length - 1;
while (left < right) {
const sum = nums[left] + nums[right];
if (sum === target) {
res.push([nums[left], nums[right]]);
// 跳过重复元素去重
while (left < right && nums[left] === nums[left + 1]) left++;
while (left < right && nums[right] === nums[right - 1]) right--;
left++;
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
return res;
}

Q9: (算法题)给定一个整数数组 nums 和一个整数 target,以及一个整数 k,编写一个函数 kSum,找出数组中所有满足元素个数为 k 且元素之和等于 target 的不重复组合。

function kSum(nums, target, k) {
nums.sort((a, b) => a - b);
function nSum(start, target, n) {
const res = [];
if (n === 2) { // 降维到 2Sum
let l = start, r = nums.length - 1;
while (l < r) {
let sum = nums[l] + nums[r];
if (sum === target) {
res.push([nums[l], nums[r]]);
while (l < r && nums[l] === nums[l + 1]) l++;
while (l < r && nums[r] === nums[r - 1]) r--;
l++; r--;
} else if (sum < target) l++;
else r--;
}
} else { // 递归拆解
for (let i = start; i < nums.length - n + 1; i++) {
if (i > start && nums[i] === nums[i - 1]) continue; // 去重
const subRes = nSum(i + 1, target - nums[i], n - 1);
for (let arr of subRes) {
res.push([nums[i], ...arr]);
}
}
}
return res;
}
return nSum(0, target, k);
}

Q10: (算法题)一个整数数组和一个值 k,需要找到这个整数数组里面连续项加起来等于 k 的情况有多少种?

// 前缀和 + 哈希表 (LeetCode 560)
function subarraySum(nums, k) {
const map = new Map();
map.set(0, 1); // 初始前缀和为0的次数为1
let count = 0;
let preSum = 0;
for (const num of nums) {
preSum += num;
if (map.has(preSum - k)) {
count += map.get(preSum - k);
}
map.set(preSum, (map.get(preSum) || 0) + 1);
}
return count;
}

Q11: (算法题)找出一个整形数组里面第二小的元素,怎么做

// 口述逻辑:遍历一次数组。维护 min 和 secondMin 两个变量。
function findSecondMinimum(nums) {
let min = Infinity, secondMin = Infinity;
for (let num of nums) {
if (num < min) {
secondMin = min; // 原本最小的退居第二
min = num;
} else if (num > min && num < secondMin) {
secondMin = num; // 介于最小和第二小之间
}
}
return secondMin === Infinity ? -1 : secondMin;
}

Q12: 算法题:买卖股票的最佳时机。(给定一个数组,代表每天的股票价格,求最大利润)。(贪心)

function maxProfit(prices) {
let minPrice = Infinity;
let maxProfit = 0;
for (let price of prices) {
if (price < minPrice) {
minPrice = price; // 记录历史最低点
} else if (price - minPrice > maxProfit) {
maxProfit = price - minPrice; // 更新最大利润
}
}
return maxProfit;
}

Q13: 算法题:有序数组的子集判断

function isSubsequence(sub, main) {
let i = 0, j = 0;
while (i < main.length && j < sub.length) {
if (main[i] === sub[j]) j++; // 匹配上了,子集指针右移
i++; // 主数组指针永远右移
}
return j === sub.length;
}

Q14: (算法题) LeetCode:3375. 使数组的值全部为 K 的最少操作次数

function minOperations(nums, k) {
const set = new Set(nums);
let hasSmaller = false;
for (let num of set) {
if (num < k) return -1; // 只能减小,如果原本就有比 K 小的,永远无法全部变成 K
}
// 需要操作的次数就是大于 K 的不同元素的种类数
return set.has(k) ? set.size - 1 : set.size;
}

Q15: 第一题:二叉树每层最右节点 (二叉树的右视图)

function rightSideView(root) {
if (!root) return [];
const res = [];
const queue = [root];
while (queue.length > 0) {
let size = queue.length;
for (let i = 0; i < size; i++) {
let node = queue.shift();
if (i === size - 1) res.push(node.val); // 记录该层最后一个节点
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
}
return res;
}

Q16: 第二题:实现一个 JSON.stringify,功能尽可能完善

function myStringify(data) {
const type = typeof data;
if (type !== 'object' || data === null) {
if (type === 'string') return `"${data}"`;
if (type === 'function' || type === 'undefined' || type === 'symbol') return undefined;
return String(data);
}
if (data instanceof Date) return `"${data.toISOString()}"`;
if (Array.isArray(data)) {
const arrStr = data.map(item => myStringify(item) || 'null');
return `[${arrStr.join(',')}]`;
}
const objStr = Object.keys(data).reduce((acc, key) => {
const valStr = myStringify(data[key]);
if (valStr !== undefined) acc.push(`"${key}":${valStr}`);
return acc;
}, []);
return `{${objStr.join(',')}}`;
}

4. 组件实现 (核心逻辑层)#

Q17: (手写代码)请编写一个倒计时组件/方法,按秒维度倒计时。

// 基于原生 JS 的核心计算逻辑,可在 React/Vue 中封装为 Hookfunction
startCountdown(endTimeStr, onTick) {
const endTime = new Date(endTimeStr).getTime();
const timer = setInterval(() => {
const now = Date.now();
const diff = Math.max(0, endTime - now); // 毫秒差
if (diff === 0) {
clearInterval(timer);
onTick({ h: 0, m: 0, s: 0, finished: true });
return;
}
const h = Math.floor(diff / (1000 * 60 * 60));
const m = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const s = Math.floor((diff % (1000 * 60)) / 1000);
onTick({ h, m, s, finished: false });
}, 1000); // 实际生产中推荐 requestAnimationFrame 以避免后台休眠导致的节流
return () => clearInterval(timer); // 返回清理函数
}

Q18: (手撕代码)JS 写一个搜索过滤组件 (带有防抖)

function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
class SearchFilter {
constructor(dataList, inputEl, resultEl) {
this.dataList = dataList;
this.resultEl = resultEl;
// 绑定防抖事件
inputEl.addEventListener('input', debounce((e) => {
this.handleSearch(e.target.value);
}, 300));
}
handleSearch(keyword) {
if (!keyword.trim()) {
this.render([]); return;
}
const filtered = this.dataList.filter(item => item.includes(keyword));
this.render(filtered);
}
render(list) {
if (list.length === 0) {
this.resultEl.innerHTML = '<div>没匹配到了</div>';
} else {
this.resultEl.innerHTML = list.map(i => `<div>${i}</div>`).join('');
}
}
}

Q19: 设计一个通用的全屏覆盖层组件(Overlay/Dialog),怎么设计?需要管理生命周期吗?

// React 伪代码:考察 Portal、生命周期管理与事件拦截
import { useEffect } from 'react';
import { createPortal } from 'react-dom';
function Dialog({ isOpen, onClose, children }) {
useEffect(() => {
if (!isOpen) return;
// 生命周期管理 1: 阻止背景滚动
document.body.style.overflow = 'hidden';
// 生命周期管理 2: 监听 ESC 键
const handleEsc = (e) => e.key === 'Escape' && onClose();
window.addEventListener('keydown', handleEsc);
return () => {
// 销毁时清理
document.body.style.overflow = 'auto';
window.removeEventListener('keydown', handleEsc);
};
}, [isOpen, onClose]);
if (!isOpen) return null;
// 使用 Portal 挂载到 body 下,脱离当前 DOM 树的 CSS 层叠上下文限制
return createPortal(
<div className="overlay" onClick={onClose} style={{position: 'fixed', inset: 0}}><div className="content" onClick={e => e.stopPropagation()}>
{/* 阻止点击穿透 */}
{children}
</div></div>,
document.body
);
}

5. 其他编程题#

Q20: (编程题)实现一个 Add 函数,运算只能通过调用 expensiveCall 实现

Q21: (编程题 2)监听列表元素曝光(可以查 API 文档)

function observeElements(selector, onVisible) {
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
onVisible(entry.target); // 触发曝光上报逻辑
obs.unobserve(entry.target); // 上报后取消监听,节省性能
}
});
}, { threshold: 0.5 }); // 露出 50% 算作曝光
document.querySelectorAll(selector).forEach(el => observer.observe(el));
}

Q22: (编程题 3)开放题 为了性能要求,我们页面经常会组装一个 combo 请求

Q23: (算法题)遇到中括号 [n] 重复对应次数,小括号 () 作为整体。

(此类题通常变种自 LeetCode 394 字符串解码,此处提供经典的栈解析法)

function decodeString(s) {
let numStack = [];
let strStack = [];
let currentStr = '';
let currentNum = 0;
for (let char of s) {
if (!isNaN(char)) {
currentNum = currentNum * 10 + Number(char);
} else if (char === '(' || char === '[') {
numStack.push(currentNum || 1); // 默认倍数为 1
strStack.push(currentStr);
currentStr = '';
currentNum = 0;
} else if (char === ')' || char === ']') {
const repeatTimes = numStack.pop();
const prevStr = strStack.pop();
currentStr = prevStr + currentStr.repeat(repeatTimes);
} else {
currentStr += char;
}
}
return currentStr;
}

Q24: 第三题:微信团队组织员工去公园郊游,需要从租车点租赁自行车 (贪心双指针)

(背景:每车最多坐两人,有最大载重 limit)

function numRescueBikes(people, limit) {
people.sort((a, b) => a - b);
let left = 0;
let right = people.length - 1;
let bikes = 0;
while (left <= right) {
// 如果最轻和最重的能拼一辆车
if (people[left] + people[right] <= limit) {
left++;
}
// 无论是否拼车,最重的人都要消耗一辆车
right--;
bikes++;
}
return bikes;
}