├── BFS ├── 100. 相同的树.js ├── 102. 二叉树的层序遍历.js ├── 104. 二叉树的最大深度.js ├── 111. 二叉树的最小深度.js ├── 1306. 跳跃游戏 III.js ├── 199. 二叉树的右视图.js ├── 45. 跳跃游戏 II.js └── 55. 跳跃游戏.js ├── DFS ├── 100. 相同的树.js ├── 101. 对称二叉树.js ├── 102. 二叉树的层序遍历.js ├── 104. 二叉树的最大深度.js ├── 108. 将有序数组转换为二叉搜索树.js ├── 110. 平衡二叉树.js ├── 111. 二叉树的最小深度.js ├── 112. 路径总和.js ├── 113. 路径总和 II.js ├── 129. 求根节点到叶节点数字之和.js ├── 144. 二叉树的前序遍历.js ├── 199. 二叉树的右视图.js ├── 236. 二叉树的最近公共祖先.js ├── 257. 二叉树的所有路径.js ├── 404. 左叶子之和.js ├── 437. 路径总和 III.js └── 450. 删除二叉搜索树中的节点.js ├── LICENSE ├── LRU 缓存机制 └── 146. LRU 缓存机制.js ├── README.md ├── code.html ├── 二分查找 ├── 50. Pow(x, n).js ├── 69. x 的平方根.js └── 704. 二分查找.js ├── 二叉树 ├── 100. 相同的树.js ├── 101. 对称二叉树.js ├── 102. 二叉树的层序遍历.js ├── 104. 二叉树的最大深度.js ├── 108. 将有序数组转换为二叉搜索树.js ├── 110. 平衡二叉树.js ├── 111. 二叉树的最小深度.js ├── 112. 路径总和.js ├── 113. 路径总和 II.js ├── 129. 求根节点到叶节点数字之和.js ├── 144. 二叉树的前序遍历.js ├── 199. 二叉树的右视图.js ├── 236. 二叉树的最近公共祖先.js ├── 257. 二叉树的所有路径.js ├── 404. 左叶子之和.js ├── 437. 路径总和 III.js └── 450. 删除二叉搜索树中的节点.js ├── 前缀和 └── 560. 和为K的子数组.js ├── 动态规划 ├── 300. 最长递增子序列.js ├── 509. 斐波那契数.js ├── 53. 最大子序和.js ├── 70. 爬楼梯.js └── 718. 最长重复子数组.js ├── 双指针 ├── 125. 验证回文串.js ├── 15. 三数之和.js ├── 16. 最接近的三数之和.js ├── 165. 比较版本号.js ├── 167. 两数之和 II - 输入有序数组.js ├── 240. 搜索二维矩阵 II.js ├── 283. 移动零.js ├── 392. 判断子序列.js ├── 455. 分发饼干.js ├── 524. 通过删除字母匹配到字典里最长单词.js └── 88. 合并两个有序数组.js ├── 排序 ├── 215. 数组中的第K个最大元素.js └── 912. 排序数组.js ├── 查找表 ├── 1. 两数之和.js ├── 350. 两个数组的交集 II.js └── 389. 找不同.js ├── 栈 └── 20. 有效的括号.js ├── 滑动窗口 ├── 209. 长度最小的子数组.js ├── 239. 滑动窗口最大值.js ├── 3. 无重复字符的最长子串.js ├── 438. 找到字符串中所有字母异位词.js └── 76. 最小覆盖子串.js ├── 递归与回溯 ├── 1291. 顺次数.js ├── 131. 分割回文串.js ├── 200. 岛屿数量.js ├── 216. 组合总和 III.js ├── 39. 组合总和.js ├── 40. 组合总和 II.js ├── 401. 二进制手表.js ├── 46. 全排列.js ├── 47. 全排列 II.js ├── 54. 螺旋矩阵.js ├── 59. 螺旋矩阵 II.js ├── 695. 岛屿的最大面积.js ├── 73. 矩阵置零.js ├── 77. 组合.js ├── 78. 子集.js ├── 784. 字母大小写全排列.js ├── 79. 单词搜索.js ├── 90. 子集 II.js ├── 93. 复原 IP 地址.js ├── 980. 不同路径 III.js ├── 剑指 Offer 38. 字符串的排列.js └── 面试题 16.11. 跳水板.js └── 链表 ├── 141. 环形链表.js ├── 19. 删除链表的倒数第 N 个结点.js ├── 2. 两数相加.js ├── 203. 移除链表元素.js ├── 206. 反转链表.js ├── 24. 两两交换链表中的节点.js ├── 92. 反转链表 II.js └── 剑指 Offer 22. 链表中倒数第k个节点.js /BFS/100. 相同的树.js: -------------------------------------------------------------------------------- 1 | // 我个人认为这道题和 101. 对称二叉树 是一样的,只不过我们人为增加了一个 根节点,将 p q 作为左右节点即可 2 | // dfs,和对称二叉树解法一致 3 | var isSameTree = function (p, q) { 4 | return dfs(p, q); 5 | }; 6 | 7 | var dfs = function (left, right) { 8 | if (!left && !right) return true; 9 | if (!left || !right) return false; 10 | if (left.val !== right.val) return false; 11 | 12 | return dfs(left.left, right.left) && dfs(left.right, right.right); 13 | } 14 | 15 | // bfs 16 | // 迭代(队列实现) 17 | var isSameTree = function (p, q) { 18 | if (!p && !q) return true; 19 | 20 | const queue = []; 21 | queue.push(p); 22 | queue.push(q); 23 | 24 | while (queue.length > 0) { 25 | const left = queue.shift(); 26 | const right = queue.shift(); 27 | 28 | if (!left && !right) continue; 29 | if (!left || !right) return false; 30 | if (left.val !== right.val) return false; 31 | 32 | queue.push(left.left); 33 | queue.push(right.left); 34 | queue.push(left.right); 35 | queue.push(right.right); 36 | } 37 | 38 | return true; 39 | }; 40 | -------------------------------------------------------------------------------- /BFS/102. 二叉树的层序遍历.js: -------------------------------------------------------------------------------- 1 | // 就是常规的层序遍历而已 2 | var levelOrder = function (root) { 3 | if (!root) return []; 4 | const res = []; 5 | const queue = [root]; 6 | 7 | while (queue.length > 0) { 8 | const len = queue.length; 9 | const curArr = []; 10 | 11 | for (let i = 0; i < len; i++) { 12 | const node = queue.shift(); 13 | curArr.push(node.val); 14 | 15 | if (node.left) { 16 | queue.push(node.left); 17 | } 18 | if (node.right) { 19 | queue.push(node.right); 20 | } 21 | } 22 | 23 | res.push(curArr); 24 | } 25 | 26 | return res; 27 | }; 28 | 29 | // 就题目的解答来说,使用 dfs 也是可以的,因为结果数组的层数和节点的层是一致 30 | var levelOrder = function (root) { 31 | if (!root) return []; 32 | const res = []; 33 | 34 | var dfs = function (node, level) { 35 | if (!node) return; 36 | 37 | if (!res[level]) { 38 | res[level] = []; 39 | } 40 | res[level].push(node.val); 41 | 42 | dfs(node.left, level + 1); 43 | dfs(node.right, level + 1); 44 | } 45 | 46 | dfs(root, 0) 47 | return res; 48 | }; -------------------------------------------------------------------------------- /BFS/104. 二叉树的最大深度.js: -------------------------------------------------------------------------------- 1 | // 思路和 111. 二叉树的最小深度 其实是一样的 2 | // dfs 3 | var maxDepth = function (root) { 4 | if (!root) return 0; 5 | let res = 0; 6 | 7 | var dfs = function (node, depth) { 8 | if (!node) return; 9 | 10 | depth++; 11 | if (!node.left && !node.right) { 12 | res = Math.max(res, depth); 13 | } 14 | 15 | dfs(node.left, depth); 16 | dfs(node.right, depth); 17 | 18 | depth--; 19 | } 20 | 21 | dfs(root, 0); 22 | return res; 23 | }; 24 | 25 | // 递归 26 | var maxDepth = function (root) { 27 | return root === null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1 28 | }; -------------------------------------------------------------------------------- /BFS/111. 二叉树的最小深度.js: -------------------------------------------------------------------------------- 1 | // dfs,相当于找到每一条路径的叶子结点,找到最小的树深度。 2 | var minDepth = function (root) { 3 | if (!root) return 0; 4 | let res = Infinity; 5 | 6 | var dfs = function (node, depth) { 7 | if (!node) return; 8 | depth++; 9 | 10 | if (!node.left && !node.right) { 11 | res = Math.min(res, depth); 12 | } 13 | 14 | dfs(node.left, depth); 15 | dfs(node.right, depth); 16 | 17 | depth--; 18 | } 19 | 20 | dfs(root, 0); 21 | return res; 22 | }; 23 | 24 | // bfs 25 | var minDepth = function (root) { 26 | if (!root) return 0; 27 | 28 | let res = 0; 29 | const queue = [root]; 30 | 31 | while (queue.length > 0) { 32 | let len = queue.length; 33 | res++; 34 | 35 | while (len > 0) { 36 | const node = queue.shift(); 37 | const left = node.left; 38 | const right = node.right; 39 | 40 | if (!left && !right) return res; 41 | if (left) queue.push(left); 42 | if (right) queue.push(right); 43 | 44 | len--; 45 | } 46 | } 47 | }; -------------------------------------------------------------------------------- /BFS/1306. 跳跃游戏 III.js: -------------------------------------------------------------------------------- 1 | /** 2 | 利用 BFS 的思路,维护一个队列 queue 表示待处理的下标,先把 start 起点放入队列中,然后从起点开始根据起点对应的值分别把左右两边对应的下标放入队列中,不断循环。形象点来说就是每次跳完一格,都把这格对应的左右两边可跳的下标放入队列里,下次继续跳。 3 | 4 | 1. 当左下标小于 0 或者右下标超出数组长度时,就不用放入队列了。 5 | 2. 每次处理完当前的格子后,要用 visited 数组记录下来,下次对于这个处理过的下标就不再放入队列中。 6 | 7 | 如果这个过程中发现了某一格是 0,那么就成功,如果整个循环结束了都没发现,那么就失败。 8 | */ 9 | var canReach = function (arr, start) { 10 | const len = arr.length; 11 | const visited = []; 12 | const queue = [start]; 13 | 14 | while (queue.length) { 15 | const idx = queue.shift(); 16 | const val = arr[idx]; 17 | if (val === 0) return true; 18 | const left = idx - val; 19 | const right = idx + val; 20 | if (left >= 0 && !visited[left]) { 21 | queue.push(left); 22 | } 23 | if (left < len && !visited[right]) { 24 | queue.push(right); 25 | } 26 | visited[idx] = true; 27 | } 28 | 29 | return false; 30 | }; -------------------------------------------------------------------------------- /BFS/199. 二叉树的右视图.js: -------------------------------------------------------------------------------- 1 | // dfs,按 root->right->left 的遍历方式,且将当前层数和结果数组大小比较来确定是否是在当前层级第一次出现。 2 | // 参考:https://leetcode-cn.com/problems/binary-tree-right-side-view/solution/jian-dan-bfsdfs-bi-xu-miao-dong-by-sweetiee/ 3 | var rightSideView = function (root) { 4 | const res = []; 5 | 6 | var dfs = function (node, depth) { 7 | if (!node) return; 8 | 9 | if (depth === res.length) { 10 | res.push(node.val); 11 | } 12 | 13 | dfs(node.right, depth + 1); 14 | dfs(node.left, depth + 1); 15 | } 16 | 17 | dfs(root, 0); 18 | return res; 19 | }; 20 | 21 | // bfs,取遍历到的每一层的最后一个节点的值 22 | var rightSideView = function (root) { 23 | if (!root) return []; 24 | const res = []; 25 | const queue = [root]; 26 | 27 | while (queue.length > 0) { 28 | const len = queue.length; 29 | 30 | for (let i = 0; i < len; i++) { 31 | const node = queue.shift(); 32 | if (node.left) { 33 | queue.push(node.left); 34 | } 35 | if (node.right) { 36 | queue.push(node.right); 37 | } 38 | if (i === len - 1) { 39 | res.push(node.val); 40 | } 41 | } 42 | } 43 | 44 | return res; 45 | }; 46 | -------------------------------------------------------------------------------- /BFS/45. 跳跃游戏 II.js: -------------------------------------------------------------------------------- 1 | // 贪心 2 | // 参考:https://leetcode-cn.com/problems/jump-game-ii/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-10/ 3 | var jump = function (nums) { 4 | let end = 0; 5 | let maxPos = 0; 6 | let steps = 0; 7 | 8 | for (let i = 0; i < nums.length - 1; i++) { 9 | maxPos = Math.max(maxPos, i + nums[i]); 10 | if (i === end) { 11 | if (end === maxPos) return -1; // 不可达到情况 12 | end = maxPos; 13 | steps++; 14 | } 15 | } 16 | 17 | return steps; 18 | }; -------------------------------------------------------------------------------- /BFS/55. 跳跃游戏.js: -------------------------------------------------------------------------------- 1 | // 把每一个元素都当作第一个元素,然后起跳。能跳的距离=当前元素下标+元素值; 2 | // 如果该距离大于或等于最后一个元素下标,说明是能跳到的。 3 | // 不过别忘记要多一个判断,判断前面元素能不能跳到后面的元素。 4 | var canJump = function (nums) { 5 | const lastIndex = nums.length - 1; 6 | let maxStep = 0; 7 | 8 | for (let i = 0; i < nums.length; i++) { 9 | if (i > maxStep) return false; 10 | if (maxStep >= lastIndex) return true; 11 | 12 | maxStep = Math.max(maxStep, i + nums[i]); 13 | } 14 | 15 | return false; 16 | }; 17 | -------------------------------------------------------------------------------- /DFS/100. 相同的树.js: -------------------------------------------------------------------------------- 1 | // 我个人认为这道题和 101. 对称二叉树 是一样的,只不过我们人为增加了一个 根节点,将 p q 作为左右节点即可 2 | // dfs,和对称二叉树解法一致 3 | var isSameTree = function (p, q) { 4 | return dfs(p, q); 5 | }; 6 | 7 | var dfs = function (left, right) { 8 | if (!left && !right) return true; 9 | if (!left || !right) return false; 10 | if (left.val !== right.val) return false; 11 | 12 | return dfs(left.left, right.left) && dfs(left.right, right.right); 13 | } 14 | 15 | // bfs 16 | // 迭代(队列实现) 17 | var isSameTree = function (p, q) { 18 | if (!p && !q) return true; 19 | 20 | const queue = []; 21 | queue.push(p); 22 | queue.push(q); 23 | 24 | while (queue.length > 0) { 25 | const left = queue.shift(); 26 | const right = queue.shift(); 27 | 28 | if (!left && !right) continue; 29 | if (!left || !right) return false; 30 | if (left.val !== right.val) return false; 31 | 32 | queue.push(left.left); 33 | queue.push(right.left); 34 | queue.push(left.right); 35 | queue.push(right.right); 36 | } 37 | 38 | return true; 39 | }; 40 | -------------------------------------------------------------------------------- /DFS/101. 对称二叉树.js: -------------------------------------------------------------------------------- 1 | // 递归 2 | // 参考:https://leetcode-cn.com/problems/symmetric-tree/solution/dong-hua-yan-shi-101-dui-cheng-er-cha-shu-by-user7/ 3 | var isSymmetric = function (root) { 4 | if (!root) return true; 5 | 6 | return dfs(root.left, root.right); 7 | }; 8 | 9 | var dfs = function (left, right) { 10 | if (!left && !right) return true; 11 | if (!left || !right) return false; 12 | if (left.val !== right.val) return false; 13 | 14 | return dfs(left.left, right.right) && dfs(left.right, right.left); 15 | } 16 | 17 | // 迭代(队列实现) 18 | var isSymmetric = function (root) { 19 | if (!root || (!root.left && !root.right)) return true; 20 | 21 | const queue = []; 22 | queue.push(root.left); 23 | queue.push(root.right); 24 | 25 | while (queue.length > 0) { 26 | const left = queue.shift(); 27 | const right = queue.shift(); 28 | 29 | if (!left && !right) continue; 30 | if (!left || !right) return false; 31 | if (left.val !== right.val) return false; 32 | 33 | queue.push(left.left); 34 | queue.push(right.right); 35 | queue.push(left.right); 36 | queue.push(right.left); 37 | } 38 | 39 | return true; 40 | }; 41 | -------------------------------------------------------------------------------- /DFS/102. 二叉树的层序遍历.js: -------------------------------------------------------------------------------- 1 | // 就是常规的层序遍历而已 2 | var levelOrder = function (root) { 3 | if (!root) return []; 4 | const res = []; 5 | const queue = [root]; 6 | 7 | while (queue.length > 0) { 8 | const len = queue.length; 9 | const curArr = []; 10 | 11 | for (let i = 0; i < len; i++) { 12 | const node = queue.shift(); 13 | curArr.push(node.val); 14 | 15 | if (node.left) { 16 | queue.push(node.left); 17 | } 18 | if (node.right) { 19 | queue.push(node.right); 20 | } 21 | } 22 | 23 | res.push(curArr); 24 | } 25 | 26 | return res; 27 | }; 28 | 29 | // 就题目的解答来说,使用 dfs 也是可以的,因为结果数组的层数和节点的曾是一致 30 | var levelOrder = function (root) { 31 | if (!root) return []; 32 | const res = []; 33 | 34 | var dfs = function (node, level) { 35 | if (!node) return; 36 | 37 | if (!res[level]) { 38 | res[level] = []; 39 | } 40 | res[level].push(node.val); 41 | 42 | dfs(node.left, level + 1); 43 | dfs(node.right, level + 1); 44 | } 45 | 46 | dfs(root, 0) 47 | return res; 48 | }; -------------------------------------------------------------------------------- /DFS/104. 二叉树的最大深度.js: -------------------------------------------------------------------------------- 1 | // 思路和 111. 二叉树的最小深度 其实是一样的 2 | // dfs 3 | var maxDepth = function (root) { 4 | if (!root) return 0; 5 | let res = 0; 6 | 7 | var dfs = function (node, depth) { 8 | if (!node) return; 9 | 10 | depth++; 11 | if (!node.left && !node.right) { 12 | res = Math.max(res, depth); 13 | } 14 | 15 | dfs(node.left, depth); 16 | dfs(node.right, depth); 17 | 18 | depth--; 19 | } 20 | 21 | dfs(root, 0); 22 | return res; 23 | }; 24 | 25 | // 递归 26 | var maxDepth = function (root) { 27 | return root === null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1 28 | }; -------------------------------------------------------------------------------- /DFS/108. 将有序数组转换为二叉搜索树.js: -------------------------------------------------------------------------------- 1 | // 二分法核递归,如果是叶子节点,不需要递归。 2 | // 参考:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/solution/tu-jie-er-cha-sou-suo-shu-gou-zao-di-gui-python-go/ 3 | var sortedArrayToBST = function (nums) { 4 | const n = nums.length; 5 | if (!n) return null; 6 | 7 | const mid = Math.floor(n / 2); 8 | const root = new TreeNode(nums[mid]); 9 | 10 | if (mid === 0) return root; 11 | 12 | root.left = sortedArrayToBST(nums.slice(0, mid)); 13 | root.right = sortedArrayToBST(nums.slice(mid + 1, n)); 14 | 15 | return root; 16 | }; 17 | -------------------------------------------------------------------------------- /DFS/110. 平衡二叉树.js: -------------------------------------------------------------------------------- 1 | // ssh 说的方法就蛮不错的。 2 | // https://github.com/sl1673495/leetcode-javascript/issues/56 3 | var isBalanced = function (root) { 4 | if (!root) return true; 5 | 6 | let isSonBalanced = Math.abs(getHeight(root.left) - getHeight(root.right)) <= 1; 7 | return isSonBalanced && isBalanced(root.left) && isBalanced(root.right); 8 | }; 9 | 10 | var getHeight = function (node) { 11 | if (!node) return 0; 12 | return Math.max(getHeight(node.left), getHeight(node.right)) + 1; 13 | } -------------------------------------------------------------------------------- /DFS/111. 二叉树的最小深度.js: -------------------------------------------------------------------------------- 1 | // dfs,相当于找到每一条路径的叶子结点,找到最小的树深度。 2 | var minDepth = function (root) { 3 | if (!root) return 0; 4 | let res = Infinity; 5 | 6 | var dfs = function (node, depth) { 7 | if (!node) return; 8 | depth++; 9 | 10 | if (!node.left && !node.right) { 11 | res = Math.min(res, depth); 12 | } 13 | 14 | dfs(node.left, depth); 15 | dfs(node.right, depth); 16 | 17 | depth--; 18 | } 19 | 20 | dfs(root, 0); 21 | return res; 22 | }; 23 | 24 | // bfs 25 | var minDepth = function (root) { 26 | if (!root) return 0; 27 | 28 | let res = 0; 29 | const queue = [root]; 30 | 31 | while (queue.length > 0) { 32 | let len = queue.length; 33 | res++; 34 | 35 | while (len > 0) { 36 | const node = queue.shift(); 37 | const left = node.left; 38 | const right = node.right; 39 | 40 | if (!left && !right) return res; 41 | if (left) queue.push(left); 42 | if (right) queue.push(right); 43 | 44 | len--; 45 | } 46 | } 47 | }; -------------------------------------------------------------------------------- /DFS/112. 路径总和.js: -------------------------------------------------------------------------------- 1 | // 经过每个节点时判断是否为叶子结点,再做判断和递归 2 | var hasPathSum = function (root, targetSum) { 3 | let resObj = { 4 | flag: false 5 | }; 6 | helper(root, 0, targetSum, resObj); 7 | 8 | return resObj.flag; 9 | }; 10 | 11 | var helper = function (root, preSum, targetSum, resObj) { 12 | if (!root) return; 13 | // 其实这里再加上这个判断,理论上来说应该是更快的,但是实际提交却更慢了,很奇怪。 14 | // if (resObj.flag) return; 15 | 16 | if (!root.left && !root.right) { 17 | if (preSum + root.val === targetSum) { 18 | resObj.flag = true; 19 | return; 20 | } 21 | } 22 | 23 | helper(root.left, preSum + root.val, targetSum, resObj); 24 | helper(root.right, preSum + root.val, targetSum, resObj); 25 | } 26 | 27 | // ssh 的简洁版 28 | var hasPathSum = function (root, sum) { 29 | if (!root) { 30 | return false 31 | } 32 | if (!root.left && !root.right) { 33 | return root.val === sum 34 | } 35 | 36 | return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val) 37 | } -------------------------------------------------------------------------------- /DFS/113. 路径总和 II.js: -------------------------------------------------------------------------------- 1 | // 每条路径到叶子结点后,看下和是否与 targetSum 相等,相等就保留。 2 | var pathSum = function (root, targetSum) { 3 | const res = []; 4 | helper(root, [], 0, targetSum, res); 5 | return res; 6 | }; 7 | 8 | var helper = function (node, paths, sum, targetSum, res) { 9 | if (!node) return; 10 | paths.push(node.val); 11 | sum += node.val; 12 | 13 | if (node.left) helper(node.left, paths, sum, targetSum, res); 14 | if (node.right) helper(node.right, paths, sum, targetSum, res); 15 | 16 | if (!node.left && !node.right) { 17 | if (sum === targetSum) { 18 | res.push(paths.slice()); 19 | } 20 | } 21 | 22 | // 退出当前递归,一定要将这次递归的节点去掉,因为要回到父节点重新开启另一条路径。 23 | paths.pop(); 24 | sum -= node.val; 25 | } -------------------------------------------------------------------------------- /DFS/129. 求根节点到叶节点数字之和.js: -------------------------------------------------------------------------------- 1 | // 递归找到所有路径,添加进一个数组中,最后遍历一遍,转为数字再相加即可。 2 | // 需要注意的是 pathArr push 的时候必须是 curPath 的浅拷贝副本,不然结果会被影响。 3 | var sumNumbers = function (root) { 4 | let res = 0; 5 | const pathArr = []; 6 | 7 | var dfs = function (node, curPath) { 8 | if (!node) return; 9 | 10 | curPath.push(node.val); 11 | if (!node.left && !node.right) { 12 | pathArr.push(curPath.slice()); 13 | } 14 | 15 | dfs(node.left, curPath); 16 | dfs(node.right, curPath); 17 | 18 | curPath.pop(); 19 | } 20 | 21 | dfs(root, []); 22 | 23 | for (let i = 0; i < pathArr.length; i++) { 24 | res += parseInt(pathArr[i].join(''), 10); 25 | } 26 | 27 | return res; 28 | }; 29 | 30 | // 上述代码可优化空间,不用记录在数组中,在判断到是叶节点直接加到 res 中即可。 31 | var sumNumbers = function (root) { 32 | let res = 0; 33 | 34 | var dfs = function (node, curPath) { 35 | if (!node) return; 36 | 37 | curPath.push(node.val); 38 | if (!node.left && !node.right) { 39 | res += parseInt(curPath.slice().join(''), 10); 40 | } 41 | 42 | dfs(node.left, curPath); 43 | dfs(node.right, curPath); 44 | 45 | curPath.pop(); 46 | } 47 | 48 | dfs(root, []); 49 | 50 | return res; 51 | }; -------------------------------------------------------------------------------- /DFS/144. 二叉树的前序遍历.js: -------------------------------------------------------------------------------- 1 | // dfs,很简单 2 | var preorderTraversal = function (root) { 3 | if (!root) return []; 4 | const res = []; 5 | 6 | var dfs = function (node) { 7 | if (!node) return; 8 | 9 | res.push(node.val); 10 | 11 | dfs(node.left); 12 | dfs(node.right); 13 | } 14 | 15 | dfs(root); 16 | return res; 17 | }; 18 | 19 | // 使用 栈 模拟 dfs 过程 20 | // 参考:https://leetcode-cn.com/problems/binary-tree-preorder-traversal/solution/leetcodesuan-fa-xiu-lian-dong-hua-yan-shi-xbian-2/ 21 | var preorderTraversal = function (root) { 22 | if (!root) return []; 23 | const res = []; 24 | const stack = [root]; 25 | 26 | while (stack.length > 0) { 27 | const node = stack.pop(); 28 | res.push(node.val); 29 | 30 | if (node.right) { 31 | stack.push(node.right); 32 | } 33 | if (node.left) { 34 | stack.push(node.left); 35 | } 36 | } 37 | 38 | return res; 39 | }; -------------------------------------------------------------------------------- /DFS/199. 二叉树的右视图.js: -------------------------------------------------------------------------------- 1 | // dfs,按 root->right->left 的遍历方式,且将当前层数和结果数组大小比较来确定是否是在当前层级第一次出现。 2 | // 参考:https://leetcode-cn.com/problems/binary-tree-right-side-view/solution/jian-dan-bfsdfs-bi-xu-miao-dong-by-sweetiee/ 3 | var rightSideView = function (root) { 4 | const res = []; 5 | 6 | var dfs = function (node, depth) { 7 | if (!node) return; 8 | 9 | if (depth === res.length) { 10 | res.push(node.val); 11 | } 12 | 13 | dfs(node.right, depth + 1); 14 | dfs(node.left, depth + 1); 15 | } 16 | 17 | dfs(root, 0); 18 | return res; 19 | }; 20 | 21 | // bfs,取遍历到的每一层的最后一个节点的值 22 | var rightSideView = function (root) { 23 | if (!root) return []; 24 | const res = []; 25 | const queue = [root]; 26 | 27 | while (queue.length > 0) { 28 | const len = queue.length; 29 | 30 | for (let i = 0; i < len; i++) { 31 | const node = queue.shift(); 32 | if (node.left) { 33 | queue.push(node.left); 34 | } 35 | if (node.right) { 36 | queue.push(node.right); 37 | } 38 | if (i === len - 1) { 39 | res.push(node.val); 40 | } 41 | } 42 | } 43 | 44 | return res; 45 | }; 46 | -------------------------------------------------------------------------------- /DFS/236. 二叉树的最近公共祖先.js: -------------------------------------------------------------------------------- 1 | // 递归最简洁写法,有点难理解。 2 | // 参考:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/solution/236-er-cha-shu-de-zui-jin-gong-gong-zu-xian-hou-xu/ 3 | var lowestCommonAncestor = function (root, p, q) { 4 | if (root === null || root === p || root === q) return root; 5 | let left = lowestCommonAncestor(root.left, p, q); 6 | let right = lowestCommonAncestor(root.right, p, q); 7 | if (left === null && right === null) return null; 8 | if (left === null) return right; 9 | if (right === null) return left; 10 | return root; 11 | }; 12 | 13 | // 常规解法,先分别找到 p 和 q 的路径节点入栈,最后一次比较两个栈,最后一个相等的节点返回即可。 14 | // 参考:https://www.bilibili.com/video/BV12Z4y15721?from=search&seid=8814694143711614664 15 | var lowestCommonAncestor = function (root, p, q) { 16 | let pPath = []; 17 | let qPath = []; 18 | let stack = []; 19 | 20 | dfsSearch(root, p, stack, pPath); 21 | stack = []; 22 | dfsSearch(root, q, stack, qPath); 23 | 24 | let resNode = root; 25 | let i = 0; 26 | while (i < pPath.length && i < qPath.length) { 27 | if (pPath[i] === qPath[i]) { 28 | resNode = pPath[i]; 29 | } 30 | i++; 31 | } 32 | return resNode; 33 | }; 34 | 35 | var dfsSearch = function (node, target, stack, path) { 36 | if (node === null) return; 37 | stack.push(node); 38 | 39 | if (node === target) { 40 | // 这里注意不要直接 path = stack,这样会改变不到传入的 pPath,因为 path 指向了新的地址 41 | for (const el of stack) { 42 | path.push(el); 43 | } 44 | return; 45 | } 46 | 47 | dfsSearch(node.left, target, stack, path); 48 | dfsSearch(node.right, target, stack, path); 49 | stack.pop(); 50 | } -------------------------------------------------------------------------------- /DFS/257. 二叉树的所有路径.js: -------------------------------------------------------------------------------- 1 | // 同样是简单的递归,找到路径即可 2 | var binaryTreePaths = function (root) { 3 | const res = []; 4 | 5 | var dfs = function (node, curPath) { 6 | if (!node) return; 7 | curPath.push(node.val); 8 | 9 | if (!node.left && !node.right) { 10 | res.push(curPath.slice().join('->')); 11 | } 12 | 13 | dfs(node.left, curPath); 14 | dfs(node.right, curPath); 15 | 16 | curPath.pop(); 17 | } 18 | 19 | dfs(root, []); 20 | return res; 21 | }; -------------------------------------------------------------------------------- /DFS/404. 左叶子之和.js: -------------------------------------------------------------------------------- 1 | // dfs 去递归的判断目标节点的左节点是否是叶子节点,如果是的话,就把全局的 res 加上目标节点的值。然后继续 DFS 目标节点的左右子节点。 2 | var sumOfLeftLeaves = function (root) { 3 | let res = 0; 4 | 5 | var dfs = function (node) { 6 | if (!node) return; 7 | 8 | if (isLeaf(node.left)) { 9 | res += node.left.val; 10 | } 11 | 12 | dfs(node.left); 13 | dfs(node.right); 14 | } 15 | 16 | dfs(root); 17 | return res; 18 | }; 19 | 20 | var isLeaf = function (node) { 21 | return !!node && !node.left && !node.right 22 | } -------------------------------------------------------------------------------- /DFS/437. 路径总和 III.js: -------------------------------------------------------------------------------- 1 | // 做这道题前一定要先做 560. 和为K的子数组,借助 前缀和 的概念解决此题。 2 | // 参考:https://leetcode-cn.com/problems/path-sum-iii/solution/dui-qian-zhui-he-jie-fa-de-yi-dian-jie-s-dey6/ 3 | var pathSum = function (root, targetSum) { 4 | const map = Object.create(null); 5 | map[0] = 1; 6 | let res = 0; 7 | 8 | var dfs = function (root, prefixSum) { 9 | if (!root) return 0; 10 | 11 | let sum = prefixSum + root.val; 12 | if (map[sum - targetSum] > 0) { 13 | res += map[sum - targetSum]; 14 | } 15 | if (map[sum] > 0) { 16 | map[sum]++; 17 | } else { 18 | map[sum] = 1; 19 | } 20 | 21 | dfs(root.left, sum) 22 | dfs(root.right, sum) 23 | 24 | map[sum]--; 25 | } 26 | 27 | dfs(root, 0); 28 | return res 29 | }; -------------------------------------------------------------------------------- /DFS/450. 删除二叉搜索树中的节点.js: -------------------------------------------------------------------------------- 1 | // 分步骤一个个去判断。 2 | // 参考:https://leetcode-cn.com/problems/delete-node-in-a-bst/solution/miao-dong-jiu-wan-shi-liao-by-terry2020-tc0o/ 3 | var deleteNode = function (root, key) { 4 | if (!root) return null; 5 | 6 | if (key > root.val) { 7 | root.right = deleteNode(root.right, key); 8 | } else if (key < root.val) { 9 | root.left = deleteNode(root.left, key); 10 | } else { 11 | if (!root.left) return root.right; 12 | if (!root.right) return root.left; 13 | 14 | let node = root.right; 15 | while (node.left) { 16 | node = node.left; 17 | } 18 | node.left = root.left; 19 | root = root.right; 20 | } 21 | 22 | return root; 23 | }; 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 chen xin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LRU 缓存机制/146. LRU 缓存机制.js: -------------------------------------------------------------------------------- 1 | // 哈希表 + 双向链表 2 | // 可参考:https://juejin.cn/post/6844904161545289742#heading-12 3 | class DoublyLinkNode { 4 | constructor(key, val) { 5 | this.key = key; 6 | this.val = val; 7 | 8 | this.pre = null; 9 | this.next = null; 10 | } 11 | } 12 | 13 | class LRUCache { 14 | constructor(max) { 15 | this.max = max; 16 | this.map = new Map(); 17 | 18 | this.head = null; 19 | this.tail = null; 20 | } 21 | 22 | get(key) { 23 | const node = this.map.get(key); 24 | if (!node) return -1; 25 | 26 | this.removeNode(node); 27 | this.appendHead(node); 28 | return node.val; 29 | } 30 | 31 | put(key, value) { 32 | let node = this.map.get(key); 33 | // 有这个缓存 34 | if (node) { 35 | node.val = value; 36 | // 被访问到的重新放到头部 37 | this.removeNode(node); 38 | this.appendHead(node); 39 | } else { 40 | // 没有这个缓存 41 | node = new DoublyLinkNode(key, value); 42 | // 如果超出容量,删除最后一个,再将新的插入到头部 43 | if (this.map.size >= this.max) { 44 | this.map.delete(this.tail.key); 45 | this.removeNode(this.tail); 46 | this.appendHead(node); 47 | this.map.set(key, node); 48 | } else { 49 | // 未超出容量,直接将新的插入到头部 50 | this.appendHead(node); 51 | this.map.set(key, node); 52 | } 53 | } 54 | 55 | } 56 | 57 | appendHead(node) { 58 | if (this.head === null) { 59 | this.head = this.tail = node; 60 | } else { 61 | node.next = this.head; 62 | this.head.pre = node; 63 | this.head = node; 64 | } 65 | } 66 | 67 | removeNode(node) { 68 | if (this.head === this.tail) { 69 | this.head = this.tail = null; 70 | } else { 71 | if (this.head === node) { 72 | this.head = node.next; 73 | node.next = null; 74 | } else if (this.tail === node) { 75 | this.tail = node.pre; 76 | this.tail.next = null; 77 | node.pre = null; 78 | } else { 79 | node.pre.next = node.next; 80 | node.next.pre = node.pre; 81 | node.pre = node.next = null; 82 | } 83 | } 84 | } 85 | } 86 | 87 | /** 88 | * Your LRUCache object will be instantiated and called as such: 89 | * var obj = new LRUCache(capacity) 90 | * var param_1 = obj.get(key) 91 | * obj.put(key,value) 92 | */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # leetcode 分类专题刷刷刷 2 | 3 | 这个仓库原来是我私人的刷题库,本来是有解答和一些学习链接的,后来断了一段时间,现在重新捡起。 4 | 5 | 现在根据 [ssh](https://github.com/sl1673495) 大佬的 [leetcode-javascript](https://github.com/sl1673495/leetcode-javascript) 仓库把每个分类下的每个推荐题都刷一下,加强一下自己的算法基础。但是这个仓库有点不好就是,按照 README 的分类顺序去刷是对新手有难度的,所以我会在下面给出正确的刷题顺序,大家只要按着这个顺序走就可以。 6 | 7 | 另外,我会把每个分类在 leetcode 建好对应的公共可访问的收藏夹,大家点击每个下面每个标题旁边的 ⭐️ 就可以打开收藏夹地址。 8 | 9 | ## 二分查找 [⭐️](https://leetcode-cn.com/problem-list/eX9fB8Ea) 10 | 11 | - [50. Pow(x, n)](https://leetcode-cn.com/problems/powx-n/) 12 | - [69. x 的平方根](https://leetcode-cn.com/problems/sqrtx/) 13 | - [704. 二分查找](https://leetcode-cn.com/problems/binary-search/) 14 | 15 | ## 查找表 [⭐️](https://leetcode-cn.com/problem-list/KYFPLOXw) 16 | 17 | - [1. 两数之和](https://leetcode-cn.com/problems/two-sum/) 18 | - [389. 找不同](https://leetcode-cn.com/problems/find-the-difference/) 19 | - [350. 两个数组的交集 II](https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/) 20 | 21 | ## 滑动窗口 [⭐️](https://leetcode-cn.com/problem-list/SELtXHM4) 22 | 23 | - [239. 滑动窗口最大值](https://leetcode-cn.com/problems/sliding-window-maximum/) 24 | - [438. 找到字符串中所有字母异位词](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/) 25 | - [76. 最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/) 26 | - [3. 无重复字符的最长子串](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) 27 | - [209. 长度最小的子数组](https://leetcode-cn.com/problems/minimum-size-subarray-sum/) 28 | 29 | ## 链表 [⭐️](https://leetcode-cn.com/problem-list/y3PLlGWQ) 30 | 31 | - [203. 移除链表元素](https://leetcode-cn.com/problems/remove-linked-list-elements/) 32 | - [2. 两数相加](https://leetcode-cn.com/problems/add-two-numbers/) 33 | - [24. 两两交换链表中的节点](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) 34 | - [剑指 Offer 22. 链表中倒数第 k 个节点](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) 35 | - [19. 删除链表的倒数第 N 个结点](https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/) 36 | - [206. 反转链表](https://leetcode-cn.com/problems/reverse-linked-list/) 37 | - [92. 反转链表 II](https://leetcode-cn.com/problems/reverse-linked-list-ii/) 38 | - [141. 环形链表](https://leetcode-cn.com/problems/linked-list-cycle/) 39 | 40 | ## LRU 缓存机制 [⭐️](https://leetcode-cn.com/problem-list/97TDHRRC) 41 | 42 | - [146. LRU 缓存机制](https://leetcode-cn.com/problems/lru-cache/) 43 | 44 | ## 前缀和 [⭐️](https://leetcode-cn.com/problem-list/kNTVZps1) 45 | 46 | - [560. 和为 K 的子数组](https://leetcode-cn.com/problems/subarray-sum-equals-k/) 47 | 48 | ## 二叉树 [⭐️](https://leetcode-cn.com/problem-list/nSzezqrS) 49 | 50 | - [236. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) 51 | - [108. 将有序数组转换为二叉搜索树](https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/) 52 | - [450. 删除二叉搜索树中的节点](https://leetcode-cn.com/problems/delete-node-in-a-bst/) 53 | - [112. 路径总和](https://leetcode-cn.com/problems/path-sum/) 54 | - [113. 路径总和 II](https://leetcode-cn.com/problems/path-sum-ii/) 55 | - [437. 路径总和 III](https://leetcode-cn.com/problems/path-sum-iii/) 56 | - [129. 求根节点到叶节点数字之和](https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/) 57 | - [257. 二叉树的所有路径](https://leetcode-cn.com/problems/binary-tree-paths/) 58 | - [404. 左叶子之和](https://leetcode-cn.com/problems/sum-of-left-leaves/) 59 | - [110. 平衡二叉树](https://leetcode-cn.com/problems/balanced-binary-tree/) 60 | - [101. 对称二叉树](https://leetcode-cn.com/problems/symmetric-tree/) 61 | - [111. 二叉树的最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/) 62 | - [104. 二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) 63 | - [199. 二叉树的右视图](https://leetcode-cn.com/problems/binary-tree-right-side-view/) 64 | - [144. 二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/) 65 | - [102. 二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 66 | - [100. 相同的树](https://leetcode-cn.com/problems/same-tree/) 67 | 68 | ## DFS [⭐️](https://leetcode-cn.com/problem-list/nSzezqrS) 69 | 70 | DFS 的内容和 二叉树 没什么区别,有心的同学可以再刷一遍? 71 | 72 | - [236. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) 73 | - [108. 将有序数组转换为二叉搜索树](https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/) 74 | - [450. 删除二叉搜索树中的节点](https://leetcode-cn.com/problems/delete-node-in-a-bst/) 75 | - [112. 路径总和](https://leetcode-cn.com/problems/path-sum/) 76 | - [113. 路径总和 II](https://leetcode-cn.com/problems/path-sum-ii/) 77 | - [437. 路径总和 III](https://leetcode-cn.com/problems/path-sum-iii/) 78 | - [129. 求根节点到叶节点数字之和](https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/) 79 | - [257. 二叉树的所有路径](https://leetcode-cn.com/problems/binary-tree-paths/) 80 | - [404. 左叶子之和](https://leetcode-cn.com/problems/sum-of-left-leaves/) 81 | - [110. 平衡二叉树](https://leetcode-cn.com/problems/balanced-binary-tree/) 82 | - [101. 对称二叉树](https://leetcode-cn.com/problems/symmetric-tree/) 83 | - [111. 二叉树的最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/) 84 | - [104. 二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) 85 | - [102. 二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 86 | - [100. 相同的树](https://leetcode-cn.com/problems/same-tree/) 87 | 88 | ## BFS [⭐️](https://leetcode-cn.com/problem-list/cdsPeEX5) 89 | 90 | 以下两题和 BFS 没关系,但是可以先预热一下: 91 | 92 | - [55. 跳跃游戏](https://leetcode-cn.com/problems/jump-game/) 93 | - [45. 跳跃游戏 II](https://leetcode-cn.com/problems/jump-game-ii/) 94 | - [1306. 跳跃游戏 III](https://leetcode-cn.com/problems/jump-game-iii/) 95 | - [111. 二叉树的最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/) 96 | - [104. 二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) 97 | - [199. 二叉树的右视图](https://leetcode-cn.com/problems/binary-tree-right-side-view/) 98 | - [102. 二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 99 | - [100. 相同的树](https://leetcode-cn.com/problems/same-tree/) 100 | 101 | ## 双指针 [⭐️](https://leetcode-cn.com/problem-list/ccZMKZDK) 102 | 103 | - [16. 最接近的三数之和](https://leetcode-cn.com/problems/3sum-closest/) 104 | - [524. 通过删除字母匹配到字典里最长单词](https://leetcode-cn.com/problems/longest-word-in-dictionary-through-deleting/) 105 | - [240. 搜索二维矩阵 II](https://leetcode-cn.com/problems/search-a-2d-matrix-ii/) 106 | - [392. 判断子序列](https://leetcode-cn.com/problems/is-subsequence/) 107 | - [455. 分发饼干](https://leetcode-cn.com/problems/assign-cookies/) 108 | - [125. 验证回文串](https://leetcode-cn.com/problems/valid-palindrome/) 109 | - [167. 两数之和 II - 输入有序数组](https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/) 110 | - [88. 合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/) 111 | - [283. 移动零](https://leetcode-cn.com/problems/move-zeroes/) 112 | 113 | ## 递归与回溯 [⭐️](https://leetcode-cn.com/problem-list/QS1BC1f6) 114 | 115 | - [面试题 16.11. 跳水板](https://leetcode-cn.com/problems/diving-board-lcci/) 116 | - [1291. 顺次数](https://leetcode-cn.com/problems/sequential-digits/) 117 | - [54. 螺旋矩阵](https://leetcode-cn.com/problems/spiral-matrix/) 118 | - [59. 螺旋矩阵 II](https://leetcode-cn.com/problems/spiral-matrix-ii/) 119 | - [73. 矩阵置零](https://leetcode-cn.com/problems/set-matrix-zeroes/) 120 | - [980. 不同路径 III](https://leetcode-cn.com/problems/unique-paths-iii/) 121 | 122 | ### 子集、组合 123 | 124 | - [78. 子集](https://leetcode-cn.com/problems/subsets/) 125 | - [90. 子集 II](https://leetcode-cn.com/problems/subsets-ii/) 126 | - [77. 组合](https://leetcode-cn.com/problems/combinations/) 127 | - [39. 组合总和](https://leetcode-cn.com/problems/combination-sum/) 128 | - [40. 组合总和 II](https://leetcode-cn.com/problems/combination-sum-ii/) 129 | - [216. 组合总和 III](https://leetcode-cn.com/problems/combination-sum-iii/) 130 | - [784. 字母大小写全排列](https://leetcode-cn.com/problems/letter-case-permutation/) 131 | 132 | ### 全排列 133 | 134 | - [46. 全排列](https://leetcode-cn.com/problems/permutations/) 135 | - [47. 全排列 II](https://leetcode-cn.com/problems/permutations-ii/) 136 | - [剑指 Offer 38. 字符串的排列](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/) 137 | 138 | ### 搜索 139 | 140 | - [401. 二进制手表](https://leetcode-cn.com/problems/binary-watch/) 141 | - [79. 单词搜索](https://leetcode-cn.com/problems/word-search/) 142 | - [200. 岛屿数量](https://leetcode-cn.com/problems/number-of-islands/solution/) 143 | - [695. 岛屿的最大面积](https://leetcode-cn.com/problems/max-area-of-island/) 144 | 145 | ### 分割 146 | 147 | - [131. 分割回文串](https://leetcode-cn.com/problems/palindrome-partitioning/) 148 | - [93. 复原 IP 地址](https://leetcode-cn.com/problems/restore-ip-addresses/) 149 | 150 | ## 动态规划 [⭐️](https://leetcode-cn.com/problem-list/l17RJFnN) 151 | 152 | - [509. 斐波那契数](https://leetcode-cn.com/problems/fibonacci-number/) 153 | - [300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/) 154 | - [718. 最长重复子数组](https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/) 155 | - [53. 最大子序和](https://leetcode-cn.com/problems/maximum-subarray/) 156 | 157 | ## 栈 [⭐️](https://leetcode-cn.com/problem-list/zqCRhoIK) 158 | 159 | - [20. 有效的括号.js](https://leetcode-cn.com/problems/valid-parentheses/) 160 | 161 | ## 排序 [⭐️](https://leetcode-cn.com/problem-list/W7hjhmNb) 162 | 163 | - [912. 排序数组](https://leetcode-cn.com/problems/sort-an-array/) 164 | - [215. 数组中的第 K 个最大元素](https://leetcode-cn.com/problems/kth-largest-element-in-an-array/) 165 | -------------------------------------------------------------------------------- /code.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 37 | 38 | 39 |
40 |
41 |
42 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /二分查找/50. Pow(x, n).js: -------------------------------------------------------------------------------- 1 | // 迭代实现快速幂 2 | var myPow = function (x, n) { 3 | let res = 1; 4 | for (let i = Math.abs(n); i !== 0; i = Math.floor(i / 2)) { 5 | if (i % 2 !== 0) { 6 | res *= x; 7 | } 8 | x *= x; 9 | } 10 | return n < 0 ? 1 / res : res; 11 | }; 12 | 13 | // 递归实现快速幂 14 | var myPow = function (x, n) { 15 | if (n === 0) return 1; 16 | if (n === 1) return x; 17 | 18 | const absN = Math.abs(n); 19 | const isNegative = n !== absN; 20 | 21 | let res = absN % 2 ? x * myPow(x, absN - 1) : myPow(x * x, absN / 2) 22 | return isNegative ? 1 / res : res; 23 | }; -------------------------------------------------------------------------------- /二分查找/69. x 的平方根.js: -------------------------------------------------------------------------------- 1 | // 从最小数开始遍历,直到某个数的平方小与等于 x 且 这个数 +1 后再平方大于 x,即可 2 | var mySqrt = function (x) { 3 | for (let i = 1; i < x; i++) { 4 | if (i * i <= x && (i + 1) * (i + 1) > x) { 5 | return i 6 | } 7 | } 8 | 9 | return x; 10 | }; 11 | 12 | // 二分法 13 | var mySqrt = function (x) { 14 | let left = 0; 15 | let right = x; 16 | let ans = -1; 17 | 18 | while (left <= right) { 19 | const mid = Math.round((left + right) / 2); 20 | const temp = mid * mid; 21 | if (temp <= x) { 22 | ans = mid; 23 | left = mid + 1; 24 | } else if (temp > x) { 25 | right = mid - 1; 26 | } 27 | } 28 | 29 | return ans; 30 | }; -------------------------------------------------------------------------------- /二分查找/704. 二分查找.js: -------------------------------------------------------------------------------- 1 | // 二分法,不断的缩小左右区间,总会找到的 2 | var search = function (nums, target) { 3 | let left = 0; 4 | let numsLen = nums.length; 5 | let right = numsLen - 1; 6 | while (left <= right) { 7 | const mid = Math.round((left + right) / 2); 8 | if (nums[mid] < target) { 9 | left = mid + 1; 10 | } else if (nums[mid] > target) { 11 | right = mid - 1; 12 | } else { 13 | return mid; 14 | } 15 | } 16 | 17 | return -1; 18 | }; -------------------------------------------------------------------------------- /二叉树/100. 相同的树.js: -------------------------------------------------------------------------------- 1 | // 我个人认为这道题和 101. 对称二叉树 是一样的,只不过我们人为增加了一个 根节点,将 p q 作为左右节点即可 2 | // dfs,和对称二叉树解法一致 3 | var isSameTree = function (p, q) { 4 | return dfs(p, q); 5 | }; 6 | 7 | var dfs = function (left, right) { 8 | if (!left && !right) return true; 9 | if (!left || !right) return false; 10 | if (left.val !== right.val) return false; 11 | 12 | return dfs(left.left, right.left) && dfs(left.right, right.right); 13 | } 14 | 15 | // bfs 16 | // 迭代(队列实现) 17 | var isSameTree = function (p, q) { 18 | if (!p && !q) return true; 19 | 20 | const queue = []; 21 | queue.push(p); 22 | queue.push(q); 23 | 24 | while (queue.length > 0) { 25 | const left = queue.shift(); 26 | const right = queue.shift(); 27 | 28 | if (!left && !right) continue; 29 | if (!left || !right) return false; 30 | if (left.val !== right.val) return false; 31 | 32 | queue.push(left.left); 33 | queue.push(right.left); 34 | queue.push(left.right); 35 | queue.push(right.right); 36 | } 37 | 38 | return true; 39 | }; 40 | -------------------------------------------------------------------------------- /二叉树/101. 对称二叉树.js: -------------------------------------------------------------------------------- 1 | // 递归 2 | // 参考:https://leetcode-cn.com/problems/symmetric-tree/solution/dong-hua-yan-shi-101-dui-cheng-er-cha-shu-by-user7/ 3 | var isSymmetric = function (root) { 4 | if (!root) return true; 5 | 6 | return dfs(root.left, root.right); 7 | }; 8 | 9 | var dfs = function (left, right) { 10 | if (!left && !right) return true; 11 | if (!left || !right) return false; 12 | if (left.val !== right.val) return false; 13 | 14 | return dfs(left.left, right.right) && dfs(left.right, right.left); 15 | } 16 | 17 | // 迭代(队列实现) 18 | var isSymmetric = function (root) { 19 | if (!root || (!root.left && !root.right)) return true; 20 | 21 | const queue = []; 22 | queue.push(root.left); 23 | queue.push(root.right); 24 | 25 | while (queue.length > 0) { 26 | const left = queue.shift(); 27 | const right = queue.shift(); 28 | 29 | if (!left && !right) continue; 30 | if (!left || !right) return false; 31 | if (left.val !== right.val) return false; 32 | 33 | queue.push(left.left); 34 | queue.push(right.right); 35 | queue.push(left.right); 36 | queue.push(right.left); 37 | } 38 | 39 | return true; 40 | }; 41 | -------------------------------------------------------------------------------- /二叉树/102. 二叉树的层序遍历.js: -------------------------------------------------------------------------------- 1 | // 就是常规的层序遍历而已 2 | var levelOrder = function (root) { 3 | if (!root) return []; 4 | const res = []; 5 | const queue = [root]; 6 | 7 | while (queue.length > 0) { 8 | const len = queue.length; 9 | const curArr = []; 10 | 11 | for (let i = 0; i < len; i++) { 12 | const node = queue.shift(); 13 | curArr.push(node.val); 14 | 15 | if (node.left) { 16 | queue.push(node.left); 17 | } 18 | if (node.right) { 19 | queue.push(node.right); 20 | } 21 | } 22 | 23 | res.push(curArr); 24 | } 25 | 26 | return res; 27 | }; 28 | 29 | // 就题目的解答来说,使用 dfs 也是可以的,因为结果数组的层数和节点的曾是一致 30 | var levelOrder = function (root) { 31 | if (!root) return []; 32 | const res = []; 33 | 34 | var dfs = function (node, level) { 35 | if (!node) return; 36 | 37 | if (!res[level]) { 38 | res[level] = []; 39 | } 40 | res[level].push(node.val); 41 | 42 | dfs(node.left, level + 1); 43 | dfs(node.right, level + 1); 44 | } 45 | 46 | dfs(root, 0) 47 | return res; 48 | }; -------------------------------------------------------------------------------- /二叉树/104. 二叉树的最大深度.js: -------------------------------------------------------------------------------- 1 | // 思路和 111. 二叉树的最小深度 其实是一样的 2 | // dfs 3 | var maxDepth = function (root) { 4 | if (!root) return 0; 5 | let res = 0; 6 | 7 | var dfs = function (node, depth) { 8 | if (!node) return; 9 | 10 | depth++; 11 | if (!node.left && !node.right) { 12 | res = Math.max(res, depth); 13 | } 14 | 15 | dfs(node.left, depth); 16 | dfs(node.right, depth); 17 | 18 | depth--; 19 | } 20 | 21 | dfs(root, 0); 22 | return res; 23 | }; 24 | 25 | // 递归 26 | var maxDepth = function (root) { 27 | return root === null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1 28 | }; -------------------------------------------------------------------------------- /二叉树/108. 将有序数组转换为二叉搜索树.js: -------------------------------------------------------------------------------- 1 | // 二分法核递归,如果是叶子节点,不需要递归。 2 | // 参考:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/solution/tu-jie-er-cha-sou-suo-shu-gou-zao-di-gui-python-go/ 3 | var sortedArrayToBST = function (nums) { 4 | const n = nums.length; 5 | if (!n) return null; 6 | 7 | const mid = Math.floor(n / 2); 8 | const root = new TreeNode(nums[mid]); 9 | 10 | if (mid === 0) return root; 11 | 12 | root.left = sortedArrayToBST(nums.slice(0, mid)); 13 | root.right = sortedArrayToBST(nums.slice(mid + 1, n)); 14 | 15 | return root; 16 | }; 17 | -------------------------------------------------------------------------------- /二叉树/110. 平衡二叉树.js: -------------------------------------------------------------------------------- 1 | // ssh 说的方法就蛮不错的。 2 | // https://github.com/sl1673495/leetcode-javascript/issues/56 3 | var isBalanced = function (root) { 4 | if (!root) return true; 5 | 6 | let isSonBalanced = Math.abs(getHeight(root.left) - getHeight(root.right)) <= 1; 7 | return isSonBalanced && isBalanced(root.left) && isBalanced(root.right); 8 | }; 9 | 10 | var getHeight = function (node) { 11 | if (!node) return 0; 12 | return Math.max(getHeight(node.left), getHeight(node.right)) + 1; 13 | } -------------------------------------------------------------------------------- /二叉树/111. 二叉树的最小深度.js: -------------------------------------------------------------------------------- 1 | // dfs,相当于找到每一条路径的叶子结点,找到最小的树深度。 2 | var minDepth = function (root) { 3 | if (!root) return 0; 4 | let res = Infinity; 5 | 6 | var dfs = function (node, depth) { 7 | if (!node) return; 8 | depth++; 9 | 10 | if (!node.left && !node.right) { 11 | res = Math.min(res, depth); 12 | } 13 | 14 | dfs(node.left, depth); 15 | dfs(node.right, depth); 16 | 17 | depth--; 18 | } 19 | 20 | dfs(root, 0); 21 | return res; 22 | }; 23 | 24 | // bfs 25 | var minDepth = function (root) { 26 | if (!root) return 0; 27 | 28 | let res = 0; 29 | const queue = [root]; 30 | 31 | while (queue.length > 0) { 32 | let len = queue.length; 33 | res++; 34 | 35 | while (len > 0) { 36 | const node = queue.shift(); 37 | const left = node.left; 38 | const right = node.right; 39 | 40 | if (!left && !right) return res; 41 | if (left) queue.push(left); 42 | if (right) queue.push(right); 43 | 44 | len--; 45 | } 46 | } 47 | }; -------------------------------------------------------------------------------- /二叉树/112. 路径总和.js: -------------------------------------------------------------------------------- 1 | // 经过每个节点时判断是否为叶子结点,再做判断和递归 2 | var hasPathSum = function (root, targetSum) { 3 | let resObj = { 4 | flag: false 5 | }; 6 | helper(root, 0, targetSum, resObj); 7 | 8 | return resObj.flag; 9 | }; 10 | 11 | var helper = function (root, preSum, targetSum, resObj) { 12 | if (!root) return; 13 | // 其实这里再加上这个判断,理论上来说应该是更快的,但是实际提交却更慢了,很奇怪。 14 | // if (resObj.flag) return; 15 | 16 | if (!root.left && !root.right) { 17 | if (preSum + root.val === targetSum) { 18 | resObj.flag = true; 19 | return; 20 | } 21 | } 22 | 23 | helper(root.left, preSum + root.val, targetSum, resObj); 24 | helper(root.right, preSum + root.val, targetSum, resObj); 25 | } 26 | 27 | // ssh 的简洁版 28 | var hasPathSum = function (root, sum) { 29 | if (!root) { 30 | return false 31 | } 32 | if (!root.left && !root.right) { 33 | return root.val === sum 34 | } 35 | 36 | return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val) 37 | } -------------------------------------------------------------------------------- /二叉树/113. 路径总和 II.js: -------------------------------------------------------------------------------- 1 | // 每条路径到叶子结点后,看下和是否与 targetSum 相等,相等就保留。 2 | var pathSum = function (root, targetSum) { 3 | const res = []; 4 | helper(root, [], 0, targetSum, res); 5 | return res; 6 | }; 7 | 8 | var helper = function (node, paths, sum, targetSum, res) { 9 | if (!node) return; 10 | paths.push(node.val); 11 | sum += node.val; 12 | 13 | if (node.left) helper(node.left, paths, sum, targetSum, res); 14 | if (node.right) helper(node.right, paths, sum, targetSum, res); 15 | 16 | if (!node.left && !node.right) { 17 | if (sum === targetSum) { 18 | res.push(paths.slice()); 19 | } 20 | } 21 | 22 | // 退出当前递归,一定要将这次递归的节点去掉,因为要回到父节点重新开启另一条路径。 23 | paths.pop(); 24 | sum -= node.val; 25 | } -------------------------------------------------------------------------------- /二叉树/129. 求根节点到叶节点数字之和.js: -------------------------------------------------------------------------------- 1 | // 递归找到所有路径,添加进一个数组中,最后遍历一遍,转为数字再相加即可。 2 | // 需要注意的是 pathArr push 的时候必须是 curPath 的浅拷贝副本,不然结果会被影响。 3 | var sumNumbers = function (root) { 4 | let res = 0; 5 | const pathArr = []; 6 | 7 | var dfs = function (node, curPath) { 8 | if (!node) return; 9 | 10 | curPath.push(node.val); 11 | if (!node.left && !node.right) { 12 | pathArr.push(curPath.slice()); 13 | } 14 | 15 | dfs(node.left, curPath); 16 | dfs(node.right, curPath); 17 | 18 | curPath.pop(); 19 | } 20 | 21 | dfs(root, []); 22 | 23 | for (let i = 0; i < pathArr.length; i++) { 24 | res += parseInt(pathArr[i].join(''), 10); 25 | } 26 | 27 | return res; 28 | }; 29 | 30 | // 上述代码可优化空间,不用记录在数组中,在判断到是叶节点直接加到 res 中即可。 31 | var sumNumbers = function (root) { 32 | let res = 0; 33 | 34 | var dfs = function (node, curPath) { 35 | if (!node) return; 36 | 37 | curPath.push(node.val); 38 | if (!node.left && !node.right) { 39 | res += parseInt(curPath.slice().join(''), 10); 40 | } 41 | 42 | dfs(node.left, curPath); 43 | dfs(node.right, curPath); 44 | 45 | curPath.pop(); 46 | } 47 | 48 | dfs(root, []); 49 | 50 | return res; 51 | }; -------------------------------------------------------------------------------- /二叉树/144. 二叉树的前序遍历.js: -------------------------------------------------------------------------------- 1 | // dfs,很简单 2 | var preorderTraversal = function (root) { 3 | if (!root) return []; 4 | const res = []; 5 | 6 | var dfs = function (node) { 7 | if (!node) return; 8 | 9 | res.push(node.val); 10 | 11 | dfs(node.left); 12 | dfs(node.right); 13 | } 14 | 15 | dfs(root); 16 | return res; 17 | }; 18 | 19 | // 使用 栈 模拟 dfs 过程 20 | // 参考:https://leetcode-cn.com/problems/binary-tree-preorder-traversal/solution/leetcodesuan-fa-xiu-lian-dong-hua-yan-shi-xbian-2/ 21 | var preorderTraversal = function (root) { 22 | if (!root) return []; 23 | const res = []; 24 | const stack = [root]; 25 | 26 | while (stack.length > 0) { 27 | const node = stack.pop(); 28 | res.push(node.val); 29 | 30 | if (node.right) { 31 | stack.push(node.right); 32 | } 33 | if (node.left) { 34 | stack.push(node.left); 35 | } 36 | } 37 | 38 | return res; 39 | }; -------------------------------------------------------------------------------- /二叉树/199. 二叉树的右视图.js: -------------------------------------------------------------------------------- 1 | // dfs,按 root->right->left 的遍历方式,且将当前层数和结果数组大小比较来确定是否是在当前层级第一次出现。 2 | // 参考:https://leetcode-cn.com/problems/binary-tree-right-side-view/solution/jian-dan-bfsdfs-bi-xu-miao-dong-by-sweetiee/ 3 | var rightSideView = function (root) { 4 | const res = []; 5 | 6 | var dfs = function (node, depth) { 7 | if (!node) return; 8 | 9 | if (depth === res.length) { 10 | res.push(node.val); 11 | } 12 | 13 | dfs(node.right, depth + 1); 14 | dfs(node.left, depth + 1); 15 | } 16 | 17 | dfs(root, 0); 18 | return res; 19 | }; 20 | 21 | // bfs,取遍历到的每一层的最后一个节点的值 22 | var rightSideView = function (root) { 23 | if (!root) return []; 24 | const res = []; 25 | const queue = [root]; 26 | 27 | while (queue.length > 0) { 28 | const len = queue.length; 29 | 30 | for (let i = 0; i < len; i++) { 31 | const node = queue.shift(); 32 | if (node.left) { 33 | queue.push(node.left); 34 | } 35 | if (node.right) { 36 | queue.push(node.right); 37 | } 38 | if (i === len - 1) { 39 | res.push(node.val); 40 | } 41 | } 42 | } 43 | 44 | return res; 45 | }; 46 | -------------------------------------------------------------------------------- /二叉树/236. 二叉树的最近公共祖先.js: -------------------------------------------------------------------------------- 1 | // 递归最简洁写法,有点难理解。 2 | // 参考:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/solution/236-er-cha-shu-de-zui-jin-gong-gong-zu-xian-hou-xu/ 3 | var lowestCommonAncestor = function (root, p, q) { 4 | if (root === null || root === p || root === q) return root; 5 | let left = lowestCommonAncestor(root.left, p, q); 6 | let right = lowestCommonAncestor(root.right, p, q); 7 | if (left === null && right === null) return null; 8 | if (left === null) return right; 9 | if (right === null) return left; 10 | return root; 11 | }; 12 | 13 | // 常规解法,先分别找到 p 和 q 的路径节点入栈,最后一次比较两个栈,最后一个相等的节点返回即可。 14 | // 参考:https://www.bilibili.com/video/BV12Z4y15721?from=search&seid=8814694143711614664 15 | var lowestCommonAncestor = function (root, p, q) { 16 | let pPath = []; 17 | let qPath = []; 18 | let stack = []; 19 | 20 | dfsSearch(root, p, stack, pPath); 21 | stack = []; 22 | dfsSearch(root, q, stack, qPath); 23 | 24 | let resNode = root; 25 | let i = 0; 26 | while (i < pPath.length && i < qPath.length) { 27 | if (pPath[i] === qPath[i]) { 28 | resNode = pPath[i]; 29 | } 30 | i++; 31 | } 32 | return resNode; 33 | }; 34 | 35 | var dfsSearch = function (node, target, stack, path) { 36 | if (node === null) return; 37 | stack.push(node); 38 | 39 | if (node === target) { 40 | // 这里注意不要直接 path = stack,这样会改变不到传入的 pPath,因为 path 指向了新的地址 41 | for (const el of stack) { 42 | path.push(el); 43 | } 44 | return; 45 | } 46 | 47 | dfsSearch(node.left, target, stack, path); 48 | dfsSearch(node.right, target, stack, path); 49 | stack.pop(); 50 | } -------------------------------------------------------------------------------- /二叉树/257. 二叉树的所有路径.js: -------------------------------------------------------------------------------- 1 | // 同样是简单的递归,找到路径即可 2 | var binaryTreePaths = function (root) { 3 | const res = []; 4 | 5 | var dfs = function (node, curPath) { 6 | if (!node) return; 7 | curPath.push(node.val); 8 | 9 | if (!node.left && !node.right) { 10 | res.push(curPath.slice().join('->')); 11 | } 12 | 13 | dfs(node.left, curPath); 14 | dfs(node.right, curPath); 15 | 16 | curPath.pop(); 17 | } 18 | 19 | dfs(root, []); 20 | return res; 21 | }; -------------------------------------------------------------------------------- /二叉树/404. 左叶子之和.js: -------------------------------------------------------------------------------- 1 | // dfs 去递归的判断目标节点的左节点是否是叶子节点,如果是的话,就把全局的 res 加上目标节点的值。然后继续 DFS 目标节点的左右子节点。 2 | var sumOfLeftLeaves = function (root) { 3 | let res = 0; 4 | 5 | var dfs = function (node) { 6 | if (!node) return; 7 | 8 | if (isLeaf(node.left)) { 9 | res += node.left.val; 10 | } 11 | 12 | dfs(node.left); 13 | dfs(node.right); 14 | } 15 | 16 | dfs(root); 17 | return res; 18 | }; 19 | 20 | var isLeaf = function (node) { 21 | return !!node && !node.left && !node.right 22 | } -------------------------------------------------------------------------------- /二叉树/437. 路径总和 III.js: -------------------------------------------------------------------------------- 1 | // 做这道题前一定要先做 560. 和为K的子数组,借助 前缀和 的概念解决此题。 2 | // 参考:https://leetcode-cn.com/problems/path-sum-iii/solution/dui-qian-zhui-he-jie-fa-de-yi-dian-jie-s-dey6/ 3 | var pathSum = function (root, targetSum) { 4 | const map = Object.create(null); 5 | map[0] = 1; 6 | let res = 0; 7 | 8 | var dfs = function (root, prefixSum) { 9 | if (!root) return 0; 10 | 11 | let sum = prefixSum + root.val; 12 | if (map[sum - targetSum] > 0) { 13 | res += map[sum - targetSum]; 14 | } 15 | if (map[sum] > 0) { 16 | map[sum]++; 17 | } else { 18 | map[sum] = 1; 19 | } 20 | 21 | dfs(root.left, sum) 22 | dfs(root.right, sum) 23 | 24 | map[sum]--; 25 | } 26 | 27 | dfs(root, 0); 28 | return res 29 | }; -------------------------------------------------------------------------------- /二叉树/450. 删除二叉搜索树中的节点.js: -------------------------------------------------------------------------------- 1 | // 分步骤一个个去判断。 2 | // 参考:https://leetcode-cn.com/problems/delete-node-in-a-bst/solution/miao-dong-jiu-wan-shi-liao-by-terry2020-tc0o/ 3 | var deleteNode = function (root, key) { 4 | if (!root) return null; 5 | 6 | if (key > root.val) { 7 | root.right = deleteNode(root.right, key); 8 | } else if (key < root.val) { 9 | root.left = deleteNode(root.left, key); 10 | } else { 11 | if (!root.left) return root.right; 12 | if (!root.right) return root.left; 13 | 14 | let node = root.right; 15 | while (node.left) { 16 | node = node.left; 17 | } 18 | node.left = root.left; 19 | root = root.right; 20 | } 21 | 22 | return root; 23 | }; 24 | -------------------------------------------------------------------------------- /前缀和/560. 和为K的子数组.js: -------------------------------------------------------------------------------- 1 | // 暴力法,算出所有子数组的和,满足相等的就记录 2 | // 会超时 3 | var subarraySum = (nums, k) => { 4 | let count = 0; 5 | for (let i = 0; i < nums.length; i++) { 6 | let sum = 0; 7 | for (let j = i; j < nums.length; j++) { 8 | sum += nums[j]; 9 | if (sum == k) count++; 10 | } 11 | } 12 | return count; 13 | }; 14 | 15 | // 前缀和 16 | // 参考:https://leetcode-cn.com/problems/subarray-sum-equals-k/solution/dai-ni-da-tong-qian-zhui-he-cong-zui-ben-fang-fa-y/ 17 | var subarraySum = (nums, k) => { 18 | let count = 0; 19 | let prefixSum = 0; 20 | const map = Object.create(null); 21 | map[0] = 1; 22 | 23 | for (let i = 0; i < nums.length; i++) { 24 | prefixSum += nums[i]; 25 | if (map[prefixSum - k] > 0) { 26 | count += map[prefixSum - k]; 27 | } 28 | if (map[prefixSum] > 0) { 29 | map[prefixSum]++; 30 | } else { 31 | map[prefixSum] = 1; 32 | } 33 | } 34 | 35 | return count; 36 | }; 37 | -------------------------------------------------------------------------------- /动态规划/300. 最长递增子序列.js: -------------------------------------------------------------------------------- 1 | // 经典 dp 2 | // 参考:https://github.com/labuladong/fucking-algorithm/blob/master/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%B3%BB%E5%88%97/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E8%AE%BE%E8%AE%A1%EF%BC%9A%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.md 3 | var lengthOfLIS = function (nums) { 4 | const dp = new Array(nums.length).fill(1); 5 | 6 | for (let i = 0; i < nums.length; i++) { 7 | for (let j = 0; j < i; j++) { 8 | if (nums[i] > nums[j]) { 9 | dp[i] = Math.max(dp[i], dp[j] + 1); 10 | } 11 | } 12 | } 13 | 14 | let res = 0; 15 | for (let i = 0; i < dp.length; i++) { 16 | res = Math.max(dp[i], res); 17 | } 18 | 19 | return res; 20 | }; -------------------------------------------------------------------------------- /动态规划/509. 斐波那契数.js: -------------------------------------------------------------------------------- 1 | // 经典递归 2 | var fib = function (n) { 3 | if (n <= 0) return 0; 4 | if (n === 1) return 1; 5 | 6 | return fib(n - 1) + fib(n - 2); 7 | }; 8 | 9 | // dp 10 | // 参考:https://leetcode-cn.com/problems/fibonacci-number/solution/dai-ma-sui-xiang-lu-509-fei-bo-na-qi-shu-n389/ 11 | var fib = function (n) { 12 | if (n <= 1) return n; 13 | const dp = []; 14 | dp[0] = 0; 15 | dp[1] = 1; 16 | for (let i = 2; i <= n; i++) { 17 | dp[i] = dp[i - 1] + dp[i - 2]; 18 | } 19 | return dp[n]; 20 | }; -------------------------------------------------------------------------------- /动态规划/53. 最大子序和.js: -------------------------------------------------------------------------------- 1 | // 贪心 2 | var maxSubArray = function (nums) { 3 | let res = nums[0]; 4 | let sum = 0; 5 | 6 | for (let i = 0; i < nums.length; i++) { 7 | if (sum > 0) { 8 | sum += nums[i]; 9 | } else { 10 | sum = nums[i]; 11 | } 12 | res = Math.max(sum, res); 13 | } 14 | 15 | return res; 16 | }; 17 | 18 | // dp 19 | var maxSubArray = function (nums) { 20 | const len = nums.length; 21 | const dp = [nums[0]]; 22 | let max = nums[0]; 23 | 24 | for (let i = 1; i < len; i++) { 25 | dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]); 26 | max = Math.max(max, dp[i]); 27 | } 28 | 29 | return max; 30 | }; 31 | 32 | // 延伸,求最大子序列,也就是输出最大子序列和的连续子数组。 33 | var maxSubArray = function (nums) { 34 | let res = nums[0]; 35 | let sum = 0; 36 | let left = 0; 37 | let right = 0; 38 | let finalLeft = 0; 39 | let finalRight = 0; 40 | 41 | for (let i = 0; i < nums.length; i++) { 42 | if (sum > 0) { 43 | sum += nums[i]; 44 | right = i; 45 | } else { 46 | sum = nums[i]; 47 | left = right = i; 48 | } 49 | if (sum > res) { 50 | finalLeft = left; 51 | finalRight = right; 52 | } 53 | res = Math.max(res, sum); 54 | } 55 | 56 | return nums.slice(finalLeft, finalRight + 1); 57 | }; -------------------------------------------------------------------------------- /动态规划/70. 爬楼梯.js: -------------------------------------------------------------------------------- 1 | // 和斐波那契数列一样,dp 2 | var climbStairs = function(n) { 3 | let dp = []; 4 | dp[1] = 1; 5 | dp[2] = 2; 6 | for (let i = 3; i <= n; i++) { 7 | dp[i] = dp[i - 1] + dp[i - 2]; 8 | } 9 | return dp[n]; 10 | }; -------------------------------------------------------------------------------- /动态规划/718. 最长重复子数组.js: -------------------------------------------------------------------------------- 1 | // 参考:https://github.com/sl1673495/leetcode-javascript/issues/114 2 | var findLength = function (A, B) { 3 | let [len1, len2] = [A.length, B.length]; 4 | let ans = 0; 5 | for (let i = 0, dp = []; i <= len1; i++) { 6 | dp[i] = (new Array(len2 + 1)).fill(0); // 初始化 7 | for (let j = 1; j <= len2 && i > 0; j++) { 8 | if (A[i - 1] === B[j - 1]) { 9 | dp[i][j] = dp[i - 1][j - 1] + 1; // dp递推 10 | ans = Math.max(ans, dp[i][j]); // 一边算dp一边更新最大值 11 | } 12 | } 13 | } 14 | return ans 15 | }; -------------------------------------------------------------------------------- /双指针/125. 验证回文串.js: -------------------------------------------------------------------------------- 1 | // 关键在于,先替换为所有非数字字母为空,并转为小写,即 s.replace(/[^0-9a-zA-Z]/g, ''); 2 | // 后面就是左右指针比较就行。 3 | var isPalindrome = function (s) { 4 | s = s.replace(/[^0-9a-zA-Z]/g, '').toLowerCase(); 5 | let left = 0; 6 | let right = s.length - 1; 7 | 8 | while (left < right) { 9 | if (s[left] === s[right]) { 10 | left++; 11 | right--; 12 | } else { 13 | return false; 14 | } 15 | } 16 | 17 | return true; 18 | }; -------------------------------------------------------------------------------- /双指针/15. 三数之和.js: -------------------------------------------------------------------------------- 1 | // 建议参控:https://www.bilibili.com/video/BV1bP411c7oJ 2 | // 排序+基准指针+双指针 3 | var threeSum = function (nums) { 4 | const res = []; 5 | const len = nums.length; 6 | if (len < 3) return []; 7 | nums.sort((a, b) => a - b); 8 | 9 | for (let i = 0; i < len - 2; i++) { 10 | if (nums[i] > 0) break; 11 | let left = i + 1; 12 | let right = len - 1; 13 | // 基准指针去重 14 | if (i > 0 && nums[i] === nums[i - 1]) continue; 15 | while (left < right) { 16 | const sum = nums[i] + nums[left] + nums[right]; 17 | if (sum > 0) { 18 | right--; 19 | continue; 20 | } 21 | if (sum < 0) { 22 | left++; 23 | continue; 24 | } 25 | res.push([nums[i], nums[left], nums[right]]); 26 | // 左右指针去重 27 | while (left < right && nums[left] === nums[left + 1]) { 28 | left++; 29 | } 30 | while (left < right && nums[right] === nums[right - 1]) { 31 | right--; 32 | } 33 | left++; 34 | right--; 35 | } 36 | } 37 | 38 | return res; 39 | }; 40 | -------------------------------------------------------------------------------- /双指针/16. 最接近的三数之和.js: -------------------------------------------------------------------------------- 1 | // 先升序,在以某个基准点 i,left = i + 1, right = len - 1; 2 | // 每次计算的差值比较,大了往左移,小了往右移。 3 | // 参考:https://github.com/sl1673495/leetcode-javascript/issues/115 4 | var threeSumClosest = function (nums, target) { 5 | const len = nums.length; 6 | let min = Infinity; 7 | let res = 0; 8 | 9 | nums.sort((a, b) => a - b) 10 | 11 | for (let i = 0; i <= len - 3; i++) { 12 | const basic = nums[i]; 13 | let left = i + 1; 14 | let right = len - 1; 15 | while (left < right) { 16 | let sum = basic + nums[left] + nums[right]; 17 | let absDiff = Math.abs(sum - target); 18 | if (absDiff < min) { 19 | min = absDiff; 20 | res = sum; 21 | } 22 | if (sum > target) { 23 | right--; 24 | } else if (sum < target) { 25 | left++; 26 | } else { 27 | return sum; 28 | } 29 | } 30 | } 31 | 32 | return res; 33 | }; -------------------------------------------------------------------------------- /双指针/165. 比较版本号.js: -------------------------------------------------------------------------------- 1 | // 很简单,不过要注意只会忽略前导0,后导0不能忽略。 2 | var compareVersion = function (version1, version2) { 3 | const v1Len = version1.length; 4 | const v2Len = version2.length; 5 | let left1 = 0; 6 | let left2 = 0; 7 | 8 | while (left1 < v1Len || left2 < v2Len) { 9 | let n1 = 0; 10 | let n2 = 0; 11 | 12 | while (left1 < v1Len && version1[left1] !== '.') { 13 | n1 = n1 * 10 + Number(version1[left1]); 14 | left1++; 15 | } 16 | while (left2 < v2Len && version2[left2] !== '.') { 17 | n2 = n2 * 10 + Number(version2[left2]); 18 | left2++; 19 | } 20 | if (n1 > n2) return 1; 21 | if (n1 < n2) return -1; 22 | left2++; 23 | left1++; 24 | } 25 | 26 | return 0; 27 | }; -------------------------------------------------------------------------------- /双指针/167. 两数之和 II - 输入有序数组.js: -------------------------------------------------------------------------------- 1 | // 有序数组,左右指针,大了右指针左移,小了左指针右移 2 | var twoSum = function (numbers, target) { 3 | let left = 0; 4 | let right = numbers.length - 1; 5 | 6 | while (left < right) { 7 | const sum = numbers[left] + numbers[right]; 8 | if (sum > target) { 9 | right--; 10 | } else if (sum < target) { 11 | left++; 12 | } else { 13 | return [left + 1, right + 1]; 14 | } 15 | } 16 | }; -------------------------------------------------------------------------------- /双指针/240. 搜索二维矩阵 II.js: -------------------------------------------------------------------------------- 1 | // 参考:https://zhuanlan.zhihu.com/p/144219651 2 | // 只能从左下角元素开始搜索,其他位置都不行。 3 | // 这道题还能确定行、列边界,进行二分搜索,但是效率不高。 4 | var searchMatrix = function (matrix, target) { 5 | let i = matrix.length - 1; 6 | let j = 0; 7 | 8 | while (i >= 0 && j < matrix[0].length) { 9 | if (matrix[i][j] > target) { 10 | i--; 11 | } else if (matrix[i][j] < target) { 12 | j++; 13 | } else { 14 | return true; 15 | } 16 | } 17 | 18 | return false; 19 | }; -------------------------------------------------------------------------------- /双指针/283. 移动零.js: -------------------------------------------------------------------------------- 1 | // 暴力法 2 | // 先遍历一次,找出所有 0 的下标,然后删除掉所有 0 元素,再 push 相应的 0 的个数到末尾。 3 | var moveZeroes = function (nums) { 4 | let zeros = [] 5 | for (let i = 0; i < nums.length; i++) { 6 | if (nums[i] === 0) { 7 | zeros.push(i) 8 | } 9 | } 10 | for (let j = zeros.length - 1; j >= 0; j--) { 11 | nums.splice(zeros[j], 1) 12 | } 13 | for (let j = 0; j < zeros.length; j++) { 14 | nums.push(0) 15 | } 16 | return nums 17 | } 18 | 19 | // 双指针 20 | // 参考:https://leetcode-cn.com/problems/move-zeroes/solution/dong-hua-yan-shi-283yi-dong-ling-by-wang_ni_ma/ 21 | var moveZeroes = function (nums) { 22 | let j = 0; 23 | 24 | for (let i = 0; i < nums.length; i++) { 25 | if (nums[i] !== 0) { 26 | nums[j] = nums[i]; 27 | j++; 28 | } 29 | } 30 | 31 | for (let i = j; i < nums.length; i++) { 32 | nums[i] = 0; 33 | } 34 | }; -------------------------------------------------------------------------------- /双指针/392. 判断子序列.js: -------------------------------------------------------------------------------- 1 | // 分别对s,t都设立一个指针,t的指针要每次比较后加1,s的指针只有在两者相等了再加1。 2 | // 这题和 524 一样的思路。 3 | var isSubsequence = function (s, t) { 4 | if (!s) return true; 5 | let sLeft = 0; 6 | let tLeft = 0; 7 | 8 | while (tLeft < t.length) { 9 | if (t[tLeft] === s[sLeft]) { 10 | sLeft++; 11 | } 12 | if (sLeft === s.length) { 13 | return true; 14 | } 15 | tLeft++; 16 | } 17 | 18 | return false; 19 | }; 20 | -------------------------------------------------------------------------------- /双指针/455. 分发饼干.js: -------------------------------------------------------------------------------- 1 | // 思路还是和前面的题没什么区别,不过我们要按升序排列两个数组。 2 | var findContentChildren = function (g, s) { 3 | g.sort((a, b) => a - b); 4 | s.sort((a, b) => a - b); 5 | 6 | let i = 0; 7 | let j = 0; 8 | let count = 0; 9 | 10 | while (i < g.length && j < s.length) { 11 | if (s[j] >= g[i]) { 12 | i++; 13 | j++; 14 | count++; 15 | } else { 16 | j++; 17 | } 18 | } 19 | 20 | return count; 21 | }; -------------------------------------------------------------------------------- /双指针/524. 通过删除字母匹配到字典里最长单词.js: -------------------------------------------------------------------------------- 1 | // 遍历字典中每一词,设立其左指针 curLeft,给 s 也设置一个左指针 sLeft 2 | // 同时从下标 0 开始,只有当前遍历到的单词的字母和 s[sLeft] 相同时,curLeft才前进。 3 | // 每个单词都从头开始。 4 | var findLongestWord = function (s, dictionary) { 5 | const arr = []; 6 | let res = ''; 7 | 8 | for (let i = 0; i < dictionary.length; i++) { 9 | const curWord = dictionary[i]; 10 | let sLeft = 0; 11 | let curLeft = 0; 12 | 13 | while (sLeft < s.length && curLeft < curWord.length) { 14 | if (s[sLeft] === curWord[curLeft]) { 15 | curLeft++; 16 | } 17 | 18 | sLeft++; 19 | } 20 | 21 | if (curLeft === curWord.length) { 22 | arr.push(curWord) 23 | } 24 | } 25 | 26 | // 找到最长和字典序最小的那个词 27 | for (let i = 0; i < arr.length; i++) { 28 | if (arr[i].length > res.length) { 29 | res = arr[i]; 30 | } else if (arr[i].length === res.length) { 31 | res = arr[i] < res ? arr[i] : res; 32 | } 33 | } 34 | 35 | return res; 36 | }; -------------------------------------------------------------------------------- /双指针/88. 合并两个有序数组.js: -------------------------------------------------------------------------------- 1 | // 从尾部开始遍历比较,额外设置一个填值的指针 p; 2 | // 参考:https://leetcode-cn.com/problems/merge-sorted-array/solution/hua-jie-suan-fa-88-he-bing-liang-ge-you-xu-shu-zu-/ 3 | var merge = function (nums1, m, nums2, n) { 4 | let right1 = m - 1; 5 | let right2 = n - 1; 6 | let p = m + n - 1; 7 | 8 | while (right1 >= 0 && right2 >= 0) { 9 | if (nums1[right1] < nums2[right2]) { 10 | nums1[p] = nums2[right2]; 11 | right2--; 12 | } else { 13 | nums1[p] = nums1[right1]; 14 | right1--; 15 | } 16 | p--; 17 | } 18 | 19 | while (right2 >= 0) { 20 | nums1[p] = nums2[right2]; 21 | right2--; 22 | p--; 23 | } 24 | }; -------------------------------------------------------------------------------- /排序/215. 数组中的第K个最大元素.js: -------------------------------------------------------------------------------- 1 | // 暴力排序 2 | var findKthLargest = function (nums, k) { 3 | const arr = nums.sort((a, b) => b - a); 4 | return arr[k - 1] 5 | }; 6 | 7 | // 快速排序思想 8 | var findKthLargest = function (nums, k) { 9 | let res = nums[0]; 10 | 11 | function quickSort(start, end) { 12 | if (start <= end) { 13 | const mid = sort(start, end); 14 | if (mid > k - 1) { 15 | quickSort(start, mid - 1); 16 | } else if (mid < k - 1) { 17 | quickSort(mid + 1, end); 18 | } else { 19 | res = nums[mid]; 20 | } 21 | } 22 | } 23 | 24 | function sort(start, end) { 25 | const base = nums[start]; 26 | let left = start; 27 | let right = end; 28 | while (left !== right) { 29 | while (nums[right] <= base && left < right) { 30 | right--; 31 | } 32 | nums[left] = nums[right]; 33 | while (nums[left] >= base && left < right) { 34 | left++; 35 | } 36 | nums[right] = nums[left]; 37 | } 38 | nums[left] = base; 39 | return left; 40 | } 41 | 42 | quickSort(0, nums.length - 1); 43 | return res; 44 | }; -------------------------------------------------------------------------------- /排序/912. 排序数组.js: -------------------------------------------------------------------------------- 1 | // 1. 选择排序 2 | // 思路:每一轮选取未排定的部分中最小的部分交换到未排定部分的最开头。 3 | var sortArray = function (nums) { 4 | for (let i = 0; i < nums.length; i++) { 5 | let minIdx = i; 6 | for (let j = i; j < nums.length; j++) { 7 | if (nums[j] < nums[minIdx]) { 8 | minIdx = j; 9 | } 10 | } 11 | if (minIdx !== i) { 12 | const temp = nums[i]; 13 | nums[i] = nums[minIdx]; 14 | nums[minIdx] = temp; 15 | } 16 | } 17 | 18 | return nums; 19 | }; 20 | 21 | // 2. 插入排序 22 | // 思路:将已排序部分当成一个小数组,未排序部分将一个一个插入到小数组当中,循环插入,直至排序完成。 23 | var sortArray = function (nums) { 24 | let len = nums.length; 25 | for (let i = 1; i < len; i++) { 26 | const cur = nums[i]; 27 | let j = i - 1; 28 | while (j >= 0 && cur < nums[j]) { 29 | nums[j + 1] = nums[j]; 30 | j--; 31 | } 32 | nums[j + 1] = cur; 33 | } 34 | 35 | return nums; 36 | }; 37 | 38 | // 3. 冒泡排序 39 | // 思路:前后两个位置比较并两两交换,将最大值冒泡到最后一位。 40 | var sortArray = function (nums) { 41 | let len = nums.length; 42 | for (let i = len - 1; i >= 0; i--) { 43 | for (let j = 1; j < len; j++) { 44 | if (nums[j] < nums[j - 1]) { 45 | const temp = nums[j - 1]; 46 | nums[j - 1] = nums[j]; 47 | nums[j] = temp; 48 | } 49 | } 50 | } 51 | 52 | return nums; 53 | }; 54 | 55 | // 4. 快速排序 56 | // 思路:选取每个区间第一个是 base,将其与 nums[left] 互换后,将 nums[left]设 为 base 值,进行一次快速排序。 57 | var sortArray = function (nums) { 58 | function quickSort(start, end, arr) { 59 | if (start < end) { 60 | let mid = sort(start, end, arr); 61 | quickSort(start, mid - 1, arr); 62 | quickSort(mid + 1, end, arr); 63 | } 64 | return arr; 65 | } 66 | 67 | function sort(start, end, arr) { 68 | const base = arr[start]; 69 | let left = start; 70 | let right = end; 71 | while (left !== right) { 72 | while (arr[right] >= base && right > left) { 73 | right--; 74 | } 75 | arr[left] = arr[right]; 76 | while (arr[left] <= base && right > left) { 77 | left++; 78 | } 79 | arr[right] = arr[left]; 80 | } 81 | arr[left] = base; 82 | return left; 83 | } 84 | 85 | quickSort(0, nums.length - 1, nums); 86 | return nums; 87 | }; 88 | -------------------------------------------------------------------------------- /查找表/1. 两数之和.js: -------------------------------------------------------------------------------- 1 | // 看答案就知道怎么 2 | var twoSum = function (nums, target) { 3 | let map = new Map(); 4 | for (let i = 0; i < nums.length; i++) { 5 | let dif = target - nums[i] 6 | if (map.has(dif)) { 7 | return [map.get(dif), i] 8 | } 9 | map.set(nums[i], i); 10 | } 11 | }; -------------------------------------------------------------------------------- /查找表/350. 两个数组的交集 II.js: -------------------------------------------------------------------------------- 1 | // 同样是建立两个表,根据数量最小值来决定push多少个 2 | var intersect = function (nums1, nums2) { 3 | const nums1Map = new Map(); 4 | const nums2Map = new Map(); 5 | 6 | for (const ele of nums1) { 7 | nums1Map.set(ele, (nums1Map.get(ele) || 0) + 1); 8 | } 9 | for (const ele of nums2) { 10 | nums2Map.set(ele, (nums2Map.get(ele) || 0) + 1); 11 | } 12 | 13 | const res = []; 14 | for (const [key, value] of nums1Map) { 15 | if (nums2Map.get(key)) { 16 | const pushCount = Math.min(value, nums2Map.get(key)) 17 | for (let i = 0; i < pushCount; i++) { 18 | res.push(key) 19 | } 20 | } 21 | } 22 | 23 | return res; 24 | }; -------------------------------------------------------------------------------- /查找表/389. 找不同.js: -------------------------------------------------------------------------------- 1 | // 分别建立 sMap 和 tMap,遍历它们每一个并在 Map 中根据出现次数递增, 2 | // 最后遍历一遍 lenght 较长的 t ,根据 value 的值的不同来确定是否是多出来的。 3 | var findTheDifference = function (s, t) { 4 | const sMap = new Map(); 5 | for (const c of s) { 6 | sMap.set(c, (sMap.get(c) || 0) + 1); 7 | } 8 | const tMap = new Map(); 9 | for (const c of t) { 10 | tMap.set(c, (tMap.get(c) || 0) + 1); 11 | } 12 | // 遍历 tMap,根据两者数量是否一致确定 13 | for (const [key, value] of tMap) { 14 | if (sMap.get(key) !== value) { 15 | return key; 16 | } 17 | } 18 | }; 19 | 20 | // 利用 ascall 码的计算,最后的差值再转为 char 就是多出的 21 | var findTheDifference = function (s, t) { 22 | let sum = 0; 23 | for (const c of t) { 24 | sum += c.charCodeAt(); 25 | } 26 | for (const c of s) { 27 | sum -= c.charCodeAt(); 28 | } 29 | return String.fromCharCode(sum); 30 | }; 31 | 32 | // 不推荐去搞位运算。。。。。。。。。。。。。。。。了解就行。 33 | -------------------------------------------------------------------------------- /栈/20. 有效的括号.js: -------------------------------------------------------------------------------- 1 | // 栈的常规用法 2 | var isValid = function (s) { 3 | if (!s) return true; 4 | const map = { 5 | '(': ')', 6 | '{': '}', 7 | '[': ']', 8 | } 9 | const stack = []; 10 | for (let i = 0; i < s.length; i++) { 11 | if (map[s[i]]) { 12 | stack.push(s[i]); 13 | } else { 14 | if (stack.length === 0) return false; 15 | const cur = stack.pop(); 16 | if (map[cur] !== s[i]) { 17 | return false; 18 | } 19 | } 20 | } 21 | 22 | return stack.length === 0; 23 | }; -------------------------------------------------------------------------------- /滑动窗口/209. 长度最小的子数组.js: -------------------------------------------------------------------------------- 1 | // 暴力法,两层遍历 2 | var minSubArrayLen = function (target, nums) { 3 | let res = 0; 4 | for (let i = 0; i < nums.length; i++) { 5 | let sum = 0; 6 | for (let j = i; j < nums.length; j++) { 7 | sum += nums[j]; 8 | if (sum >= target) { 9 | if (res === 0) res = j - i + 1; 10 | res = Math.min(res, j - i + 1); 11 | break; 12 | } 13 | } 14 | } 15 | return res; 16 | }; 17 | 18 | // 滑动窗口,left 和 right 右移 19 | var minSubArrayLen = function (target, nums) { 20 | const numLen = nums.length; 21 | let left = 0; 22 | let right = -1; 23 | let res = 0; 24 | let sum = 0; 25 | 26 | while (left < numLen && right < numLen) { 27 | if (sum >= target) { 28 | if (res === 0) res = right - left + 1; 29 | res = Math.min(res, right - left + 1); 30 | sum -= nums[left]; 31 | left++; 32 | } else { 33 | right++; 34 | sum += nums[right] 35 | } 36 | } 37 | return res; 38 | }; -------------------------------------------------------------------------------- /滑动窗口/239. 滑动窗口最大值.js: -------------------------------------------------------------------------------- 1 | // 两次遍历,暴力解法 O(kn) 2 | var maxSlidingWindow = function (nums, k) { 3 | const res = []; 4 | const numsLen = nums.length; 5 | for (let i = 0; i < numsLen; i++) { 6 | if (i + k > numsLen) break; 7 | 8 | let max = nums[i]; 9 | for (let j = i + 1; j < i + k; j++) { 10 | if (max < nums[j]) { 11 | max = nums[j]; 12 | } 13 | } 14 | 15 | res.push(max); 16 | } 17 | return res; 18 | }; 19 | 20 | // 单调队列 21 | var maxSlidingWindow = function (nums, k) { 22 | const res = []; 23 | const queue = []; 24 | for (let i = 0; i < nums.length; i++) { 25 | if (i - k >= queue[0]) { 26 | queue.shift(); 27 | } 28 | while (nums[queue[queue.length - 1]] <= nums[i]) { 29 | queue.pop(); 30 | } 31 | queue.push(i); 32 | 33 | if ((i + 1) >= k) { 34 | res.push(nums[queue[0]]); 35 | } 36 | } 37 | return res; 38 | }; 39 | 40 | -------------------------------------------------------------------------------- /滑动窗口/3. 无重复字符的最长子串.js: -------------------------------------------------------------------------------- 1 | // 滑动窗口,还是很耗时; 2 | // 如果当前子串重复,left 向右移; 3 | // 如果当前子串不重复,right 向右移; 4 | // 这过程中记录所有不重复时的子串长度。 5 | var lengthOfLongestSubstring = function (s) { 6 | let left = 0; 7 | let right = 0; 8 | let res = 0, 9 | subStr = ""; 10 | 11 | while (left < s.length && right <= s.length) { 12 | const isRepeat = hasRepeat(subStr); 13 | if (isRepeat) { 14 | subStr = s.slice(left++, right); 15 | } else { 16 | const curSubStrLen = subStr.length; 17 | if (curSubStrLen > res || res === 0) { 18 | res = curSubStrLen; 19 | } 20 | subStr += s[right++]; 21 | } 22 | } 23 | 24 | return res; 25 | }; 26 | 27 | var hasRepeat = function (str) { 28 | const obj = {}; 29 | for (const c of str) { 30 | obj[c] = (obj[c] || 0) + 1; 31 | } 32 | for (const val of Object.values(obj)) { 33 | if (val > 1) return true; 34 | } 35 | return false; 36 | }; 37 | 38 | // 同样是滑动窗口,但是改进了写法: 39 | var lengthOfLongestSubstring = function (s) { 40 | let sLen = s.length; 41 | let left = 0; 42 | let right = -1; 43 | let res = 0; 44 | const obj = Object.create(null); 45 | 46 | while (left < sLen && right <= sLen) { 47 | const nextChar = s[right + 1]; 48 | if (!obj[nextChar] && nextChar !== undefined) { 49 | obj[nextChar] = 1; 50 | right++; 51 | } else { 52 | obj[s[left]] = 0; 53 | left++; 54 | } 55 | res = Math.max(res, right - left + 1) 56 | } 57 | 58 | return res; 59 | }; 60 | -------------------------------------------------------------------------------- /滑动窗口/438. 找到字符串中所有字母异位词.js: -------------------------------------------------------------------------------- 1 | // 滑动窗口,对窗口内的字符串和 p 分别建立 Map 来确定各种字母数量是否相等。 2 | // 超时! 3 | var findAnagrams = function (s, p) { 4 | const sLen = s.length; 5 | const pLen = p.length; 6 | if (pLen > sLen) return []; 7 | 8 | const res = []; 9 | for (let i = 0; i < s.length; i++) { 10 | if (i + pLen > sLen) break; 11 | 12 | const sDepart = s.slice(i, i + pLen); 13 | if (isAnagram(sDepart, p)) { 14 | res.push(i); 15 | } 16 | } 17 | 18 | return res; 19 | }; 20 | 21 | var isAnagram = function (a, b) { 22 | const aMap = new Map(); 23 | const bMap = new Map(); 24 | for (const c of a) { 25 | aMap.set(c, (aMap.get(c) || 0) + 1); 26 | } 27 | for (const c of b) { 28 | bMap.set(c, (bMap.get(c) || 0) + 1); 29 | } 30 | for (const [key, value] of aMap) { 31 | if (value !== bMap.get(key)) return false; 32 | } 33 | return true; 34 | }; 35 | 36 | // 改进上述方法 37 | // 因为每次滑动我们都重新建立 s 和 p 的 Map,遍历也是从头遍历,会增加很多不必要的计算。 38 | // 首先,p 的 Map 是死的,不需要重建; 39 | // 其次,s 也不需要重新建立,只要每次把滑动出去的 字母 所对应的数量 -1 即可,移入的 +1。 40 | // 注意 sMap 中 value 为 0 的情况要去除。 41 | var findAnagrams = function (s, p) { 42 | const sLen = s.length; 43 | const pLen = p.length; 44 | if (pLen > sLen) return []; 45 | 46 | const pMap = new Map; 47 | for (const c of p) { 48 | pMap.set(c, (pMap.get(c) || 0) + 1); 49 | } 50 | 51 | const sMap = new Map; 52 | const ss = s.slice(0, pLen) 53 | for (const c of ss) { 54 | sMap.set(c, (sMap.get(c) || 0) + 1); 55 | } 56 | 57 | const res = []; 58 | for (let i = 0; i < sLen; i++) { 59 | if (i + pLen > sLen) break; 60 | 61 | if (isAnagram(sMap, pMap)) { 62 | res.push(i); 63 | } 64 | 65 | sMap.set(s[i], sMap.get(s[i]) - 1); 66 | sMap.set(s[i + pLen], (sMap.get(s[i + pLen]) || 0) + 1); 67 | } 68 | 69 | return res; 70 | }; 71 | 72 | var isAnagram = function (sMap, pMap) { 73 | for (const key of sMap.keys()) { 74 | if (sMap.get(key) === 0) continue; 75 | if (sMap.get(key) !== pMap.get(key)) return false; 76 | } 77 | return true; 78 | }; 79 | -------------------------------------------------------------------------------- /滑动窗口/76. 最小覆盖子串.js: -------------------------------------------------------------------------------- 1 | // 暴力的解法,虽然也是滑动窗口,根据 t 的长度为初试窗口大小; 2 | // 然后在窗口内对 s 进行扫描,查看是否有符合的; 3 | // 但是这样会每次都重新建立 windowMap。 4 | // 会超时! 5 | var minWindow = function (s, t) { 6 | const tLen = t.length; 7 | const sLen = s.length; 8 | let windowLen = tLen; 9 | 10 | if (sLen < tLen) return ""; 11 | 12 | const tMap = new Map(); 13 | for (const c of t) { 14 | tMap.set(c, (tMap.get(c) || 0) + 1); 15 | } 16 | 17 | while (windowLen <= sLen) { 18 | const windowMap = new Map(); 19 | const ss = s.slice(0, windowLen); 20 | 21 | for (const c of ss) { 22 | windowMap.set(c, (windowMap.get(c) || 0) + 1); 23 | } 24 | 25 | for (let i = 0; i < sLen; i++) { 26 | if (i + windowLen > sLen) break; 27 | 28 | if (isSubstring(windowMap, tMap)) { 29 | return s.slice(i, i + windowLen); 30 | } 31 | 32 | windowMap.set(s[i], windowMap.get(s[i]) - 1); 33 | windowMap.set( 34 | s[i + windowLen], 35 | (windowMap.get(s[i + windowLen]) || 0) + 1 36 | ); 37 | } 38 | 39 | windowLen++; 40 | } 41 | 42 | return ""; 43 | }; 44 | 45 | var isSubstring = function (winMap, tMap) { 46 | for (const key of tMap.keys()) { 47 | if (!tMap.get(key)) continue; 48 | if ((winMap.get(key) || 0) < tMap.get(key)) return false; 49 | } 50 | return true; 51 | }; 52 | 53 | // O(n) 事件复杂度的解法如下: 54 | // 滑动窗口的left 和 right 要移动:如果 right 右移后不是子串,就右移 right 直到满足;如果 right 右移后是子串,就开始左移 left,循环往复。 55 | var minWindow = function (s, t) { 56 | const tLen = t.length; 57 | const sLen = s.length; 58 | 59 | if (sLen < tLen) return ""; 60 | 61 | const tMap = new Map(); 62 | for (const c of t) { 63 | tMap.set(c, (tMap.get(c) || 0) + 1); 64 | } 65 | 66 | const sMap = new Map(); 67 | let left = 0; 68 | let right = -1; 69 | let res = ""; 70 | while (left <= sLen - tLen && right <= sLen) { 71 | let isValid = isSubstring(sMap, tMap); 72 | if (isValid) { 73 | const currentValidLength = right - left + 1; 74 | if (currentValidLength < res.length || res === "") { 75 | res = s.slice(left, right + 1); 76 | } 77 | sMap.set(s[left], sMap.get(s[left]) - 1); 78 | left++; 79 | } else { 80 | sMap.set(s[right + 1], (sMap.get(s[right + 1]) || 0) + 1); 81 | right++; 82 | } 83 | } 84 | 85 | return res; 86 | }; 87 | 88 | var isSubstring = function (sMap, tMap) { 89 | for (const key of tMap.keys()) { 90 | if ((sMap.get(key) || 0) < tMap.get(key)) return false; 91 | } 92 | return true; 93 | }; 94 | -------------------------------------------------------------------------------- /递归与回溯/1291. 顺次数.js: -------------------------------------------------------------------------------- 1 | // 看代码就能看懂,不过和常规的回溯模板不一样,循环是在外层进行的。 2 | var sequentialDigits = function (low, high) { 3 | const res = []; 4 | 5 | var backtracking = function (k) { 6 | if (k >= low && k <= high) { 7 | res.push(k); 8 | } 9 | k = k * 10 + k % 10 + 1; 10 | if (k > high || k % 10 === 0) return; 11 | backtracking(k); 12 | } 13 | 14 | for (let i = 1; i <= 9; i++) { 15 | backtracking(i); 16 | } 17 | 18 | res.sort((a, b) => a - b); 19 | return res; 20 | }; -------------------------------------------------------------------------------- /递归与回溯/131. 分割回文串.js: -------------------------------------------------------------------------------- 1 | // 回溯 2 | // 参考:https://leetcode-cn.com/problems/palindrome-partitioning/solution/131-fen-ge-hui-wen-chuan-hui-su-sou-suo-yp2jq/ 3 | var partition = function (s) { 4 | const res = []; 5 | 6 | var backtracking = function (path, start) { 7 | if (start >= s.length) { 8 | res.push([...path]); 9 | } 10 | for (let i = start; i < s.length; i++) { 11 | if (!isPalindrome(s, start, i)) continue; 12 | 13 | const str = s.slice(start, i + 1); 14 | path.push(str); 15 | backtracking(path, i + 1); 16 | path.pop(); 17 | } 18 | } 19 | 20 | backtracking([], 0); 21 | return res; 22 | }; 23 | 24 | var isPalindrome = function (s, left, right) { 25 | for (let i = left, j = right; i < j; i++, j--) { 26 | if (s[i] != s[j]) { 27 | return false; 28 | } 29 | } 30 | return true; 31 | } 32 | -------------------------------------------------------------------------------- /递归与回溯/200. 岛屿数量.js: -------------------------------------------------------------------------------- 1 | // 参考 2 | // https://leetcode-cn.com/problems/number-of-islands/solution/dao-yu-lei-wen-ti-de-tong-yong-jie-fa-dfs-bian-li-/ 3 | var numIslands = function (grid) { 4 | let res = 0; 5 | for (let r = 0; r < grid.length; r++) { 6 | for (let c = 0; c < grid[0].length; c++) { 7 | if (grid[r][c] === "1") { 8 | dfs(grid, r, c); 9 | res++; 10 | } 11 | } 12 | } 13 | return res; 14 | }; 15 | 16 | var dfs = function (grid, r, c) { 17 | if (!inArea(grid, r, c)) return; 18 | if (grid[r][c] !== "1") return; 19 | 20 | grid[r][c] = "2"; // 标记已走过 21 | dfs(grid, r + 1, c); 22 | dfs(grid, r - 1, c); 23 | dfs(grid, r, c + 1); 24 | dfs(grid, r, c - 1); 25 | } 26 | 27 | var inArea = function (grid, r, c) { 28 | return r >= 0 && r < grid.length && c >= 0 && grid[0].length; 29 | } -------------------------------------------------------------------------------- /递归与回溯/216. 组合总和 III.js: -------------------------------------------------------------------------------- 1 | // 做了前面的组合题和子集题,这题完全可以做出来 2 | var combinationSum3 = function (k, n) { 3 | const res = []; 4 | if (k === 0 || n === 0) { 5 | return res; 6 | } 7 | 8 | var backtracking = function (path, start, sum) { 9 | if (path.length > k) return; 10 | if (path.length === k && sum === n) { 11 | res.push([...path]); 12 | return; 13 | } 14 | for (let i = start; i <= 9; i++) { 15 | if (sum > n) { 16 | continue; 17 | } 18 | path.push(i); 19 | backtracking(path, i + 1, sum + i); 20 | path.pop(); 21 | } 22 | } 23 | 24 | backtracking([], 1, 0); 25 | return res; 26 | }; -------------------------------------------------------------------------------- /递归与回溯/39. 组合总和.js: -------------------------------------------------------------------------------- 1 | // 回溯 2 | // 参考:https://leetcode-cn.com/problems/subsets/solution/c-zong-jie-liao-hui-su-wen-ti-lei-xing-dai-ni-gao-/ 3 | var combinationSum = function (candidates, target) { 4 | const res = []; 5 | 6 | var backtracking = function (path, start, sum) { 7 | if (target === sum) { 8 | res.push([...path]); 9 | return; 10 | } 11 | for (let i = start; i < candidates.length; i++) { 12 | if (sum > target) { 13 | continue; 14 | } 15 | path.push(candidates[i]); 16 | backtracking(path, i, sum + candidates[i]); 17 | path.pop(); 18 | } 19 | } 20 | 21 | backtracking([], 0, 0); 22 | return res; 23 | }; -------------------------------------------------------------------------------- /递归与回溯/40. 组合总和 II.js: -------------------------------------------------------------------------------- 1 | // 回溯+判断去重,和 子集2 一样 2 | var combinationSum2 = function (candidates, target) { 3 | const res = []; 4 | candidates.sort((a, b) => a - b); 5 | 6 | var backtracking = function (path, start, sum) { 7 | if (sum === target) { 8 | res.push([...path]); 9 | return; 10 | } 11 | for (let i = start; i < candidates.length; i++) { 12 | if (i > start && candidates[i] === candidates[i - 1] || sum > target) { 13 | continue; 14 | } 15 | path.push(candidates[i]); 16 | backtracking(path, i + 1, sum + candidates[i]); 17 | path.pop(); 18 | } 19 | } 20 | 21 | backtracking([], 0, 0); 22 | return res; 23 | }; -------------------------------------------------------------------------------- /递归与回溯/401. 二进制手表.js: -------------------------------------------------------------------------------- 1 | // 回溯 2 | var readBinaryWatch = function (turnedOn) { 3 | const res = []; 4 | 5 | var backtracking = function (num, start, h, m) { 6 | if (num === 0) { 7 | if (h > 11 || m > 59) { 8 | return; 9 | } 10 | let hour = h.toString(); 11 | let minute = m.toString(); 12 | if (minute.length === 1) { 13 | minute = '0' + minute; 14 | } 15 | res.push(hour + ":" + minute); 16 | } 17 | 18 | for (let i = start; i <= 9; i++) { 19 | if (h > 11 || m > 59) { 20 | continue; 21 | } 22 | const store = [h, m]; // 记录状态 23 | if (i <= 3) { 24 | h += 2 ** i; 25 | } else { 26 | m += 2 ** (i - 4); 27 | } 28 | backtracking(num - 1, i + 1, h, m);//进入下一层,注意下一层的 start 是 i+1 29 | // 恢复状态 30 | h = store[0]; 31 | m = store[1]; 32 | } 33 | } 34 | 35 | backtracking(turnedOn, 0, 0, 0); 36 | return res; 37 | }; -------------------------------------------------------------------------------- /递归与回溯/46. 全排列.js: -------------------------------------------------------------------------------- 1 | // 回溯,具体看下面注释。 2 | var permute = function (nums) { 3 | // 保存结果数组,保存每个路径(排列) 4 | const res = []; 5 | const len = nums.length; 6 | 7 | // 定义回溯递归函数 8 | // 传入节点是否被使用过的数组 used 和路径栈 path。 9 | // used 用来标记节点是否被用过, path 用来存储路径,定义为一个栈 10 | var backtracking = function (used, path) { 11 | // 递归出口 12 | // 如果到达叶子节点,将路径推入结果数组,并返回 13 | if (path.length === len) { 14 | res.push([...path]); 15 | } 16 | 17 | // 遍历候选字符 18 | for (let i = 0; i < len; i++) { 19 | // 使用过就下一轮循环 20 | if (used[i]) continue; 21 | 22 | // undefind 和 fasle 都会进来 23 | // 这里说明这个数还没有被使用,入栈 path 24 | path.push(nums[i]); 25 | // 标记这个数被使用过了 26 | used[i] = true; 27 | // 开始进行递归 28 | backtracking(used, path); 29 | // 回溯【状态重置】撤销之前的操作 30 | path.pop(); 31 | used[i] = false; 32 | } 33 | } 34 | 35 | // 调用回溯函数,传入参数 36 | backtracking([], []); 37 | // 返回结果数组 38 | return res; 39 | }; -------------------------------------------------------------------------------- /递归与回溯/47. 全排列 II.js: -------------------------------------------------------------------------------- 1 | // 与 46. 全排列 思路一样,都是回溯,不过要先排序切增加判断条件 2 | // 看下面注释 3 | var permuteUnique = function (nums) { 4 | const res = []; 5 | const len = nums.length; 6 | // 先排序,目的是为了把重复的数字放到一起形成连续,便于后面判断 7 | nums.sort((a, b) => a - b); 8 | 9 | var backtracking = function (used, path) { 10 | if (path.length === len) { 11 | res.push([...path]); 12 | } 13 | for (let i = 0; i < len; i++) { 14 | if (used[i]) continue; 15 | // 现在假如 第二个数字也是 1,和第一个 1 重复了: 16 | // 1.首先下标肯定要大于等于 0 17 | // 2.前后数字相等 18 | // 3.前面的数字必须标记为 使用过了 ,如果没有这个判断,后面的逻辑都到不了,仔细想想就知道了。 19 | if (i - 1 >= 0 && nums[i - 1] === nums[i] && !used[i - 1]) continue; 20 | 21 | path.push(nums[i]); 22 | used[i] = true; 23 | backtracking(used, path); 24 | path.pop(); 25 | used[i] = false; 26 | } 27 | } 28 | 29 | backtracking([], []); 30 | return res; 31 | }; -------------------------------------------------------------------------------- /递归与回溯/54. 螺旋矩阵.js: -------------------------------------------------------------------------------- 1 | // 参考:https://leetcode-cn.com/problems/spiral-matrix/solution/ju-zhen-bian-li-wen-ti-de-si-bu-qu-by-fu-91za/ 2 | var spiralOrder = function (matrix) { 3 | let m = matrix.length; 4 | let n = matrix[0].length; 5 | const order = []; 6 | // 边界 7 | let left = 0; 8 | let top = 0; 9 | let right = n - 1; 10 | let bottom = m - 1; 11 | // 方向 12 | let curDire = 0; // 0:右 1:下 2:左 3:上 13 | let dires = [[0, 1], [1, 0], [0, -1], [-1, 0]]; //移动方向对应的数据加减 14 | // 当前位置 15 | let x = 0; 16 | let y = 0; 17 | 18 | while (order.length !== m * n) { 19 | order.push(matrix[x][y]); 20 | 21 | if (curDire === 0 && y === right) { 22 | curDire++; 23 | top++; 24 | } else if (curDire === 1 && x === bottom) { 25 | curDire++; 26 | right--; 27 | } else if (curDire === 2 && y === left) { 28 | curDire++; 29 | bottom-- 30 | } else if (curDire === 3 && x === top) { 31 | curDire++; 32 | left++; 33 | } 34 | 35 | curDire %= 4; 36 | x += dires[curDire][0]; 37 | y += dires[curDire][1]; 38 | } 39 | 40 | return order; 41 | }; -------------------------------------------------------------------------------- /递归与回溯/59. 螺旋矩阵 II.js: -------------------------------------------------------------------------------- 1 | // 边界 2 | var generateMatrix = function (n) { 3 | const res = Array.from({ length: n }).map(() => Array.from({ length: n })) 4 | // 边界 5 | let left = 0; 6 | let top = 0; 7 | let right = n - 1; 8 | let bottom = n - 1; 9 | // 填入的数字大小 10 | let num = 1; 11 | while (left <= right && top <= bottom) { 12 | for (let i = left; i <= right; i++) { 13 | res[top][i] = num; 14 | num++; 15 | } 16 | for (let i = top + 1; i <= bottom; i++) { 17 | res[i][right] = num; 18 | num++; 19 | } 20 | for (let i = right - 1; i >= left; i--) { 21 | res[bottom][i] = num; 22 | num++; 23 | } 24 | for (let i = bottom - 1; i >= top + 1; i--) { 25 | res[i][left] = num; 26 | num++; 27 | } 28 | left++; 29 | top++; 30 | right--; 31 | bottom--; 32 | } 33 | 34 | return res; 35 | }; -------------------------------------------------------------------------------- /递归与回溯/695. 岛屿的最大面积.js: -------------------------------------------------------------------------------- 1 | // 参考 2 | // https://leetcode-cn.com/problems/number-of-islands/solution/dao-yu-lei-wen-ti-de-tong-yong-jie-fa-dfs-bian-li-/ 3 | var maxAreaOfIsland = function (grid) { 4 | let res = 0; 5 | for (let r = 0; r < grid.length; r++) { 6 | for (let c = 0; c < grid[0].length; c++) { 7 | if (grid[r][c] === 1) { 8 | const area = dfs(grid, r, c); 9 | res = Math.max(res, area); 10 | } 11 | } 12 | } 13 | return res; 14 | }; 15 | 16 | var dfs = function (grid, r, c) { 17 | if (!inArea(grid, r, c)) return 0; 18 | if (grid[r][c] !== 1) return 0; 19 | 20 | grid[r][c] = 2; // 标记为已访问过 21 | return 1 22 | + dfs(grid, r + 1, c) 23 | + dfs(grid, r - 1, c) 24 | + dfs(grid, r, c + 1) 25 | + dfs(grid, r, c - 1); 26 | } 27 | 28 | var inArea = function (grid, r, c) { 29 | if (r >= 0 && r < grid.length && c >= 0 && c < grid[0].length) { 30 | return true; 31 | } 32 | return false; 33 | } -------------------------------------------------------------------------------- /递归与回溯/73. 矩阵置零.js: -------------------------------------------------------------------------------- 1 | // 参考:https://leetcode-cn.com/problems/set-matrix-zeroes/solution/o1kong-jian-by-powcai/ 2 | 3 | // 思路一:用 O(m+n) 额外空间 4 | var setZeroes = function (matrix) { 5 | const rowZero = []; 6 | const colZero = []; 7 | const row = matrix.length; 8 | const col = matrix[0].length; 9 | 10 | for (let i = 0; i < row; i++) { 11 | for (let j = 0; j < col; j++) { 12 | if (matrix[i][j] === 0) { 13 | rowZero.push(i); 14 | colZero.push(j); 15 | } 16 | } 17 | } 18 | 19 | for (let i = 0; i < row; i++) { 20 | for (let j = 0; j < col; j++) { 21 | if (rowZero.includes(i) || colZero.includes(j)) { 22 | matrix[i][j] = 0; 23 | } 24 | } 25 | } 26 | }; 27 | 28 | // 思路二:O(1) 空间 29 | var setZeroes = function (matrix) { 30 | const row = matrix.length; 31 | const col = matrix[0].length; 32 | let row0 = false; 33 | let col0 = false; 34 | 35 | for (let i = 0; i < row; i++) { 36 | if (matrix[i][0] === 0) { 37 | col0 = true; 38 | break; 39 | } 40 | } 41 | 42 | for (let i = 0; i < col; i++) { 43 | if (matrix[0][i] === 0) { 44 | row0 = true; 45 | break; 46 | } 47 | } 48 | 49 | for (let i = 1; i < row; i++) { 50 | for (let j = 1; j < col; j++) { 51 | if (matrix[i][j] === 0) { 52 | matrix[0][j] = matrix[i][0] = 0; 53 | } 54 | } 55 | } 56 | 57 | for (let i = 1; i < row; i++) { 58 | if (matrix[i][0] === 0) { 59 | for (let j = 0; j < col; j++) { 60 | matrix[i][j] = 0; 61 | } 62 | } 63 | } 64 | 65 | for (let i = 1; i < col; i++) { 66 | if (matrix[0][i] === 0) { 67 | for (let j = 0; j < row; j++) { 68 | matrix[j][i] = 0; 69 | } 70 | } 71 | } 72 | 73 | if (col0) { 74 | for (let i = 0; i < row; i++) { 75 | matrix[i][0] = 0; 76 | } 77 | } 78 | if (row0) { 79 | for (let i = 0; i < col; i++) { 80 | matrix[0][i] = 0; 81 | } 82 | } 83 | }; -------------------------------------------------------------------------------- /递归与回溯/77. 组合.js: -------------------------------------------------------------------------------- 1 | // 回溯 2 | // 参考:https://leetcode-cn.com/problems/combinations/solution/hui-su-suan-fa-jian-zhi-python-dai-ma-java-dai-ma-/ 3 | var combine = function (n, k) { 4 | const res = []; 5 | if (k <= 0 || n < k) { 6 | return res; 7 | } 8 | 9 | var backtracking = function (path, start) { 10 | if (path.length === k) { 11 | res.push([...path]); 12 | return; 13 | } 14 | for (let i = start; i <= n; i++) { 15 | path.push(i); 16 | backtracking(path, i + 1); 17 | path.pop(); 18 | } 19 | } 20 | 21 | backtracking([], 1); 22 | return res; 23 | }; -------------------------------------------------------------------------------- /递归与回溯/78. 子集.js: -------------------------------------------------------------------------------- 1 | // 回溯 2 | // 参考:https://leetcode-cn.com/problems/subsets/solution/c-zong-jie-liao-hui-su-wen-ti-lei-xing-dai-ni-gao-/ 3 | var subsets = function (nums) { 4 | const res = []; 5 | 6 | var backtracking = function (path, start) { 7 | res.push([...path]); 8 | for (let i = start; i < nums.length; i++) { 9 | path.push(nums[i]); 10 | backtracking(path, i + 1); 11 | path.pop(); 12 | } 13 | } 14 | 15 | backtracking([], 0); 16 | return res; 17 | }; -------------------------------------------------------------------------------- /递归与回溯/784. 字母大小写全排列.js: -------------------------------------------------------------------------------- 1 | // 简单递归即可 2 | var letterCasePermutation = function (s) { 3 | const res = []; 4 | const len = s.length; 5 | 6 | var recursion = function (str, i) { 7 | if (str.length > len || i > len) return 8 | if (str.length === len) { 9 | res.push(str); 10 | return; 11 | } 12 | if (/[a-z]/.test(s[i])) { 13 | recursion(str + s[i], i + 1) 14 | recursion(str + s[i].toUpperCase(), i + 1) 15 | } else if (/[A-Z]/.test(s[i])) { 16 | recursion(str + s[i], i + 1) 17 | recursion(str + s[i].toLowerCase(), i + 1) 18 | } else { 19 | recursion(str + s[i], i + 1) 20 | } 21 | } 22 | 23 | recursion('', 0); 24 | return res; 25 | }; 26 | 27 | // 回溯写法 28 | var letterCasePermutation = function (s) { 29 | const res = []; 30 | const sArr = s.split(''); 31 | 32 | var backtracking = function (path, start) { 33 | if (path.length === sArr.length) { 34 | res.push([...path].join('')); 35 | } 36 | for (let i = start; i < sArr.length; i++) { 37 | if (/[0-9]/.test(sArr[i])) { 38 | path.push(sArr[i]); 39 | backtracking(path, i + 1); 40 | path.pop(); 41 | } else { 42 | const a = sArr[i].toLowerCase(); 43 | path.push(a); 44 | backtracking(path, i + 1); 45 | path.pop(); 46 | 47 | const A = sArr[i].toUpperCase(); 48 | path.push(A); 49 | backtracking(path, i + 1); 50 | path.pop(); 51 | } 52 | 53 | } 54 | } 55 | 56 | backtracking([], 0); 57 | return res; 58 | }; -------------------------------------------------------------------------------- /递归与回溯/79. 单词搜索.js: -------------------------------------------------------------------------------- 1 | // 回溯,主要是四个方向 2 | var exist = function (board, word) { 3 | // 上右下左 4 | const dires = [[-1, 0], [0, 1], [1, 0], [0, -1]]; 5 | const rows = board.length; 6 | if (rows === 0) return false; 7 | const cols = board[0].length; 8 | const used = Array.from({ length: rows }).map(() => Array.from({ length: cols })) 9 | const wordLen = word.length; 10 | 11 | var backtracking = function (x, y, start) { 12 | if (start === wordLen - 1) { 13 | return board[x][y] === word[start]; 14 | } 15 | if (board[x][y] === word[start]) { 16 | used[x][y] = true; 17 | for (const d of dires) { 18 | let newX = x + d[0]; 19 | let newY = y + d[1]; 20 | if (newX >= 0 && newX < rows && newY >= 0 && newY < cols && !used[newX][newY]) { 21 | if (backtracking(newX, newY, start + 1)) { 22 | return true; 23 | } 24 | } 25 | } 26 | used[x][y] = false; 27 | } 28 | 29 | return false; 30 | } 31 | 32 | for (let i = 0; i < rows; i++) { 33 | for (let j = 0; j < cols; j++) { 34 | if (backtracking(i, j, 0)) { 35 | return true; 36 | } 37 | } 38 | } 39 | 40 | return false; 41 | }; -------------------------------------------------------------------------------- /递归与回溯/90. 子集 II.js: -------------------------------------------------------------------------------- 1 | // 同样的回溯,剪枝 2 | var subsetsWithDup = function (nums) { 3 | const res = []; 4 | nums.sort((a, b) => a - b); 5 | 6 | var backtracking = function (nums, path, start) { 7 | res.push([...path]); 8 | for (let i = start; i < nums.length; i++) { 9 | if (i > start && nums[i] === nums[i - 1]) { 10 | continue; 11 | } 12 | path.push(nums[i]); 13 | backtracking(nums, path, i + 1); 14 | path.pop(); 15 | } 16 | } 17 | 18 | backtracking(nums, [], 0) 19 | return res; 20 | }; -------------------------------------------------------------------------------- /递归与回溯/93. 复原 IP 地址.js: -------------------------------------------------------------------------------- 1 | // 回溯+剪枝 2 | var restoreIpAddresses = function (s) { 3 | const res = []; 4 | 5 | var backtracking = function (path, start) { 6 | const len = path.length; 7 | if (len > 4) return; 8 | if (len === 4 && start === s.length) { 9 | res.push([...path].join('.')); 10 | return; 11 | } 12 | for (let i = start; i < s.length; i++) { 13 | const str = s.substring(start, i + 1); 14 | if (str.length > 3 || Number(str) > 255) break; 15 | if (str.length > 1 && str[0] === "0") break; 16 | path.push(str); 17 | backtracking(path, i + 1); 18 | path.pop() 19 | } 20 | } 21 | 22 | backtracking([], 0); 23 | return res; 24 | }; -------------------------------------------------------------------------------- /递归与回溯/980. 不同路径 III.js: -------------------------------------------------------------------------------- 1 | // 回溯法 2 | // 参考:https://leetcode-cn.com/problems/unique-paths-iii/solution/dfs-hui-su-shuang-bai-by-quantum-10/ 3 | var uniquePathsIII = function (grid) { 4 | let startX = 0; 5 | let startY = 0; 6 | let stepNum = 1; 7 | 8 | for (let i = 0; i < grid.length; i++) { 9 | for (let j = 0; j < grid[0].length; j++) { 10 | if (grid[i][j] === 1) { 11 | startX = j; 12 | startY = i; 13 | continue; 14 | } 15 | if (grid[i][j] === 0) stepNum++; 16 | } 17 | } 18 | 19 | return dfs(startX, startY, stepNum, grid); 20 | }; 21 | 22 | var dfs = function (x, y, stepNum, grid) { 23 | if (x < 0 || x >= grid[0].length || y < 0 || y >= grid.length || grid[y][x] == -1) return 0; 24 | if (grid[y][x] == 2) return stepNum === 0 ? 1 : 0; 25 | grid[y][x] = -1; //已走过的标记为障碍 26 | let res = 0; 27 | res += dfs(x - 1, y, stepNum - 1, grid); 28 | res += dfs(x + 1, y, stepNum - 1, grid); 29 | res += dfs(x, y - 1, stepNum - 1, grid); 30 | res += dfs(x, y + 1, stepNum - 1, grid); 31 | grid[y][x] = 0; //dfs遍历完该位置为起始位置的情况后,置零,以不影响后面的dfs 32 | return res; 33 | } -------------------------------------------------------------------------------- /递归与回溯/剑指 Offer 38. 字符串的排列.js: -------------------------------------------------------------------------------- 1 | // 和 全排列2 本质一样 2 | var permutation = function (s) { 3 | const res = []; 4 | arr = s.split('').sort((a, b) => a.charCodeAt() - b.charCodeAt()); 5 | 6 | var backtracking = function (used, path) { 7 | if (path.length === arr.length) { 8 | res.push([...path].join('')); 9 | } 10 | for (let i = 0; i < arr.length; i++) { 11 | if (used[i]) continue; 12 | if (i - 1 >= 0 && arr[i - 1] === arr[i] && !used[i - 1]) continue; 13 | 14 | path.push(arr[i]); 15 | used[i] = true; 16 | backtracking(used, path); 17 | path.pop(); 18 | used[i] = false; 19 | } 20 | } 21 | 22 | backtracking([], []); 23 | return res; 24 | }; -------------------------------------------------------------------------------- /递归与回溯/面试题 16.11. 跳水板.js: -------------------------------------------------------------------------------- 1 | // 遍历,短的从 k 到 0,组成的长度一定是升序的。 2 | var divingBoard = function (shorter, longer, k) { 3 | if (k === 0) return []; 4 | if (shorter === longer) return [shorter * k]; 5 | 6 | const res = []; 7 | for (let i = k; i >= 0; i--) { 8 | const shortNum = i; 9 | const longNum = k - i; 10 | res.push(longNum * longer + shortNum * shorter); 11 | } 12 | 13 | return res; 14 | }; -------------------------------------------------------------------------------- /链表/141. 环形链表.js: -------------------------------------------------------------------------------- 1 | var hasCycle = function (head) { 2 | const map = new Map(); 3 | 4 | while (head) { 5 | if (map.get(head)) { 6 | return true; 7 | } 8 | map.set(head, true); 9 | head = head.next; 10 | } 11 | 12 | return false; 13 | }; -------------------------------------------------------------------------------- /链表/19. 删除链表的倒数第 N 个结点.js: -------------------------------------------------------------------------------- 1 | // 快慢指针 2 | var removeNthFromEnd = function (head, n) { 3 | const resNode = new ListNode('head'); 4 | resNode.next = head; 5 | let left = right = resNode; 6 | 7 | while (n !== 0) { 8 | right = right.next; 9 | n--; 10 | } 11 | while (right.next !== null) { 12 | right = right.next; 13 | left = left.next; 14 | } 15 | left.next = left.next.next; 16 | 17 | return resNode.next; 18 | } -------------------------------------------------------------------------------- /链表/2. 两数相加.js: -------------------------------------------------------------------------------- 1 | // 链表每位相加,记录进位。 注意的是,因为是倒序链表,所以我们要从两个链表的第一位就要开始相加。 2 | // 如果最后一位进位是大于 0 的,要在尾部再加一个进位节点。 3 | var addTwoNumbers = function (l1, l2) { 4 | const resHead = new ListNode('head'); 5 | let temp = resHead; 6 | let progression = 0; 7 | while (l1 || l2) { 8 | const val1 = l1 ? l1.val : 0; 9 | const val2 = l2 ? l2.val : 0; 10 | const sum = val1 + val2 + progression; 11 | progression = Math.floor(sum / 10); 12 | temp.next = new ListNode(sum % 10); 13 | temp = temp.next; 14 | 15 | if (l1) l1 = l1.next; 16 | if (l2) l2 = l2.next; 17 | } 18 | 19 | if (progression > 0) temp.next = new ListNode(progression); 20 | return resHead.next; 21 | } 22 | -------------------------------------------------------------------------------- /链表/203. 移除链表元素.js: -------------------------------------------------------------------------------- 1 | // 简单的链表删除,设立一个傀儡节点即可。 2 | var removeElements = function (head, val) { 3 | const root = new ListNode(); 4 | root.next = head; 5 | let cur = root; 6 | while (cur) { 7 | if (!cur.next) break; 8 | if (cur.next.val === val) { 9 | cur.next = cur.next.next; 10 | } else { 11 | cur = cur.next; 12 | } 13 | } 14 | return root.next; 15 | }; -------------------------------------------------------------------------------- /链表/206. 反转链表.js: -------------------------------------------------------------------------------- 1 | // 迭代法反转链表,主要设置3个指针,每次只将 nxt 节点指向 cur 节点。 2 | // 注意不能将 nxt 放到 while 外面定义,因为 head 有可能是空,找不到 next。 3 | var reverseList = function (head) { 4 | let pre = null; 5 | let cur = head; 6 | while (cur !== null) { 7 | let nxt = cur.next; 8 | cur.next = pre; 9 | pre = cur; 10 | cur = nxt; 11 | } 12 | return pre; 13 | } 14 | 15 | // 递归 16 | // 可参考:https://leetcode-cn.com/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-shuang-zhi-zhen-di-gui-yao-mo-/ 17 | var reverseList = function (head) { 18 | if (head === null || head.next === null) { 19 | return head; 20 | } 21 | const ret = reverseList(head.next); 22 | head.next.next = head; 23 | head.next = null; 24 | 25 | return ret; 26 | } 27 | -------------------------------------------------------------------------------- /链表/24. 两两交换链表中的节点.js: -------------------------------------------------------------------------------- 1 | // 三指针 2 | // 可参考这个解释:https://leetcode-cn.com/problems/swap-nodes-in-pairs/solution/yuan-lai-hui-luo-ji-qing-xi-jian-dan-yi-8t93h/ 3 | var swapPairs = function (head) { 4 | const resHead = new ListNode('head'); 5 | resHead.next = head; 6 | 7 | let curNode = resHead; 8 | while (curNode !== null && curNode.next !== null && curNode.next.next !== null) { 9 | const fir = curNode; 10 | const sec = curNode.next; 11 | const thd = sec.next; 12 | 13 | sec.next = thd.next; 14 | fir.next = thd; 15 | thd.next = sec; 16 | 17 | curNode = curNode.next.next; 18 | } 19 | return resHead.next; 20 | }; 21 | -------------------------------------------------------------------------------- /链表/92. 反转链表 II.js: -------------------------------------------------------------------------------- 1 | // 将链表分为三段,记录断开地方的位置,反转后再连起来。 2 | // 可参考:https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/die-dai-fa-zhu-bu-jie-jue-wen-ti-you-shi-pin-jie-s/ 3 | var reverseBetween = function (head, left, right) { 4 | let dummyNode = new ListNode(-1); 5 | dummyNode.next = head; 6 | let startNode = dummyNode; 7 | let endNode = dummyNode; 8 | for (let i = 0; i < left - 1; i++) { 9 | startNode = startNode.next; 10 | } 11 | for (let i = 0; i < right + 1; i++) { 12 | endNode = endNode.next; 13 | } 14 | let reverseEndNode = startNode.next; 15 | let reverseStartNode = reverseN(reverseEndNode, right - left + 1); 16 | startNode.next = reverseStartNode; 17 | reverseEndNode.next = endNode; 18 | 19 | return dummyNode.next; 20 | }; 21 | 22 | var reverseN = function (head, n) { 23 | let pre = null; 24 | let cur = head; 25 | for (let i = 0; i < n; i++) { 26 | let nxt = cur.next; 27 | cur.next = pre; 28 | pre = cur; 29 | cur = nxt; 30 | } 31 | return pre; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /链表/剑指 Offer 22. 链表中倒数第k个节点.js: -------------------------------------------------------------------------------- 1 | // 快慢指针 2 | var getKthFromEnd = function (head, k) { 3 | const tmpNode = new ListNode(); 4 | tmpNode.next = head; 5 | let left = tmpNode; 6 | let right = tmpNode; 7 | while (k !== 0) { 8 | right = right.next; 9 | k--; 10 | } 11 | while (right.next !== null) { 12 | right = right.next; 13 | left = left.next; 14 | } 15 | return left.next; 16 | }; --------------------------------------------------------------------------------