【手写leetcode 简单题】诶?LeetCode 简单算法吗?其实也不简单!速看100 JS(面试版)

11 阅读31分钟

适用:前端/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. 优先背:1-30 题(两数之和、有效括号、链表、双指针、二叉树递归)

  2. 不会就写暴力 + 注释,面试官一定会给分

  3. JS 万能一行公式:

    1. 数组去重:[...new Set(arr)]
    2. 反转字符串:s.split('').reverse().join('')
    3. 排序:arr.sort((a,b)=>a-b)
    4. 求和:arr.reduce((a,b)=>a+b, 0)

祝你面试一次稳过!