├── Push.bat ├── code ├── 64. 求1+2+…+n.js ├── 19. 正则表达式匹配.js ├── 45. 把数组排成最小的数.js ├── 20. 表示数值的字符串.js ├── 55 - I. 二叉树的深度.js ├── 15. 二进制中1的个数.js ├── 10- I. 斐波那契数列.js ├── 58 - II. 左旋转字符串.js ├── 17. 打印从1到最大的n位数.js ├── 62. 圆圈中最后剩下的数字.js ├── 52. 两个链表的第一个公共节点.js ├── 58 - I. 翻转单词顺序.js ├── 65. 不用加减乘除做加法.js ├── 10- II. 青蛙跳台阶问题.js ├── 16. 数值的整数次方.js ├── 50. 第一个只出现一次的字符.js ├── 56 - II. 数组中数字出现的次数 II.js ├── 06. 从尾到头打印链表.js ├── 22. 链表中倒数第k个节点.js ├── 18. 删除链表的节点.js ├── 53 - II. 0~n-1中缺失的数字.js ├── 63. 股票的最大利润.js ├── 14- II. 剪绳子 II.js ├── 60. n个骰子的点数.js ├── 32 - I. 从上到下打印二叉树.js ├── 35. 复杂链表的复制.js ├── 46. 把数字翻译成字符串.js ├── 24. 反转链表.js ├── 14- I. 剪绳子.js ├── 55 - II. 平衡二叉树.js ├── 56 - I. 数组中数字出现的次数.js ├── 31. 栈的压入、弹出序列.js ├── 26. 树的子结构.js ├── 30. 包含min函数的栈.js ├── 11. 旋转数组的最小数字.js ├── 57 - II. 和为s的连续正数序列.js ├── 54. 二叉搜索树的第k大节点.js ├── 57. 和为s的两个数字.js ├── 68 - II. 二叉树的最近公共祖先.js ├── 61. 扑克牌中的顺子.js ├── 27. 二叉树的镜像.js ├── 68 - I. 二叉搜索树的最近公共祖先.js ├── 32 - II. 从上到下打印二叉树 II.js ├── 48. 最长不含重复字符的子字符串.js ├── 36. 二叉搜索树与双向链表.js ├── 09. 用两个栈实现队列.js ├── 04. 二维数组中的查找.js ├── 66. 构建乘积数组.js ├── 38. 字符串的排列.js ├── 33. 二叉搜索树的后序遍历序列.js ├── 47. 礼物的最大价值.js ├── 32 - III. 从上到下打印二叉树 III.js ├── 25. 合并两个排序的链表.js ├── 21. 调整数组顺序使奇数位于偶数前面.js ├── 07. 重建二叉树.js ├── 03. 数组中重复的数字.js ├── 59 - II. 队列的最大值.js ├── 53 - I. 在排序数组中查找数字 I.js ├── 34. 二叉树中和为某一值的路径.js ├── 42. 连续子数组的最大和.js ├── 29. 顺时针打印矩阵.js ├── 05. 替换空格.js ├── 12. 矩阵中的路径.js ├── 13. 机器人的运动范围.js ├── 39. 数组中出现次数超过一半的数字.js ├── 59 - I. 滑动窗口的最大值.js ├── 67. 把字符串转换成整数.js ├── 28. 对称的二叉树.js ├── 51. 数组中的逆序对.js ├── 41. 数据流中的中位数.js ├── 40. 最小的k个数.js └── 49. 丑数.js ├── problems ├── 00.demo.md ├── 64. 求1+2+…+n.md ├── 45. 把数组排成最小的数.md ├── 55 - I. 二叉树的深度.md ├── 15. 二进制中1的个数.md ├── 62. 圆圈中最后剩下的数字.md ├── 17. 打印从1到最大的n位数.md ├── 58 - I. 翻转单词顺序.md ├── 56 - II. 数组中数字出现的次数 II.md ├── 52. 两个链表的第一个公共节点.md ├── 58 - II. 左旋转字符串.md ├── 06. 从尾到头打印链表.md ├── 16. 数值的整数次方.md ├── 50. 第一个只出现一次的字符.md ├── 10- I. 斐波那契数列.md ├── 24. 反转链表.md ├── 18. 删除链表的节点.md ├── 14- II. 剪绳子 II.md ├── 32 - I. 从上到下打印二叉树.md ├── 53 - II. 0~n-1中缺失的数字.md ├── 10- II. 青蛙跳台阶问题.md ├── 35. 复杂链表的复制.md ├── 21. 调整数组顺序使奇数位于偶数前面.md ├── 26. 树的子结构.md ├── 55 - II. 平衡二叉树.md ├── 54. 二叉搜索树的第k大节点.md ├── 31. 栈的压入、弹出序列.md ├── 65. 不用加减乘除做加法.md ├── 57. 和为s的两个数字.md ├── 11. 旋转数组的最小数字.md ├── 27. 二叉树的镜像.md ├── 32 - II. 从上到下打印二叉树 II.md ├── 46. 把数字翻译成字符串.md ├── 68 - II. 二叉树的最近公共祖先.md ├── 66. 构建乘积数组.md ├── 57 - II. 和为s的连续正数序列.md ├── 61. 扑克牌中的顺子.md ├── 30. 包含min函数的栈.md ├── 48. 最长不含重复字符的子字符串.md ├── 14- I. 剪绳子.md ├── 38. 字符串的排列.md ├── 04. 二维数组中的查找.md ├── 33. 二叉搜索树的后序遍历序列.md ├── 42. 连续子数组的最大和.md ├── 36. 二叉搜索树与双向链表.md ├── 32 - III. 从上到下打印二叉树 III.md ├── 22. 链表中倒数第k个节点.md ├── 25. 合并两个排序的链表.md ├── 09. 用两个栈实现队列.md ├── 53 - I. 在排序数组中查找数字 I.md ├── 59 - II. 队列的最大值.md ├── 03. 数组中重复的数字.md ├── 34. 二叉树中和为某一值的路径.md ├── 29. 顺时针打印矩阵.md ├── 68 - I. 二叉搜索树的最近公共祖先.md ├── 07. 重建二叉树.md ├── 56 - I. 数组中数字出现的次数.md ├── 13. 机器人的运动范围.md ├── 47. 礼物的最大价值.md ├── 05. 替换空格.md ├── 12. 矩阵中的路径.md ├── 67. 把字符串转换成整数.md ├── 59 - I. 滑动窗口的最大值.md ├── 39. 数组中出现次数超过一半的数字.md ├── 63. 股票的最大利润.md ├── 28. 对称的二叉树.md ├── 51. 数组中的逆序对.md ├── 40. 最小的k个数.md ├── 49. 丑数.md └── 41. 数据流中的中位数.md └── README.md /Push.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | git add . 3 | git commit -m update 4 | git push -------------------------------------------------------------------------------- /code/64. 求1+2+…+n.js: -------------------------------------------------------------------------------- 1 | const sumNums = n => n && sumNums(n - 1) + n; 2 | -------------------------------------------------------------------------------- /code/19. 正则表达式匹配.js: -------------------------------------------------------------------------------- 1 | const isMatch = (s, p) => new RegExp(`^${p}$`).test(s); 2 | -------------------------------------------------------------------------------- /code/45. 把数组排成最小的数.js: -------------------------------------------------------------------------------- 1 | const minNumber = nums => nums.sort((a, b) => `${a}${b}` - `${b}${a}`).join(''); 2 | -------------------------------------------------------------------------------- /code/20. 表示数值的字符串.js: -------------------------------------------------------------------------------- 1 | const isNumber = s => { 2 | const num = s.trim(); 3 | return /^[+-]?(\d+(\.\d*)?|(\.\d+))(e[+-]?\d+)?$/i.test(num); 4 | }; 5 | -------------------------------------------------------------------------------- /problems/00.demo.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「」 4 | 5 | # 思路: 6 | 7 | 8 | 9 | # 代码: 10 | 11 | ## JavaScript 12 | 13 | ```javascript 14 | 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /code/55 - I. 二叉树的深度.js: -------------------------------------------------------------------------------- 1 | const maxDepth = root => { 2 | // 递归退出条件 3 | if (!root) return 0; 4 | return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; 5 | }; 6 | -------------------------------------------------------------------------------- /code/15. 二进制中1的个数.js: -------------------------------------------------------------------------------- 1 | const hammingWeight = n => { 2 | let count = 0; 3 | while (n !== 0) { 4 | n = n & (n - 1); 5 | count++; 6 | } 7 | return count; 8 | }; 9 | -------------------------------------------------------------------------------- /code/10- I. 斐波那契数列.js: -------------------------------------------------------------------------------- 1 | const fib = n => { 2 | const dp = [0, 1]; 3 | for (let i = 2; i <= n; i++) { 4 | dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007; 5 | } 6 | return dp[n]; 7 | }; 8 | -------------------------------------------------------------------------------- /code/58 - II. 左旋转字符串.js: -------------------------------------------------------------------------------- 1 | const reverseLeftWords = (s, k) => { 2 | const len = s.length; 3 | const n = k % len; 4 | const double = `${s}${s}`; 5 | return double.slice(n, n + len); 6 | }; 7 | -------------------------------------------------------------------------------- /code/17. 打印从1到最大的n位数.js: -------------------------------------------------------------------------------- 1 | const printNumbers = n => { 2 | let max = 10 ** n - 1; 3 | const res = []; 4 | for (let i = 1; i <= max; i++) { 5 | res.push(i); 6 | } 7 | return res; 8 | }; 9 | -------------------------------------------------------------------------------- /code/62. 圆圈中最后剩下的数字.js: -------------------------------------------------------------------------------- 1 | // 从最后两个数开始反推 2 | // 补全所有的数,得到的下标就是所求的数 3 | const lastRemaining = (n, m) => { 4 | let res = 0; 5 | for (let i = 2; i <= n; i++) { 6 | res = (res + m) % i; 7 | } 8 | return res; 9 | }; 10 | -------------------------------------------------------------------------------- /code/52. 两个链表的第一个公共节点.js: -------------------------------------------------------------------------------- 1 | const getIntersectionNode = (A, B) => { 2 | let [pA, pB] = [A, B]; 3 | while (pA !== pB) { 4 | pA = pA === null ? B : pA.next; 5 | pB = pB === null ? A : pB.next; 6 | } 7 | return pA; 8 | }; 9 | -------------------------------------------------------------------------------- /code/58 - I. 翻转单词顺序.js: -------------------------------------------------------------------------------- 1 | const reverseWords = s => { 2 | const arr = s.split(' '); 3 | const res = []; 4 | for (let i = arr.length - 1; i >= 0; i--) { 5 | arr[i] && res.push(arr[i]); 6 | } 7 | return res.join(' '); 8 | }; 9 | -------------------------------------------------------------------------------- /code/65. 不用加减乘除做加法.js: -------------------------------------------------------------------------------- 1 | const add = (a, b) => { 2 | while (b) { 3 | // 进位 4 | const c = (a & b) << 1; 5 | // 不考虑进位的加法 6 | a ^= b; 7 | // 将进位赋值给b 8 | b = c; 9 | } 10 | return a; 11 | }; 12 | -------------------------------------------------------------------------------- /code/10- II. 青蛙跳台阶问题.js: -------------------------------------------------------------------------------- 1 | const numWays = n => { 2 | // 为了通过,强行加的条件 3 | if (n === 0) return 1; 4 | const dp = [null, 1, 2]; 5 | for (let i = 3; i <= n; i++) { 6 | dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007; 7 | } 8 | return dp[n]; 9 | }; 10 | -------------------------------------------------------------------------------- /code/16. 数值的整数次方.js: -------------------------------------------------------------------------------- 1 | const myPow = (x, n) => { 2 | // 递归出口 3 | if (n === 0) return 1; 4 | // n小于0,特殊情况 5 | if (n < 0) return 1 / myPow(x, -n); 6 | // n奇数 7 | if (n & 1) return x * myPow(x, n - 1); 8 | // n偶数 9 | return myPow(x * x, n / 2); 10 | }; 11 | -------------------------------------------------------------------------------- /problems/64. 求1+2+…+n.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 64. 求1+2+…+n](https://leetcode-cn.com/problems/qiu-12n-lcof/)」 4 | 5 | # 思路: 6 | 7 | 使用递归即可。 8 | 9 | # 代码: 10 | 11 | ## JavaScript 12 | 13 | ```javascript 14 | const sumNums = n => n && sumNums(n - 1) + n; 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /code/50. 第一个只出现一次的字符.js: -------------------------------------------------------------------------------- 1 | const firstUniqChar = str => { 2 | if (!str) return ' '; 3 | const m = new Map(); 4 | for (const s of str) { 5 | m.set(s, (m.get(s) || 0) + 1); 6 | } 7 | for (const s of m) { 8 | if (s[1] === 1) return s[0]; 9 | } 10 | return ' '; 11 | }; 12 | -------------------------------------------------------------------------------- /code/56 - II. 数组中数字出现的次数 II.js: -------------------------------------------------------------------------------- 1 | const singleNumber = nums => { 2 | const map = new Map(); 3 | for (let i = 0; i < nums.length; i++) { 4 | map.set(nums[i], (map.get(nums[i]) || 0) + 1); 5 | } 6 | for (const item of map) { 7 | if (item[1] === 1) return item[0]; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /code/06. 从尾到头打印链表.js: -------------------------------------------------------------------------------- 1 | const reversePrint = head => { 2 | const stack = []; 3 | const res = []; 4 | while (head) { 5 | stack.push(head.val); 6 | head = head.next; 7 | } 8 | const len = stack.length; 9 | for (let i = 0; i < len; i++) { 10 | res.push(stack.pop()); 11 | } 12 | return res; 13 | }; 14 | -------------------------------------------------------------------------------- /problems/45. 把数组排成最小的数.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 45. 把数组排成最小的数](https://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/)」 4 | 5 | # 思路: 6 | 7 | 自定义排序,比较`'ab'`和`'ba'`(都是字符串)的大小。 8 | 9 | # 代码: 10 | 11 | ## JavaScript 12 | 13 | ```javascript 14 | const minNumber = nums => nums.sort((a, b) => `${a}${b}` - `${b}${a}`).join(''); 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /code/22. 链表中倒数第k个节点.js: -------------------------------------------------------------------------------- 1 | const getKthFromEnd = (head, k) => { 2 | let fast, slow; 3 | fast = slow = head; 4 | while (k--) { 5 | // 快指针先走k步 6 | fast = fast.next; 7 | } 8 | while (fast) { 9 | // 再一起走,知道快指针走到头 10 | fast = fast.next; 11 | slow = slow.next; 12 | } 13 | // 此时的慢指针指的就是倒数第k个 14 | return slow; 15 | }; 16 | -------------------------------------------------------------------------------- /code/18. 删除链表的节点.js: -------------------------------------------------------------------------------- 1 | const deleteNode = (head, val) => { 2 | // 定义虚拟节点 3 | const res = new ListNode(-1); 4 | // 虚拟节点连接到head 5 | res.next = head; 6 | // 定义p指针,最开始指向虚拟节点 7 | let p = res; 8 | // 遍历链表 9 | while (p?.next) { 10 | // 如果下一个值等于val,则删除下一个值 11 | if (p.next.val === val) p.next = p.next.next; 12 | p = p.next; 13 | } 14 | return res.next; 15 | }; 16 | -------------------------------------------------------------------------------- /code/53 - II. 0~n-1中缺失的数字.js: -------------------------------------------------------------------------------- 1 | const missingNumber = nums => { 2 | let [low, high] = [0, nums.length - 1]; 3 | while (low <= high) { 4 | const mid = (low + high) >> 1; 5 | if (nums[mid] === mid) { 6 | // 左半边是完整的 7 | low = mid + 1; 8 | } else { 9 | // 左半边不完整 10 | high = mid - 1; 11 | } 12 | } 13 | return low; 14 | }; 15 | -------------------------------------------------------------------------------- /code/63. 股票的最大利润.js: -------------------------------------------------------------------------------- 1 | const maxProfit = prices => { 2 | // 先定义第一天为最低价格 3 | let min = prices[0]; 4 | // 利润 5 | let profit = 0; 6 | // 遍历数据 7 | for (let i = 1; i < prices.length; i++) { 8 | // 如果发现比最低价格还低的,更新最低价格 9 | min = Math.min(min, prices[i]); 10 | // 如果发现当前利润比之前高的,更新利润 11 | profit = Math.max(profit, prices[i] - min); 12 | } 13 | return profit; 14 | }; 15 | -------------------------------------------------------------------------------- /problems/55 - I. 二叉树的深度.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 55 - I. 二叉树的深度](https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/)」 4 | 5 | # 思路: 6 | 7 | 使用递归,返回左子树高度和右子树高度的最大值`+1`。 8 | 9 | # 代码: 10 | 11 | ## JavaScript 12 | 13 | ```javascript 14 | const maxDepth = root => { 15 | // 递归退出条件 16 | if (!root) return 0; 17 | return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; 18 | }; 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- /code/14- II. 剪绳子 II.js: -------------------------------------------------------------------------------- 1 | // 贪心:每次拆成n个3,如果剩下是4,则保留4,然后相乘 2 | 3 | const cuttingRope = n => { 4 | // 特殊情况处理 5 | const arr = [null, null, 1, 2, 4]; 6 | if (n <= 4) return arr[n]; 7 | const mod = 1000000007; 8 | let res = 1; 9 | while (n > 4) { 10 | // 每次减掉3 11 | res = (res * 3) % mod; 12 | n -= 3; 13 | } 14 | // 最后剩下一段小于等于4的长度 15 | res *= n; 16 | return res % mod; 17 | }; 18 | -------------------------------------------------------------------------------- /code/60. n个骰子的点数.js: -------------------------------------------------------------------------------- 1 | const dicesProbability = n => { 2 | let dp = new Array(7).fill(1 / 6); 3 | let t; 4 | for (let i = 1; i < n; i++) { 5 | t = new Array(6 * (i + 1) + 1).fill(0); 6 | for (let j = i; j <= 6 * i; j++) { 7 | for (let k = 1; k <= 6; k++) { 8 | t[j + k] += dp[j] / 6; 9 | } 10 | } 11 | dp = t; 12 | } 13 | return dp.slice(n); 14 | }; 15 | -------------------------------------------------------------------------------- /code/32 - I. 从上到下打印二叉树.js: -------------------------------------------------------------------------------- 1 | const levelOrder = root => { 2 | if (!root) return []; 3 | // 创建队列 4 | const queue = [root]; 5 | const res = []; 6 | while (queue.length) { 7 | // 获取根节点,根节点出队 8 | const n = queue.shift(); 9 | // 访问队头 10 | res.push(n.val); 11 | // 队头的子节点依次入队 12 | n.left && queue.push(n.left); 13 | n.right && queue.push(n.right); 14 | } 15 | return res; 16 | }; 17 | -------------------------------------------------------------------------------- /code/35. 复杂链表的复制.js: -------------------------------------------------------------------------------- 1 | const copyRandomList = head => { 2 | const map = new Map(); 3 | 4 | // 第一次遍历,复制节点值 5 | let p = head; 6 | while (p) { 7 | map.set(p, new Node(p.val)); 8 | p = p.next; 9 | } 10 | 11 | // 第二次遍历,复制节点关系 12 | p = head; 13 | while (p) { 14 | map.get(p).next = map.get(p.next) || null; 15 | map.get(p).random = map.get(p.random) || null; 16 | p = p.next; 17 | } 18 | 19 | return map.get(head); 20 | }; 21 | -------------------------------------------------------------------------------- /code/46. 把数字翻译成字符串.js: -------------------------------------------------------------------------------- 1 | const translateNum = num => { 2 | const str = `${num}`; 3 | const dp = [1, 1]; 4 | const len = str.length; 5 | for (let i = 2; i < len + 1; i++) { 6 | const preNum = parseInt(str[i - 2] + str[i - 1]); 7 | if (preNum >= 10 && preNum <= 25) { 8 | dp[i] = dp[i - 1] + dp[i - 2]; 9 | } else { 10 | dp[i] = dp[i - 1]; 11 | } 12 | } 13 | return dp[len]; 14 | }; 15 | -------------------------------------------------------------------------------- /code/24. 反转链表.js: -------------------------------------------------------------------------------- 1 | const reverseList = head => { 2 | // 定义cur指向头部 3 | // pre指向头部前面的null 4 | let [cur, pre] = [head, null]; 5 | // 遍历链表 6 | while (cur) { 7 | // 定义next为cur下一个 8 | const next = cur.next; 9 | // cur的指向改为pre 10 | cur.next = pre; 11 | // pre改为当前cur的指向 12 | pre = cur; 13 | // cur的指向改为next的指向 14 | cur = next; 15 | } 16 | // 遍历结束,cur指向null,返回pre 17 | return pre; 18 | }; 19 | -------------------------------------------------------------------------------- /problems/15. 二进制中1的个数.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 15. 二进制中1的个数](https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/)」 4 | 5 | # 思路: 6 | 7 | `n=n&(n-1)`可以去掉二进制数中最右边的一个`1`,循环计数,每次去掉一个`1`,`count++`。 8 | 9 | # 代码: 10 | 11 | ## JavaScript 12 | 13 | ```javascript 14 | const hammingWeight = n => { 15 | let count = 0; 16 | while (n !== 0) { 17 | n = n & (n - 1); 18 | count++; 19 | } 20 | return count; 21 | }; 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /problems/62. 圆圈中最后剩下的数字.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 62. 圆圈中最后剩下的数字](https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 从最后两个数开始反推 8 | - 补全所有的数,得到的下标就是所求的数 9 | 10 | # 代码: 11 | 12 | ## JavaScript 13 | 14 | ```javascript 15 | const lastRemaining = (n, m) => { 16 | let res = 0; 17 | for (let i = 2; i <= n; i++) { 18 | res = (res + m) % i; 19 | } 20 | return res; 21 | }; 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /code/14- I. 剪绳子.js: -------------------------------------------------------------------------------- 1 | const cuttingRope = n => { 2 | const dp = [null, null, 1]; 3 | for (let i = 3; i <= n; i++) { 4 | // 当前dp[i]初始化为0 5 | dp[i] = 0; 6 | for (let j = 1; j <= i - 1; j++) { 7 | // 因为j在遍历的过程中,会算出很多dp[i],取最大的 8 | // (i - j) * j表示拆分成2个数 9 | // dp[i - j] * j表示拆分成2个及以上的数 10 | dp[i] = Math.max(dp[i], (i - j) * j, dp[i - j] * j); 11 | } 12 | } 13 | return dp[n]; 14 | }; 15 | -------------------------------------------------------------------------------- /code/55 - II. 平衡二叉树.js: -------------------------------------------------------------------------------- 1 | const isBalanced = root => { 2 | // 如果返回-1,则该节点不平衡 3 | const check = node => { 4 | if (!node) return 0; 5 | const left = check(node.left); 6 | const right = check(node.right); 7 | if (left === -1 || right === -1 || Math.abs(left - right) > 1) { 8 | return -1; 9 | } 10 | return Math.max(left, right) + 1; 11 | }; 12 | // 若等于-1,则不是平衡二叉树 13 | return check(root) !== -1; 14 | }; 15 | -------------------------------------------------------------------------------- /problems/17. 打印从1到最大的n位数.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 17. 打印从1到最大的n位数](https://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/)」 4 | 5 | # 思路: 6 | 7 | 最大的n位数为`10^n-1`,循环输出即可,不用考虑大数问题。 8 | 9 | # 代码: 10 | 11 | ## JavaScript 12 | 13 | ```javascript 14 | const printNumbers = n => { 15 | let max = 10 ** n - 1; 16 | const res = []; 17 | for (let i = 1; i <= max; i++) { 18 | res.push(i); 19 | } 20 | return res; 21 | }; 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /code/56 - I. 数组中数字出现的次数.js: -------------------------------------------------------------------------------- 1 | const singleNumbers = nums => { 2 | const temp = nums.reduce((a, b) => a ^ b, 0); 3 | 4 | // 此时temp是两个不同的数异或的结果 5 | // 寻找k,k是temp最低位为1、其余位是0的二进制数 6 | let k = 1; 7 | while ((temp & k) === 0) k = k << 1; 8 | 9 | let [num1, num2] = [0, 0]; 10 | for (const num of nums) { 11 | // 分组,目的是将两个不同的数分开 12 | if (num & k) { 13 | num1 ^= num; 14 | } else { 15 | num2 ^= num; 16 | } 17 | } 18 | 19 | return [num1, num2]; 20 | }; 21 | -------------------------------------------------------------------------------- /code/31. 栈的压入、弹出序列.js: -------------------------------------------------------------------------------- 1 | const validateStackSequences = (pushed, popped) => { 2 | const stack = []; 3 | let index = 0; 4 | const len = pushed.length; 5 | for (let i = 0; i < len; i++) { 6 | stack.push(pushed[i]); 7 | while (popped[index] !== undefined && popped[index] === stack[stack.length - 1]) { 8 | stack.pop(); 9 | index++; 10 | } 11 | } 12 | return !stack.length; 13 | }; 14 | 15 | console.log(validateStackSequences([0], [0])); 16 | -------------------------------------------------------------------------------- /code/26. 树的子结构.js: -------------------------------------------------------------------------------- 1 | // 该递归函数是用来判断A当前节点和B是否相同 2 | const dfs = (A, B) => { 3 | // 递归出口 4 | if (!B) return true; 5 | // A空,B不为空,false 6 | if (!A) return false; 7 | return A.val === B.val && dfs(A.left, B.left) && dfs(A.right, B.right); 8 | }; 9 | 10 | const isSubStructure = (A, B) => { 11 | // 递归出口,A空或B空,false 12 | if (!A || !B) return false; 13 | // 递归判断A当前节点、A左子树、A右子树任意一个与B相同,就返回true 14 | return dfs(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B); 15 | }; 16 | -------------------------------------------------------------------------------- /code/30. 包含min函数的栈.js: -------------------------------------------------------------------------------- 1 | class MinStack { 2 | constructor() { 3 | this.stack = []; 4 | } 5 | push(x) { 6 | this.stack.push({ 7 | val: x, 8 | min: this.stack.length ? Math.min(x, this.min()) : x, 9 | }); 10 | } 11 | pop() { 12 | this.stack.pop(); 13 | } 14 | top() { 15 | return this.stack[this.stack.length - 1].val; 16 | } 17 | min() { 18 | return this.stack[this.stack.length - 1].min; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /code/11. 旋转数组的最小数字.js: -------------------------------------------------------------------------------- 1 | const minArray = numbers => { 2 | let [low, high] = [0, numbers.length - 1]; 3 | while (low <= high) { 4 | const mid = (low + high) >> 1; 5 | if (numbers[mid] > numbers[high]) { 6 | // 最小值一定在mid右侧 7 | low = mid + 1; 8 | } else if (numbers[mid] < numbers[high]) { 9 | // 最小值在mid左侧,或者mid就是最小值 10 | high = mid; 11 | } else { 12 | high--; 13 | } 14 | } 15 | return numbers[low]; 16 | }; 17 | -------------------------------------------------------------------------------- /code/57 - II. 和为s的连续正数序列.js: -------------------------------------------------------------------------------- 1 | const findContinuousSequence = target => { 2 | const res = []; 3 | const window = [1, 2]; 4 | let sum = 3; 5 | 6 | while (window[0] <= target >> 1) { 7 | if (sum < target) { 8 | const num = window[window.length - 1] + 1; 9 | sum += num; 10 | window.push(num); 11 | } else if (sum > target) { 12 | sum -= window.shift(); 13 | } else { 14 | res.push([...window]); 15 | sum -= window.shift(); 16 | } 17 | } 18 | return res; 19 | }; 20 | -------------------------------------------------------------------------------- /code/54. 二叉搜索树的第k大节点.js: -------------------------------------------------------------------------------- 1 | // 二叉搜索树,具有下列性质的二叉树: 2 | // 1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 3 | // 2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 4 | // 3. 它的左、右子树也分别为二叉排序树。 5 | 6 | const kthLargest = (root, k) => { 7 | // 遍历顺序(访问右节点在前):右-根-左 8 | const unInOrder = node => { 9 | if (!node) return; 10 | unInOrder(node.right); 11 | res.push(node.val); 12 | unInOrder(node.left); 13 | }; 14 | const res = []; 15 | unInOrder(root, res); 16 | // 得到从大到小排序的数组 17 | return res[k - 1]; 18 | }; 19 | -------------------------------------------------------------------------------- /code/57. 和为s的两个数字.js: -------------------------------------------------------------------------------- 1 | const twoSum = (nums, target) => { 2 | let [left, right] = [0, nums.length - 1]; 3 | while (left < right) { 4 | sum = nums[left] + nums[right]; 5 | if (sum > target) { 6 | // 太大了,右指针左移 7 | right--; 8 | } else if (sum < target) { 9 | // 太小了,左指针右移 10 | left++; 11 | } else { 12 | // 找到,直接返回两个数 13 | return [nums[left], nums[right]]; 14 | } 15 | } 16 | // 遍历完后没找到,返回空数组 17 | return []; 18 | }; 19 | -------------------------------------------------------------------------------- /code/68 - II. 二叉树的最近公共祖先.js: -------------------------------------------------------------------------------- 1 | const lowestCommonAncestor = (root, p, q) => { 2 | // 树空、根节点等于p或q,那么root是最近公共祖先 3 | if (!root || root === p || root === q) return root; 4 | // 向左子树寻找节点相同的点 5 | const left = lowestCommonAncestor(root.left, p, q); 6 | // 向右子树寻找节点相同的点 7 | const right = lowestCommonAncestor(root.right, p, q); 8 | // 若左右各找到一个,那么当前根节点就是最近公共祖先 9 | if (left && right) return root; 10 | // 只有左边找到,那么最近公共祖先在左边 11 | if (left) return left; 12 | // 只有右边找到,那么最近公共祖先在左边 13 | if (right) return right; 14 | }; 15 | -------------------------------------------------------------------------------- /code/61. 扑克牌中的顺子.js: -------------------------------------------------------------------------------- 1 | const isStraight = nums => { 2 | const set = new Set(); 3 | // 初始化最大值,最小值 4 | let [max, min] = [0, 14]; 5 | for (const num of nums) { 6 | // 遇到王,跳过 7 | if (num === 0) continue; 8 | // 更新最大值 9 | max = Math.max(max, num); 10 | // 更新最小值 11 | min = Math.min(min, num); 12 | // 如果牌重复,肯定不是顺子 13 | if (set.has(num)) return false; 14 | // 牌加入set 15 | set.add(num); 16 | } 17 | // 除了大小王之外,若最大牌和最小牌之差小于5,则可以是顺子 18 | return max - min < 5; 19 | }; 20 | -------------------------------------------------------------------------------- /problems/58 - I. 翻转单词顺序.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 58 - I. 翻转单词顺序](https://leetcode-cn.com/problems/fan-zhuan-dan-ci-shun-xu-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 先用空格分隔为数组 8 | - 用另一个数组存放倒过来的顺序 9 | - 为了去掉单词中出现多个空格的情况,需要判断`arr[i]`是否为空 10 | - 返回字符串 11 | 12 | # 代码: 13 | 14 | ## JavaScript 15 | 16 | ```javascript 17 | const reverseWords = s => { 18 | const arr = s.split(' '); 19 | const res = []; 20 | for (let i = arr.length - 1; i >= 0; i--) { 21 | arr[i] && res.push(arr[i]); 22 | } 23 | return res.join(' '); 24 | }; 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /code/27. 二叉树的镜像.js: -------------------------------------------------------------------------------- 1 | // 写法1 2 | const mirrorTree = root => { 3 | // 递归出口 4 | if (!root) return null; 5 | // 交换当前左右节点 6 | [root.left, root.right] = [root.right, root.left]; 7 | // 递归交换左子树的左右节点 8 | mirrorTree(root.left); 9 | // 递归交换右子树的左右节点 10 | mirrorTree(root.right); 11 | // 返回当前节点给上一级 12 | return root; 13 | }; 14 | 15 | // 写法2 16 | const mirrorTree2 = root => { 17 | if (!root) return null; 18 | 19 | const node = new TreeNode(root.val); 20 | 21 | node.left = mirrorTree(root.right); 22 | node.right = mirrorTree(root.left); 23 | 24 | return node; 25 | }; 26 | -------------------------------------------------------------------------------- /code/68 - I. 二叉搜索树的最近公共祖先.js: -------------------------------------------------------------------------------- 1 | const lowestCommonAncestor = (root, p, q) => { 2 | if (!root) return null; 3 | // 如果相等,公共祖先就是他们本身 4 | if (p.val === q.val) return p; 5 | // 遍历root 6 | while (root) { 7 | // 当前节点值在p,q的左边,遍历右子树 8 | if (root.val < q.val && root.val < p.val) { 9 | root = root.right; 10 | } else if (root.val > q.val && root.val > p.val) { 11 | // 当前节点值在p,q的右边,遍历左子树 12 | root = root.left; 13 | } else { 14 | // 当前节点值在p,q的中间,那么当前节点就是公共祖先 15 | return root; 16 | } 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /problems/56 - II. 数组中数字出现的次数 II.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 56 - II. 数组中数字出现的次数 II](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/)」 4 | 5 | # 思路: 6 | 7 | 直接用`Map`统计各个数出现的次数。在遍历一次`Map`,返回出现次数为`1`的数。 8 | 9 | # 代码: 10 | 11 | ## JavaScript 12 | 13 | ```javascript 14 | const singleNumber = nums => { 15 | const map = new Map(); 16 | for (let i = 0; i < nums.length; i++) { 17 | map.set(nums[i], (map.get(nums[i]) || 0) + 1); 18 | } 19 | for (const item of map) { 20 | if (item[1] === 1) return item[0]; 21 | } 22 | }; 23 | ``` 24 | 25 | -------------------------------------------------------------------------------- /problems/52. 两个链表的第一个公共节点.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 52. 两个链表的第一个公共节点](https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 如果链表一样长且有交点,则第一次遍历就能找到交点,返回 8 | - 如果不一样长且有交点,则第二次遍历就能找到交点,返回 9 | - 如果没有交点,则第二次遍历结束都是`null`,遍历结束,返回`null` 10 | 11 | # 代码: 12 | 13 | ## JavaScript 14 | 15 | ```javascript 16 | const getIntersectionNode = (A, B) => { 17 | let [pA, pB] = [A, B]; 18 | while (pA !== pB) { 19 | pA = pA === null ? B : pA.next; 20 | pB = pB === null ? A : pB.next; 21 | } 22 | return pA; 23 | }; 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /problems/58 - II. 左旋转字符串.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 58 - II. 左旋转字符串](https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 先将字符串重复一次,拼接在结尾 8 | - 计算`n = k % len` 9 | - 然后再从双倍字符串的`n`处截取,截取`len`个即可 10 | 11 | 例如,有如下举例: 12 | 13 | ![](https://jack-img.oss-cn-hangzhou.aliyuncs.com/img/20211030143126.png) 14 | 15 | # 代码: 16 | 17 | ## JavaScript 18 | 19 | ```javascript 20 | const reverseLeftWords = (s, k) => { 21 | const len = s.length; 22 | const n = k % len; 23 | const double = `${s}${s}`; 24 | return double.slice(n, n + len); 25 | }; 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /code/32 - II. 从上到下打印二叉树 II.js: -------------------------------------------------------------------------------- 1 | const levelOrder = root => { 2 | if (!root) return []; 3 | const res = []; 4 | // 根节点入队 5 | const q = [root]; 6 | // 当队列还有值时,一直执行 7 | while (q.length) { 8 | let len = q.length; 9 | res.push([]); 10 | while (len--) { 11 | // 获取根节点,根节点出队 12 | const n = q.shift(); 13 | // 根节点加入res栈顶元素 14 | res[res.length - 1].push(n.val); 15 | // 队头左右节点入队 16 | n.left && q.push(n.left); 17 | n.right && q.push(n.right); 18 | } 19 | } 20 | return res; 21 | }; 22 | -------------------------------------------------------------------------------- /problems/06. 从尾到头打印链表.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 06. 从尾到头打印链表](https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/)」 4 | 5 | # 思路: 6 | 7 | 使用一个辅助栈,遍历链表时,先将元素放入辅助栈。 8 | 9 | 最后从尾遍历辅助栈。 10 | 11 | # 代码: 12 | 13 | ## JavaScript 14 | 15 | ```javascript 16 | const reversePrint = head => { 17 | const stack = []; 18 | const res = []; 19 | while (head) { 20 | stack.push(head.val); 21 | head = head.next; 22 | } 23 | const len = stack.length; 24 | for (let i = 0; i < len; i++) { 25 | res.push(stack.pop()); 26 | } 27 | return res; 28 | }; 29 | ``` 30 | -------------------------------------------------------------------------------- /problems/16. 数值的整数次方.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 16. 数值的整数次方](https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 递归出口:`n`为`0`,返回`1` 8 | - 若`n`小于`0`,特殊处理 9 | - 若`n`为奇数,算`myPow(x, n - 1)`,转化为偶数 10 | - 若`n`为偶数,算`myPow(x * x, n / 2)` 11 | 12 | # 代码: 13 | 14 | ## JavaScript 15 | 16 | ```javascript 17 | const myPow = (x, n) => { 18 | // 递归出口 19 | if (n === 0) return 1; 20 | // n小于0,特殊情况 21 | if (n < 0) return 1 / myPow(x, -n); 22 | // n奇数 23 | if (n & 1) return x * myPow(x, n - 1); 24 | // n偶数 25 | return myPow(x * x, n / 2); 26 | }; 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /code/48. 最长不含重复字符的子字符串.js: -------------------------------------------------------------------------------- 1 | const lengthOfLongestSubstring = s => { 2 | // 滑动窗口 3 | const window = []; 4 | let max = 0; 5 | const len = s.length; 6 | for (let i = 0; i < len; i++) { 7 | // 当前字符在窗口中的索引 8 | const index = window.indexOf(s[i]); 9 | if (index !== -1) { 10 | // 当前字符在窗口中出现过,那么就将窗口中[0,index]区间的全部去除 11 | // 从index+1开始新一轮的窗口 12 | window.splice(0, index + 1); 13 | } 14 | // 当前字符加入窗口 15 | window.push(s[i]); 16 | // 更新最大值 17 | if (max < window.length) max = window.length; 18 | } 19 | return max; 20 | }; 21 | -------------------------------------------------------------------------------- /problems/50. 第一个只出现一次的字符.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 50. 第一个只出现一次的字符](https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 第一次遍历字符串,用`Map`统计出现的次数 8 | - 第二次遍历`Map`,返回第一个次数为`1`的字符 9 | - 若遍历`Map`结束后,还没有找到,则返回`" "` 10 | 11 | # 代码: 12 | 13 | ## JavaScript 14 | 15 | ```javascript 16 | const firstUniqChar = str => { 17 | if (!str) return ' '; 18 | const m = new Map(); 19 | for (const s of str) { 20 | m.set(s, (m.get(s) || 0) + 1); 21 | } 22 | for (const s of m) { 23 | if (s[1] === 1) return s[0]; 24 | } 25 | return ' '; 26 | }; 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /code/36. 二叉搜索树与双向链表.js: -------------------------------------------------------------------------------- 1 | const treeToDoublyList = root => { 2 | const dfs = cur => { 3 | if (!cur) return; 4 | dfs(cur.left); 5 | if (!pre) { 6 | // pre为空,正在访问表头节点,赋为head 7 | head = cur; 8 | } else { 9 | // pre有值,更新指针,双向连接 10 | pre.right = cur; 11 | cur.left = pre; 12 | } 13 | // 更新pre,将当前节点作为一下个节点的上一个节点 14 | pre = cur; 15 | dfs(cur.right); 16 | }; 17 | let pre, head; 18 | if (!root) return; 19 | dfs(root); 20 | // 收尾相连 21 | head.left = pre; 22 | pre.right = head; 23 | return head; 24 | }; 25 | -------------------------------------------------------------------------------- /code/09. 用两个栈实现队列.js: -------------------------------------------------------------------------------- 1 | class CQueue { 2 | constructor() { 3 | this.stackA = []; 4 | this.stackB = []; 5 | } 6 | appendTail(value) { 7 | this.stackA.push(value); 8 | } 9 | deleteHead() { 10 | if (this.stackB.length) { 11 | return this.stackB.pop(); 12 | } else { 13 | while (this.stackA.length) { 14 | this.stackB.push(this.stackA.pop()); 15 | } 16 | if (!this.stackB.length) { 17 | return -1; 18 | } else { 19 | return this.stackB.pop(); 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /problems/10- I. 斐波那契数列.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 10- I. 斐波那契数列](https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/)」 4 | 5 | # 思路: 6 | 7 | > 这是一道**动态规划**经典例题,使用**动态规划**来解题。 8 | 9 | 1. `dp[i]`含义:斐波那契数列的第`i`个数为`dp[i]` 10 | 2. 递推公式:根据定义易得:`dp[i] = dp[i - 1] + dp[i - 2]` 11 | 3. dp数组初始化:题目已经给出:`dp = [0, 1]` 12 | 4. 遍历顺序:根据定义,当前数依赖于前两个数,所以从前向后遍历 13 | 5. 注意取模`(% 1000000007)` 14 | 15 | # 代码: 16 | 17 | ## JavaScript 18 | 19 | ```javascript 20 | const fib = n => { 21 | const dp = [0, 1]; 22 | for (let i = 2; i <= n; i++) { 23 | dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007; 24 | } 25 | return dp[n]; 26 | }; 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /code/04. 二维数组中的查找.js: -------------------------------------------------------------------------------- 1 | const findNumberIn2DArray = (matrix, target) => { 2 | const [m, n] = [matrix.length, matrix[0]?.length]; 3 | if (!m) return false; 4 | // 定义左下角的坐标 5 | let [row, col] = [m - 1, 0]; 6 | // 坐标在矩阵内,就一直寻找 7 | while (row >= 0 && col <= n - 1) { 8 | // 当前元素 9 | const item = matrix[row][col]; 10 | if (item === target) { 11 | // 找到,返回true 12 | return true; 13 | } else if (item > target) { 14 | // 太大了,上移一行 15 | row--; 16 | } else { 17 | // 太小了,右移一列 18 | col++; 19 | } 20 | } 21 | return false; 22 | }; 23 | -------------------------------------------------------------------------------- /code/66. 构建乘积数组.js: -------------------------------------------------------------------------------- 1 | const constructArr = a => { 2 | const len = a.length; 3 | // 定义左累积数组、右累积数组,初始值都为 1 4 | const [resLeft, resRight] = [new Array(len).fill(1), new Array(len).fill(1)]; 5 | for (let i = 1; i < len; i++) { 6 | // 从i=1开始,作左累积运算 7 | resLeft[i] = resLeft[i - 1] * a[i - 1]; 8 | } 9 | for (let i = len - 2; i >= 0; i--) { 10 | // 从i=len - 2开始,作右累积运算 11 | resRight[i] = resRight[i + 1] * a[i + 1]; 12 | } 13 | const res = []; 14 | for (let i = 0; i < len; i++) { 15 | // 两个数组的元素对应相乘,即可得到当前元素的答案 16 | res.push(resLeft[i] * resRight[i]); 17 | } 18 | return res; 19 | }; 20 | -------------------------------------------------------------------------------- /code/38. 字符串的排列.js: -------------------------------------------------------------------------------- 1 | const permutation = s => { 2 | if (s.length === 0) return ['']; 3 | // 递归出口 4 | if (s.length === 1) return [s]; 5 | const res = []; 6 | const len = s.length; 7 | for (let i = 0; i < len; i++) { 8 | // 取出一个字符为char 9 | const char = s[i]; 10 | // newStr=去掉char后剩下的字符 11 | let newStr = s.slice(0, i) + s.slice(i + 1); 12 | // 递归产生newStr的全排列,返回的是数组 13 | const next = permutation(newStr); 14 | // 将char与newStr的全排列拼接,放入res 15 | next.forEach(item => { 16 | res.push(char + item); 17 | }); 18 | } 19 | // 去重 20 | return [...new Set(res)]; 21 | }; 22 | -------------------------------------------------------------------------------- /problems/24. 反转链表.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 24. 反转链表](https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/)」 4 | 5 | # 思路: 6 | 7 | 指针遍历链表,边遍历边改变指向。 8 | 9 | # 代码: 10 | 11 | ## JavaScript 12 | 13 | ```javascript 14 | const reverseList = head => { 15 | // 定义cur指向头部 16 | // pre指向头部前面的null 17 | let [cur, pre] = [head, null]; 18 | // 遍历链表 19 | while (cur) { 20 | // 定义next为cur下一个 21 | const next = cur.next; 22 | // cur的指向改为pre 23 | cur.next = pre; 24 | // pre改为当前cur的指向 25 | pre = cur; 26 | // cur的指向改为next的指向 27 | cur = next; 28 | } 29 | // 遍历结束,cur指向null,返回pre 30 | return pre; 31 | }; 32 | ``` 33 | 34 | -------------------------------------------------------------------------------- /problems/18. 删除链表的节点.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 18. 删除链表的节点](https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 定义虚拟节点,用指针遍历链表 8 | - 如果下一个值等于`val`,则删除下一个值 9 | - 使用ES6的`?.`运算符,判断`p`是否存在 10 | 11 | # 代码: 12 | 13 | ## JavaScript 14 | 15 | ```javascript 16 | const deleteNode = (head, val) => { 17 | // 定义虚拟节点 18 | const res = new ListNode(-1); 19 | // 虚拟节点连接到head 20 | res.next = head; 21 | // 定义p指针,最开始指向虚拟节点天头部 22 | let p = res; 23 | // 遍历链表 24 | while (p?.next) { 25 | // 如果下一个值等于val,则删除下一个值 26 | if (p.next.val === val) p.next = p.next.next; 27 | p = p.next; 28 | } 29 | return res.next; 30 | }; 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /code/33. 二叉搜索树的后序遍历序列.js: -------------------------------------------------------------------------------- 1 | const verifyPostorder = postorder => { 2 | const len = postorder.length; 3 | // 递归出口 4 | if (len <= 1) return true; 5 | // 后序遍历,最后一个是根节点 6 | const root = postorder[len - 1]; 7 | // 找到第一个大于根节点的位置i 8 | let i = 0; 9 | while (postorder[i] < root) i++; 10 | // 从i开始,判断i右边到根节点之间的元素,也就是右子树是否都大于根节点值 11 | const res = postorder.slice(i, len - 1).every(x => x > root); 12 | // 若否,直接返回false 13 | // 若是,递归判断左右子树 14 | return res 15 | ? verifyPostorder(postorder.slice(0, i)) && verifyPostorder(postorder.slice(i, len - 1)) 16 | : false; 17 | }; 18 | 19 | // 后序遍历,最后一个是根节点 20 | // 找到第一个大于根节点的位置i 21 | // 从i开始,判断i右边到根节点之间的元素,是否都大于根节点值,若否,直接返回false;若是,递归判断左右子树 22 | -------------------------------------------------------------------------------- /problems/14- II. 剪绳子 II.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 14- II. 剪绳子 II](https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/)」 4 | 5 | # 思路: 6 | 7 | 贪心思路: 8 | 9 | - 绳子长度大于`4`的时候,不断减去长度`3` 10 | - 直到绳子长度小于等于`4` 11 | - 最后所有段相乘,就是最大的乘积 12 | 13 | > 自己未经数学证明,有兴趣的小伙伴可自行查证。 14 | 15 | # 代码: 16 | 17 | ## JavaScript 18 | 19 | ```javascript 20 | const cuttingRope = n => { 21 | // 特殊情况处理 22 | const arr = [null, null, 1, 2, 4]; 23 | if (n <= 4) return arr[n]; 24 | const mod = 1000000007; 25 | let res = 1; 26 | while (n > 4) { 27 | // 每次减掉3 28 | res = (res * 3) % mod; 29 | n -= 3; 30 | } 31 | // 最后剩下一段小于等于4的长度 32 | res *= n; 33 | return res % mod; 34 | }; 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /problems/32 - I. 从上到下打印二叉树.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 32 - I. 从上到下打印二叉树](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/)」 4 | 5 | # 思路: 6 | 7 | 实际上就是树的**广度优先遍历**。 8 | 9 | 详情见注释。 10 | 11 | # 代码: 12 | 13 | ## JavaScript 14 | 15 | ```javascript 16 | const levelOrder = root => { 17 | if (!root) return []; 18 | // 创建队列 19 | const queue = [root]; 20 | const res = []; 21 | while (queue.length) { 22 | // 获取根节点,根节点出队 23 | const n = queue.shift(); 24 | // 访问队头 25 | res.push(n.val); 26 | // 队头的子节点依次入队 27 | n.left && queue.push(n.left); 28 | n.right && queue.push(n.right); 29 | } 30 | return res; 31 | }; 32 | ``` 33 | 34 | -------------------------------------------------------------------------------- /problems/53 - II. 0~n-1中缺失的数字.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 53 - II. 0~n-1中缺失的数字](https://leetcode-cn.com/problems/que-shi-de-shu-zi-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 有序数组 ——> 二分查找 8 | - `nums[mid] === mid`:左半边完整,缩小范围,开始找右半边 9 | - `nums[mid] !== mid`:左半边不完整,缩小范围,在左半边找 10 | 11 | # 代码: 12 | 13 | ## JavaScript 14 | 15 | ```javascript 16 | const missingNumber = nums => { 17 | let [low, high] = [0, nums.length - 1]; 18 | while (low <= high) { 19 | const mid = (low + high) >> 1; 20 | if (nums[mid] === mid) { 21 | // 左半边是完整的 22 | low = mid + 1; 23 | } else { 24 | // 左半边不完整 25 | high = mid - 1; 26 | } 27 | } 28 | return low; 29 | }; 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /code/47. 礼物的最大价值.js: -------------------------------------------------------------------------------- 1 | const maxValue = grid => { 2 | // 行、列 3 | const [m, n] = [grid.length, grid[0].length]; 4 | // 创建行m列n的二维数组 5 | const dp = new Array(m).fill(0).map(() => new Array(n).fill(0)); 6 | // (0,0)位置初始化 7 | dp[0][0] = grid[0][0]; 8 | // 第一列初始化 9 | for (let i = 1; i < m; i++) { 10 | dp[i][0] = dp[i - 1][0] + grid[i][0]; 11 | } 12 | // 第一行初始化 13 | for (let j = 1; j < n; j++) { 14 | dp[0][j] = dp[0][j - 1] + grid[0][j]; 15 | } 16 | // 遍历,完善dp数组 17 | for (let i = 1; i < m; i++) { 18 | for (let j = 1; j < n; j++) { 19 | dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; 20 | } 21 | } 22 | return dp[m - 1][n - 1]; 23 | }; 24 | -------------------------------------------------------------------------------- /problems/10- II. 青蛙跳台阶问题.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 10- II. 青蛙跳台阶问题](https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/)」 4 | 5 | # 思路: 6 | 7 | 第n阶的数量由前两阶的数量相加而来,故用动态规划。 8 | 9 | 1. dp[i]表示第i阶有dp[i]种方法 10 | 2. 递推公式:dp[i] = dp[i - 1] + dp[i - 2] 11 | 3. dp数组初始化:dp = [null, 1, 2],dp[0]没有意义,从i=3开始循环 12 | 4. 遍历顺序:从前往后 13 | 14 | # 代码: 15 | 16 | ## JavaScript 17 | 18 | 为什么不直接初始化`dp = [1, 1, 2]`? 19 | >dp[0]没有意义,宁可外加条件,也要统一dp数组的含义。 20 | 21 | ```javascript 22 | const numWays = n => { 23 | // 为了通过,强行加的条件 24 | if (n === 0) return 1; 25 | const dp = [null, 1, 2]; 26 | for (let i = 3; i <= n; i++) { 27 | dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007; 28 | } 29 | return dp[n]; 30 | }; 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /problems/35. 复杂链表的复制.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 35. 复杂链表的复制](https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 遍历旧链表,用`Map`复制各节点值。`Map`的**键**为旧节点,**值**为新节点的**值**。 8 | - 再次遍历旧链表,复制各节点的**连接关系** 9 | 10 | # 代码: 11 | 12 | ## JavaScript 13 | 14 | ```javascript 15 | const copyRandomList = head => { 16 | const map = new Map(); 17 | 18 | // 第一次遍历,复制节点值 19 | let p = head; 20 | while (p) { 21 | map.set(p, new Node(p.val)); 22 | p = p.next; 23 | } 24 | 25 | // 第二次遍历,复制节点关系 26 | p = head; 27 | while (p) { 28 | map.get(p).next = map.get(p.next) || null; 29 | map.get(p).random = map.get(p.random) || null; 30 | p = p.next; 31 | } 32 | 33 | return map.get(head); 34 | }; 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /code/32 - III. 从上到下打印二叉树 III.js: -------------------------------------------------------------------------------- 1 | const levelOrder = root => { 2 | if (!root) return []; 3 | const res = []; 4 | // 根节点入队 5 | const q = [root]; 6 | // 是否需要反转的标志位 7 | let flag = false; 8 | // 当队列还有值时,一直执行 9 | while (q.length) { 10 | let len = q.length; 11 | res.push([]); 12 | while (len--) { 13 | // 获取根节点,根节点出队 14 | const n = q.shift(); 15 | // 根节点加入res栈顶元素 16 | res[res.length - 1].push(n.val); 17 | // 队头左右节点入队 18 | n.left && q.push(n.left); 19 | n.right && q.push(n.right); 20 | } 21 | // 每隔一层,反转一次 22 | flag && res[res.length - 1].reverse(); 23 | flag = !flag; 24 | } 25 | return res; 26 | }; 27 | -------------------------------------------------------------------------------- /problems/21. 调整数组顺序使奇数位于偶数前面.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 21. 调整数组顺序使奇数位于偶数前面](https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 定义双指针 8 | - 左指针找偶数,右指针找奇数 9 | - 交换左右指针的值 10 | - 循环寻找 11 | 12 | # 代码: 13 | 14 | ## JavaScript 15 | 16 | ```javascript 17 | const exchange = nums => { 18 | // 定义双指针 19 | let [left, right] = [0, nums.length - 1]; 20 | while (left < right) { 21 | // 奇数,继续向右找,直到找到偶数 22 | while (left < right && nums[left] & 1) left++; 23 | // 偶数,继续向左找,直到找到奇数 24 | while (left < right && !(nums[right] & 1)) right--; 25 | // 交换奇偶数 26 | [nums[left], nums[right]] = [nums[right], nums[left]]; 27 | } 28 | return nums; 29 | }; 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /problems/26. 树的子结构.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 26. 树的子结构](https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/)」 4 | 5 | # 思路: 6 | 7 | 1. `A`空或`B`空,返回`false` 8 | 2. 递归判断`A`当前节点、`A`左子树、`A`右子树任意一个与`B`相同,就返回`true` 9 | 10 | # 代码: 11 | 12 | ## JavaScript 13 | 14 | ```javascript 15 | // 该递归函数是用来判断A当前节点和B是否相同 16 | const dfs = (A, B) => { 17 | // 递归出口 18 | if (!B) return true; 19 | // A空,B不为空,false 20 | if (!A) return false; 21 | return A.val === B.val && dfs(A.left, B.left) && dfs(A.right, B.right); 22 | }; 23 | 24 | const isSubStructure = (A, B) => { 25 | // 递归出口,A空或B空,false 26 | if (!A || !B) return false; 27 | // 递归判断A当前节点、A左子树、A右子树任意一个与B相同,就返回true 28 | return dfs(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B); 29 | }; 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /problems/55 - II. 平衡二叉树.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 55 - II. 平衡二叉树](https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof/)」 4 | 5 | # 思路: 6 | 7 | 在**求二叉树的深度**函数的代码上稍作修改即可: 8 | 9 | - 若左子树返回`-1`,或右子树返回`-1`,或者高度差的绝对值大于1,就返回`-1` 10 | - 否则就返回当前节点数的深度 11 | 12 | # 代码: 13 | 14 | ## JavaScript 15 | 16 | ```javascript 17 | const isBalanced = root => { 18 | // 如果返回-1,则该节点不平衡 19 | const check = node => { 20 | if (!node) return 0; 21 | const left = check(node.left); 22 | const right = check(node.right); 23 | if (left === -1 || right === -1 || Math.abs(left - right) > 1) { 24 | return -1; 25 | } 26 | return Math.max(left, right) + 1; 27 | }; 28 | // 若等于-1,则不是平衡二叉树 29 | return check(root) !== -1; 30 | }; 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /problems/54. 二叉搜索树的第k大节点.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 54. 二叉搜索树的第k大节点](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/)」 4 | 5 | # 思路: 6 | 7 | 二叉搜索树,是具有下列性质的二叉树: 8 | 1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 9 | 2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 10 | 3. 它的左、右子树也分别为二叉排序树。 11 | 12 | 所以,只要按照**右-根-左**的顺序遍历,就可以得到一个降序的数组,再拿到第`k-1`个元素即可。 13 | 14 | # 代码: 15 | 16 | ## JavaScript 17 | 18 | ```javascript 19 | const kthLargest = (root, k) => { 20 | // 遍历顺序(访问右节点在前):右-根-左 21 | const unInOrder = node => { 22 | if (!node) return; 23 | unInOrder(node.right); 24 | res.push(node.val); 25 | unInOrder(node.left); 26 | }; 27 | const res = []; 28 | unInOrder(root, res); 29 | // 得到从大到小排序的数组 30 | return res[k - 1]; 31 | }; 32 | ``` 33 | 34 | -------------------------------------------------------------------------------- /code/25. 合并两个排序的链表.js: -------------------------------------------------------------------------------- 1 | const mergeTwoLists = (l1, l2) => { 2 | // 定义一个虚拟节点,最后返回虚拟节点的下一个节点 3 | const res = new ListNode(0); 4 | // 定义p指向虚拟节点 5 | let p = res; 6 | // 定义p1,p2分别指向两个链表头部 7 | let [p1, p2] = [l1, l2]; 8 | // 当p1, p2都有值的时候 9 | while (p1 && p2) { 10 | // 如果p1指向的值小,则p指向p1的值 11 | // p1右移 12 | // 否则p指向p2的值,p2右移 13 | if (p1.val < p2.val) { 14 | p.next = p1; 15 | p1 = p1.next; 16 | } else { 17 | p.next = p2; 18 | p2 = p2.next; 19 | } 20 | // 记得p也要右移 21 | p = p.next; 22 | } 23 | // 到最后退出循环了,p1,p2肯定有且只有一个是null 24 | // 那么另一个不是null的还没有连接到p上 25 | // 把p再连接到不是null的那个 26 | p.next = p1 ? p1 : p2; 27 | // 返回虚拟节点的下一个节点 28 | return res.next; 29 | }; 30 | -------------------------------------------------------------------------------- /problems/31. 栈的压入、弹出序列.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 31. 栈的压入、弹出序列](https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/)」 4 | 5 | # 思路: 6 | 7 | 1. 新建另一个栈,`index=0`,,将`pushed`数组中的数,依次推入栈 8 | 2. 入栈后,`while`判断`popped[index]`与新建的栈栈顶元素是否相等 9 | 3. 若相等,则弹出栈顶,`index++` 10 | 4. 最后判断栈是否为空 11 | 12 | # 代码: 13 | 14 | ## JavaScript 15 | 16 | ```javascript 17 | const validateStackSequences = (pushed, popped) => { 18 | const stack = []; 19 | let index = 0; 20 | const len = pushed.length; 21 | for (let i = 0; i < len; i++) { 22 | stack.push(pushed[i]); 23 | while (popped[index] !== undefined && popped[index] === stack[stack.length - 1]) { 24 | stack.pop(); 25 | index++; 26 | } 27 | } 28 | return !stack.length; 29 | }; 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /code/21. 调整数组顺序使奇数位于偶数前面.js: -------------------------------------------------------------------------------- 1 | // 笨方法 2 | // const exchange = nums => { 3 | // const [odd, even] = [[], []]; 4 | // for (const num of nums) { 5 | // if (num % 2) { 6 | // odd.push(num); 7 | // } else { 8 | // even.push(num); 9 | // } 10 | // } 11 | // return [...odd, ...even]; 12 | // }; 13 | 14 | // 双指针 15 | const exchange = nums => { 16 | // 定义双指针 17 | let [left, right] = [0, nums.length - 1]; 18 | while (left < right) { 19 | // 奇数,继续向右找,直到找到偶数 20 | while (left < right && nums[left] & 1) left++; 21 | // 偶数,继续向左找,直到找到奇数 22 | while (left < right && !(nums[right] & 1)) right--; 23 | // 交换奇偶数 24 | [nums[left], nums[right]] = [nums[right], nums[left]]; 25 | } 26 | return nums; 27 | }; 28 | -------------------------------------------------------------------------------- /code/07. 重建二叉树.js: -------------------------------------------------------------------------------- 1 | const buildTree = (preorder, inorder) => { 2 | if (!preorder.length) return null; 3 | if (preorder === 1) return new TreeNode(preorder[0]); 4 | 5 | // 先序遍历的第一个就是根节点 6 | let rootVal = preorder[0]; 7 | // 找到根节点在中序遍历中的索引 8 | let index = inorder.indexOf(rootVal); 9 | 10 | // 先序左子树 11 | let preLeft = preorder.slice(1, index + 1); 12 | // 先序右子树 13 | let preRight = preorder.slice(index + 1); 14 | // 中序左子树 15 | let inLeft = inorder.slice(0, index); 16 | // 中序右子树 17 | let inRight = inorder.slice(index + 1); 18 | 19 | // 构建二叉树 20 | const node = new TreeNode(rootVal); 21 | // 递归构建左子树 22 | node.left = buildTree(preLeft, inLeft); 23 | // 递归构建右子树 24 | node.right = buildTree(preRight, inRight); 25 | 26 | return node; 27 | }; 28 | -------------------------------------------------------------------------------- /code/03. 数组中重复的数字.js: -------------------------------------------------------------------------------- 1 | // 使用Set判重 2 | // const findRepeatNumber = nums => { 3 | // const set = new Set(); 4 | // const len = nums.length; 5 | // for (let i = 0; i < len; i++) { 6 | // const num = nums[i]; 7 | // if (set.has(num)) return num; 8 | // set.add(num); 9 | // } 10 | // }; 11 | 12 | // 原地交换 13 | const findRepeatNumber = nums => { 14 | let i = 0; 15 | const len = nums.length; 16 | while (i < len) { 17 | // 若该数和下标相等,则i++,检查下一个数 18 | if (nums[i] === i) { 19 | i++; 20 | continue; 21 | } 22 | // 若该数作为下标对应的数,等于该数,说明重复了,返回 23 | if (nums[nums[i]] === nums[i]) return nums[i]; 24 | // 否则,一直交换该数和该数作为下标对应的数,一直到数等于下标为止 25 | [nums[nums[i]], nums[i]] = [nums[i], nums[nums[i]]]; 26 | } 27 | return -1; 28 | }; 29 | -------------------------------------------------------------------------------- /code/59 - II. 队列的最大值.js: -------------------------------------------------------------------------------- 1 | class MaxQueue { 2 | constructor() { 3 | this.queue = []; 4 | // 单调递减队列 5 | this.maxQueue = []; 6 | } 7 | max_value() { 8 | // 取出单调递减队列中的第一个元素 9 | return this.maxQueue.length ? this.maxQueue[0] : -1; 10 | } 11 | push_back(value) { 12 | this.queue.push(value); 13 | // 将单调递减队列中所有比value小的元素全部去除,保证其单调递减 14 | while (this.maxQueue.length && value > this.maxQueue[this.maxQueue.length - 1]) { 15 | this.maxQueue.pop(); 16 | } 17 | this.maxQueue.push(value); 18 | } 19 | pop_front() { 20 | if (!this.maxQueue.length) return -1; 21 | // 保证若queue中元素弹出,maxQueue中相同元素也被弹出 22 | if (this.queue[0] === this.maxQueue[0]) this.maxQueue.shift(); 23 | return this.queue.shift(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /problems/65. 不用加减乘除做加法.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 65. 不用加减乘除做加法](https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/)」 4 | 5 | # 思路: 6 | 7 | 对于给定的`a`和`b`: 8 | 9 | - 不考虑进位的加法:`a^b` 10 | - 进位:`(a & b) << 1` 11 | 12 | 那么不断地将**进位**再次作不考虑进位的加法运算,直到进位为`0`即可。 13 | 14 | ### 迭代 15 | 16 | ```javascript 17 | const add = (a, b) => { 18 | while (b) { 19 | // 进位 20 | const c = (a & b) << 1; 21 | // 不考虑进位的加法 22 | a ^= b; 23 | // 将进位赋值给b 24 | b = c; 25 | } 26 | return a; 27 | }; 28 | ``` 29 | 30 | ### 递归 31 | 32 | 也可以用递归实现: 33 | 34 | ```javascript 35 | const add = (a, b) => { 36 | if (!b) return a; 37 | return add(a ^ b, (a & b) << 1); 38 | }; 39 | ``` 40 | 41 | 简单一点也可以: 42 | 43 | ```javascript 44 | const add = (a, b) => (b ? add(a ^ b, (a & b) << 1) : a); 45 | ``` 46 | -------------------------------------------------------------------------------- /code/53 - I. 在排序数组中查找数字 I.js: -------------------------------------------------------------------------------- 1 | const search = (nums, target) => { 2 | // 定义上下限、找到的标志flag 3 | let [low, high, flag] = [0, nums.length - 1, null]; 4 | 5 | while (low <= high) { 6 | const mid = (low + high) >> 1; 7 | const midNum = nums[mid]; 8 | if (midNum > target) { 9 | high = mid - 1; 10 | } else if (midNum < target) { 11 | low = mid + 1; 12 | } else { 13 | // 如果找到了,将mid赋值给flag,存的是索引 14 | flag = mid; 15 | // 找到一个,直接退出循环 16 | break; 17 | } 18 | } 19 | // while结束后,判断是否找到,没找到直接返回0 20 | if (flag === null) return 0; 21 | 22 | // 从flag开始,向两边扩散 23 | low = high = flag; 24 | while (nums[low - 1] === target) low--; 25 | while (nums[high + 1] === target) high++; 26 | 27 | // 返回计数 28 | return high - low + 1; 29 | }; 30 | -------------------------------------------------------------------------------- /code/34. 二叉树中和为某一值的路径.js: -------------------------------------------------------------------------------- 1 | const backTrack = (node, targetSum, stack, sum, res) => { 2 | // 将节点入栈 3 | stack.push(node.val); 4 | // 节点加入到sum中 5 | sum += node.val; 6 | // 到达了叶子节点,并且当前记录的sum正好满足条件 7 | if (!node.left && !node.right && sum === targetSum) { 8 | // 将栈中的节点拷贝一份,推入返回结构res中 9 | // 若不拷贝的话,推入的是引用,stack改变,res里的stack也会改变 10 | res.push([...stack]); 11 | } 12 | // 如果存在左节点,继续遍历左子树,右子树同理 13 | node.left && backTrack(node.left, targetSum, stack, sum, res); 14 | node.right && backTrack(node.right, targetSum, stack, sum, res); 15 | // 向上回溯,stack弹出一个节点 16 | stack.pop(); 17 | }; 18 | 19 | const pathSum = (root, targetSum) => { 20 | // 存储结果的数组 21 | const res = []; 22 | // 回溯用的栈 23 | const stack = []; 24 | // 开始回溯 25 | root && backTrack(root, targetSum, stack, 0, res); 26 | return res; 27 | }; 28 | -------------------------------------------------------------------------------- /problems/57. 和为s的两个数字.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 57. 和为s的两个数字](https://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof/)」 4 | 5 | # 思路: 6 | 7 | 给的数字数组是升序排列的,故用双指针法求解: 8 | 9 | - 定义双指针分别指向数组头尾,即最小、最大的数 10 | - 判断左右指针对应的数之和,与目标数的关系 11 | - 若大于目标数,需要小一点,右指针左移 12 | - 若小于目标数,需要大一点,左指针右移 13 | - 若等于目标数,返回这两个数 14 | 15 | # 代码: 16 | 17 | ## JavaScript 18 | 19 | ```javascript 20 | const twoSum = (nums, target) => { 21 | let [left, right] = [0, nums.length - 1]; 22 | while (left < right) { 23 | sum = nums[left] + nums[right]; 24 | if (sum > target) { 25 | // 太大了,右指针左移 26 | right--; 27 | } else if (sum < target) { 28 | // 太小了,左指针右移 29 | left++; 30 | } else { 31 | // 找到,直接返回两个数 32 | return [nums[left], nums[right]]; 33 | } 34 | } 35 | // 遍历完后没找到,返回空数组 36 | return []; 37 | }; 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /problems/11. 旋转数组的最小数字.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 11. 旋转数组的最小数字](https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/)」 4 | 5 | # 思路: 6 | 7 | 1. 用二分查找即可 8 | 2. 若`mid`大于`high`的数,则最小值一定在`mid`右侧 9 | 3. 若`mid`小于`high`的数,则最小值有**两种可能**:(1)最小值在`mid`最侧(2)`mid`就是最小值 10 | 4. 若`mid`等于`high`的数,`high--` 11 | 5. 最后返回`low`所在的数 12 | 13 | # 代码: 14 | 15 | ## JavaScript 16 | 17 | ```javascript 18 | const minArray = numbers => { 19 | let [low, high] = [0, numbers.length - 1]; 20 | while (low <= high) { 21 | const mid = (low + high) >> 1; 22 | if (numbers[mid] > numbers[high]) { 23 | // 最小值一定在mid右侧 24 | low = mid + 1; 25 | } else if (numbers[mid] < numbers[high]) { 26 | // 最小值在mid左侧,或者mid就是最小值 27 | high = mid; 28 | } else { 29 | high--; 30 | } 31 | } 32 | return numbers[low]; 33 | }; 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /problems/27. 二叉树的镜像.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 27. 二叉树的镜像](https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 递归出口:当前节点为空,返回`null` 8 | - 递归中要做的事:交换左右节点 9 | - 将当前节点返回给上一级 10 | 11 | # 代码: 12 | 13 | ## JavaScript 14 | 15 | 写法1: 16 | 17 | ```javascript 18 | const mirrorTree = root => { 19 | // 递归出口 20 | if (!root) return null; 21 | // 交换当前左右节点 22 | [root.left, root.right] = [root.right, root.left]; 23 | // 递归交换左子树的左右节点 24 | mirrorTree(root.left); 25 | // 递归交换右子树的左右节点 26 | mirrorTree(root.right); 27 | // 返回当前节点给上一级 28 | return root; 29 | }; 30 | ``` 31 | 32 | 写法2: 33 | 34 | ```javascript 35 | const mirrorTree = root => { 36 | if (!root) return null; 37 | 38 | const node = new TreeNode(root.val); 39 | 40 | node.left = mirrorTree(root.right); 41 | node.right = mirrorTree(root.left); 42 | 43 | return node; 44 | }; 45 | ``` 46 | 47 | -------------------------------------------------------------------------------- /problems/32 - II. 从上到下打印二叉树 II.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 32 - II. 从上到下打印二叉树 II](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/)」 4 | 5 | # 思路: 6 | 7 | 其实就是树的广度优先遍历。 8 | 9 | 额外地,遍历到新的一层时,`push`一个空数组,当前层的元素都放入这个数组中,就可以按层打印了。 10 | 11 | # 代码: 12 | 13 | ## JavaScript 14 | 15 | ```javascript 16 | const levelOrder = root => { 17 | if (!root) return []; 18 | const res = []; 19 | // 根节点入队 20 | const q = [root]; 21 | // 当队列还有值时,一直执行 22 | while (q.length) { 23 | let len = q.length; 24 | res.push([]); 25 | while (len--) { 26 | // 获取根节点,根节点出队 27 | const n = q.shift(); 28 | // 根节点加入res栈顶元素 29 | res[res.length - 1].push(n.val); 30 | // 队头左右节点入队 31 | n.left && q.push(n.left); 32 | n.right && q.push(n.right); 33 | } 34 | } 35 | return res; 36 | }; 37 | ``` 38 | 39 | -------------------------------------------------------------------------------- /problems/46. 把数字翻译成字符串.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 46. 把数字翻译成字符串](https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/)」 4 | 5 | # 思路: 6 | 7 | 使用动态规划来解题: 8 | 9 | 1. `dp[i]`含义:`0~i-1`范围的数字,能翻译的个数 10 | 2. 递推公式:`i-1`和`i-2`若能被翻译,则`dp[i] = dp[i - 1] + dp[i - 2]`;否则,`dp[i] = dp[i - 1]` 11 | 3. `dp`数组初始化:`dp[0]`代表空字符串,当然只有1种。`dp[1]`代表`str[0]`,单个字符,当然也只有1种,所以`dp = [1, 1]` 12 | 4. 当前`dp[i]`依赖于之前项,所以从前向后遍历 13 | 14 | # 代码: 15 | 16 | ## JavaScript 17 | 18 | ```javascript 19 | const translateNum = num => { 20 | const str = `${num}`; 21 | const dp = [1, 1]; 22 | const len = str.length; 23 | for (let i = 2; i < len + 1; i++) { 24 | const preNum = parseInt(str[i - 2] + str[i - 1]); 25 | if (preNum >= 10 && preNum <= 25) { 26 | dp[i] = dp[i - 1] + dp[i - 2]; 27 | } else { 28 | dp[i] = dp[i - 1]; 29 | } 30 | } 31 | return dp[len]; 32 | }; 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- /problems/68 - II. 二叉树的最近公共祖先.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 68 - II. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 若根节点等于`p`或`q`,那么`root`是最近公共祖先 8 | - 向左右子树寻找节点相同的点 9 | - 若左右各找到一个,那么当前根节点就是最近公共祖先 10 | - 若只有左边找到,那么最近公共祖先在左边 11 | - 若只有右边找到,那么最近公共祖先在左边 12 | 13 | # 代码: 14 | 15 | ## JavaScript 16 | 17 | ```javascript 18 | const lowestCommonAncestor = (root, p, q) => { 19 | if (!root) return null; 20 | // 根节点等于p或q,那么root是最近公共祖先 21 | if (root === p || root === q) return root; 22 | // 向左子树寻找节点相同的点 23 | const left = lowestCommonAncestor(root.left, p, q); 24 | // 向右子树寻找节点相同的点 25 | const right = lowestCommonAncestor(root.right, p, q); 26 | // 若左右各找到一个,那么当前根节点就是最近公共祖先 27 | if (left && right) return root; 28 | // 只有左边找到,那么最近公共祖先在左边 29 | if (left) return left; 30 | // 只有右边找到,那么最近公共祖先在左边 31 | if (right) return right; 32 | }; 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- /code/42. 连续子数组的最大和.js: -------------------------------------------------------------------------------- 1 | // 动态规划: 2 | // 若前一个元素>0,则将其加到当前元素上 3 | // 最后返回最大的元素 4 | const maxSubArray = nums => { 5 | if (nums.length === 1) return nums[0]; 6 | let max = nums[0]; 7 | for (let i = 1; i < nums.length; i++) { 8 | // 如果前一个的累加和小于0,就不要累加了 9 | if (nums[i - 1] > 0) { 10 | nums[i] += nums[i - 1]; 11 | } 12 | // 更新max 13 | max = max > nums[i] ? max : nums[i]; 14 | } 15 | return max; 16 | }; 17 | 18 | // 贪心算法 19 | // 若当前元素之前的和<0,丢弃当前元素之前的数列 20 | // const maxSubArray = nums => { 21 | // let curSum = nums[0]; 22 | // let maxSum = nums[0]; 23 | // for (let i = 1; i < nums.length; i++) { 24 | // // 若当前元素之前的和<0,丢弃当前元素之前的数列 25 | // curSum = Math.max(nums[i], nums[i] + curSum); 26 | // // 将当前值与最大值比较,取最大 27 | // maxSum = Math.max(curSum, maxSum); 28 | // } 29 | // return maxSum; 30 | // }; 31 | 32 | // console.log(maxSubArray([-2, -1])); 33 | -------------------------------------------------------------------------------- /problems/66. 构建乘积数组.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 66. 构建乘积数组](https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof/)」 4 | 5 | # 思路: 6 | 7 | 1. 和**求前缀和**的思路一致,分别求出**左累积数组**和**右累积数组** 8 | 2. 两个数组的元素对应相乘,即可得到当前元素的答案 9 | 10 | # 代码: 11 | 12 | ## JavaScript 13 | 14 | ```javascript 15 | const constructArr = a => { 16 | const len = a.length; 17 | // 定义左累积数组、右累积数组,初始值都为 1 18 | const [resLeft, resRight] = [new Array(len).fill(1), new Array(len).fill(1)]; 19 | for (let i = 1; i < len; i++) { 20 | // 从i=1开始,作左累积运算 21 | resLeft[i] = resLeft[i - 1] * a[i - 1]; 22 | } 23 | for (let i = len - 2; i >= 0; i--) { 24 | // 从i=len - 2开始,作右累积运算 25 | resRight[i] = resRight[i + 1] * a[i + 1]; 26 | } 27 | const res = []; 28 | for (let i = 0; i < len; i++) { 29 | // 两个数组的元素对应相乘,即可得到当前元素的答案 30 | res.push(resLeft[i] * resRight[i]); 31 | } 32 | return res; 33 | }; 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /problems/57 - II. 和为s的连续正数序列.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 57 - II. 和为s的连续正数序列](https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 滑动窗口思路,窗口初始化为`[1, 2]`,初始`sum`为`3` 8 | - 因为输出的序列至少有`2`个数,所以若窗口第一个数大于`target/2`时,就不再继续了 9 | - 若`sum`太小,向窗口添加下一个数,更新`sum` 10 | - 若`sum`太大,弹出窗口第一个数,更新`sum` 11 | - 若`sum===target`,则将窗口的数放入`res`,随后弹出第一个数,继续滑动 12 | 13 | # 代码: 14 | 15 | ## JavaScript 16 | 17 | ```javascript 18 | const findContinuousSequence = target => { 19 | const res = []; 20 | const window = [1, 2]; 21 | let sum = 3; 22 | 23 | while (window[0] <= target >> 1) { 24 | if (sum < target) { 25 | const num = window[window.length - 1] + 1; 26 | sum += num; 27 | window.push(num); 28 | } else if (sum > target) { 29 | sum -= window.shift(); 30 | } else { 31 | res.push([...window]); 32 | sum -= window.shift(); 33 | } 34 | } 35 | return res; 36 | }; 37 | ``` 38 | 39 | -------------------------------------------------------------------------------- /problems/61. 扑克牌中的顺子.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 61. 扑克牌中的顺子](https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof/)」 4 | 5 | # 思路: 6 | 7 | 使用`Set`来判断数字是否重复,遍历`nums`,用两个变量维护牌的最大值和最小值,遍历时检查是否重复。最后判断最大值和最小值之差即可,具体如下: 8 | 9 | - 大小王能代替任何数字,遇到大小王,直接跳过本次循环 10 | - 更新最大值和最小值 11 | - 若遇到重复的牌,肯定不是顺子了,返回`false` 12 | - 最后判断最大值和最小值之差,若大于等于5,肯定不是顺子。因为已经去重,所以只要小于5,肯定是顺子。 13 | 14 | # 代码: 15 | 16 | ## JavaScript 17 | 18 | ```javascript 19 | const isStraight = nums => { 20 | const set = new Set(); 21 | // 初始化最大值,最小值 22 | let [max, min] = [0, 14]; 23 | for (const num of nums) { 24 | // 遇到王,跳过 25 | if (num === 0) continue; 26 | // 更新最大值 27 | max = Math.max(max, num); 28 | // 更新最小值 29 | min = Math.min(min, num); 30 | // 如果牌重复,肯定不是顺子 31 | if (set.has(num)) return false; 32 | // 牌加入set 33 | set.add(num); 34 | } 35 | // 除了大小王之外,若最大牌和最小牌之差小于5,则可以是顺子 36 | return max - min < 5; 37 | }; 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /code/29. 顺时针打印矩阵.js: -------------------------------------------------------------------------------- 1 | const spiralOrder = matrix => { 2 | const res = []; 3 | 4 | // 定义行、列 5 | // 可能是空数组,所以加一个'?' 6 | const [m, n] = [matrix.length, matrix[0]?.length]; 7 | if (!m || !n) return res; 8 | 9 | // 初始化:左、右、上、下 10 | let [left, right, up, down] = [0, n - 1, 0, m - 1]; 11 | 12 | while (1) { 13 | // 访问上边,从左到右,访问完毕后,up++ 14 | for (let j = left; j <= right; j++) res.push(matrix[up][j]); 15 | up++; 16 | // 若up比down大,说明都访问完了,退出 17 | if (up > down) break; 18 | 19 | // 以下同理 20 | for (let i = up; i <= down; i++) res.push(matrix[i][right]); 21 | right--; 22 | if (right < left) break; 23 | 24 | for (let j = right; j >= left; j--) res.push(matrix[down][j]); 25 | down--; 26 | if (down < up) break; 27 | 28 | for (let i = down; i >= up; i--) res.push(matrix[i][left]); 29 | left++; 30 | if (left > right) break; 31 | } 32 | 33 | return res; 34 | }; 35 | -------------------------------------------------------------------------------- /problems/30. 包含min函数的栈.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 30. 包含min函数的栈](https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof/)」 4 | 5 | # 思路: 6 | 7 | - `pop()`:正常弹出顶部元素 8 | - `top()`:正常获取顶部元素 9 | - `push()`:每次`push`的是一个对象,对象上有两个属性:`val`和`min`。`val`就是当前元素的值,`min`是当前栈中的最小值。每次`push()`,相应的`min`都重新计算,保证栈顶元素的`min`属性,一直都是最小值。 10 | - 若栈空,min就是val 11 | - 若栈不空,min是栈顶元素的min属性和当前val的较小者 12 | - `getMin()`:返回栈顶元素的`min`属性即可,为常数时间。 13 | 14 | # 代码: 15 | 16 | ## JavaScript 17 | 18 | ```javascript 19 | class MinStack { 20 | constructor() { 21 | this.stack = []; 22 | } 23 | push(x) { 24 | this.stack.push({ 25 | val: x, 26 | min: this.stack.length ? Math.min(x, this.min()) : x, 27 | }); 28 | } 29 | pop() { 30 | this.stack.pop(); 31 | } 32 | top() { 33 | return this.stack[this.stack.length - 1].val; 34 | } 35 | min() { 36 | return this.stack[this.stack.length - 1].min; 37 | } 38 | } 39 | ``` 40 | 41 | -------------------------------------------------------------------------------- /code/05. 替换空格.js: -------------------------------------------------------------------------------- 1 | const replaceSpace = s => { 2 | // 转化为数组 3 | const arr = s.split(''); 4 | // 统计空格数量 5 | let spaceCount = 0; 6 | const len = arr.length; 7 | for (let i = 0; i < len; i++) { 8 | if (arr[i] === ' ') spaceCount++; 9 | } 10 | // 更新数组长度,每个空格,要多出2个位置 11 | arr.length += 2 * spaceCount; 12 | // 定义双指针 13 | // left:旧长度-1 14 | // right:新长度-1 15 | let [left, right] = [len - 1, arr.length - 1]; 16 | while (left >= 0) { 17 | if (arr[left] !== ' ') { 18 | // 如果遇到字符,直接将左指针的值赋给右指针 19 | arr[right] = arr[left]; 20 | } else { 21 | // 遇到空格,依次填入%20,右指针左移2 22 | arr[right - 2] = '%'; 23 | arr[right - 1] = '2'; 24 | arr[right] = '0'; 25 | right -= 2; 26 | } 27 | // 分别左移1 28 | left--; 29 | right--; 30 | } 31 | // 返回字符串 32 | return arr.join(''); 33 | }; 34 | 35 | // const replaceSpace = s => s.split(' ').join('%20'); 36 | -------------------------------------------------------------------------------- /problems/48. 最长不含重复字符的子字符串.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 48. 最长不含重复字符的子字符串](https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/)」 4 | 5 | # 思路: 6 | 7 | 遍历字符串,循环内进行如下操作 8 | 1. 当前字符在窗口中的索引为`index` 9 | 2. 若当前字符在窗口中出现过,那么就将窗口中`[0,index]`区间的全部去除,从`index+1`开始新一轮的窗口 10 | 3. 当前字符加入窗口 11 | 4. 更新最大值 12 | 13 | # 代码: 14 | 15 | ## JavaScript 16 | 17 | ```javascript 18 | const lengthOfLongestSubstring = s => { 19 | // 滑动窗口 20 | const window = []; 21 | let max = 0; 22 | const len = s.length; 23 | for (let i = 0; i < len; i++) { 24 | // 当前字符在窗口中的索引 25 | const index = window.indexOf(s[i]); 26 | if (index !== -1) { 27 | // 当前字符在窗口中出现过,那么就将窗口中[0,index]区间的全部去除 28 | // 从index+1开始新一轮的窗口 29 | window.splice(0, index + 1); 30 | } 31 | // 当前字符加入窗口 32 | window.push(s[i]); 33 | // 更新最大值 34 | if (max < window.length) max = window.length; 35 | } 36 | return max; 37 | }; 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /code/12. 矩阵中的路径.js: -------------------------------------------------------------------------------- 1 | const exist = (board, word) => { 2 | const [m, n] = [board.length, board[0].length]; 3 | 4 | const dfs = (i, j, index) => { 5 | // 越界、或者字符不匹配 6 | if (i < 0 || i >= m || j < 0 || j >= n || board[i][j] !== word[index]) return false; 7 | // 索引等于单词长度-1,说明全匹配上了 8 | if (index === word.length - 1) return true; 9 | // 保存当前字符 10 | const temp = board[i][j]; 11 | // 将当前字符设置为空,防止四个方向dfs再次遍历到 12 | board[i][j] = ''; 13 | // 四个方向遍历 14 | const res = 15 | dfs(i + 1, j, index + 1) || 16 | dfs(i, j + 1, index + 1) || 17 | dfs(i - 1, j, index + 1) || 18 | dfs(i, j - 1, index + 1); 19 | // 恢复当前字符 20 | board[i][j] = temp; 21 | return res; 22 | }; 23 | 24 | // 从第一个匹配的字符处开始dfs 25 | for (let i = 0; i < m; i++) { 26 | for (let j = 0; j < n; j++) { 27 | if (dfs(i, j, 0)) return true; 28 | } 29 | } 30 | 31 | return false; 32 | }; 33 | -------------------------------------------------------------------------------- /problems/14- I. 剪绳子.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 14- I. 剪绳子](https://leetcode-cn.com/problems/jian-sheng-zi-lcof/)」 4 | 5 | # 思路: 6 | 7 | 使用动态规划解题: 8 | - `dp[i]`含义:拆分数字`i`,可得到最大的乘积为`dp[i]` 9 | - 递推公式:对于某一个`i`,用`j`从1遍历到`i-1`: 10 | - `(i-j)*j`表示拆分成2个数 11 | - `dp[i-j]*j`表示拆分成2个及以上的数 12 | - 因为`j`在遍历的过程中,会算出很多`dp[i]`,取三者最大的 13 | - 所以`dp[i] = Math.max(dp[i], (i - j) * j, dp[i - j] * j)` 14 | - dp[i]初始化:`n=0`和`n=1`没有意义,`dp[2]=1`,所以直接初始化`dp = [null, null, 1]` 15 | - 从前向后遍历 16 | 17 | # 代码: 18 | 19 | ## JavaScript 20 | 21 | ```javascript 22 | const cuttingRope = n => { 23 | const dp = [null, null, 1]; 24 | for (let i = 3; i <= n; i++) { 25 | // 当前dp[i]初始化为0 26 | dp[i] = 0; 27 | for (let j = 1; j <= i - 1; j++) { 28 | // 因为j在遍历的过程中,会算出很多dp[i],取最大的 29 | // (i - j) * j表示拆分成2个数 30 | // dp[i - j] * j表示拆分成2个及以上的数 31 | dp[i] = Math.max(dp[i], (i - j) * j, dp[i - j] * j); 32 | } 33 | } 34 | return dp[n]; 35 | }; 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /problems/38. 字符串的排列.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 38. 字符串的排列](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/)」 4 | 5 | # 思路: 6 | 7 | 用递归来写: 8 | 9 | - 递归出口:当前字符串长度小于等于`1` 10 | - 当前递归要做的事:依次去掉字符串一个字符,得到剩余字符串的全排列,将去掉的字符与剩余字符串的全排列拼接 11 | - 返回上一级的内容:将拼接的所有字符放入数组,并去重,返回这个数组给上一级 12 | 13 | # 代码: 14 | 15 | ## JavaScript 16 | 17 | ```javascript 18 | const permutation = s => { 19 | if (s.length === 0) return ['']; 20 | // 递归出口 21 | if (s.length === 1) return [s]; 22 | const res = []; 23 | const len = s.length; 24 | for (let i = 0; i < len; i++) { 25 | // 取出一个字符为char 26 | const char = s[i]; 27 | // newStr=去掉char后剩下的字符 28 | let newStr = s.slice(0, i) + s.slice(i + 1); 29 | // 递归产生newStr的全排列,返回的是数组 30 | const next = permutation(newStr); 31 | // 将char与newStr的全排列拼接,放入res 32 | next.forEach(item => { 33 | res.push(char + item); 34 | }); 35 | } 36 | // 去重 37 | return [...new Set(res)]; 38 | }; 39 | ``` 40 | 41 | -------------------------------------------------------------------------------- /code/13. 机器人的运动范围.js: -------------------------------------------------------------------------------- 1 | // 判断当前格子是否能过的函数 2 | const isTrue = (m, n, k) => { 3 | let [sum1, sum2] = [0, 0]; 4 | while (m !== 0) { 5 | sum1 += m % 10; 6 | m = Math.floor(m / 10); 7 | } 8 | while (n !== 0) { 9 | sum2 += n % 10; 10 | n = Math.floor(n / 10); 11 | } 12 | return sum1 + sum2 <= k; 13 | }; 14 | 15 | const movingCount = (m, n, k) => { 16 | // 构建已访问的数组,初始全部为false 17 | const visited = new Array(m).fill(0).map(() => new Array(n).fill(false)); 18 | let res = 0; 19 | const dfs = (i, j) => { 20 | // 若越界,或遇到已访问过的,return 21 | if (i < 0 || j < 0 || i >= m || j >= n || visited[i][j]) return; 22 | // 标记访问 23 | visited[i][j] = true; 24 | if (isTrue(i, j, k)) { 25 | // 如果当前格子能访问,计数+1,并向四个方向继续访问 26 | res++; 27 | dfs(i + 1, j); 28 | dfs(i, j + 1); 29 | dfs(i - 1, j); 30 | dfs(i, j - 1); 31 | } 32 | }; 33 | dfs(0, 0); 34 | return res; 35 | }; 36 | -------------------------------------------------------------------------------- /problems/04. 二维数组中的查找.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 04. 二维数组中的查找](https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/)」 4 | 5 | # 思路: 6 | 7 | 类似**二分查找**的做法: 8 | 9 | 1. 从**左下角**开始寻找,因为左下角的元素是当前行最小的、当前列最大的 10 | 2. 比较元素,如果太大了,上移一行 11 | 3. 如果太小了,右移一列 12 | 4. 找到就返回`true` 13 | 5. 遍历完,返回`false` 14 | 15 | # 代码: 16 | 17 | ## JavaScript 18 | 19 | ```javascript 20 | const findNumberIn2DArray = (matrix, target) => { 21 | const [m, n] = [matrix.length, matrix[0]?.length]; 22 | if (!m) return false; 23 | // 定义左下角的坐标 24 | let [row, col] = [m - 1, 0]; 25 | // 坐标在矩阵内,就一直寻找 26 | while (row >= 0 && col <= n - 1) { 27 | // 当前元素 28 | const item = matrix[row][col]; 29 | if (item === target) { 30 | // 找到,返回true 31 | return true; 32 | } else if (item > target) { 33 | // 太大了,上移一行 34 | row--; 35 | } else { 36 | // 太小了,右移一列 37 | col++; 38 | } 39 | } 40 | return false; 41 | }; 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /problems/33. 二叉搜索树的后序遍历序列.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 33. 二叉搜索树的后序遍历序列](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/)」 4 | 5 | # 思路: 6 | 7 | 此题中输入的数组的任意两个数字都互不相同,所以此题的二叉搜索树的左子树节点都**小于**根节点,右子树节点都**大于**根节点。 8 | 9 | - 后序遍历,最后一个是根节点 10 | - 找到第一个大于根节点的位置`i` 11 | - 从`i`开始,判断`i`右边到根节点之间的元素,是否都大于根节点值,若否,直接返回`false`;若是,递归判断左右子树 12 | 13 | # 代码: 14 | 15 | ## JavaScript 16 | 17 | ```javascript 18 | const verifyPostorder = postorder => { 19 | const len = postorder.length; 20 | // 递归出口 21 | if (len <= 1) return true; 22 | // 后序遍历,最后一个是根节点 23 | const root = postorder[len - 1]; 24 | // 找到第一个大于根节点的位置i 25 | let i = 0; 26 | while (postorder[i] < root) i++; 27 | // 从i开始,判断i右边到根节点之间的元素,也就是右子树是否都大于根节点值 28 | const res = postorder.slice(i, len - 1).every(x => x > root); 29 | // 若否,直接返回false 30 | // 若是,递归判断左右子树 31 | return res 32 | ? verifyPostorder(postorder.slice(0, i)) && verifyPostorder(postorder.slice(i, len - 1)) 33 | : false; 34 | }; 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /problems/42. 连续子数组的最大和.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 42. 连续子数组的最大和](https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/)」 4 | 5 | # 思路: 6 | 7 | ## 动态规划 8 | 1. 若前一个元素大于`0`,则将其加到当前元素上 9 | 2. 边遍历边更新`max` 10 | 11 | ```javascript 12 | const maxSubArray = nums => { 13 | if (nums.length === 1) return nums[0]; 14 | let max = nums[0]; 15 | for (let i = 1; i < nums.length; i++) { 16 | // 如果前一个的累加和小于0,就不要累加了 17 | if (nums[i - 1] > 0) { 18 | nums[i] += nums[i - 1]; 19 | } 20 | // 更新max 21 | max = max > nums[i] ? max : nums[i]; 22 | } 23 | return max; 24 | }; 25 | ``` 26 | 27 | ## 贪心算法 28 | ```javascript 29 | const maxSubArray = nums => { 30 | let curSum = nums[0]; 31 | let maxSum = nums[0]; 32 | for (let i = 1; i < nums.length; i++) { 33 | // 若当前元素之前的和<0,丢弃当前元素之前的和 34 | curSum = Math.max(nums[i], nums[i] + curSum); 35 | // 将当前值与最大值比较,取最大 36 | maxSum = Math.max(curSum, maxSum); 37 | } 38 | return maxSum; 39 | }; 40 | ``` -------------------------------------------------------------------------------- /problems/36. 二叉搜索树与双向链表.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 36. 二叉搜索树与双向链表](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/)」 4 | 5 | # 思路: 6 | 7 | 1. 因为是二叉搜索树,采用**中序遍历**刚好满足顺序条件 8 | 2. 中序遍历时,先判断`pre`是否有值,若无值,则表示当前是表头节点,赋值为`head`;若有值,更新指针,双向连接(`pre.right = cur;cur.left = pre;`) 9 | 3. 更新`pre`,将当前节点作为一下个节点的上一个节点 10 | 4. 最后要将头部节点和尾部节点相连 11 | 12 | # 代码: 13 | 14 | ## JavaScript 15 | 16 | ```javascript 17 | const treeToDoublyList = root => { 18 | const dfs = cur => { 19 | if (!cur) return; 20 | dfs(cur.left); 21 | if (!pre) { 22 | // pre为空,正在访问表头节点,赋为head 23 | head = cur; 24 | } else { 25 | // pre有值,更新指针,双向连接 26 | pre.right = cur; 27 | cur.left = pre; 28 | } 29 | // 更新pre,将当前节点作为一下个节点的上一个节点 30 | pre = cur; 31 | dfs(cur.right); 32 | }; 33 | let pre, head; 34 | if (!root) return; 35 | dfs(root); 36 | // 首尾相连 37 | head.left = pre; 38 | pre.right = head; 39 | return head; 40 | }; 41 | ``` 42 | 43 | -------------------------------------------------------------------------------- /problems/32 - III. 从上到下打印二叉树 III.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 32 - III. 从上到下打印二叉树 III](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/)」 4 | 5 | # 思路: 6 | 7 | 这题和二叉树按层打印的思路一样,就是多了几行代码。 8 | 9 | 需要定义一个是否反转的标志位`flag`,每隔一层,反转一次即可。 10 | 11 | # 代码: 12 | 13 | ## JavaScript 14 | 15 | ```javascript 16 | const levelOrder = root => { 17 | if (!root) return []; 18 | const res = []; 19 | // 根节点入队 20 | const q = [root]; 21 | // 是否需要反转的标志位 22 | let flag = false; 23 | // 当队列还有值时,一直执行 24 | while (q.length) { 25 | let len = q.length; 26 | res.push([]); 27 | while (len--) { 28 | // 获取根节点,根节点出队 29 | const n = q.shift(); 30 | // 根节点加入res栈顶元素 31 | res[res.length - 1].push(n.val); 32 | // 队头左右节点入队 33 | n.left && q.push(n.left); 34 | n.right && q.push(n.right); 35 | } 36 | // 每隔一层,反转一次 37 | flag && res[res.length - 1].reverse(); 38 | flag = !flag; 39 | } 40 | return res; 41 | }; 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /problems/22. 链表中倒数第k个节点.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 22. 链表中倒数第k个节点](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/)」 4 | 5 | # 思路: 6 | 7 | 1. 定义快慢指针,初始都指向**链表头部** 8 | 2. **快指针**先走`k`步 9 | 3. 再两指针一起走,直到**快指针**走到头为止 10 | 4. 此时的**慢指针**指向的就是倒数第`k`个节点 11 | 5. 返回**慢指针**即可 12 | 13 | **举例**: 14 | 15 | 以官方示例为例: 16 | 17 | 链表`1->2->3->4->5`,和`k = 2`。 18 | 19 | 首先定义快慢指针,快指针先走`2`步。 20 | 21 | ![](https://jack-img.oss-cn-hangzhou.aliyuncs.com/img/20211027122302.png) 22 | 23 | 再两指针一起走,直到**快指针**走到头为止。 24 | 25 | ![](https://jack-img.oss-cn-hangzhou.aliyuncs.com/img/20211027122314.png) 26 | 27 | 此时的**慢指针**指向的就是倒数第`k`个节点。注意题目规定链表的尾节点是倒数第`1`个节点。 28 | 29 | # 代码: 30 | 31 | ## JavaScript 32 | 33 | ```javascript 34 | const getKthFromEnd = (head, k) => { 35 | let fast, slow; 36 | fast = slow = head; 37 | while (k--) { 38 | // 快指针先走k步 39 | fast = fast.next; 40 | } 41 | while (fast) { 42 | // 再一起走,知道快指针走到头 43 | fast = fast.next; 44 | slow = slow.next; 45 | } 46 | // 此时的慢指针指的就是倒数第k个 47 | return slow; 48 | }; 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /problems/25. 合并两个排序的链表.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 25. 合并两个排序的链表](https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 思路都在注释中,稍微画一下图就懂了 8 | - 定义一个虚拟节点,最后返回虚拟节点的下一个节点 9 | - 定义`p1`,`p2`分别指向两个链表头部,一起遍历 10 | - `p1`和`p2`哪个对应的值小,哪个就连接到答案链表 11 | 12 | # 代码: 13 | 14 | ## JavaScript 15 | 16 | ```javascript 17 | const mergeTwoLists = (l1, l2) => { 18 | // 定义一个虚拟节点,最后返回虚拟节点的下一个节点 19 | const res = new ListNode(0); 20 | // 定义p指向虚拟节点 21 | let p = res; 22 | // 定义p1,p2分别指向两个链表头部 23 | let [p1, p2] = [l1, l2]; 24 | // 当p1, p2都有值的时候 25 | while (p1 && p2) { 26 | // 如果p1指向的值小,则p指向p1的值 27 | // p1右移 28 | // 否则p指向p2的值,p2右移 29 | if (p1.val < p2.val) { 30 | p.next = p1; 31 | p1 = p1.next; 32 | } else { 33 | p.next = p2; 34 | p2 = p2.next; 35 | } 36 | // 记得p也要右移 37 | p = p.next; 38 | } 39 | // 到最后退出循环了,p1,p2肯定有且只有一个是null 40 | // 那么另一个不是null的还没有连接到p上 41 | // 把p再连接到不是null的那个 42 | p.next = p1 ? p1 : p2; 43 | // 返回虚拟节点的下一个节点 44 | return res.next; 45 | }; 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /problems/09. 用两个栈实现队列.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 09. 用两个栈实现队列](https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/)」 4 | 5 | # 思路: 6 | 7 | 两个栈实现队列,所以只能用`push`和`pop`方法。 8 | 9 | 使用`A`来进行添加的操作,使用`B`来进行辅助 10 | 11 | - 添加元素时,将元素`push`进`A` 12 | - 删除元素时,先看`B`中是否有值,若有值,直接`pop`并返回 13 | - 若`B`中无值,将`A`中元素全部`pop`出,并`push`进`B` 14 | - `B`在`pop`出一个元素即可,若`B`中无元素,返回`-1` 15 | 16 | 这样才能满足最先`push`进`A`的元素,在`B`的最顶层,所以先从`B`中`pop`出,满足**先入先出**特性。 17 | 18 | # 代码: 19 | 20 | ## JavaScript 21 | 22 | ```javascript 23 | class CQueue { 24 | constructor() { 25 | this.stackA = []; 26 | this.stackB = []; 27 | } 28 | appendTail(value) { 29 | this.stackA.push(value); 30 | } 31 | deleteHead() { 32 | if (this.stackB.length) { 33 | return this.stackB.pop(); 34 | } else { 35 | while (this.stackA.length) { 36 | this.stackB.push(this.stackA.pop()); 37 | } 38 | if (!this.stackB.length) { 39 | return -1; 40 | } else { 41 | return this.stackB.pop(); 42 | } 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /code/39. 数组中出现次数超过一半的数字.js: -------------------------------------------------------------------------------- 1 | // 摩尔投票 2 | const majorityElement = nums => { 3 | let count = 1; 4 | let majority = nums[0]; 5 | for (let i = 1; i < nums.length; i++) { 6 | if (count === 0) { 7 | majority = nums[i]; 8 | } 9 | if (nums[i] === majority) { 10 | count++; 11 | } else { 12 | count--; 13 | } 14 | } 15 | return majority; 16 | }; 17 | 18 | // 栈抵消 19 | // const majorityElement = nums => { 20 | // let stack = [nums[0]]; 21 | // for (let i = 1; i < nums.length; i++) { 22 | // // 如果栈空,则直接入栈,跳过此轮循环 23 | // if (stack.length === 0) { 24 | // stack.push(nums[i]); 25 | // continue; 26 | // } 27 | 28 | // if (stack[stack.length - 1] === nums[i]) { 29 | // // 相等入栈 30 | // stack.push(nums[i]); 31 | // } else { 32 | // // 不相等出栈 33 | // stack.pop(); 34 | // } 35 | // } 36 | // // 返回栈顶元素 37 | // return stack[stack.length - 1]; 38 | // }; 39 | 40 | // 排序后取中值 41 | const majorityElement = nums => { 42 | nums.sort((a, b) => a - b); 43 | return nums[nums.length >> 1]; 44 | }; 45 | -------------------------------------------------------------------------------- /problems/53 - I. 在排序数组中查找数字 I.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 53 - I. 在排序数组中查找数字 I](https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/)」 4 | 5 | # 思路: 6 | 7 | 已经排序好的数组,推荐使用**二分搜索**。 8 | 9 | 先用二分搜索找到目标数的一个索引,再从两边扩散,统计数量。 10 | 11 | 若二分查找没找到目标数,直接返回`0`。 12 | 13 | # 代码: 14 | 15 | ## JavaScript 16 | 17 | ```javascript 18 | const search = (nums, target) => { 19 | // 定义上下限、找到的标志flag 20 | let [low, high, flag] = [0, nums.length - 1, null]; 21 | 22 | while (low <= high) { 23 | const mid = (low + high) >> 1; 24 | const midNum = nums[mid]; 25 | if (midNum > target) { 26 | high = mid - 1; 27 | } else if (midNum < target) { 28 | low = mid + 1; 29 | } else { 30 | // 如果找到了,将mid赋值给flag,存的是索引 31 | flag = mid; 32 | // 找到一个,直接退出循环 33 | break; 34 | } 35 | } 36 | // while结束后,判断是否找到,没找到直接返回0 37 | if (flag === null) return 0; 38 | 39 | // 从flag开始,向两边扩散 40 | low = high = flag; 41 | while (nums[low - 1] === target) low--; 42 | while (nums[high + 1] === target) high++; 43 | 44 | // 返回计数 45 | return high - low + 1; 46 | }; 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /problems/59 - II. 队列的最大值.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 59 - II. 队列的最大值](https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/)」 4 | 5 | # 思路: 6 | 7 | 创建另一个保存最大值的**单调递减队列**: 8 | - `push_back`:将单调递减队列中所有比`value`小的元素全部去除,保证其**单调递减**。 9 | - `pop_front`:保证若`queue`中元素弹出,`maxQueue`中相同元素也被弹出。 10 | - `max_value`:取出单调递减队列中的第一个元素。 11 | 12 | # 代码: 13 | 14 | ## JavaScript 15 | 16 | ```javascript 17 | class MaxQueue { 18 | constructor() { 19 | this.queue = []; 20 | this.maxQueue = []; 21 | } 22 | max_value() { 23 | // 取出单调递减队列中的第一个元素 24 | return this.maxQueue.length ? this.maxQueue[0] : -1; 25 | } 26 | push_back(value) { 27 | this.queue.push(value); 28 | // 将单调递减队列中所有比value小的元素全部去除,保证其单调递减 29 | while (this.maxQueue.length && value > this.maxQueue[this.maxQueue.length - 1]) { 30 | this.maxQueue.pop(); 31 | } 32 | this.maxQueue.push(value); 33 | } 34 | pop_front() { 35 | if (!this.maxQueue.length) return -1; 36 | // 保证若queue中元素弹出,maxQueue中相同元素也被弹出 37 | if (this.queue[0] === this.maxQueue[0]) this.maxQueue.shift(); 38 | return this.queue.shift(); 39 | } 40 | } 41 | ``` 42 | 43 | -------------------------------------------------------------------------------- /problems/03. 数组中重复的数字.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 03. 数组中重复的数字](https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/)」 4 | 5 | # 思路: 6 | 7 | ## 使用Set 8 | 9 | 1. 创建`Set` 10 | 2. 遍历数组,判断当前元素是否在`Set`中出现过,若出现过,则直接返回该元素 11 | 3. 若没出现过,将当前元素加入到`Set` 12 | 13 | ```javascript 14 | const findRepeatNumber = nums => { 15 | const set = new Set(); 16 | const len = nums.length; 17 | for (let i = 0; i < len; i++) { 18 | const num = nums[i]; 19 | if (set.has(num)) return num; 20 | set.add(num); 21 | } 22 | }; 23 | ``` 24 | 25 | ## 原地交换 26 | 27 | - 从头遍历数组,若发现元素值和下标`i`不相等的,将该元素与下标`i`对应的数交换,直到相等为止。 28 | - 若发现元素值与下标i对应的数相等,则重复,返回元素值。 29 | 30 | ```javascript 31 | const findRepeatNumber = nums => { 32 | let i = 0; 33 | const len = nums.length; 34 | while (i < len) { 35 | // 若该数和下标相等,则i++,检查下一个数 36 | if (nums[i] === i) { 37 | i++; 38 | continue; 39 | } 40 | // 若该数作为下标对应的数,等于该数,说明重复了,返回 41 | if (nums[nums[i]] === nums[i]) return nums[i]; 42 | // 否则,一直交换该数和该数作为下标对应的数,一直到数等于下标为止 43 | [nums[nums[i]], nums[i]] = [nums[i], nums[nums[i]]]; 44 | } 45 | return -1; 46 | }; 47 | ``` 48 | 49 | 50 | -------------------------------------------------------------------------------- /problems/34. 二叉树中和为某一值的路径.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 34. 二叉树中和为某一值的路径](https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 用一个栈记录走过的路径 8 | - 每访问一个节点,将节点值入栈,并记录当前的`sum`总数 9 | - 若到达了叶子节点,并且记录的`sum`满足条件,将栈中的节点推入答案 10 | - 若存在子节点,继续遍历子节点 11 | - 向上回溯,路径栈弹出顶部元素 12 | 13 | # 代码: 14 | 15 | ## JavaScript 16 | 17 | ```javascript 18 | const backTrack = (node, targetSum, stack, sum, res) => { 19 | // 将节点入栈 20 | stack.push(node.val); 21 | // 节点加入到sum中 22 | sum += node.val; 23 | // 到达了叶子节点,并且当前记录的sum正好满足条件 24 | if (!node.left && !node.right && sum === targetSum) { 25 | // 将栈中的节点拷贝一份,推入返回结构res中 26 | // 若不拷贝的话,推入的是引用,stack改变,res里的stack也会改变 27 | res.push([...stack]); 28 | } 29 | // 如果存在左节点,继续遍历左子树,右子树同理 30 | node.left && backTrack(node.left, targetSum, stack, sum, res); 31 | node.right && backTrack(node.right, targetSum, stack, sum, res); 32 | // 向上回溯,stack弹出一个节点 33 | stack.pop(); 34 | }; 35 | 36 | const pathSum = (root, targetSum) => { 37 | // 存储结果的数组 38 | const res = []; 39 | // 回溯用的栈 40 | const stack = []; 41 | // 开始回溯 42 | root && backTrack(root, targetSum, stack, 0, res); 43 | return res; 44 | }; 45 | ``` 46 | 47 | -------------------------------------------------------------------------------- /problems/29. 顺时针打印矩阵.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 29. 顺时针打印矩阵](https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 首先要判断空数组,返回`[]` 8 | - 无限循环,访问四条边,若越界,则说明访问完了,退出 9 | 10 | # 代码: 11 | 12 | ## JavaScript 13 | 14 | ```javascript 15 | const spiralOrder = matrix => { 16 | const res = []; 17 | 18 | // 定义行、列 19 | // 可能是空数组,所以加一个'?' 20 | const [m, n] = [matrix.length, matrix[0]?.length]; 21 | if (!m || !n) return res; 22 | 23 | // 初始化:左、右、上、下 24 | let [left, right, up, down] = [0, n - 1, 0, m - 1]; 25 | 26 | while (1) { 27 | // 访问上边,从左到右,访问完毕后,up++ 28 | for (let j = left; j <= right; j++) res.push(matrix[up][j]); 29 | up++; 30 | // 若up比down大,说明都访问完了,退出 31 | if (up > down) break; 32 | 33 | // 以下同理 34 | for (let i = up; i <= down; i++) res.push(matrix[i][right]); 35 | right--; 36 | if (right < left) break; 37 | 38 | for (let j = right; j >= left; j--) res.push(matrix[down][j]); 39 | down--; 40 | if (down < up) break; 41 | 42 | for (let i = down; i >= up; i--) res.push(matrix[i][left]); 43 | left++; 44 | if (left > right) break; 45 | } 46 | 47 | return res; 48 | }; 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /code/59 - I. 滑动窗口的最大值.js: -------------------------------------------------------------------------------- 1 | // const maxSlidingWindow = (nums, k) => { 2 | // const res = []; 3 | // const len = nums.length; 4 | // if (!len) return res; 5 | // for (let i = 0; i <= len - k; i++) { 6 | // const temp = []; 7 | // for (let j = i; j < i + k; j++) { 8 | // temp.push(nums[j]); 9 | // } 10 | // res.push(Math.max(...temp)); 11 | // } 12 | // return res; 13 | // }; 14 | 15 | const maxSlidingWindow = (nums, k) => { 16 | const len = nums.length; 17 | if (!len) return []; 18 | // q存放的是nums的下标i 19 | const q = []; 20 | for (let i = 0; i < k; i++) { 21 | // 循环比较当前元素和q队尾对应的元素大小,若当前元素大,将队尾元素出队 22 | while (q.length && nums[i] >= nums[q[q.length - 1]]) { 23 | q.pop(); 24 | } 25 | q.push(i); 26 | } 27 | // 这时候q的队头元素对应的值,肯定是初始窗口中最大的 28 | const res = [nums[q[0]]]; 29 | for (let i = k; i < len; i++) { 30 | // 窗口开始滑动,做之前相同的操作 31 | while (q.length && nums[i] >= nums[q[q.length - 1]]) { 32 | q.pop(); 33 | } 34 | q.push(i); 35 | // 窗口滑动,将不应该在窗口中的元素弹出 36 | while (q[0] <= i - k) q.shift(); 37 | // 这时候q的队头元素对应的值,肯定是当前窗口中最大的 38 | res.push(nums[q[0]]); 39 | } 40 | return res; 41 | }; 42 | -------------------------------------------------------------------------------- /problems/68 - I. 二叉搜索树的最近公共祖先.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 68 - I. 二叉搜索树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/)」 4 | 5 | # 思路: 6 | 7 | ## 递归 8 | 9 | ```javascript 10 | const lowestCommonAncestor = (root, p, q) => { 11 | // 递归出口 12 | if (!root) return root; 13 | // 根节点值都大于两个数,则遍历左子树 14 | if (root.val > p.val && root.val > q.val) { 15 | return lowestCommonAncestor(root.left, p, q); 16 | } 17 | // 根节点值都小于两个数,则遍历右子树 18 | if (root.val < p.val && root.val < q.val) { 19 | return lowestCommonAncestor(root.right, p, q); 20 | } 21 | // 否则,根节点一定在中间 22 | return root; 23 | }; 24 | 25 | ``` 26 | 27 | ## 非递归 28 | ```javascript 29 | const lowestCommonAncestor = (root, p, q) => { 30 | if (!root) return null; 31 | // 如果相等,公共祖先就是他们本身 32 | if (p.val === q.val) return p; 33 | // 遍历root 34 | while (root) { 35 | // 当前节点值在p,q的左边,遍历右子树 36 | if (root.val < q.val && root.val < p.val) { 37 | root = root.right; 38 | } else if (root.val > q.val && root.val > p.val) { 39 | // 当前节点值在p,q的右边,遍历左子树 40 | root = root.left; 41 | } else { 42 | // 当前节点值在p,q的中间,那么当前节点就是公共祖先 43 | return root; 44 | } 45 | } 46 | }; 47 | ``` -------------------------------------------------------------------------------- /problems/07. 重建二叉树.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 07. 重建二叉树](https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/)」 4 | 5 | # 思路: 6 | 7 | 先序遍历和中序遍历中,**左**、**根**部分的长度是一样的,所以,只要找到中序遍历的**左**、**根**部分,先序遍历的**左**、**根**部分也知道了。 8 | 9 | 先序遍历,遍历的第一个元素肯定是**根节点**,找到根节点在中序遍历中的索引,由于根节点就是一个元素,那么根节点左边的都是**左**部分,根节点右边的都是**右**部分。 10 | 11 | 这样,中序遍历的**左**、**根**、**右**部分都找到了,那么,先序遍历的**左**、**根**、**右**部分也求出来了。 12 | 13 | 由于先序遍历的**左**、**右**部分内也是先序遍历,所以递归构建即可,中序遍历同理。 14 | 15 | # 代码: 16 | 17 | ## JavaScript 18 | 19 | ```javascript 20 | const buildTree = (preorder, inorder) => { 21 | if (preorder.length === 0) return null; 22 | if (preorder === 1) return new TreeNode(preorder[0]); 23 | 24 | // 先序遍历的第一个就是根节点 25 | let rootVal = preorder[0]; 26 | // 找到根节点在中序遍历中的索引 27 | let index = inorder.indexOf(rootVal); 28 | 29 | // 先序左子树 30 | let preLeft = preorder.slice(1, index + 1); 31 | // 先序右子树 32 | let preRight = preorder.slice(index + 1); 33 | // 中序左子树 34 | let inLeft = inorder.slice(0, index); 35 | // 中序右子树 36 | let inRight = inorder.slice(index + 1); 37 | 38 | // 构建二叉树 39 | const node = new TreeNode(rootVal); 40 | // 递归构建左子树 41 | node.left = buildTree(preLeft, inLeft); 42 | // 递归构建右子树 43 | node.right = buildTree(preRight, inRight); 44 | 45 | return node; 46 | }; 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /code/67. 把字符串转换成整数.js: -------------------------------------------------------------------------------- 1 | const strToInt = str => { 2 | // 去除头部空格 3 | str = str.trim(); 4 | // 仅限数字0~9 5 | const reg = /\d/; 6 | // 是否为负数,默认否 7 | let isNega = false; 8 | if (str[0] === '-' || str[0] === '+') { 9 | // 如果第一个字符是正负号,则判断第二个字符是否是数字 10 | if (!reg.test(str[1])) return 0; 11 | } else { 12 | // 如果第一个字符不是正负号,则判断第一个字符是否是数字 13 | if (!reg.test(str[0])) return 0; 14 | } 15 | 16 | // 数字数组 17 | const nums = []; 18 | const len = str.length; 19 | for (let i = 0; i < len; i++) { 20 | // nums为空的情况下,遇到正负号 21 | if (!nums.length && str[i] === '-') { 22 | isNega = true; 23 | continue; 24 | } else if (!nums.length && str[i] === '+') { 25 | continue; 26 | } 27 | // 将数字字符放入nums,若遇到非数字,直接跳出循环 28 | if (reg.test(str[i])) { 29 | nums.push(str[i]); 30 | } else { 31 | break; 32 | } 33 | } 34 | let res = 0; 35 | const lenNums = nums.length; 36 | // 构造最后的数字 37 | for (let i = lenNums - 1; i >= 0; i--) { 38 | res += nums[i] * 10 ** (lenNums - 1 - i); 39 | } 40 | const [MIN, MAX] = [-(2 ** 31), 2 ** 31 - 1]; 41 | // 若是负数,判断-res和MIN的大小 42 | // 若不是负数,判断res和MAX的大小 43 | return isNega ? (-res < MIN ? MIN : -res) : res > MAX ? MAX : res; 44 | }; 45 | -------------------------------------------------------------------------------- /problems/56 - I. 数组中数字出现的次数.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 56 - I. 数组中数字出现的次数](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/)」 4 | 5 | # 思路: 6 | 7 | 这道题和[136. 只出现一次的数字](https://leetcode-cn.com/problems/single-number/)解法类似。 8 | 9 | 异或运算: 10 | 1. `0`和**任何数**异或=**任何数本身** 11 | 2. **任何数**和**自身**异或=`0` 12 | 3. 异或满足**交换律**、**结合律** 13 | 14 | 知道了以上三条,就可以解题了。 15 | 16 | 总体思路,将`nums`的数按照一定规则分成两组,相同的数必然会被分到一组,关键是**两个不同的数一定要分在不同组**。再每组进行异或,得到的结果就是原数组中两个不同的数,具体如下: 17 | 18 | - 首先从0开始,与数组中所有的数进行异或,得到结果`temp` 19 | - `temp`等价为数组中两个不同的数**异或的结果** 20 | - 寻找`temp`中,为`1`的最低的位`k`。例如`[1,2,1,3,2,5]`,得到`temp=6`,也就是`110`,`110`的为`1`的最低的位是第1位,则`k=10` 21 | - `temp`为`110`,是`011`和`101`异或的结果,为`1`的最低位代表两个不同的数,在这一位上不同。那么让两个不同的数,和`k`进行**与**操作,就能分开了 22 | - 分组,`011&10`,`101&10`,可以将两个不同的数分在两组,其余相同的数肯定会被分在同一组 23 | - 每组分别进行异或运算,得到两个答案 24 | 25 | # 代码: 26 | 27 | ## JavaScript 28 | 29 | ```javascript 30 | const singleNumbers = nums => { 31 | const temp = nums.reduce((a, b) => a ^ b, 0); 32 | 33 | // 此时temp是两个不同的数异或的结果 34 | // 寻找k,k是temp最低位为1、其余位是0的二进制数 35 | let k = 1; 36 | while ((temp & k) === 0) k = k << 1; 37 | 38 | let [num1, num2] = [0, 0]; 39 | for (const num of nums) { 40 | // 分组,目的是将两个不同的数分开 41 | if (num & k) { 42 | num1 ^= num; 43 | } else { 44 | num2 ^= num; 45 | } 46 | } 47 | 48 | return [num1, num2]; 49 | }; 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /problems/13. 机器人的运动范围.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 13. 机器人的运动范围](https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 创建一个用来判断能否访问的函数 8 | - 构建已访问的数组,初始全部为`false` 9 | - 创建`dfs`函数,从`(0,0)`开始深度优先访问 10 | - 若越界,或遇到已访问过的,`return` 11 | - 如果当前格子能访问,计数`+1`,并向四个方向继续访问 12 | - 最后返回计数值 13 | 14 | # 代码: 15 | 16 | ## JavaScript 17 | 18 | ```javascript 19 | // 判断当前格子是否能过的函数 20 | const isTrue = (m, n, k) => { 21 | let [sum1, sum2] = [0, 0]; 22 | while (m !== 0) { 23 | sum1 += m % 10; 24 | m = Math.floor(m / 10); 25 | } 26 | while (n !== 0) { 27 | sum2 += n % 10; 28 | n = Math.floor(n / 10); 29 | } 30 | return sum1 + sum2 <= k; 31 | }; 32 | 33 | const movingCount = (m, n, k) => { 34 | // 构建已访问的数组,初始全部为false 35 | const visited = new Array(m).fill(0).map(() => new Array(n).fill(false)); 36 | let res = 0; 37 | const dfs = (i, j) => { 38 | // 若越界,或遇到已访问过的,return 39 | if (i < 0 || j < 0 || i >= m || j >= n || visited[i][j]) return; 40 | // 标记访问 41 | visited[i][j] = true; 42 | if (isTrue(i, j, k)) { 43 | // 如果当前格子能访问,计数+1,并向四个方向继续访问 44 | res++; 45 | dfs(i + 1, j); 46 | dfs(i, j + 1); 47 | dfs(i - 1, j); 48 | dfs(i, j - 1); 49 | } 50 | }; 51 | dfs(0, 0); 52 | return res; 53 | }; 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /problems/47. 礼物的最大价值.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 47. 礼物的最大价值](https://leetcode-cn.com/problems/li-wu-de-zui-da-jie-zhi-lcof/)」 4 | 5 | # 思路: 6 | 7 | 使用动态规划来解题: 8 | 9 | 1. `dp[i][j]`含义: 10 | - 从`(0,0)`移动到`(i,j)`,可以拿的最大价值为`dp[i][j]` 11 | 2. `dp`数组初始化: 12 | - `(0,0)`位置肯定还是原来的值。 13 | - 第一列`(i,0)`:只能从上边走到该位置,所以`dp[i][0] = dp[i - 1][0] + grid[i][0]` 14 | - 第一行`(0,j)`:只能从左边走到该位置,所以`dp[0][j] = dp[0][j - 1] + grid[0][j]` 15 | 3. 递推公式: 16 | - 对于除了第一行、第一列的`dp[i][j]`,可以有两个地方走到这里,上边和左边,取他们的最大值,再加上当前位置的价值 17 | - `dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]` 18 | 4. 遍历顺序: 19 | - 当前值依赖于上边的值、左边的值,所以从左往右、从上到下,一行一行遍历 20 | 21 | # 代码: 22 | 23 | ## JavaScript 24 | 25 | ```javascript 26 | const maxValue = grid => { 27 | // 行、列 28 | const [m, n] = [grid.length, grid[0].length]; 29 | // 创建行m列n的二维数组 30 | const dp = new Array(m).fill(0).map(() => new Array(n).fill(0)); 31 | // (0,0)位置初始化 32 | dp[0][0] = grid[0][0]; 33 | // 第一列初始化 34 | for (let i = 1; i < m; i++) { 35 | dp[i][0] = dp[i - 1][0] + grid[i][0]; 36 | } 37 | // 第一行初始化 38 | for (let j = 1; j < n; j++) { 39 | dp[0][j] = dp[0][j - 1] + grid[0][j]; 40 | } 41 | // 遍历,完善dp数组 42 | for (let i = 1; i < m; i++) { 43 | for (let j = 1; j < n; j++) { 44 | dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; 45 | } 46 | } 47 | return dp[m - 1][n - 1]; 48 | }; 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /problems/05. 替换空格.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 05. 替换空格](https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/)」 4 | 5 | # 思路: 6 | 7 | 这道题可以用很简单的方法实现,比如: 8 | 9 | ```javascript 10 | const replaceSpace = s => s.split(' ').join('%20'); 11 | ``` 12 | 13 | 或者直接用`replace`也可以。 14 | 15 | 但要我们自己手动实现的话,可以使用**双指针解法**: 16 | 17 | 1. 先统计空格数量 18 | 2. 字符串转化为数组,更新数组长度 19 | 3. 定义双指针:`left`:旧长度-1、`right`:新长度-1 20 | 4. 从后往前遍历,如果左指针遇到空格,右指针依次填入`%20`,并左移2 21 | 5. 如果左指针遇到字符,直接将左指针的值赋给右指针 22 | 6. 左指针和右指针还有分别左移1 23 | 7. 最后返回字符串 24 | 25 | # 代码: 26 | 27 | ## JavaScript 28 | 29 | ```javascript 30 | const replaceSpace = s => { 31 | // 转化为数组 32 | const arr = s.split(''); 33 | // 统计空格数量 34 | let spaceCount = 0; 35 | const len = arr.length; 36 | for (let i = 0; i < len; i++) { 37 | if (arr[i] === ' ') spaceCount++; 38 | } 39 | // 更新数组长度,每个空格,要多出2个位置 40 | arr.length += 2 * spaceCount; 41 | // 定义双指针 42 | // left:旧长度-1 43 | // right:新长度-1 44 | let [left, right] = [len - 1, arr.length - 1]; 45 | while (left >= 0) { 46 | if (arr[left] !== ' ') { 47 | // 如果遇到字符,直接将左指针的值赋给右指针 48 | arr[right] = arr[left]; 49 | } else { 50 | // 遇到空格,依次填入%20,右指针左移2 51 | arr[right - 2] = '%'; 52 | arr[right - 1] = '2'; 53 | arr[right] = '0'; 54 | right -= 2; 55 | } 56 | // 分别左移1 57 | left--; 58 | right--; 59 | } 60 | // 返回字符串 61 | return arr.join(''); 62 | }; 63 | ``` 64 | 65 | -------------------------------------------------------------------------------- /problems/12. 矩阵中的路径.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 12. 矩阵中的路径](https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 先从头遍历矩阵,找到第一个符合的字符,开始深度优先遍历 8 | - 深度优先遍历: 9 | - 定义`index`代表索引,或者说已经匹配了多少个字符 10 | - 要写递归的出口:`i`、`j`越界或者字符不匹配 11 | - 匹配成功:`index`等于`word.length - 1`,匹配成功,直接返回`true` 12 | - 为了同一个单元格内的字母不允许被重复使用,遍历到某字符后,将当前字符设置为`''`,防止四个方向dfs再次遍历到 13 | - 四个方向遍历完毕后,再恢复这个字符 14 | - 最后一只匹配失败,返回`false` 15 | 16 | # 代码: 17 | 18 | ## JavaScript 19 | 20 | ```javascript 21 | const exist = (board, word) => { 22 | const [m, n] = [board.length, board[0].length]; 23 | 24 | const dfs = (i, j, index) => { 25 | // 越界、或者字符不匹配 26 | if (i < 0 || i >= m || j < 0 || j >= n || board[i][j] !== word[index]) return false; 27 | // 索引等于单词长度-1,说明全匹配上了 28 | if (index === word.length - 1) return true; 29 | // 保存当前字符 30 | const temp = board[i][j]; 31 | // 将当前字符设置为空,防止四个方向dfs再次遍历到 32 | board[i][j] = ''; 33 | // 四个方向遍历 34 | const res = 35 | dfs(i + 1, j, index + 1) || 36 | dfs(i, j + 1, index + 1) || 37 | dfs(i - 1, j, index + 1) || 38 | dfs(i, j - 1, index + 1); 39 | // 恢复当前字符 40 | board[i][j] = temp; 41 | return res; 42 | }; 43 | 44 | // 从第一个匹配的字符处开始dfs 45 | for (let i = 0; i < m; i++) { 46 | for (let j = 0; j < n; j++) { 47 | if (dfs(i, j, 0)) return true; 48 | } 49 | } 50 | 51 | return false; 52 | }; 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /code/28. 对称的二叉树.js: -------------------------------------------------------------------------------- 1 | // 递归 2 | // const isSymmetric = root => { 3 | // if (!root) return true; 4 | // // 判断两个二叉树是否镜像 5 | // const check = (node1, node2) => { 6 | // // 两个空树,true 7 | // if (!node1 && !node2) return true; 8 | // // 只有一个空树,false 9 | // if (!node1 || !node2) return false; 10 | // // 镜像的条件: 11 | // // 1. 节点值相等 12 | // // 2. node1的左子树和node2的右子树是镜像 13 | // // 3. node1的右子树和node2的左子树是镜像 14 | // return ( 15 | // node1.val === node2.val && 16 | // check(node1.left, node2.right) && 17 | // check(node1.right, node2.left) 18 | // ); 19 | // }; 20 | // // 判断树的两个子树是否镜像即可 21 | // return check(root.left, root.right); 22 | // }; 23 | 24 | // 迭代:利用层序遍历的思想 25 | const isSymmetric = root => { 26 | if (!root) return true; 27 | // 根节点的左右节点入队 28 | const queue = [root.left, root.right]; 29 | while (queue.length) { 30 | // 当前队列的长度 31 | const len = queue.length; 32 | // 每次对比两个节点,所以i=i+2 33 | for (let i = 0; i < len; i += 2) { 34 | // 两个节点出队 35 | const left = queue.shift(); 36 | const right = queue.shift(); 37 | // 有一个为空,另一个不为空,直接返回false 38 | if ((left && !right) || (!left && right)) return false; 39 | // 两节点都不为空 40 | if (left && right) { 41 | // 值不相等,返回false 42 | if (left.val !== right.val) return false; 43 | // 将两个节点的左右节点,按照对应关系入队 44 | queue.push(left.left, right.right, left.right, right.left); 45 | } 46 | } 47 | } 48 | // 遍历结束返回true 49 | return true; 50 | }; 51 | -------------------------------------------------------------------------------- /problems/67. 把字符串转换成整数.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 67. 把字符串转换成整数](https://leetcode-cn.com/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 先判断要转换的数字的正负号 8 | - 再判断能否能进行正确转换 9 | - 再将每个数字提取到数组 10 | - 根据数组,构造最后的数字 11 | - 返回结果时,加上正负号,再考虑数字范围的问题 12 | 13 | # 代码: 14 | 15 | ## JavaScript 16 | 17 | ```javascript 18 | const strToInt = str => { 19 | // 去除头部空格 20 | str = str.trim(); 21 | // 仅限数字0~9 22 | const reg = /\d/; 23 | // 是否为负数,默认否 24 | let isNega = false; 25 | if (str[0] === '-' || str[0] === '+') { 26 | // 如果第一个字符是正负号,则判断第二个字符是否是数字 27 | if (!reg.test(str[1])) return 0; 28 | } else { 29 | // 如果第一个字符不是正负号,则判断第一个字符是否是数字 30 | if (!reg.test(str[0])) return 0; 31 | } 32 | 33 | // 数字数组 34 | const nums = []; 35 | const len = str.length; 36 | for (let i = 0; i < len; i++) { 37 | // nums为空的情况下,遇到正负号 38 | if (!nums.length && str[i] === '-') { 39 | isNega = true; 40 | continue; 41 | } else if (!nums.length && str[i] === '+') { 42 | continue; 43 | } 44 | // 将数字字符放入nums,若遇到非数字,直接跳出循环 45 | if (reg.test(str[i])) { 46 | nums.push(str[i]); 47 | } else { 48 | break; 49 | } 50 | } 51 | let res = 0; 52 | const lenNums = nums.length; 53 | // 构造最后的数字 54 | for (let i = lenNums - 1; i >= 0; i--) { 55 | res += nums[i] * 10 ** (lenNums - 1 - i); 56 | } 57 | const [MIN, MAX] = [-(2 ** 31), 2 ** 31 - 1]; 58 | // 若是负数,判断-res和MIN的大小 59 | // 若是证书,判断res和MAX的大小 60 | return isNega ? (-res < MIN ? MIN : -res) : res > MAX ? MAX : res; 61 | }; 62 | ``` 63 | 64 | -------------------------------------------------------------------------------- /problems/59 - I. 滑动窗口的最大值.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 59 - I. 滑动窗口的最大值](https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/)」 4 | 5 | # 思路: 6 | 7 | ## 暴力 8 | 9 | 每次滑动窗口,重新计算窗口中元素的最大值 10 | 11 | ```JavaScript 12 | const maxSlidingWindow = (nums, k) => { 13 | const res = []; 14 | const len = nums.length; 15 | if (!len) return res; 16 | for (let i = 0; i <= len - k; i++) { 17 | const temp = []; 18 | for (let j = i; j < i + k; j++) { 19 | temp.push(nums[j]); 20 | } 21 | res.push(Math.max(...temp)); 22 | } 23 | return res; 24 | }; 25 | ``` 26 | 27 | ## 单调队列 28 | 29 | - 创建队列`q`,存放的是`nums`的下标`i` 30 | - 窗口初始化时,将最初的`k`个元素的下标入队,并要保证对应的值是依次递减的,若前一个值小于后一个值,则将前一个值下标弹出 31 | - 这样就可以保证,`q`的队头元素对应的值,肯定是初始窗口中最大的 32 | - 窗口开始滑动,做之前相同的操作。还要将窗口外的值下标弹出队列 33 | - 每次滑动,取出队头下标对应的值即可 34 | 35 | ```JavaScript 36 | const maxSlidingWindow = (nums, k) => { 37 | const len = nums.length; 38 | if (!len) return []; 39 | // q存放的是nums的下标i 40 | const q = []; 41 | for (let i = 0; i < k; i++) { 42 | // 循环比较当前元素和q队尾对应的元素大小,若当前元素大,将队尾元素出队 43 | while (q.length && nums[i] >= nums[q[q.length - 1]]) { 44 | q.pop(); 45 | } 46 | q.push(i); 47 | } 48 | // 这时候q的队头元素对应的值,肯定是初始窗口中最大的 49 | const res = [nums[q[0]]]; 50 | for (let i = k; i < len; i++) { 51 | // 窗口开始滑动,做之前相同的操作 52 | while (q.length && nums[i] >= nums[q[q.length - 1]]) { 53 | q.pop(); 54 | } 55 | q.push(i); 56 | // 窗口滑动,将不应该在窗口中的元素弹出 57 | while (q[0] <= i - k) q.shift(); 58 | // 这时候q的队头元素对应的值,肯定是当前窗口中最大的 59 | res.push(nums[q[0]]); 60 | } 61 | return res; 62 | }; 63 | ``` 64 | -------------------------------------------------------------------------------- /problems/39. 数组中出现次数超过一半的数字.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 39. 数组中出现次数超过一半的数字](https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/)」 4 | 5 | # 思路: 6 | 7 | ## 方法一:抵消 8 | - 用每一个出现最多的数和其他不同的数抵消,因为最多的数大于一半,所以最后剩下的肯定就是最多的数。 9 | - 用`count`来表示抵消,相同+1,不同抵消-1。 10 | - 一开始假设`nums[0]`是最大的数,如果遍历开始就发现`count==0`,被抵消了,那么马上换最大值。 11 | 12 | ```javascript 13 | const majorityElement = nums => { 14 | let count = 1; 15 | let majority = nums[0]; 16 | for (let i = 1; i < nums.length; i++) { 17 | if (count === 0) { 18 | majority = nums[i]; 19 | } 20 | if (nums[i] === majority) { 21 | count++; 22 | } else { 23 | count--; 24 | } 25 | } 26 | return majority; 27 | }; 28 | ``` 29 | 30 | 这种思想还可以用栈来实现: 31 | 32 | - 首先将`nums[0]`入栈,遍历`nums`,比较栈顶元素和`nums[i]`,如果相等则`nums[i]`入栈,不相等则栈顶出栈。 33 | - 比较之前加个判断条件:如果栈空,则直接将`nums[i]`入栈,`continue`。 34 | - 遍历完成后,该抵消的都抵消,最后栈顶元素肯定是出现最多的数。 35 | 36 | ```javascript 37 | const majorityElement = nums => { 38 | let stack = [nums[0]]; 39 | for (let i = 1; i < nums.length; i++) { 40 | // 如果栈空,则直接入栈,跳过此轮循环 41 | if (stack.length === 0) { 42 | stack.push(nums[i]); 43 | continue; 44 | } 45 | 46 | if (stack[stack.length - 1] === nums[i]) { 47 | // 相等入栈 48 | stack.push(nums[i]); 49 | } else { 50 | // 不相等出栈 51 | stack.pop(); 52 | } 53 | } 54 | // 返回栈顶元素 55 | return stack[stack.length - 1]; 56 | }; 57 | ``` 58 | 59 | 60 | ## 方法二:排序后取中值 61 | 因为最多的数大于一半,排序后取中值就是出现最多的数。 62 | ```javascript 63 | const majorityElement = nums => { 64 | nums.sort((a, b) => a - b); 65 | return nums[nums.length >> 1]; 66 | }; 67 | ``` -------------------------------------------------------------------------------- /problems/63. 股票的最大利润.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 63. 股票的最大利润](https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof/)」 4 | 5 | # 思路: 6 | 7 | ## 贪心算法 8 | 1. 先定义最低价格和利润 9 | 2. 遍历数组,更新最低价格和利润 10 | 3. 最后遍历完成,返回利润 11 | 12 | ```javascript 13 | const maxProfit = prices => { 14 | // 先定义第一天为最低价格 15 | let min = prices[0]; 16 | // 利润 17 | let profit = 0; 18 | // 遍历数据 19 | for (let i = 1; i < prices.length; i++) { 20 | // 如果发现比最低价格还低的,更新最低价格 21 | min = Math.min(min, prices[i]); 22 | // 如果发现当前利润比之前高的,更新利润 23 | profit = Math.max(profit, prices[i] - min); 24 | } 25 | return profit; 26 | }; 27 | ``` 28 | 29 | ## 动态规划 30 | 31 | 1. `dp[i][0]`:第i天持有股票所得最多现金。`dp[i][1]`:第i天不持有股票所得最多现金 32 | 2. 如果第i天持有股票即`dp[i][0]`,由两个方式得到: 33 | (1)第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金,即:`dp[i][0]=dp[i-1][0]` 34 | (2)第i天买⼊股票,所得现金就是买⼊今天的股票后所得现金即:`dp[i][0]=-prices[i]` 35 | (3)最后取最大的,`dp[i][0] = max(dp[i-1][0], -prices[i])` 36 | 3. 如果第i天不持有股票即`dp[i][1]`,由两个方式得到: 37 | (1)第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金,即:`dp[i][1]=dp[i-1][1]` 38 | (2)第i天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金,即:`dp[i][1]=prices[i]+dp[i-1][0]` 39 | (3)最后取最大的,`dp[i][1] = max(dp[i-1][1], prices[i]+dp[i-1][0])` 40 | 4. dp数组初始化:第0天就有股票,`dp[0][0]=-prices[0]`,第0天不持有股票,则现金为0,则`dp[0][1]=0` 41 | 42 | ```JavaScript 43 | const maxProfit = prices => { 44 | const len = prices.length; 45 | // 创建dp数组 46 | const dp = new Array(len).fill([0, 0]); 47 | // dp数组初始化 48 | dp[0] = [-prices[0], 0]; 49 | for (let i = 1; i < len; i++) { 50 | // 更新dp[i] 51 | dp[i] = [ 52 | Math.max(dp[i - 1][0], -prices[i]), 53 | Math.max(dp[i - 1][1], prices[i] + dp[i - 1][0]), 54 | ]; 55 | } 56 | return dp[len - 1][1]; 57 | }; 58 | ``` 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /code/51. 数组中的逆序对.js: -------------------------------------------------------------------------------- 1 | const reversePairs = nums => { 2 | // 记录逆序 3 | let count = 0; 4 | // 归并排序 5 | const mergeSort = arr => { 6 | // arr为一个值直接返回 7 | if (arr.length < 2) return arr; 8 | // 长度为2排序返回 9 | if (arr.length === 2) { 10 | if (arr[0] <= arr[1]) return arr; 11 | // 记录逆序 12 | count++; 13 | return [arr[1], arr[0]]; 14 | } 15 | // 获取二分位置 16 | const mid = arr.length >> 1; 17 | // 将左边部分排序 18 | const left = mergeSort(arr.slice(0, mid)); 19 | // 将右边部分排序 20 | const right = mergeSort(arr.slice(mid)); 21 | // 保存结果 22 | const res = []; 23 | // 两组的起始指针位置 24 | let [leftIndex, rightIndex] = [0, 0]; 25 | // 循环直到两边遍历完 26 | while (leftIndex < left.length || rightIndex < right.length) { 27 | // left结束 28 | if (leftIndex >= left.length) { 29 | // 将剩余right添加到res 30 | res.push.apply(res, right.slice(rightIndex)); 31 | // 跳出循环 32 | break; 33 | } 34 | // right结束 35 | if (rightIndex >= right.length) { 36 | // 将剩余left添加到res 37 | res.push.apply(res, left.slice(leftIndex)); 38 | // 跳出循环 39 | break; 40 | } 41 | // 左值小 42 | if (left[leftIndex] <= right[rightIndex]) { 43 | // 将左值push 44 | res.push(left[leftIndex]); 45 | // 左下标右移 46 | leftIndex++; 47 | } else { 48 | //右值小 49 | // 将右值push 50 | res.push(right[rightIndex]); 51 | // 右下标右移 52 | rightIndex++; 53 | // 记录逆序(因为left是有序的,所以leftIndex开始都存在逆序) 54 | count += left.length - leftIndex; 55 | } 56 | } 57 | // 返回有序数组 58 | return res; 59 | }; 60 | mergeSort(nums); 61 | return count; 62 | }; 63 | -------------------------------------------------------------------------------- /problems/28. 对称的二叉树.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 28. 对称的二叉树](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/)」 4 | 5 | # 思路: 6 | 7 | ## 递归: 8 | 9 | 判断一个二叉树是否对称,只需判断二叉树的两个子树是否镜像即可。 10 | 镜像的条件: 11 | 1. 节点值相等 12 | 2. `node1`的左子树和`node2`的右子树是镜像 13 | 3. `node1`的右子树和`node2`的左子树是镜像 14 | 15 | ## 迭代: 16 | 17 | 利用层序遍历的思想,一层一层判断,是否对称。 18 | 19 | # 代码: 20 | 21 | ## JavaScript 22 | 23 | #### 递归: 24 | 25 | ```javascript 26 | const isSymmetric = root => { 27 | if (!root) return true; 28 | // 判断两个二叉树是否镜像 29 | const check = (node1, node2) => { 30 | // 两个空树,true 31 | if (!node1 && !node2) return true; 32 | // 只有一个空树,false 33 | if (!node1 || !node2) return false; 34 | // 镜像的条件: 35 | // 1. 节点值相等 36 | // 2. node1的左子树和node2的右子树是镜像 37 | // 3. node1的右子树和node2的左子树是镜像 38 | return ( 39 | node1.val === node2.val && 40 | check(node1.left, node2.right) && 41 | check(node1.right, node2.left) 42 | ); 43 | }; 44 | // 判断树的两个子树是否镜像即可 45 | return check(root.left, root.right); 46 | }; 47 | ``` 48 | 49 | #### 迭代: 50 | 51 | ```javascript 52 | const isSymmetric = root => { 53 | if (!root) return true; 54 | // 根节点的左右节点入队 55 | const queue = [root.left, root.right]; 56 | while (queue.length) { 57 | // 当前队列的长度 58 | const len = queue.length; 59 | // 每次对比两个节点,所以i=i+2 60 | for (let i = 0; i < len; i += 2) { 61 | // 两个节点出队 62 | const left = queue.shift(); 63 | const right = queue.shift(); 64 | // 有一个为空,另一个不为空,直接返回false 65 | if ((left && !right) || (!left && right)) return false; 66 | // 两节点都不为空 67 | if (left && right) { 68 | // 值不相等,返回false 69 | if (left.val !== right.val) return false; 70 | // 将两个节点的左右节点,按照对应关系入队 71 | queue.push(left.left, right.right, left.right, right.left); 72 | } 73 | } 74 | } 75 | // 遍历结束返回true 76 | return true; 77 | }; 78 | ``` 79 | 80 | -------------------------------------------------------------------------------- /problems/51. 数组中的逆序对.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 51. 数组中的逆序对](https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/)」 4 | 5 | # 思路: 6 | 7 | 归并排序。 8 | 9 | # 代码: 10 | 11 | ## JavaScript 12 | 13 | ```javascript 14 | const reversePairs = nums => { 15 | // 记录逆序 16 | let count = 0; 17 | // 归并排序 18 | const mergeSort = arr => { 19 | // arr为一个值直接返回 20 | if (arr.length < 2) return arr; 21 | // 长度为2排序返回 22 | if (arr.length === 2) { 23 | if (arr[0] <= arr[1]) return arr; 24 | // 记录逆序 25 | count++; 26 | return [arr[1], arr[0]]; 27 | } 28 | // 获取二分位置 29 | const mid = arr.length >> 1; 30 | // 将左边部分排序 31 | const left = mergeSort(arr.slice(0, mid)); 32 | // 将右边部分排序 33 | const right = mergeSort(arr.slice(mid)); 34 | // 保存结果 35 | const res = []; 36 | // 两组的起始指针位置 37 | let [leftIndex, rightIndex] = [0, 0]; 38 | // 循环直到两边遍历完 39 | while (leftIndex < left.length || rightIndex < right.length) { 40 | // left结束 41 | if (leftIndex >= left.length) { 42 | // 将剩余right添加到res 43 | res.push.apply(res, right.slice(rightIndex)); 44 | // 跳出循环 45 | break; 46 | } 47 | // right结束 48 | if (rightIndex >= right.length) { 49 | // 将剩余left添加到res 50 | res.push.apply(res, left.slice(leftIndex)); 51 | // 跳出循环 52 | break; 53 | } 54 | // 左值小 55 | if (left[leftIndex] <= right[rightIndex]) { 56 | // 将左值push 57 | res.push(left[leftIndex]); 58 | // 左下标右移 59 | leftIndex++; 60 | } else { 61 | //右值小 62 | // 将右值push 63 | res.push(right[rightIndex]); 64 | // 右下标右移 65 | rightIndex++; 66 | // 记录逆序(因为left是有序的,所以leftIndex开始都存在逆序) 67 | count += left.length - leftIndex; 68 | } 69 | } 70 | // 返回有序数组 71 | return res; 72 | }; 73 | mergeSort(nums); 74 | return count; 75 | }; 76 | ``` 77 | 78 | -------------------------------------------------------------------------------- /code/41. 数据流中的中位数.js: -------------------------------------------------------------------------------- 1 | // 默认最大堆 2 | const defaultCmp = (x, y) => x > y; 3 | // 交换元素 4 | const swap = (arr, i, j) => ([arr[i], arr[j]] = [arr[j], arr[i]]); 5 | // 堆类,默认最大堆 6 | class Heap { 7 | constructor(cmp = defaultCmp) { 8 | this.container = []; 9 | this.cmp = cmp; 10 | } 11 | // 插入 12 | insert(data) { 13 | const { container, cmp } = this; 14 | container.push(data); 15 | let index = this.size() - 1; 16 | while (index) { 17 | let parent = (index - 1) >> 1; 18 | if (!cmp(container[index], container[parent])) { 19 | return; 20 | } 21 | swap(container, index, parent); 22 | index = parent; 23 | } 24 | } 25 | // 弹出堆顶,并返回 26 | pop() { 27 | const { container, cmp } = this; 28 | if (!this.size()) { 29 | return null; 30 | } 31 | 32 | swap(container, 0, this.size() - 1); 33 | const res = container.pop(); 34 | const length = this.size(); 35 | let index = 0, 36 | exchange = index * 2 + 1; 37 | 38 | while (exchange < length) { 39 | // // 以最大堆的情况来说:如果有右节点,并且右节点的值大于左节点的值 40 | let right = index * 2 + 2; 41 | if (right < length && cmp(container[right], container[exchange])) { 42 | exchange = right; 43 | } 44 | if (!cmp(container[exchange], container[index])) { 45 | break; 46 | } 47 | swap(container, exchange, index); 48 | index = exchange; 49 | exchange = index * 2 + 1; 50 | } 51 | 52 | return res; 53 | } 54 | // 获取堆大小 55 | size() { 56 | return this.container.length; 57 | } 58 | // 获取堆顶 59 | peek() { 60 | if (this.size()) return this.container[0]; 61 | return null; 62 | } 63 | } 64 | 65 | class MedianFinder { 66 | constructor() { 67 | // 最小堆 68 | this.min = new Heap((x, y) => x < y); 69 | // 最大堆 70 | this.max = new Heap(); 71 | } 72 | addNum(num) { 73 | if (this.max.size() !== this.min.size()) { 74 | // 当N为奇数,需要向min添加一个元素 75 | // 先将num插入max,再将max堆顶弹出,插入min 76 | this.max.insert(num); 77 | this.min.insert(this.max.pop()); 78 | } else { 79 | // 当N为偶数,需要向max添加一个元素 80 | // 先将num插入min,再将min堆顶弹出,插入max 81 | this.min.insert(num); 82 | this.max.insert(this.min.pop()); 83 | } 84 | } 85 | findMedian() { 86 | return this.max.container.length === this.min.container.length 87 | ? (this.max.peek() + this.min.peek()) / 2 88 | : this.max.peek(); 89 | } 90 | } -------------------------------------------------------------------------------- /code/40. 最小的k个数.js: -------------------------------------------------------------------------------- 1 | // 默认最大堆 2 | const defaultCmp = (x, y) => x > y; 3 | // 交换元素 4 | const swap = (arr, i, j) => ([arr[i], arr[j]] = [arr[j], arr[i]]); 5 | // 堆类,默认最大堆 6 | class Heap { 7 | constructor(cmp = defaultCmp) { 8 | this.container = []; 9 | this.cmp = cmp; 10 | } 11 | // 插入 12 | insert(data) { 13 | const { container, cmp } = this; 14 | container.push(data); 15 | let index = this.size() - 1; 16 | while (index) { 17 | let parent = (index - 1) >> 1; 18 | if (!cmp(container[index], container[parent])) { 19 | return; 20 | } 21 | swap(container, index, parent); 22 | index = parent; 23 | } 24 | } 25 | // 弹出堆顶,并返回 26 | pop() { 27 | const { container, cmp } = this; 28 | if (!this.size()) { 29 | return null; 30 | } 31 | 32 | swap(container, 0, this.size() - 1); 33 | const res = container.pop(); 34 | const length = this.size(); 35 | let index = 0, 36 | exchange = index * 2 + 1; 37 | 38 | while (exchange < length) { 39 | // // 以最大堆的情况来说:如果有右节点,并且右节点的值大于左节点的值 40 | let right = index * 2 + 2; 41 | if (right < length && cmp(container[right], container[exchange])) { 42 | exchange = right; 43 | } 44 | if (!cmp(container[exchange], container[index])) { 45 | break; 46 | } 47 | swap(container, exchange, index); 48 | index = exchange; 49 | exchange = index * 2 + 1; 50 | } 51 | 52 | return res; 53 | } 54 | // 获取堆大小 55 | size() { 56 | return this.container.length; 57 | } 58 | // 获取堆顶 59 | peek() { 60 | if (this.size()) return this.container[0]; 61 | return null; 62 | } 63 | } 64 | 65 | const getLeastNumbers = (arr, k) => { 66 | const res = []; 67 | // 创建最大堆 68 | const maxHeap = new Heap(); 69 | // 先将数组前k个元素放入堆 70 | for (let i = 0; i < k; i++) { 71 | maxHeap.insert(arr[i]); 72 | } 73 | // 再遍历数组其余元素 74 | const len = arr.length; 75 | for (let i = k; i < len; i++) { 76 | // 若遇到比堆顶小的元素,堆顶弹出,当前数入堆 77 | if (arr[i] < maxHeap.peek()) { 78 | maxHeap.pop(); 79 | maxHeap.insert(arr[i]); 80 | } 81 | } 82 | // 最后从堆中弹出k个数即可 83 | for (let i = 0; i < k; i++) { 84 | res.push(maxHeap.pop()); 85 | } 86 | return res; 87 | }; 88 | -------------------------------------------------------------------------------- /code/49. 丑数.js: -------------------------------------------------------------------------------- 1 | // 默认最大堆 2 | const defaultCmp = (x, y) => x > y; 3 | // 交换元素 4 | const swap = (arr, i, j) => ([arr[i], arr[j]] = [arr[j], arr[i]]); 5 | // 堆类,默认最大堆 6 | class Heap { 7 | constructor(cmp = defaultCmp) { 8 | this.container = []; 9 | this.cmp = cmp; 10 | } 11 | // 插入 12 | insert(data) { 13 | const { container, cmp } = this; 14 | container.push(data); 15 | let index = this.size() - 1; 16 | while (index) { 17 | let parent = (index - 1) >> 1; 18 | if (!cmp(container[index], container[parent])) { 19 | return; 20 | } 21 | swap(container, index, parent); 22 | index = parent; 23 | } 24 | } 25 | // 弹出堆顶,并返回 26 | pop() { 27 | const { container, cmp } = this; 28 | if (!this.size()) { 29 | return null; 30 | } 31 | 32 | swap(container, 0, this.size() - 1); 33 | const res = container.pop(); 34 | const length = this.size(); 35 | let index = 0, 36 | exchange = index * 2 + 1; 37 | 38 | while (exchange < length) { 39 | // // 以最大堆的情况来说:如果有右节点,并且右节点的值大于左节点的值 40 | let right = index * 2 + 2; 41 | if (right < length && cmp(container[right], container[exchange])) { 42 | exchange = right; 43 | } 44 | if (!cmp(container[exchange], container[index])) { 45 | break; 46 | } 47 | swap(container, exchange, index); 48 | index = exchange; 49 | exchange = index * 2 + 1; 50 | } 51 | 52 | return res; 53 | } 54 | // 获取堆大小 55 | size() { 56 | return this.container.length; 57 | } 58 | // 获取堆顶 59 | peek() { 60 | if (this.size()) return this.container[0]; 61 | return null; 62 | } 63 | } 64 | 65 | const nthUglyNumber = n => { 66 | // 因数 67 | const factors = [2, 3, 5]; 68 | // 创建最小堆 69 | const heap = new Heap((x, y) => x < y); 70 | const set = new Set(); 71 | // 最小的丑数1入堆、加入set 72 | heap.insert(1); 73 | set.add(1); 74 | let ugly; 75 | for (let i = 0; i < n; i++) { 76 | // 弹出堆中最小的丑数 77 | ugly = heap.pop(); 78 | for (const factor of factors) { 79 | // 下一个丑数 80 | const next = ugly * factor; 81 | // 判断是否已经加入过了 82 | if (!set.has(next)) { 83 | set.add(next); 84 | heap.insert(next); 85 | } 86 | } 87 | } 88 | // 执行n次,返回第n个丑数 89 | return ugly; 90 | }; 91 | -------------------------------------------------------------------------------- /problems/40. 最小的k个数.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 40. 最小的k个数](https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/)」 4 | 5 | # 思路: 6 | 7 | - 维护一个最大堆,先将数组前`k`个元素入堆 8 | - 再遍历数组其余元素 9 | - 若遇到比堆顶小的数,那么堆顶肯定不是**最小的`k`个数之一**,堆顶弹出,当前数入堆 10 | - 最后从堆中弹出`k`个数即可 11 | 12 | # 代码: 13 | 14 | ## JavaScript 15 | 16 | ```javascript 17 | // 默认最大堆 18 | const defaultCmp = (x, y) => x > y; 19 | // 交换元素 20 | const swap = (arr, i, j) => ([arr[i], arr[j]] = [arr[j], arr[i]]); 21 | // 堆类,默认最大堆 22 | class Heap { 23 | constructor(cmp = defaultCmp) { 24 | this.container = []; 25 | this.cmp = cmp; 26 | } 27 | // 插入 28 | insert(data) { 29 | const { container, cmp } = this; 30 | container.push(data); 31 | let index = this.size() - 1; 32 | while (index) { 33 | let parent = (index - 1) >> 1; 34 | if (!cmp(container[index], container[parent])) { 35 | return; 36 | } 37 | swap(container, index, parent); 38 | index = parent; 39 | } 40 | } 41 | // 弹出堆顶,并返回 42 | pop() { 43 | const { container, cmp } = this; 44 | if (!this.size()) { 45 | return null; 46 | } 47 | 48 | swap(container, 0, this.size() - 1); 49 | const res = container.pop(); 50 | const length = this.size(); 51 | let index = 0, 52 | exchange = index * 2 + 1; 53 | 54 | while (exchange < length) { 55 | // // 以最大堆的情况来说:如果有右节点,并且右节点的值大于左节点的值 56 | let right = index * 2 + 2; 57 | if (right < length && cmp(container[right], container[exchange])) { 58 | exchange = right; 59 | } 60 | if (!cmp(container[exchange], container[index])) { 61 | break; 62 | } 63 | swap(container, exchange, index); 64 | index = exchange; 65 | exchange = index * 2 + 1; 66 | } 67 | 68 | return res; 69 | } 70 | // 获取堆大小 71 | size() { 72 | return this.container.length; 73 | } 74 | // 获取堆顶 75 | peek() { 76 | if (this.size()) return this.container[0]; 77 | return null; 78 | } 79 | } 80 | 81 | const getLeastNumbers = (arr, k) => { 82 | const res = []; 83 | // 创建最大堆 84 | const maxHeap = new Heap(); 85 | // 先将数组前k个元素放入堆 86 | for (let i = 0; i < k; i++) { 87 | maxHeap.insert(arr[i]); 88 | } 89 | // 再遍历数组其余元素 90 | const len = arr.length; 91 | for (let i = k; i < len; i++) { 92 | // 若遇到比堆顶小的元素,堆顶弹出,当前数入堆 93 | if (arr[i] < maxHeap.peek()) { 94 | maxHeap.pop(); 95 | maxHeap.insert(arr[i]); 96 | } 97 | } 98 | // 最后从堆中弹出k个数即可 99 | for (let i = 0; i < k; i++) { 100 | res.push(maxHeap.pop()); 101 | } 102 | return res; 103 | }; 104 | ``` 105 | 106 | -------------------------------------------------------------------------------- /problems/49. 丑数.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 49. 丑数](https://leetcode-cn.com/problems/chou-shu-lcof/)」 4 | 5 | # 思路: 6 | 7 | 实现堆类。 8 | 9 | 1. 创建最小堆 10 | 2. 首先将`1`入堆 11 | 3. 弹出堆顶元素`x`,为当前堆中最小的堆,将`2x`、`3x`、`5x`也入堆,入堆前判断是否加入过 12 | 4. 执行`n`次,弹出的堆顶元素就是第`n`个丑数 13 | 14 | # 代码: 15 | 16 | ## JavaScript 17 | 18 | ```javascript 19 | // 默认最大堆 20 | const defaultCmp = (x, y) => x > y; 21 | // 交换元素 22 | const swap = (arr, i, j) => ([arr[i], arr[j]] = [arr[j], arr[i]]); 23 | // 堆类,默认最大堆 24 | class Heap { 25 | constructor(cmp = defaultCmp) { 26 | this.container = []; 27 | this.cmp = cmp; 28 | } 29 | // 插入 30 | insert(data) { 31 | const { container, cmp } = this; 32 | container.push(data); 33 | let index = this.size() - 1; 34 | while (index) { 35 | let parent = (index - 1) >> 1; 36 | if (!cmp(container[index], container[parent])) { 37 | return; 38 | } 39 | swap(container, index, parent); 40 | index = parent; 41 | } 42 | } 43 | // 弹出堆顶,并返回 44 | pop() { 45 | const { container, cmp } = this; 46 | if (!this.size()) { 47 | return null; 48 | } 49 | 50 | swap(container, 0, this.size() - 1); 51 | const res = container.pop(); 52 | const length = this.size(); 53 | let index = 0, 54 | exchange = index * 2 + 1; 55 | 56 | while (exchange < length) { 57 | // // 以最大堆的情况来说:如果有右节点,并且右节点的值大于左节点的值 58 | let right = index * 2 + 2; 59 | if (right < length && cmp(container[right], container[exchange])) { 60 | exchange = right; 61 | } 62 | if (!cmp(container[exchange], container[index])) { 63 | break; 64 | } 65 | swap(container, exchange, index); 66 | index = exchange; 67 | exchange = index * 2 + 1; 68 | } 69 | 70 | return res; 71 | } 72 | // 获取堆大小 73 | size() { 74 | return this.container.length; 75 | } 76 | // 获取堆顶 77 | peek() { 78 | if (this.size()) return this.container[0]; 79 | return null; 80 | } 81 | } 82 | 83 | const nthUglyNumber = n => { 84 | // 因数 85 | const factors = [2, 3, 5]; 86 | // 创建最小堆 87 | const heap = new Heap((x, y) => x < y); 88 | const set = new Set(); 89 | // 最小的丑数1入堆、加入set 90 | heap.insert(1); 91 | set.add(1); 92 | let ugly; 93 | for (let i = 0; i < n; i++) { 94 | // 弹出堆中最小的丑数 95 | ugly = heap.pop(); 96 | for (const factor of factors) { 97 | // 下一个丑数 98 | const next = ugly * factor; 99 | // 判断是否已经加入过了 100 | if (!set.has(next)) { 101 | set.add(next); 102 | heap.insert(next); 103 | } 104 | } 105 | } 106 | // 执行n次,返回第n个丑数 107 | return ugly; 108 | }; 109 | ``` 110 | 111 | -------------------------------------------------------------------------------- /problems/41. 数据流中的中位数.md: -------------------------------------------------------------------------------- 1 | # 题目链接: 2 | 3 | 「[剑指 Offer 41. 数据流中的中位数](https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/)」 4 | 5 | # 思路: 6 | 7 | 8 | 1. 使用两个堆,来存储数据流 9 | 10 | 2. 使用**最小堆**`min`保存较大的一半,**最大堆**`max`保存较小的一半 11 | 12 | 3. 若总长度N为奇数,则**最小堆**个数为`(N+1)/2`,**最大堆**个数为`(N-1)/2` 13 | 14 | 4. 若总长度N为偶数,则最小堆个数为`N/2`,最大堆个数为`N/2` 15 | 16 | 5. 插入操作:当N为偶数,需要向`max`添加一个元素,先将`num`插入`min`,再将`min`堆顶弹出,插入`max` 17 | 18 | 6. 插入操作:当N为奇数,需要向`min`添加一个元素,先将`num`插入`max`,再将`max`堆顶弹出,插入`min` 19 | 20 | 7. 中位数:可以由最小堆和最大堆的堆顶元素得到,若N为奇数,中位数=`最大堆的堆顶`;若N为偶数,则中位数=`(最小堆的堆顶+最大堆的堆顶)/2` 21 | 22 | 8. 插入的顺序,也可以换一下。但是最后取中位数时,奇数的情况也要改变 23 | 24 | # 代码: 25 | 26 | ## JavaScript 27 | 28 | ```javascript 29 | // 默认最大堆 30 | const defaultCmp = (x, y) => x > y; 31 | // 交换元素 32 | const swap = (arr, i, j) => ([arr[i], arr[j]] = [arr[j], arr[i]]); 33 | // 堆类,默认最大堆 34 | class Heap { 35 | constructor(cmp = defaultCmp) { 36 | this.container = []; 37 | this.cmp = cmp; 38 | } 39 | // 插入 40 | insert(data) { 41 | const { container, cmp } = this; 42 | container.push(data); 43 | let index = this.size() - 1; 44 | while (index) { 45 | let parent = (index - 1) >> 1; 46 | if (!cmp(container[index], container[parent])) { 47 | return; 48 | } 49 | swap(container, index, parent); 50 | index = parent; 51 | } 52 | } 53 | // 弹出堆顶,并返回 54 | pop() { 55 | const { container, cmp } = this; 56 | if (!this.size()) { 57 | return null; 58 | } 59 | 60 | swap(container, 0, this.size() - 1); 61 | const res = container.pop(); 62 | const length = this.size(); 63 | let index = 0, 64 | exchange = index * 2 + 1; 65 | 66 | while (exchange < length) { 67 | // // 以最大堆的情况来说:如果有右节点,并且右节点的值大于左节点的值 68 | let right = index * 2 + 2; 69 | if (right < length && cmp(container[right], container[exchange])) { 70 | exchange = right; 71 | } 72 | if (!cmp(container[exchange], container[index])) { 73 | break; 74 | } 75 | swap(container, exchange, index); 76 | index = exchange; 77 | exchange = index * 2 + 1; 78 | } 79 | 80 | return res; 81 | } 82 | // 获取堆大小 83 | size() { 84 | return this.container.length; 85 | } 86 | // 获取堆顶 87 | peek() { 88 | if (this.size()) return this.container[0]; 89 | return null; 90 | } 91 | } 92 | 93 | class MedianFinder { 94 | constructor() { 95 | // 最小堆 96 | this.min = new Heap((x, y) => x < y); 97 | // 最大堆 98 | this.max = new Heap(); 99 | } 100 | addNum(num) { 101 | if (this.max.size() !== this.min.size()) { 102 | // 当N为奇数,需要向min添加一个元素 103 | // 先将num插入max,再将max堆顶弹出,插入min 104 | this.max.insert(num); 105 | this.min.insert(this.max.pop()); 106 | } else { 107 | // 当N为偶数,需要向max添加一个元素 108 | // 先将num插入min,再将min堆顶弹出,插入max 109 | this.min.insert(num); 110 | this.max.insert(this.min.pop()); 111 | } 112 | } 113 | findMedian() { 114 | return this.max.container.length === this.min.container.length 115 | ? (this.max.peek() + this.min.peek()) / 2 116 | : this.max.peek(); 117 | } 118 | } 119 | ``` 120 | 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 😀该仓库用来整理 LeetCode 热门面试题,方便归纳复习,目前已整理《剑指Offer》版本! 2 | 3 | 💪由于能力有限,解题思路可能不是最优解,**若有不足,欢迎指出~** 4 | 5 | ⌨️所有源代码放在`code`文件夹中! 6 | 7 | 🖨️整理的思路放在`problems`文件夹中,正在更新中... 8 | 9 | 🤣欢迎小伙伴们提供自己的思路、或是其他语言版本的代码,共同交流,一起进步! 10 | 11 | ## 1. 二分查找 12 | 13 | | 题目 | 难度 | 14 | | :----------------------------------------------------------: | :--: | 15 | | [剑指 Offer 04. 二维数组中的查找](https://github.com/lzxjack/coding-interviews/blob/master/problems/04.%20%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9F%A5%E6%89%BE.md) | 中等 | 16 | | [剑指 Offer 11. 旋转数组的最小数字](https://github.com/lzxjack/coding-interviews/blob/master/problems/11.%20%E6%97%8B%E8%BD%AC%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B0%E5%AD%97.md) | 简单 | 17 | | [剑指 Offer 53 - I. 在排序数组中查找数字 I](https://github.com/lzxjack/coding-interviews/blob/master/problems/53%20-%20I.%20%E5%9C%A8%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E6%95%B0%E5%AD%97%20I.md) | 简单 | 18 | | [剑指 Offer 53 - II. 0~n-1中缺失的数字](https://github.com/lzxjack/coding-interviews/blob/master/problems/53%20-%20II.%200%EF%BD%9En-1%E4%B8%AD%E7%BC%BA%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 简单 | 19 | 20 | ## 2. 双指针 21 | 22 | | 题目 | 难度 | 23 | | :----------------------------------------------------------: | :--: | 24 | | [剑指 Offer 05. 替换空格](https://github.com/lzxjack/coding-interviews/blob/master/problems/05.%20%E6%9B%BF%E6%8D%A2%E7%A9%BA%E6%A0%BC.md) | 简单 | 25 | | [剑指 Offer 21. 调整数组顺序使奇数位于偶数前面](https://github.com/lzxjack/coding-interviews/blob/master/problems/21.%20%E8%B0%83%E6%95%B4%E6%95%B0%E7%BB%84%E9%A1%BA%E5%BA%8F%E4%BD%BF%E5%A5%87%E6%95%B0%E4%BD%8D%E4%BA%8E%E5%81%B6%E6%95%B0%E5%89%8D%E9%9D%A2.md) | 简单 | 26 | | [剑指 Offer 22. 链表中倒数第k个节点](https://github.com/lzxjack/coding-interviews/blob/master/problems/22.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E5%80%92%E6%95%B0%E7%AC%ACk%E4%B8%AA%E8%8A%82%E7%82%B9.md) | 简单 | 27 | | [剑指 Offer 25. 合并两个排序的链表](https://github.com/lzxjack/coding-interviews/blob/master/problems/25.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%8E%92%E5%BA%8F%E7%9A%84%E9%93%BE%E8%A1%A8.md) | 简单 | 28 | | [剑指 Offer 57. 和为s的两个数字](https://github.com/lzxjack/coding-interviews/blob/master/problems/57.%20%E5%92%8C%E4%B8%BAs%E7%9A%84%E4%B8%A4%E4%B8%AA%E6%95%B0%E5%AD%97.md) | 简单 | 29 | | [剑指 Offer 57 - II. 和为s的连续正数序列](https://github.com/lzxjack/coding-interviews/blob/master/problems/57%20-%20II.%20%E5%92%8C%E4%B8%BAs%E7%9A%84%E8%BF%9E%E7%BB%AD%E6%AD%A3%E6%95%B0%E5%BA%8F%E5%88%97.md) | 简单 | 30 | 31 | ## 3. Map() 32 | 33 | | 题目 | 难度 | 34 | | :----------------------------------------------------------: | :--: | 35 | | [剑指 Offer 50. 第一个只出现一次的字符](https://github.com/lzxjack/coding-interviews/blob/master/problems/50.%20%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E5%AD%97%E7%AC%A6.md) | 简单 | 36 | | [剑指 Offer 56 - II. 数组中数字出现的次数 II](https://github.com/lzxjack/coding-interviews/blob/master/problems/56%20-%20II.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E6%95%B0%E5%AD%97%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0%20II.md) | 中等 | 37 | 38 | ## 4. 链表 39 | 40 | | 题目 | 难度 | 41 | | :----------------------------------------------------------: | :---: | 42 | | [剑指 Offer 06. 从尾到头打印链表](https://github.com/lzxjack/coding-interviews/blob/master/problems/06.%20%E4%BB%8E%E5%B0%BE%E5%88%B0%E5%A4%B4%E6%89%93%E5%8D%B0%E9%93%BE%E8%A1%A8.md) | 简单 | 43 | | [剑指 Offer 18. 删除链表的节点](https://github.com/lzxjack/coding-interviews/blob/master/problems/18.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E8%8A%82%E7%82%B9.md) | 简单 | 44 | | [剑指 Offer 24. 反转链表](https://github.com/lzxjack/coding-interviews/blob/master/problems/24.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 简单 | 45 | | [剑指 Offer 35. 复杂链表的复制](https://github.com/lzxjack/coding-interviews/blob/master/problems/35.%20%E5%A4%8D%E6%9D%82%E9%93%BE%E8%A1%A8%E7%9A%84%E5%A4%8D%E5%88%B6.md) | 中等 | 46 | | [剑指 Offer 36. 二叉搜索树与双向链表](https://github.com/lzxjack/coding-interviews/blob/master/problems/36.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%8E%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md) | *中等 | 47 | | [剑指 Offer 52. 两个链表的第一个公共节点](https://github.com/lzxjack/coding-interviews/blob/master/problems/52.%20%E4%B8%A4%E4%B8%AA%E9%93%BE%E8%A1%A8%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%85%AC%E5%85%B1%E8%8A%82%E7%82%B9.md) | 简单 | 48 | 49 | ## 5. 递归 50 | 51 | | 题目 | 难度 | 52 | | :----------------------------------------------------------: | :--: | 53 | | [剑指 Offer 07. 重建二叉树](https://github.com/lzxjack/coding-interviews/blob/master/problems/07.%20%E9%87%8D%E5%BB%BA%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 中等 | 54 | | [剑指 Offer 16. 数值的整数次方](https://github.com/lzxjack/coding-interviews/blob/master/problems/16.%20%E6%95%B0%E5%80%BC%E7%9A%84%E6%95%B4%E6%95%B0%E6%AC%A1%E6%96%B9.md) | 中等 | 55 | | [剑指 Offer 26. 树的子结构](https://github.com/lzxjack/coding-interviews/blob/master/problems/26.%20%E6%A0%91%E7%9A%84%E5%AD%90%E7%BB%93%E6%9E%84.md) | 中等 | 56 | | [剑指 Offer 27. 二叉树的镜像](https://github.com/lzxjack/coding-interviews/blob/master/problems/27.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%95%9C%E5%83%8F.md) | 简单 | 57 | | [剑指 Offer 28. 对称的二叉树](https://github.com/lzxjack/coding-interviews/blob/master/problems/28.%20%E5%AF%B9%E7%A7%B0%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 简单 | 58 | | [剑指 Offer 33. 二叉搜索树的后序遍历序列](https://github.com/lzxjack/coding-interviews/blob/master/problems/33.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97.md) | 中等 | 59 | | [剑指 Offer 38. 字符串的排列](https://github.com/lzxjack/coding-interviews/blob/master/problems/38.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97.md) | 中等 | 60 | | [剑指 Offer 55 - II. 平衡二叉树](https://github.com/lzxjack/coding-interviews/blob/master/problems/55%20-%20II.%20%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 简单 | 61 | | [剑指 Offer 64. 求1+2+…+n](https://github.com/lzxjack/coding-interviews/blob/master/problems/64.%20%E6%B1%821%2B2%2B%E2%80%A6%2Bn.md) | 中等 | 62 | | [剑指 Offer 68 - II. 二叉树的最近公共祖先](https://github.com/lzxjack/coding-interviews/blob/master/problems/68%20-%20II.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md) | 简单 | 63 | 64 | ## 6. 栈、队列 65 | 66 | | 题目 | 难度 | 67 | | :----------------------------------------------------------: | :--: | 68 | | [剑指 Offer 09. 用两个栈实现队列](https://github.com/lzxjack/coding-interviews/blob/master/problems/09.%20%E7%94%A8%E4%B8%A4%E4%B8%AA%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md) | 简单 | 69 | | [剑指 Offer 30. 包含min函数的栈](https://github.com/lzxjack/coding-interviews/blob/master/problems/30.%20%E5%8C%85%E5%90%ABmin%E5%87%BD%E6%95%B0%E7%9A%84%E6%A0%88.md) | 简单 | 70 | | [剑指 Offer 31. 栈的压入、弹出序列](https://github.com/lzxjack/coding-interviews/blob/master/problems/31.%20%E6%A0%88%E7%9A%84%E5%8E%8B%E5%85%A5%E3%80%81%E5%BC%B9%E5%87%BA%E5%BA%8F%E5%88%97.md) | 中等 | 71 | 72 | ## 7. 动态规划 73 | 74 | | 题目 | 难度 | 75 | | :----------------------------------------------------------: | :--: | 76 | | [剑指 Offer 10- I. 斐波那契数列](https://github.com/lzxjack/coding-interviews/blob/master/problems/10-%20I.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97.md) | 简单 | 77 | | [剑指 Offer 10- II. 青蛙跳台阶问题](https://github.com/lzxjack/coding-interviews/blob/master/problems/10-%20II.%20%E9%9D%92%E8%9B%99%E8%B7%B3%E5%8F%B0%E9%98%B6%E9%97%AE%E9%A2%98.md) | 简单 | 78 | | [剑指 Offer 14- I. 剪绳子](https://github.com/lzxjack/coding-interviews/blob/master/problems/14-%20I.%20%E5%89%AA%E7%BB%B3%E5%AD%90.md) | 中等 | 79 | | [剑指 Offer 46. 把数字翻译成字符串](https://github.com/lzxjack/coding-interviews/blob/master/problems/46.%20%E6%8A%8A%E6%95%B0%E5%AD%97%E7%BF%BB%E8%AF%91%E6%88%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 中等 | 80 | | [剑指 Offer 47. 礼物的最大价值](https://github.com/lzxjack/coding-interviews/blob/master/problems/47.%20%E7%A4%BC%E7%89%A9%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC.md) | 中等 | 81 | | [剑指 Offer 63. 股票的最大利润](https://github.com/lzxjack/coding-interviews/blob/master/problems/63.%20%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E5%A4%A7%E5%88%A9%E6%B6%A6.md) | 中等 | 82 | 83 | ## 8. 贪心算法 84 | 85 | | 题目 | 难度 | 86 | | :----------------------------------------------------------: | :--: | 87 | | [剑指 Offer 14- II. 剪绳子 II](https://github.com/lzxjack/coding-interviews/blob/master/problems/14-%20II.%20%E5%89%AA%E7%BB%B3%E5%AD%90%20II.md) | 中等 | 88 | | [剑指 Offer 42. 连续子数组的最大和](https://github.com/lzxjack/coding-interviews/blob/master/problems/42.%20%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md) | 简单 | 89 | | [剑指 Offer 63. 股票的最大利润](https://github.com/lzxjack/coding-interviews/blob/master/problems/63.%20%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E5%A4%A7%E5%88%A9%E6%B6%A6.md) | 中等 | 90 | 91 | ## 9. 回溯算法 92 | 93 | | 题目 | 难度 | 94 | | :----------------------------------------------------------: | :--: | 95 | | [剑指 Offer 34. 二叉树中和为某一值的路径](https://github.com/lzxjack/coding-interviews/blob/master/problems/34.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%92%8C%E4%B8%BA%E6%9F%90%E4%B8%80%E5%80%BC%E7%9A%84%E8%B7%AF%E5%BE%84.md) | 中等 | 96 | 97 | ## 10. DFS深度优先遍历 98 | 99 | | 题目 | 难度 | 100 | | :----------------------------------------------------------: | :--: | 101 | | [剑指 Offer 12. 矩阵中的路径](https://github.com/lzxjack/coding-interviews/blob/master/problems/12.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E8%B7%AF%E5%BE%84.md) | 中等 | 102 | | [剑指 Offer 13. 机器人的运动范围](https://github.com/lzxjack/coding-interviews/blob/master/problems/13.%20%E6%9C%BA%E5%99%A8%E4%BA%BA%E7%9A%84%E8%BF%90%E5%8A%A8%E8%8C%83%E5%9B%B4.md) | 中等 | 103 | 104 | ## 11. 位运算、数学、处理技巧 105 | 106 | | 题目 | 难度 | 107 | | :----------------------------------------------------------: | :--: | 108 | | [剑指 Offer 03. 数组中重复的数字](https://github.com/lzxjack/coding-interviews/blob/master/problems/03.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E5%AD%97.md) | 简单 | 109 | | [剑指 Offer 15. 二进制中1的个数](https://github.com/lzxjack/coding-interviews/blob/master/problems/15.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%B8%AD1%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 简单 | 110 | | [剑指 Offer 17. 打印从1到最大的n位数](https://github.com/lzxjack/coding-interviews/blob/master/problems/17.%20%E6%89%93%E5%8D%B0%E4%BB%8E1%E5%88%B0%E6%9C%80%E5%A4%A7%E7%9A%84n%E4%BD%8D%E6%95%B0.md) | 简单 | 111 | | [剑指 Offer 39. 数组中出现次数超过一半的数字](https://github.com/lzxjack/coding-interviews/blob/master/problems/39.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E8%B6%85%E8%BF%87%E4%B8%80%E5%8D%8A%E7%9A%84%E6%95%B0%E5%AD%97.md) | 简单 | 112 | | [剑指 Offer 56 - I. 数组中数字出现的次数](https://github.com/lzxjack/coding-interviews/blob/master/problems/56%20-%20I.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E6%95%B0%E5%AD%97%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0.md) | 中等 | 113 | | [剑指 Offer 61. 扑克牌中的顺子](https://github.com/lzxjack/coding-interviews/blob/master/problems/61.%20%E6%89%91%E5%85%8B%E7%89%8C%E4%B8%AD%E7%9A%84%E9%A1%BA%E5%AD%90.md) | 简单 | 114 | | [剑指 Offer 62. 圆圈中最后剩下的数字](https://github.com/lzxjack/coding-interviews/blob/master/problems/62.%20%E5%9C%86%E5%9C%88%E4%B8%AD%E6%9C%80%E5%90%8E%E5%89%A9%E4%B8%8B%E7%9A%84%E6%95%B0%E5%AD%97.md) | 简单 | 115 | | [剑指 Offer 65. 不用加减乘除做加法](https://github.com/lzxjack/coding-interviews/blob/master/problems/65.%20%E4%B8%8D%E7%94%A8%E5%8A%A0%E5%87%8F%E4%B9%98%E9%99%A4%E5%81%9A%E5%8A%A0%E6%B3%95.md) | 简单 | 116 | | [剑指 Offer 66. 构建乘积数组](https://github.com/lzxjack/coding-interviews/blob/master/problems/66.%20%E6%9E%84%E5%BB%BA%E4%B9%98%E7%A7%AF%E6%95%B0%E7%BB%84.md) | 中等 | 117 | 118 | ## 12. 矩阵 119 | 120 | | 题目 | 难度 | 121 | | :----------------------------------------------------------: | :--: | 122 | | [剑指 Offer 29. 顺时针打印矩阵](https://github.com/lzxjack/coding-interviews/blob/master/problems/29.%20%E9%A1%BA%E6%97%B6%E9%92%88%E6%89%93%E5%8D%B0%E7%9F%A9%E9%98%B5.md) | 简单 | 123 | 124 | ## 13. 树 125 | 126 | | 题目 | 难度 | 127 | | :----------------------------------------------------------: | :--: | 128 | | [剑指 Offer 32 - I. 从上到下打印二叉树](https://github.com/lzxjack/coding-interviews/blob/master/problems/32%20-%20I.%20%E4%BB%8E%E4%B8%8A%E5%88%B0%E4%B8%8B%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 中等 | 129 | | [剑指 Offer 32 - II. 从上到下打印二叉树 II](https://github.com/lzxjack/coding-interviews/blob/master/problems/32%20-%20II.%20%E4%BB%8E%E4%B8%8A%E5%88%B0%E4%B8%8B%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91%20II.md) | 简单 | 130 | | [剑指 Offer 32 - III. 从上到下打印二叉树 III](https://github.com/lzxjack/coding-interviews/blob/master/problems/32%20-%20III.%20%E4%BB%8E%E4%B8%8A%E5%88%B0%E4%B8%8B%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91%20III.md) | 中等 | 131 | | [剑指 Offer 54. 二叉搜索树的第k大节点](https://github.com/lzxjack/coding-interviews/blob/master/problems/54.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E7%AC%ACk%E5%A4%A7%E8%8A%82%E7%82%B9.md) | 简单 | 132 | | [剑指 Offer 55 - I. 二叉树的深度](https://github.com/lzxjack/coding-interviews/blob/master/problems/55%20-%20I.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%B7%B1%E5%BA%A6.md) | 简单 | 133 | | [剑指 Offer 68 - I. 二叉搜索树的最近公共祖先](https://github.com/lzxjack/coding-interviews/blob/master/problems/68%20-%20I.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md) | 简单 | 134 | 135 | ## 14. 堆 136 | 137 | | 题目 | 难度 | 138 | | :----------------------------------------------------------: | :--: | 139 | | [剑指 Offer 40. 最小的k个数](https://github.com/lzxjack/coding-interviews/blob/master/problems/40.%20%E6%9C%80%E5%B0%8F%E7%9A%84k%E4%B8%AA%E6%95%B0.md) | 简单 | 140 | | [剑指 Offer 41. 数据流中的中位数](https://github.com/lzxjack/coding-interviews/blob/master/problems/41.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 困难 | 141 | | [剑指 Offer 49. 丑数](https://github.com/lzxjack/coding-interviews/blob/master/problems/49.%20%E4%B8%91%E6%95%B0.md) | 中等 | 142 | 143 | ## 15. 滑动窗口 144 | 145 | | 题目 | 难度 | 146 | | :----------------------------------------------------------: | :--: | 147 | | [剑指 Offer 48. 最长不含重复字符的子字符串](https://github.com/lzxjack/coding-interviews/blob/master/problems/48.%20%E6%9C%80%E9%95%BF%E4%B8%8D%E5%90%AB%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 中等 | 148 | 149 | ## 16. 排序 150 | 151 | | 题目 | 难度 | 152 | | :----------------------------------------------------------: | :--: | 153 | | [剑指 Offer 45. 把数组排成最小的数](https://github.com/lzxjack/coding-interviews/blob/master/problems/45.%20%E6%8A%8A%E6%95%B0%E7%BB%84%E6%8E%92%E6%88%90%E6%9C%80%E5%B0%8F%E7%9A%84%E6%95%B0.md) | 中等 | 154 | | [剑指 Offer 51. 数组中的逆序对](https://github.com/lzxjack/coding-interviews/blob/master/problems/51.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%80%86%E5%BA%8F%E5%AF%B9.md) | 困难 | 155 | 156 | ## 17. 单调栈 157 | 158 | | 题目 | 难度 | 159 | | :----------------------------------------------------------: | :--: | 160 | | [剑指 Offer 59 - I. 滑动窗口的最大值](https://github.com/lzxjack/coding-interviews/blob/master/problems/59%20-%20I.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 困难 | 161 | | [剑指 Offer 59 - II. 队列的最大值](https://github.com/lzxjack/coding-interviews/blob/master/problems/59%20-%20II.%20%E9%98%9F%E5%88%97%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 中等 | 162 | 163 | ## 其他(简单) 164 | 165 | | 题目 | 难度 | 166 | | :----------------------------------------------------------: | :--: | 167 | | [剑指 Offer 58 - I. 翻转单词顺序](https://github.com/lzxjack/coding-interviews/blob/master/problems/58%20-%20I.%20%E7%BF%BB%E8%BD%AC%E5%8D%95%E8%AF%8D%E9%A1%BA%E5%BA%8F.md) | 简单 | 168 | | [剑指 Offer 58 - II. 左旋转字符串](https://github.com/lzxjack/coding-interviews/blob/master/problems/58%20-%20II.%20%E5%B7%A6%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 简单 | 169 | | [剑指 Offer 67. 把字符串转换成整数](https://github.com/lzxjack/coding-interviews/blob/master/problems/67.%20%E6%8A%8A%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E6%88%90%E6%95%B4%E6%95%B0.md) | 中等 | --------------------------------------------------------------------------------