适用:前端/JS 面试、临时突击、快速默写
每题均含:链接 + 一句话题意 + 一句话记忆 + 带注释代码 + 分类
数组 & 哈希(1–15)
1. 两数之和
leetcode.cn/problems/tw…
题意:数组中找两数和为 target,返回下标
记忆:Map 存值与下标,遍历查差值是否存在
var twoSum = function(nums, target) {
const map = new Map(); // 创建哈希表存储值和下标
for (let i = 0; i < nums.length; i++) {
const diff = target - nums[i]; // 计算目标值与当前值的差值
if (map.has(diff)) return [map.get(diff), i]; // 如果差值存在于哈希表中,返回下标
map.set(nums[i], i); // 将当前值和下标存入哈希表
}
};
分类:数组 / 哈希
2. 只出现一次的数字
leetcode.cn/problems/si…
题意:其余数出现两次,找只出现一次的数
记忆:异或运算,相同抵消,0^x=x
var singleNumber = function(nums) {
let res = 0; // 初始化结果为0
for (let num of nums) res ^= num; // 异或运算,相同抵消,0^x=x
return res; // 返回唯一出现的数字
};
分类:位运算 / 数组
3. 多数元素
leetcode.cn/problems/ma…
题意:找出现次数 > 数组长度一半的数
记忆:摩尔投票,相同+1不同-1,剩者为王
var majorityElement = function(nums) {
let count = 0, candidate = 0; // 初始化计数和候选元素
for (let num of nums) {
if (count === 0) candidate = num; // 计数为0时更新候选元素
count += num === candidate ? 1 : -1; // 相同加1,不同减1
}
return candidate; // 返回多数元素
};
分类:数组 / 摩尔投票
4. 移动零
leetcode.cn/problems/mo…
题意:将 0 移到末尾,非零保持顺序
记忆:双指针覆盖非零,后面补 0
var moveZeroes = function(nums) {
let idx = 0; // 慢指针,记录非零元素的位置
for (let n of nums) if (n !== 0) nums[idx++] = n; // 非零元素前移
while (idx < nums.length) nums[idx++] = 0; // 剩余位置补0
};
分类:数组 / 双指针
5. 移除元素
leetcode.cn/problems/re…
题意:原地移除值为 val 的元素,返回新长度
记忆:慢指针覆盖,快指针跳过目标值
var removeElement = function(nums, val) {
let i = 0; // 慢指针,记录非目标元素的位置
for (let j = 0; j < nums.length; j++) {
if (nums[j] !== val) nums[i++] = nums[j]; // 非目标元素前移
}
return i; // 返回新长度
};
分类:数组 / 双指针
6. 删除排序数组中的重复项
leetcode.cn/problems/re…
题意:有序数组原地去重,返回新长度
记忆:快慢指针,不同则前移覆盖
var removeDuplicates = function(nums) {
let i = 0; // 慢指针,记录不重复元素的位置
for (let j = 1; j < nums.length; j++) {
if (nums[j] !== nums[i]) nums[++i] = nums[j]; // 遇到不同元素,前移并更新慢指针
}
return i + 1; // 返回新长度
};
分类:数组 / 双指针
7. 买卖股票的最佳时机
leetcode.cn/problems/be…
题意:一次买卖,求最大利润
记忆:记录最小价格,遍历算最大差值
var maxProfit = function(prices) {
let min = prices[0], max = 0; // 初始化最小价格和最大利润
for (let p of prices) {
min = Math.min(min, p); // 更新最小价格
max = Math.max(max, p - min); // 计算当前利润并更新最大利润
}
return max; // 返回最大利润
};
分类:数组 / 贪心
8. 有序数组的平方
leetcode.cn/problems/sq…
题意:非递减数组平方后仍非递减
记忆:双指针从两端取大,逆序填入
var sortedSquares = function(nums) {
const res = new Array(nums.length); // 结果数组
let l = 0, r = nums.length - 1, idx = r; // 左右指针和结果数组指针
while (l <= r) {
const left = nums[l] ** 2, right = nums[r] ** 2; // 计算左右指针位置的平方
if (left > right) {
res[idx--] = left; // 左平方大,放入结果并左移
l++;
} else {
res[idx--] = right; // 右平方大,放入结果并右移
r--;
}
}
return res; // 返回结果数组
};
分类:数组 / 双指针
9. 合并两个有序数组
leetcode.cn/problems/me…
题意:原地合并两个有序数组到 nums1
记忆:从后往前双指针,大的放后面
var merge = function(nums1, m, nums2, n) {
let i = m - 1, j = n - 1, k = m + n - 1; // 初始化三个指针,分别指向nums1末尾、nums2末尾和合并后的末尾
while (j >= 0) {
if (i >= 0 && nums1[i] > nums2[j]) nums1[k--] = nums1[i--]; // nums1元素大,放入结果
else nums1[k--] = nums2[j--]; // nums2元素大或nums1已遍历完,放入nums2元素
}
};
分类:数组 / 双指针
10. 加一
leetcode.cn/problems/pl…
题意:数组表示数字 +1,返回新数组
记忆:从后加,进位则置 0,最后前插 1
var plusOne = function(digits) {
for (let i = digits.length - 1; i >= 0; i--) {
digits[i]++; // 当前位加1
if (digits[i] < 10) return digits; // 无进位,直接返回
digits[i] = 0; // 有进位,当前位置0
}
digits.unshift(1); // 最高位有进位,添加1
return digits; // 返回结果
};
分类:数组 / 数学
11. 缺失数字
leetcode.cn/problems/mi…
题意:0~n 中找缺失的数字
记忆:总和减去数组元素和
var missingNumber = function(nums) {
const n = nums.length; // 数组长度
const sum = n * (n + 1) / 2; // 0到n的总和
return sum - nums.reduce((a, b) => a + b, 0); // 总和减去数组元素和,得到缺失的数
};
分类:数组 / 数学
12. 第三大的数
leetcode.cn/problems/th…
题意:返回第三大的数,无则返回最大
记忆:维护三个变量存第一、二、三大
var thirdMax = function(nums) {
let a = b = c = -Infinity; // 分别记录第一、二、三大的数
for (let n of nums) {
if (n === a || n === b || n === c) continue; // 跳过重复值
if (n > a) [c, b, a] = [b, a, n]; // 大于最大值,更新三个变量
else if (n > b) [c, b] = [b, n]; // 大于第二大值,更新后两个变量
else if (n > c) c = n; // 大于第三大值,更新第三个变量
}
return c === -Infinity ? a : c; // 无第三大值则返回最大值
};
分类:数组
13. 好数对的数目
leetcode.cn/problems/nu…
题意:统计 i<j 且值相等的对数
记忆:计数后累加 c*(c-1)/2
var numIdenticalPairs = function(nums) {
const cnt = {}; // 计数对象
let res = 0; // 结果
for (let n of nums) cnt[n] = (cnt[n] || 0) + 1; // 统计每个数字出现的次数
for (let k in cnt) res += cnt[k] * (cnt[k] - 1) / 2; // 计算组合数,累加结果
return res; // 返回结果
};
分类:哈希 / 数学
14. 独一无二的出现次数
leetcode.cn/problems/un…
题意:判断数字出现次数是否互不相同
记忆:计数后用 Set 判重
var uniqueOccurrences = function(arr) {
const cnt = {}; // 计数对象
for (let n of arr) cnt[n] = (cnt[n] || 0) + 1; // 统计每个数字出现的次数
const v = Object.values(cnt); // 获取出现次数数组
return new Set(v).size === v.length; // 判断是否有重复的出现次数
};
分类:哈希 / 数组
15. 特殊数组的特征值
leetcode.cn/problems/sp…
题意:找 x 使恰好 x 个数 ≥x
记忆:遍历 x 并统计满足条件数量
var specialArray = function(nums) {
for (let x = 0; x <= nums.length; x++) {
const cnt = nums.filter(v => v >= x).length; // 统计大于等于x的元素个数
if (cnt === x) return x; // 找到符合条件的x,返回
}
return -1; // 无符合条件的x,返回-1
};
分类:数组 / 模拟
字符串(16–30)
16. 有效的字母异位词
leetcode.cn/problems/va…
题意:判断两字符串是否字母相同顺序不同
记忆:排序后比较是否相等
var isAnagram = function(s, t) {
return s.split('').sort().join('') === t.split('').sort().join(''); // 排序后比较是否相等
};
分类:字符串 / 排序
17. 反转字符串
leetcode.cn/problems/re…
题意:原地反转字符数组
记忆:双指针首尾交换
var reverseString = function(s) {
let l = 0, r = s.length - 1; // 左右指针
while (l < r) [s[l++], s[r--]] = [s[r], s[l]]; // 交换左右指针位置的字符
};
分类:字符串 / 双指针
18. 字符串中的第一个唯一字符
leetcode.cn/problems/fi…
题意:找第一个只出现一次的字符下标
记忆:Map 计数,再遍历找第一个计数 1
var firstUniqChar = function(s) {
const map = new Map(); // 计数Map
for (let c of s) map.set(c, (map.get(c) || 0) + 1); // 统计每个字符出现的次数
for (let i = 0; i < s.length; i++) {
if (map.get(s[i]) === 1) return i; // 找到第一个出现次数为1的字符下标
}
return -1; // 无唯一字符,返回-1
};
分类:字符串 / 哈希
19. 最长公共前缀
leetcode.cn/problems/lo…
题意:求字符串数组最长公共前缀
记忆:以首串为基准,逐个缩短比对
var longestCommonPrefix = function(strs) {
if (!strs.length) return ''; // 空数组返回空字符串
let res = strs[0]; // 以首字符串为基准
for (let s of strs) {
while (!s.startsWith(res)) res = res.slice(0, -1); // 缩短基准字符串直到所有字符串都以它为前缀
}
return res; // 返回最长公共前缀
};
分类:字符串
20. 实现 strStr()
leetcode.cn/problems/fi…
题意:找子串首次出现下标
记忆:直接用 indexOf
var strStr = function(haystack, needle) {
return haystack.indexOf(needle); // 使用indexOf方法查找子串首次出现的下标
};
分类:字符串
21. 最后一个单词的长度
leetcode.cn/problems/le…
题意:求最后一个单词长度,忽略末尾空格
记忆:trim 后从后向前计数
var lengthOfLastWord = function(s) {
s = s.trim(); // 去除首尾空格
let count = 0; // 计数
for (let i = s.length - 1; i >= 0; i--) {
if (s[i] === ' ') break; // 遇到空格停止计数
count++; // 统计最后一个单词的长度
}
return count; // 返回最后一个单词的长度
};
分类:字符串
22. 罗马数字转整数
leetcode.cn/problems/ro…
题意:罗马字符串转数字
记忆:左小右减,否则加
var romanToInt = function(s) {
const map = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 }; // 罗马数字映射表
let res = 0; // 结果
for (let i = 0; i < s.length; i++) {
if (map[s[i]] < map[s[i + 1]]) res -= map[s[i]]; // 左小右大,减去当前值
else res += map[s[i]]; // 否则加上当前值
}
return res; // 返回结果
};
分类:字符串 / 哈希
23. 赎金信
leetcode.cn/problems/ra…
题意:判断 ransomNote 能否由 magazine 构成
记忆:统计 magazine 字符,再扣减验证
var canConstruct = function(ransomNote, magazine) {
const cnt = {}; // 计数对象
for (let c of magazine) cnt[c] = (cnt[c] || 0) + 1; // 统计magazine中每个字符的出现次数
for (let c of ransomNote) {
if (!cnt[c]) return false; // 字符不足,返回false
cnt[c]--; // 消耗字符
}
return true; // 所有字符都满足,返回true
};
分类:字符串 / 哈希
24. 找不同
leetcode.cn/problems/fi…
题意:t 比 s 多一个字符,找出它
记忆:ASCII 码求和相减
var findTheDifference = function(s, t) {
let sum = 0; // 存储ASCII码总和
for (let c of t) sum += c.charCodeAt(); // 计算t中所有字符的ASCII码总和
for (let c of s) sum -= c.charCodeAt(); // 减去s中所有字符的ASCII码总和
return String.fromCharCode(sum); // 将差值转换为字符
};
分类:字符串 / 数学
25. 二进制求和
leetcode.cn/problems/ad…
题意:两个二进制字符串相加
记忆:从尾相加,记录进位,结果反转
var addBinary = function(a, b) {
let i = a.length - 1, j = b.length - 1, carry = 0, res = ''; // 初始化指针、进位和结果
while (i >= 0 || j >= 0 || carry) {
const sum = (+a[i--] || 0) + (+b[j--] || 0) + carry; // 计算当前位的和
res = sum % 2 + res; // 计算当前位的结果
carry = sum >> 1; // 计算进位
}
return res; // 返回结果
};
分类:字符串 / 数学
26. 字符串相加
leetcode.cn/problems/ad…
题意:大数字符串相加
记忆:尾对齐相加,处理进位
var addStrings = function(num1, num2) {
let i = num1.length - 1, j = num2.length - 1, carry = 0, res = ''; // 初始化指针、进位和结果
while (i >= 0 || j >= 0 || carry) {
const sum = (+num1[i--] || 0) + (+num2[j--] || 0) + carry; // 计算当前位的和
res = sum % 10 + res; // 计算当前位的结果
carry = Math.floor(sum / 10); // 计算进位
}
return res; // 返回结果
};
分类:字符串 / 数学
27. 替换空格
leetcode.cn/problems/ti…
题意:空格替换为 %20
记忆:遍历拼接或直接 replace
var replaceSpace = function(s) {
let res = ''; // 结果字符串
for (let c of s) res += c === ' ' ? '%20' : c; // 空格替换为%20,其他字符不变
return res; // 返回结果
};
分类:字符串
28. 左旋转字符串
leetcode.cn/problems/zu…
题意:前 n 个字符移到末尾
记忆:切片后拼接
var reverseLeftWords = function(s, n) {
return s.slice(n) + s.slice(0, n); // 拼接后n个字符和前n个字符
};
分类:字符串
29. 分割平衡字符串
leetcode.cn/problems/sp…
题意:L/R 平衡子串最大个数
记忆:计数平衡,相等则分段+1
var balancedStringSplit = function(s) {
let cnt = 0, res = 0; // 计数和结果
for (let c of s) {
cnt += c === 'L' ? 1 : -1; // L加1,R减1
if (cnt === 0) res++; // 平衡时计数加1
}
return res; // 返回平衡子串个数
};
分类:字符串 / 贪心
30. 宝石与石头
leetcode.cn/problems/je…
题意:统计石头中宝石数量
记忆:Set 存宝石,遍历统计
var numJewelsInStones = function(jewels, stones) {
const set = new Set(jewels); // 宝石集合
return stones.split('').filter(c => set.has(c)).length; // 统计石头中宝石的数量
};
分类:字符串 / 哈希
链表(31–40)
31. 合并两个有序链表
leetcode.cn/problems/me…
题意:合并两个升序链表
记忆:虚拟头节点,谁小接谁,最后接剩余
var mergeTwoLists = function(l1, l2) {
const dummy = new ListNode(0); // 虚拟头节点
let cur = dummy; // 当前指针
while (l1 && l2) {
if (l1.val < l2.val) {
cur.next = l1; // 连接l1
l1 = l1.next; // 移动l1指针
} else {
cur.next = l2; // 连接l2
l2 = l2.next; // 移动l2指针
}
cur = cur.next; // 移动当前指针
}
cur.next = l1 || l2; // 连接剩余节点
return dummy.next; // 返回合并后的链表
};
分类:链表 / 双指针
32. 反转链表
leetcode.cn/problems/re…
题意:反转单链表
记忆:三指针迭代,prev/cur/next
var reverseList = function(head) {
let prev = null, cur = head; // 初始化前一个节点和当前节点
while (cur) {
const next = cur.next; // 保存下一个节点
cur.next = prev; // 反转当前节点的指针
prev = cur; // 移动前一个节点指针
cur = next; // 移动当前节点指针
}
return prev; // 返回反转后的头节点
};
分类:链表
33. 环形链表
leetcode.cn/problems/li…
题意:判断链表是否有环
记忆:快慢指针,相遇则有环
var hasCycle = function(head) {
let slow = head, fast = head; // 快慢指针
while (fast && fast.next) {
slow = slow.next; // 慢指针走一步
fast = fast.next.next; // 快指针走两步
if (slow === fast) return true; // 相遇则有环
}
return false; // 无环
};
分类:链表 / 快慢指针
34. 相交链表
leetcode.cn/problems/in…
题意:找两链表交点
记忆:双指针互换路线,相遇即交点
var getIntersectionNode = function(headA, headB) {
let a = headA, b = headB; // 初始化两个指针
while (a !== b) {
a = a ? a.next : headB; // a到达末尾则指向headB
b = b ? b.next : headA; // b到达末尾则指向headA
}
return a; // 相遇点即为交点
};
分类:链表 / 双指针
35. 回文链表
leetcode.cn/problems/pa…
题意:判断链表是否回文
记忆:转数组后双指针比较
var isPalindrome = function(head) {
const arr = []; // 存储链表值的数组
let cur = head; // 当前指针
while (cur) {
arr.push(cur.val); // 将链表值存入数组
cur = cur.next; // 移动指针
}
let l = 0, r = arr.length - 1; // 左右指针
while (l < r) {
if (arr[l] !== arr[r]) return false; // 不相等则不是回文
l++; r--; // 移动指针
}
return true; // 是回文
};
分类:链表 / 双指针
36. 移除链表元素
leetcode.cn/problems/re…
题意:删除链表中值为 val 的节点
记忆:虚拟头节点,遍历跳过目标值
var removeElements = function(head, val) {
const dummy = new ListNode(0, head); // 虚拟头节点
let cur = dummy; // 当前指针
while (cur.next) {
if (cur.next.val === val) cur.next = cur.next.next; // 跳过目标值节点
else cur = cur.next; // 移动指针
}
return dummy.next; // 返回处理后的链表
};
分类:链表
37. 链表的中间结点
leetcode.cn/problems/mi…
题意:找链表中点
记忆:快慢指针,快走两步慢走一步
var middleNode = function(head) {
let slow = head, fast = head; // 快慢指针
while (fast && fast.next) {
slow = slow.next; // 慢指针走一步
fast = fast.next.next; // 快指针走两步
}
return slow; // 慢指针指向中点
};
分类:链表 / 快慢指针
38. 回文数
leetcode.cn/problems/pa…
题意:判断整数是否回文
记忆:转字符串双指针比较
var isPalindrome = function(x) {
const s = x.toString(); // 转换为字符串
let l = 0, r = s.length - 1; // 左右指针
while (l < r) {
if (s[l] !== s[r]) return false; // 不相等则不是回文
l++; r--; // 移动指针
}
return true; // 是回文
};
分类:字符串 / 双指针
39. 有效的括号
leetcode.cn/problems/va…
题意:判断括号是否合法闭合
记忆:左括号入栈,右括号匹配栈顶
var isValid = function(s) {
const stack = []; // 栈
const map = { ')': '(', '}': '{', ']': '[' }; // 右括号到左括号的映射
for (let c of s) {
if (!map[c]) stack.push(c); // 左括号入栈
else if (stack.pop() !== map[c]) return false; // 右括号匹配栈顶,不匹配则返回false
}
return stack.length === 0; // 栈空则有效
};
分类:栈 / 字符串
40. 最小栈
leetcode.cn/problems/mi…
题意:实现 O(1) 获取最小值的栈
记忆:主栈+最小栈同步存当前最小值
var MinStack = function() {
this.stack = []; // 主栈
this.minStack = []; // 最小栈
};
MinStack.prototype.push = function(val) {
this.stack.push(val); // 压入主栈
const min = this.minStack.length ? Math.min(val, this.minStack.at(-1)) : val; // 计算当前最小值
this.minStack.push(min); // 压入最小栈
};
MinStack.prototype.pop = function() {
this.stack.pop(); // 弹出主栈
this.minStack.pop(); // 弹出最小栈
};
MinStack.prototype.top = function() {
return this.stack.at(-1); // 返回栈顶元素
};
MinStack.prototype.getMin = function() {
return this.minStack.at(-1); // 返回最小值
};
分类:栈 / 设计
二叉树(41–55)
41. 二叉树的中序遍历
leetcode.cn/problems/bi…
题意:左-根-右遍历
记忆:栈模拟,一路走左,出栈访问再走右
var inorderTraversal = function(root) {
const res = [], stack = []; // 结果数组和栈
let cur = root; // 当前节点
while (cur || stack.length) {
while (cur) {
stack.push(cur); // 压入当前节点
cur = cur.left; // 遍历左子树
}
cur = stack.pop(); // 弹出节点
res.push(cur.val); // 访问节点
cur = cur.right; // 遍历右子树
}
return res; // 返回结果
};
分类:二叉树 / 栈
42. 相同的树
leetcode.cn/problems/sa…
题意:判断两棵树是否相同
记忆:递归判空、判值,递归左右子树
var isSameTree = function(p, q) {
if (!p && !q) return true; // 两树都为空,返回true
if (!p || !q || p.val !== q.val) return false; // 一树为空或值不同,返回false
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); // 递归比较左右子树
};
分类:二叉树 / 递归
43. 对称二叉树
leetcode.cn/problems/sy…
题意:判断树是否轴对称
记忆:递归比较左左与右右、左右与右左
var isSymmetric = function(root) {
var dfs = function(l, r) {
if (!l && !r) return true; // 两节点都为空,返回true
if (!l || !r || l.val !== r.val) return false; // 一节点为空或值不同,返回false
return dfs(l.left, r.right) && dfs(l.right, r.left); // 递归比较左右子树
}
return dfs(root.left, root.right); // 从根节点的左右子树开始比较
};
分类:二叉树 / 递归
44. 二叉树的最大深度
leetcode.cn/problems/ma…
题意:求树高度
记忆:1 + max(左深度,右深度)
var maxDepth = function(root) {
if (!root) return 0; // 空节点深度为0
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); // 深度为1加上左右子树的最大深度
};
分类:二叉树 / 递归
45. 二叉树的最小深度
leetcode.cn/problems/mi…
题意:根到叶子最短路径
记忆:递归,单侧空则取另一侧
var minDepth = function(root) {
if (!root) return 0; // 空节点深度为0
if (!root.left) return 1 + minDepth(root.right); // 左子树为空,返回右子树深度+1
if (!root.right) return 1 + minDepth(root.left); // 右子树为空,返回左子树深度+1
return 1 + Math.min(minDepth(root.left), minDepth(root.right)); // 左右子树都不为空,返回较小深度+1
};
分类:二叉树 / 递归
46. 平衡二叉树
leetcode.cn/problems/ba…
题意:左右高度差 ≤1
记忆:后序求高度,差>1返回-1
var isBalanced = function(root) {
const dfs = node => {
if (!node) return 0; // 空节点深度为0
const l = dfs(node.left); // 左子树深度
const r = dfs(node.right); // 右子树深度
if (l === -1 || r === -1 || Math.abs(l - r) > 1) return -1; // 不平衡返回-1
return Math.max(l, r) + 1; // 返回当前节点深度
}
return dfs(root) !== -1; // 深度不为-1则平衡
};
分类:二叉树 / 递归
47. 路径总和
leetcode.cn/problems/pa…
题意:是否存在根到叶子路径和为 target
记忆:递归减值,叶子判是否为0
var hasPathSum = function(root, targetSum) {
if (!root) return false; // 空节点返回false
if (!root.left && !root.right) return targetSum === root.val; // 叶子节点,判断是否等于目标值
return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val); // 递归左右子树,减去当前节点值
};
分类:二叉树 / DFS
48. 将有序数组转换为二叉搜索树
leetcode.cn/problems/co…
题意:升序数组转平衡 BST
记忆:中点为根,递归左右
var sortedArrayToBST = function(nums) {
if (!nums.length) return null; // 空数组返回null
const mid = nums.length >> 1; // 取中点
const root = new TreeNode(nums[mid]); // 中点作为根节点
root.left = sortedArrayToBST(nums.slice(0, mid)); // 左子数组构建左子树
root.right = sortedArrayToBST(nums.slice(mid + 1)); // 右子数组构建右子树
return root; // 返回根节点
};
分类:二叉树 / 递归
49. 二叉搜索树的最小绝对差
leetcode.cn/problems/mi…
题意:任意两节点差最小值
记忆:中序有序,相邻比较最小
var getMinimumDifference = function(root) {
let prev = null, min = Infinity; // 前一个节点值和最小差值
const dfs = node => {
if (!node) return; // 空节点返回
dfs(node.left); // 遍历左子树
if (prev !== null) min = Math.min(min, node.val - prev); // 计算当前节点与前一个节点的差值
prev = node.val; // 更新前一个节点值
dfs(node.right); // 遍历右子树
};
dfs(root); // 开始遍历
return min; // 返回最小差值
};
分类:二叉树 / DFS
50. 叶子相似的树
leetcode.cn/problems/le…
题意:两棵树叶子序列是否相同
记忆:分别收集叶子序列再比较
var leafSimilar = function(root1, root2) {
const getLeaf = root => {
const res = []; // 存储叶子节点值
const dfs = node => {
if (!node) return; // 空节点返回
if (!node.left && !node.right) res.push(node.val); // 叶子节点,添加到结果
dfs(node.left); // 遍历左子树
dfs(node.right); // 遍历右子树
};
dfs(root); // 开始遍历
return res; // 返回叶子节点序列
};
return getLeaf(root1).join() === getLeaf(root2).join(); // 比较两个树的叶子节点序列
};
分类:二叉树 / DFS
51. 递增顺序搜索树
leetcode.cn/problems/in…
题意:展平为右斜递增树
记忆:中序遍历,右链拼接
var increasingBST = function(root) {
const dummy = new TreeNode(0); // 虚拟头节点
let cur = dummy; // 当前指针
const dfs = node => {
if (!node) return; // 空节点返回
dfs(node.left); // 遍历左子树
cur.right = node; // 当前节点作为右孩子
node.left = null; // 左指针置空
cur = node; // 移动当前指针
dfs(node.right); // 遍历右子树
};
dfs(root); // 开始遍历
return dummy.right; // 返回结果
};
分类:二叉树 / DFS
52. 从二叉搜索树到更大和树
leetcode.cn/problems/bi…
题意:反中序累加更新节点值
记忆:右-根-左遍历,累加求和
var bstToGst = function(root) {
let sum = 0; // 累加和
const dfs = node => {
if (!node) return; // 空节点返回
dfs(node.right); // 遍历右子树
sum += node.val; // 累加当前节点值
node.val = sum; // 更新当前节点值
dfs(node.left); // 遍历左子树
};
dfs(root); // 开始遍历
return root; // 返回结果
};
分类:二叉树 / DFS
53. 二叉树的层序遍历 II
leetcode.cn/problems/bi…
题意:自底向上层序遍历
记忆:正常层序后反转数组
var levelOrderBottom = function(root) {
if (!root) return []; // 空树返回空数组
const res = [], q = [root]; // 结果数组和队列
while (q.length) {
const size = q.length; // 当前层的节点数
const level = []; // 当前层的节点值
for (let i = 0; i < size; i++) {
const node = q.shift(); // 取出队首节点
level.push(node.val); // 添加到当前层
if (node.left) q.push(node.left); // 左孩子入队
if (node.right) q.push(node.right); // 右孩子入队
}
res.unshift(level); // 当前层添加到结果数组的开头
}
return res; // 返回结果
};
分类:二叉树 / BFS
54. 二叉树的所有路径
leetcode.cn/problems/bi…
题意:输出根到所有叶子路径
记忆:DFS 拼接路径,叶子加入结果
var binaryTreePaths = function(root) {
const res = []; // 结果数组
var dfs = function(node, path) {
if (!node) return; // 空节点返回
path += node.val; // 添加当前节点值到路径
if (!node.left && !node.right) res.push(path); // 叶子节点,添加到结果
else {
path += '->'; // 非叶子节点,添加箭头
dfs(node.left, path); // 递归左子树
dfs(node.right, path); // 递归右子树
}
};
dfs(root, ''); // 开始遍历
return res; // 返回结果
};
分类:二叉树 / DFS
55. 二叉搜索树的众数
leetcode.cn/problems/fi…
题意:找出现次数最多的数
记忆:中序统计频率,记录最大值
var findMode = function(root) {
let max = 0, cur = 0, pre = null, res = []; // 最大频率、当前频率、前一个节点值、结果数组
const dfs = node => {
if (!node) return; // 空节点返回
dfs(node.left); // 遍历左子树
cur = node.val === pre ? cur + 1 : 1; // 计算当前节点的频率
if (cur > max) {
max = cur; // 更新最大频率
res = [node.val]; // 更新结果数组
} else if (cur === max) res.push(node.val); // 频率等于最大,添加到结果
pre = node.val; // 更新前一个节点值
dfs(node.right); // 遍历右子树
};
dfs(root); // 开始遍历
return res; // 返回结果
};
分类:二叉树 / DFS
二分、滑动窗口、贪心(56–65)
56. 搜索插入位置
leetcode.cn/problems/se…
题意:找 target 下标或应插入位置
记忆:二分查找,最后返回左指针
var searchInsert = function(nums, target) {
let l = 0, r = nums.length - 1; // 左右指针
while (l <= r) {
const mid = (l + r) >> 1; // 计算中点
if (nums[mid] === target) return mid; // 找到目标值,返回下标
if (nums[mid] < target) l = mid + 1; // 目标值在右半部分
else r = mid - 1; // 目标值在左半部分
}
return l; // 未找到,返回应插入的位置
};
分类:二分查找
57. x 的平方根
leetcode.cn/problems/sq…
题意:平方根向下取整
记忆:二分 0~x
var mySqrt = function(x) {
let l = 0, r = x; // 左右指针
while (l <= r) {
const mid = (l + r) >> 1; // 计算中点
if (mid * mid === x) return mid; // 找到精确平方根,返回
if (mid * mid < x) l = mid + 1; // 平方根在右半部分
else r = mid - 1; // 平方根在左半部分
}
return r; // 返回向下取整的平方根
};
分类:二分查找
58. 山脉数组的峰顶索引
leetcode.cn/problems/pe…
题意:找先增后减数组峰值
记忆:二分找第一个下降位置
var peakIndexInMountainArray = function(arr) {
let l = 0, r = arr.length - 1; // 左右指针
while (l < r) {
const mid = (l + r) >> 1; // 计算中点
if (arr[mid] < arr[mid + 1]) l = mid + 1; // 中点在上升坡,峰值在右半部分
else r = mid; // 中点在下降坡,峰值在左半部分
}
return l; // 找到峰值下标
};
分类:二分查找
59. 长度最小的子数组
leetcode.cn/problems/mi…
题意:和 ≥ target 的最短子数组
记忆:滑动窗口,右扩左缩,记录最小长度
var minSubArrayLen = function(target, nums) {
let l = 0, sum = 0, min = Infinity; // 左指针、当前和、最小长度
for (let r = 0; r < nums.length; r++) {
sum += nums[r]; // 右指针右移,扩大窗口
while (sum >= target) {
min = Math.min(min, r - l + 1); // 更新最小长度
sum -= nums[l++]; // 左指针右移,缩小窗口
}
}
return min === Infinity ? 0 : min; // 无符合条件的子数组返回0,否则返回最小长度
};
分类:滑动窗口 / 数组
60. 最大子数组和
leetcode.cn/problems/ma…
题意:最大和连续子数组
记忆:当前和为负则重置,否则累加
var maxSubArray = function(nums) {
let max = nums[0], cur = 0; // 最大和、当前和
for (let num of nums) {
cur = Math.max(num, cur + num); // 选择当前数或当前数加上之前的和
max = Math.max(max, cur); // 更新最大和
}
return max; // 返回最大和
};
分类:动态规划 / 贪心
动态规划 & 数学(66–80)
66. 爬楼梯
leetcode.cn/problems/cl…
题意:每次1/2阶,到n阶方法数
记忆:类斐波那契,滚动迭代
var climbStairs = function(n) {
if (n <= 2) return n; // 特殊情况处理
let a = 1, b = 2; // a表示n-2阶的方法数,b表示n-1阶的方法数
for (let i = 3; i <= n; i++) [a, b] = [b, a + b]; // 滚动计算,每次更新a和b
return b; // 返回n阶的方法数
};
分类:动态规划
67. 斐波那契数
leetcode.cn/problems/fi…
题意:求 F(n)
记忆:迭代滚动 a,b
var fib = function(n) {
if (n <= 1) return n; // 特殊情况处理
let a = 0, b = 1; // a表示F(n-2),b表示F(n-1)
for (let i = 2; i <= n; i++) [a, b] = [b, a + b]; // 滚动计算,每次更新a和b
return b; // 返回F(n)
};
分类:动态规划 / 数学
68. 杨辉三角
leetcode.cn/problems/pa…
题意:生成前n行杨辉三角
记忆:首尾1,中间=上一行左右和
var generate = function(numRows) {
const res = []; // 结果数组
for (let i = 0; i < numRows; i++) {
const row = new Array(i + 1).fill(1); // 当前行,初始化为1
for (let j = 1; j < i; j++) {
row[j] = res[i - 1][j - 1] + res[i - 1][j]; // 中间元素等于上一行左右元素之和
}
res.push(row); // 添加当前行到结果
}
return res; // 返回结果
};
分类:数组 / DP
69. 杨辉三角 II
leetcode.cn/problems/pa…
题意:返回第rowIndex行
记忆:滚动数组,从后更新
var getRow = function(rowIndex) {
const res = new Array(rowIndex + 1).fill(1); // 初始化结果数组
for (let i = 1; i <= rowIndex; i++) {
for (let j = i - 1; j > 0; j--) res[j] += res[j - 1]; // 从后向前更新,避免覆盖
}
return res; // 返回结果
};
分类:数组 / DP
70. 快乐数
leetcode.cn/problems/ha…
题意:重复平方和最终为1则快乐
记忆:快慢指针判环,无环到1为真
var isHappy = function(n) {
const getSum = x => x.toString().split('').reduce((a, b) => a + b * b, 0); // 计算数字各位平方和
let slow = n, fast = getSum(n); // 快慢指针
while (fast !== 1 && slow !== fast) {
slow = getSum(slow); // 慢指针走一步
fast = getSum(getSum(fast)); // 快指针走两步
}
return fast === 1; // 快指针为1则是快乐数,否则进入循环
};
分类:数学 / 快慢指针
71. 计数质数
leetcode.cn/problems/co…
题意:小于n的质数个数
记忆:埃氏筛标记非质数
var countPrimes = function(n) {
if (n < 3) return 0; // 小于3的数没有质数
const isPrime = new Array(n).fill(true); // 初始化质数标记数组
isPrime[0] = isPrime[1] = false; // 0和1不是质数
for (let i = 2; i * i < n; i++) {
if (isPrime[i]) {
for (let j = i * i; j < n; j += i) isPrime[j] = false; // 标记非质数
}
}
return isPrime.filter(Boolean).length; // 统计质数个数
};
分类:数学 / 筛法
72. 丑数
leetcode.cn/problems/ug…
题意:只含2/3/5质因数
记忆:不断除以2/3/5,最后剩1则是
var isUgly = function(n) {
if (n <= 0) return false; // 非正数不是丑数
[2, 3, 5].forEach(p => { while (n % p === 0) n /= p }); // 不断除以2、3、5
return n === 1; // 最后剩1则是丑数
};
分类:数学
73. Nim 游戏
leetcode.cn/problems/ni…
题意:n块石头,每次拿1-3,先手能否赢
记忆:n不是4的倍数就赢
var canWinNim = function(n) {
return n % 4 !== 0; // 不是4的倍数就能赢
};
分类:数学
74. 最小差值 I
leetcode.cn/problems/sm…
题意:数±k后最大最小差值最小
记忆:极差-2k,小于0则0
var smallestRangeI = function(nums, k) {
const min = Math.min(...nums); // 数组最小值
const max = Math.max(...nums); // 数组最大值
return Math.max(0, max - min - 2 * k); // 计算最小可能的差值
};
分类:数组 / 数学
75. 范围求和 II
leetcode.cn/problems/ra…
题意:矩阵多次+1,求最大值个数
记忆:最小行×最小列
var maxCount = function(m, n, ops) {
let minR = m, minC = n; // 初始化最小行和列
for (let [a, b] of ops) {
minR = Math.min(minR, a); // 更新最小行
minC = Math.min(minC, b); // 更新最小列
}
return minR * minC; // 返回最大值个数
};
分类:数学 / 模拟
简单模拟 & 其他(81–100)
81. Fizz Buzz
leetcode.cn/problems/fi…
题意:3→Fizz,5→Buzz,都→FizzBuzz
记忆:遍历判断拼接
var fizzBuzz = function(n) {
const res = []; // 结果数组
for (let i = 1; i <= n; i++) {
let s = ''; // 临时字符串
if (i % 3 === 0) s += 'Fizz'; // 能被3整除,添加Fizz
if (i % 5 === 0) s += 'Buzz'; // 能被5整除,添加Buzz
res.push(s || i + ''); // 为空则添加数字,否则添加FizzBuzz
}
return res; // 返回结果
};
分类:数学 / 模拟
82. 按奇偶排序数组
leetcode.cn/problems/so…
题意:偶数在前奇数在后
记忆:双指针交换奇偶
var sortArrayByParity = function(nums) {
let l = 0, r = nums.length - 1; // 左右指针
while (l < r) {
if (nums[l] % 2 > nums[r] % 2) [nums[l], nums[r]] = [nums[r], nums[l]]; // 左奇右偶,交换
if (nums[l] % 2 === 0) l++; // 左偶,左指针右移
if (nums[r] % 2 === 1) r--; // 右奇,右指针左移
}
return nums; // 返回结果
};
分类:数组 / 双指针
83. 按奇偶排序数组 II
leetcode.cn/problems/so…
题意:偶下标偶,奇下标奇
记忆:双指针找错位交换
var sortArrayByParityII = function(nums) {
let i = 0, j = 1, n = nums.length; // i指向偶下标,j指向奇下标
while (i < n && j < n) {
while (i < n && nums[i] % 2 === 0) i += 2; // 找到偶下标但不是偶数的位置
while (j < n && nums[j] % 2 === 1) j += 2; // 找到奇下标但不是奇数的位置
if (i < n && j < n) [nums[i], nums[j]] = [nums[j], nums[i]]; // 交换两个位置的元素
}
return nums; // 返回结果
};
分类:数组 / 双指针
84. 拥有最多糖果的孩子
leetcode.cn/problems/ki…
题意:+额外糖果后是否最多
记忆:先求最大值,再逐个判断
var kidsWithCandies = function(candies, extraCandies) {
const max = Math.max(...candies); // 找出当前最大糖果数
return candies.map(c => c + extraCandies >= max); // 计算每个孩子添加额外糖果后是否是最多
};
分类:数组
85. 重新排列数组
leetcode.cn/problems/sh…
题意:交错重组数组
记忆:按位交错构建结果
var shuffle = function(nums, n) {
const res = []; // 结果数组
for (let i = 0; i < n; i++) {
res.push(nums[i], nums[i + n]); // 交替添加前n个和后n个元素
}
return res; // 返回结果
};
分类:数组
86. 统计有序矩阵中的负数
leetcode.cn/problems/co…
题意:统计矩阵负数个数
记忆:右上角遍历,左移下移
var countNegatives = function(grid) {
let m = grid.length, n = grid[0].length; // 矩阵的行数和列数
let i = 0, j = n - 1, cnt = 0; // i是行指针,j是列指针,cnt是负数计数
while (i < m && j >= 0) {
if (grid[i][j] < 0) {
cnt += m - i; // 该列从当前行到最后一行都是负数
j--; // 左移列指针
} else i++; // 下移行指针
}
return cnt; // 返回负数个数
};
分类:矩阵 / 双指针
87. 矩阵对角线和
leetcode.cn/problems/ma…
题意:主副对角线和,中心不重复
记忆:遍历i,相加,奇数减中心
var diagonalSum = function(mat) {
let n = mat.length, sum = 0; // n是矩阵的边长,sum是对角线和
for (let i = 0; i < n; i++) sum += mat[i][i] + mat[i][n - 1 - i]; // 计算主副对角线元素和
if (n % 2 === 1) sum -= mat[n >> 1][n >> 1]; // 奇数边长时,中心元素被重复计算,需要减去
return sum; // 返回对角线和
};
分类:矩阵
88. 可被 5 整除的二进制前缀
leetcode.cn/problems/bi…
题意:前缀二进制是否被5整除
记忆:边遍历边取模防溢出
var prefixesDivBy5 = function(nums) {
let cur = 0, res = []; // cur是当前前缀的数值,res是结果数组
for (let n of nums) {
cur = (cur * 2 + n) % 5; // 计算当前前缀的数值,取模5避免溢出
res.push(cur === 0); // 判断是否能被5整除
}
return res; // 返回结果
};
分类:数学 / 模拟
89. 十进制整数的反码
leetcode.cn/problems/nu…
题意:按位取反忽略前导0
记忆:全1掩码异或
var findComplement = function(num) {
let mask = 1; // 初始化掩码
while (mask < num) mask = mask << 1 | 1; // 生成与num等长的全1掩码
return num ^ mask; // 异或操作得到反码
};
分类:位运算
90. 数组异或操作
leetcode.cn/problems/xo…
题意:start, start+2…异或
记忆:循环异或
var xorOperation = function(n, start) {
let res = 0; // 结果
for (let i = 0; i < n; i++) res ^= start + 2 * i; // 计算每个元素并异或
return res; // 返回结果
};
分类:位运算
91. 两句话中的不常见单词
leetcode.cn/problems/un…
题意:只在一句话出现一次的单词
记忆:合并统计,次数1即为答案
var uncommonFromSentences = function(s1, s2) {
const cnt = {}; // 计数对象
const arr = [...s1.split(' '), ...s2.split(' ')]; // 合并两个句子的单词
for (let w of arr) cnt[w] = (cnt[w] || 0) + 1; // 统计每个单词出现的次数
return Object.keys(cnt).filter(k => cnt[k] === 1); // 过滤出只出现一次的单词
};
分类:字符串 / 哈希
92. 查找常用字符
leetcode.cn/problems/fi…
题意:所有串共有的字符
记忆:统计计数,取最小值
var commonChars = function(words) {
let count = new Array(26).fill(Infinity); // 初始化计数数组为无穷大
for (let w of words) {
const cur = new Array(26).fill(0); // 当前单词的字符计数
for (let c of w) cur[c.charCodeAt() - 97]++; // 统计当前单词每个字符的出现次数
for (let i = 0; i < 26; i++) count[i] = Math.min(count[i], cur[i]); // 更新全局最小计数
}
const res = []; // 结果数组
for (let i = 0; i < 26; i++) {
while (count[i]--) res.push(String.fromCharCode(i + 97)); // 生成结果
}
return res; // 返回结果
};
分类:字符串 / 哈希
93. 单词规律
leetcode.cn/problems/wo…
题意:字符与单词双向一一对应
记忆:双 Map 互相映射,不一致则返回 false
var wordPattern = function(pattern, s) {
const arr = s.split(' '); // 将字符串s分割成单词数组
if (pattern.length !== arr.length) return false; // 长度不匹配,返回false
const m1 = new Map(), m2 = new Map(); // 双Map,分别映射字符到单词和单词到字符
for (let i = 0; i < pattern.length; i++) {
const c = pattern[i], w = arr[i]; // 当前字符和单词
if (m1.has(c) && m1.get(c) !== w) return false; // 字符已经映射到不同的单词,返回false
if (m2.has(w) && m2.get(w) !== c) return false; // 单词已经映射到不同的字符,返回false
m1.set(c, w); // 建立字符到单词的映射
m2.set(w, c); // 建立单词到字符的映射
}
return true; // 所有字符和单词都匹配,返回true
};
分类:哈希 / 字符串
94. 解码字母到整数映射
leetcode.cn/problems/de…
题意:1-26→a-z,10#→j
记忆:从后向前,遇 # 跳两位
var freqAlphabets = function(s) {
let res = '', i = s.length - 1; // 结果字符串和指针
while (i >= 0) {
if (s[i] === '#') {
const num = +s.slice(i - 2, i); // 提取两位数
res = String.fromCharCode(96 + num) + res; // 转换为对应字符
i -= 3; // 跳过#和两位数
} else {
res = String.fromCharCode(96 + (+s[i])) + res; // 转换为对应字符
i--; // 移动指针
}
}
return res; // 返回结果
};
分类:字符串
95. 统计位数为偶数的数字
leetcode.cn/problems/fi…
题意:数字位数为偶数的个数
记忆:转字符串判断长度奇偶
var findNumbers = function(nums) {
return nums.filter(n => n.toString().length % 2 === 0).length; // 过滤出位数为偶数的数字并返回个数
};
分类:数组 / 数学
96. 仅含 1 的二进制子串
leetcode.cn/problems/nu…
题意:统计全 1 子串数量
记忆:连续 1 计数,累加 n*(n+1)/2
var numSub = function(s) {
let cur = 0, res = 0, mod = 1e9 + 7; // cur是当前连续1的个数,res是结果,mod是取模值
for (let c of s) {
cur = c === '1' ? cur + 1 : 0; // 当前字符是1,cur加1,否则重置为0
res = (res + cur) % mod; // 累加当前连续1的个数到结果
}
return res; // 返回结果
};
分类:字符串 / 滑动窗口
97. 旅行终点站
leetcode.cn/problems/de…
题意:找不再作为起点的终点城市
记忆:起点存入 Set,遍历终点找不在 Set 的
var destCity = function(paths) {
const start = new Set(); // 存储所有起点城市
for (let [s] of paths) start.add(s); // 将所有起点城市加入Set
for (let [, d] of paths) {
if (!start.has(d)) return d; // 找到不在起点中的终点城市
}
return ''; // 无终点城市,返回空字符串
};
分类:哈希 / 图
98. 设计停车系统
leetcode.cn/problems/de…
题意:简单限制大、中、小车数量
记忆:数组计数,判断剩余
var ParkingSystem = function(big, medium, small) {
this.cnt = [0, big, medium, small]; // 初始化各类型车位数量
};
ParkingSystem.prototype.addCar = function(carType) {
if (this.cnt[carType]) {
this.cnt[carType]--; // 减少对应类型车位数量
return true; // 停车成功
}
return false; // 车位不足,停车失败
};
分类:设计 / 模拟
99. 猜数字
leetcode.cn/problems/gu…
题意:相同位置数字相同个数
记忆:遍历比较计数
var game = function(guess, answer) {
return guess.filter((v, i) => v === answer[i]).length; // 过滤出相同位置数字相同的元素并返回个数
};
分类:数组
100. IP 地址无效化
leetcode.cn/problems/de…
题意:. 替换为 [.]
记忆:直接 replace
var defangIPaddr = function(address) {
return address.replace(/./g, '[.]'); // 使用正则表达式替换所有.为[.]
};
分类:字符串
🔥 面试急救指南
-
优先背:1-30 题(两数之和、有效括号、链表、双指针、二叉树递归)
-
不会就写暴力 + 注释,面试官一定会给分
-
JS 万能一行公式:
- 数组去重:
[...new Set(arr)] - 反转字符串:
s.split('').reverse().join('') - 排序:
arr.sort((a,b)=>a-b) - 求和:
arr.reduce((a,b)=>a+b, 0)
- 数组去重:
祝你面试一次稳过!