├── .gitignore ├── .prettierrc ├── .vscode └── launch.json ├── BFS问题 ├── 在每个树行中找最大值-515.js ├── 跳跃游戏 III-1306.js └── 跳跃游戏 IV-1345.js ├── DFS问题 ├── index.md ├── 图像渲染-733.js ├── 岛屿数量.js ├── 岛屿的周长-463.js ├── 岛屿的最大面积-659.js ├── 被围绕的区域-循环版.js ├── 被围绕的区域-正解版.js └── 被围绕的区域.js ├── LRU缓存.js ├── README.md ├── package.json ├── 三数之和.js ├── 两个数组的交集II-350.js ├── 二分查找 ├── x 的平方根-69.js └── 二分查找.js ├── 二叉树 ├── 二叉搜索树中第K小的元素.js ├── 二叉树的右视图-199.js ├── 二叉树的层次遍历 II-107.js ├── 二叉树的所有路径-257.js ├── 二叉树的最大深度-104.js ├── 二叉树的最小深度-111.js ├── 二叉树的最近公共祖先.js ├── 删除二叉搜索树中的节点-450.js ├── 对称二叉树-101.js ├── 将有序数组转为二叉搜索树.js ├── 左叶子之和-404.js ├── 平衡二叉树-110.js ├── 求根到叶子节点数字之和-129.js ├── 路径总和 II-113.js ├── 路径总和 III-437.js └── 路径总和-112.js ├── 二叉树的前中后序遍历.js ├── 位运算 └── 找不同-389.js ├── 前K个高频元素.js ├── 动态规划 ├── 01背包-DP版.js ├── 01背包-递归版.js ├── 一和零-474.js ├── 三角形的最小路径和-120.js ├── 下降路径最小和-931.js ├── 乘积最大子数组-152.js ├── 买卖股票的最佳时机.js ├── 使用最小花费爬楼梯-746.js ├── 分割等和子集-416.js ├── 单词拆分 II.js ├── 单词拆分-139.js ├── 可获得的最大点数-1423.js ├── 完全平方数-279.js ├── 恢复空格-面试题 17.13.js ├── 打家劫舍-198.js ├── 打家劫舍III-337.js ├── 找硬币-动态规划版.js ├── 找硬币-记忆递归版.js ├── 找硬币-递归版.js ├── 括号生成-22.js ├── 斐波那契数列-509.js ├── 无重叠区间-435.js ├── 最大子序和-53.js ├── 最大正方形-221.js ├── 最小路径和-64.js ├── 最长上升子序列-300.js ├── 最长公共子序列-1143.js ├── 最长单词-面试题 17.15.js ├── 最长回文子串-5.js ├── 最长重复子数组-718.js ├── 爬楼梯-70.js ├── 目标和-494.js ├── 零钱兑换II-518.js └── 面试题08.11.硬币.js ├── 单值二叉树.js ├── 双指针 ├── 删除排序数组中的重复项-26.js ├── 合并两个有序数组-88.js ├── 搜索二维矩阵 II-240.js ├── 最接近的三数之和-16.js └── 通过删除字母匹配到字典里最长单词-524.js ├── 可被K整除的子数组.js ├── 合并两个有序数组.js ├── 合并两个有序链表.js ├── 回文子串-647.js ├── 字符串字符最短路径.js ├── 工具 ├── 二叉树.js ├── 交换.js ├── 排序速度.js ├── 链表.js └── 随机值.js ├── 广度优先遍历和深度优先遍历.js ├── 归并排序.js ├── 找出子字符串第一次出现的位置-28.js ├── 排序 ├── 冒泡排序.js ├── 快速排序-三路.js ├── 快速排序-空间.js ├── 快速排序.js └── 选择排序.js ├── 最长公共前缀.js ├── 有序数组求交集.js ├── 有序数组的单个元素-540.js ├── 有序矩阵中第K小的元素-378.js ├── 标题分组.js ├── 栈和队列 ├── 二叉树的前序遍历-144.js ├── 从上到下打印二叉树III-面试题32.js ├── 有效的括号-20.js ├── 简化路径-71.js └── 逆波兰表达式求值-150.js ├── 格雷编码.js ├── 滑动窗口 ├── 找到字符串中所有字母异位词-438.js ├── 无重复字符的最长子串-3.js ├── 最小覆盖子串.js ├── 滑动窗口的最大值-面试题59 - I.js └── 长度最小的子数组-209.js ├── 种花问题.js ├── 移动零-283.js ├── 移除元素.js ├── 翻转二叉树.js ├── 贪心算法 ├── 分发饼干-455.js └── 判断子序列-392.js ├── 递归与回溯 ├── N 皇后-51.js ├── 不同路径 III-980.js ├── 二进制手表-401.js ├── 全排列 II-47.js ├── 全排列-46.js ├── 分割回文串-131.js ├── 单词搜索 II-212.Trie版本.js ├── 单词搜索 II-212.js ├── 单词搜索-79.js ├── 和为K的子数组-560.js ├── 复原ip地址-93.js ├── 子集 II-90.js ├── 子集-78.js ├── 字母大小写全排列-784.js ├── 字母迭代组合器-1286.js ├── 括号-面试题 08.09.js ├── 活字印刷-1079.js ├── 电话号码的字母组合-17.js ├── 矩阵置零-73.js ├── 组合-77.js ├── 组合总和-39.js ├── 组合总和-40.js ├── 螺旋矩阵-54.js ├── 解数独-37.js ├── 跳水板-面试题 16.11.js ├── 递归矩阵-59.js ├── 顺次数-1291.js └── 黄金矿工-1219.js ├── 链表 ├── 两两交换链表中的节点-24.js ├── 两数相加 II-445.js ├── 两数相加-3.js ├── 删除链表中的节点-面试题18.js ├── 删除链表的倒数第N个节点-19.js ├── 反转链表-206.js ├── 反转链表II-92.js └── 移除链表元素-203.js ├── 验证回文字符串2.js ├── 验证子序列.js └── 验证平衡二叉树.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | ._* 3 | .cache 4 | .project 5 | .settings 6 | .tmproj 7 | *.esproj 8 | *.sublime-project 9 | *.sublime-workspace 10 | nbproject 11 | thumbs.db 12 | *.iml 13 | 14 | # Folders to ignore 15 | .hg 16 | .svn 17 | .CVS 18 | .idea 19 | node_modules/ 20 | jscoverage_lib/ 21 | bower_components/ 22 | dist/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "semi": false 5 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "启动程序", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${file}" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /BFS问题/在每个树行中找最大值-515.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 您需要在二叉树的每一行中找到最大的值。 3 | 4 | 示例: 5 | 6 | 输入: 7 | 8 | 5 9 | / \ 10 | 14 2 11 | / \ \ 12 | 1 3 9 13 | 14 | 输出: [1, 3, 9] 15 | 16 | 来源:力扣(LeetCode) 17 | 链接:https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row 18 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 19 | */ 20 | 21 | /** 22 | * Definition for a binary tree node. 23 | * function TreeNode(val) { 24 | * this.val = val; 25 | * this.left = this.right = null; 26 | * } 27 | */ 28 | /** 29 | * @param {TreeNode} root 30 | * @return {number[]} 31 | */ 32 | let largestValues = function (root) { 33 | if (!root) return []; 34 | let queue = [root]; 35 | let maximums = []; 36 | 37 | while (queue.length) { 38 | let max = Number.MIN_SAFE_INTEGER; 39 | // 这里需要先缓存length 这个length代表当前层级的所有节点 40 | // 在循环开始后 会push新的节点 length就不稳定了 41 | let len = queue.length; 42 | for (let i = 0; i < len; i++) { 43 | let node = queue[i]; 44 | max = Math.max(node.val, max); 45 | 46 | if (node.left) { 47 | queue.push(node.left); 48 | } 49 | if (node.right) { 50 | queue.push(node.right); 51 | } 52 | } 53 | 54 | // 本「层级」处理完毕,截取掉。 55 | queue.splice(0, len); 56 | 57 | // 这个for循环结束后 代表当前层级的节点全部处理完毕 58 | // 直接把计算出来的最大值push到数组里即可。 59 | maximums.push(max); 60 | } 61 | 62 | return maximums; 63 | }; 64 | 65 | /** test case **/ 66 | function TreeNode(val) { 67 | this.val = val; 68 | this.left = this.right = null; 69 | } 70 | 71 | /** 预期:[1, 3, 9] */ 72 | let root = new TreeNode(1); 73 | root.left = new TreeNode(3); 74 | root.right = new TreeNode(2); 75 | root.left.left = new TreeNode(5); 76 | root.left.right = new TreeNode(3); 77 | root.right.right = new TreeNode(9); 78 | 79 | /** 80 | 5 81 | / 82 | 14 83 | / 84 | 1 85 | 86 | */ 87 | /** 预期:[5, 14, 1] */ 88 | let root2 = new TreeNode(5); 89 | root2.left = new TreeNode(14); 90 | root2.left.left = new TreeNode(1); 91 | 92 | console.log(largestValues(root)); 93 | console.log(largestValues(root2)); 94 | -------------------------------------------------------------------------------- /BFS问题/跳跃游戏 III-1306.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} arr 3 | * @param {number} start 4 | * @return {boolean} 5 | */ 6 | let canReach = function (arr, start) { 7 | let n = arr.length 8 | let visited = [] 9 | let queue = [start] 10 | while (queue.length) { 11 | let index = queue.pop() 12 | let val = arr[index] 13 | if (val === 0) { 14 | return true 15 | } 16 | let left = index - val 17 | let right = index + val 18 | if (left >= 0 && !visited[left]) { 19 | queue.push(left) 20 | } 21 | if (right < n && !visited[right]) { 22 | queue.push(right) 23 | } 24 | visited[index] = true 25 | } 26 | return false 27 | }; -------------------------------------------------------------------------------- /BFS问题/跳跃游戏 IV-1345.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} arr 3 | * @return {number} 4 | */ 5 | let minJumps = function (arr) { 6 | let n = arr.length 7 | if (n === 1) { 8 | return 0 9 | } 10 | 11 | // 连续出现超过两次的数字就抛弃掉 12 | let newArr = [] 13 | let sameCount = 0 14 | for (let i = 0; i < arr.length; i++) { 15 | if (arr[i] === arr[i - 1]) { 16 | sameCount += 1 17 | if (sameCount >= 2) { 18 | continue 19 | } else { 20 | newArr.push(arr[i]) 21 | } 22 | } else { 23 | newArr.push(arr[i]) 24 | sameCount = 0 25 | } 26 | } 27 | arr = newArr 28 | n = arr.length 29 | // 遍历一遍 记录每个数字出现的下标位置 30 | let indexesMap = new Map() 31 | for (let i = 0; i < n; i++) { 32 | let val = arr[i] 33 | let indexes = indexesMap.get(val) 34 | if (!indexes) { 35 | indexesMap.set(val, [i]) 36 | } else { 37 | indexes.push(i) 38 | } 39 | } 40 | 41 | let visited = [] 42 | let count = 0 43 | let queue = [0] 44 | while (queue.length) { 45 | count++ 46 | let len = queue.length 47 | for (let i = 0; i < len; i++) { 48 | let index = queue.shift() 49 | // 找到了 由于bfs的特性 此时用的跳跃次数一定是最少的 50 | if (index === n - 1) { 51 | return count - 1 52 | } 53 | 54 | // 没找到 继续把可以跳的几个位置都放入队列中 55 | let val = arr[index] 56 | let left = index - 1 57 | let right = index + 1 58 | let sameIndexes = indexesMap.get(val) 59 | 60 | if (left >= 0 && !visited[left]) queue.push(left) 61 | if (right < n && !visited[right]) queue.push(right) 62 | for (let sameIndex of sameIndexes) { 63 | if (sameIndex !== index && !visited[sameIndex]) { 64 | queue.push(sameIndex) 65 | } 66 | } 67 | 68 | visited[index] = true 69 | } 70 | } 71 | return n 72 | } 73 | -------------------------------------------------------------------------------- /DFS问题/index.md: -------------------------------------------------------------------------------- 1 | # 社区看到的优解 2 | 3 | 其实这题本身是我想复杂了,我是一个个格子去遍历,然后再上下左右去扩展延伸。 4 | 5 | 但是其实只需要遍历四个边界上的节点,遇到 O 的边界点才开始蔓延遍历,并且把遍历到的节点都标记为 M(防止重复遍历) 6 | 7 | 最后再一次性遍历整个二维数组,遇到 M 的标记都转为 O(因为是从边界蔓延的,一定是不符合 X 的条件的)。 8 | 9 | 这样遍历所走的路就会少很多。 10 | 11 | ```js 12 | let solve = function (board) { 13 | if (board.length == 0) return null; 14 | 15 | for (let y = 0; y < board.length; y++) { 16 | for (let x = 0; x < board[0].length; x++) { 17 | if (board[y][x] == "O" && (y == 0 || y == board.length - 1 || x == 0 || x == board[0].length - 1)) { 18 | dfs(board, y, x); 19 | } 20 | } 21 | } 22 | 23 | for (let y = 0; y < board.length; y++) { 24 | for (let x = 0; x < board[0].length; x++) { 25 | if (board[y][x] == "W") { 26 | board[y][x] = "O"; 27 | } else { 28 | board[y][x] = "X"; 29 | } 30 | } 31 | } 32 | 33 | return board; 34 | }; 35 | 36 | function dfs(board, y, x) { 37 | if (y < 0 || x < 0 || y >= board.length || x >= board[0].length || board[y][x] == "X" || board[y][x] == "W") { 38 | return; 39 | } 40 | board[y][x] = "W"; 41 | dfs(board, y + 1, x); 42 | dfs(board, y - 1, x); 43 | dfs(board, y, x + 1); 44 | dfs(board, y, x - 1); 45 | return; 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /DFS问题/图像渲染-733.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} image 3 | * @param {number} sr 4 | * @param {number} sc 5 | * @param {number} newColor 6 | * @return {number[][]} 7 | */ 8 | let floodFill = function (image, sr, sc, newColor) { 9 | let current = image[sr][sc]; 10 | dfs(image, sr, sc, current, newColor); 11 | return image; 12 | }; 13 | 14 | function dfs(image, sr, sc, current, newColor) { 15 | let row = image[sr]; 16 | if (row === undefined) return; 17 | 18 | let cell = row[sc]; 19 | if (cell === undefined || cell !== current || cell === newColor) return; 20 | 21 | row[sc] = newColor; 22 | dfs(image, sr + 1, sc, current, newColor); 23 | dfs(image, sr - 1, sc, current, newColor); 24 | dfs(image, sr, sc + 1, current, newColor); 25 | dfs(image, sr, sc - 1, current, newColor); 26 | } 27 | 28 | console.log( 29 | floodFill( 30 | [ 31 | [0, 0, 0], 32 | [0, 0, 0], 33 | ], 34 | 0, 35 | 0, 36 | 2 37 | ) 38 | ); 39 | -------------------------------------------------------------------------------- /DFS问题/岛屿数量.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 岛屿问题 3 | 4 | 给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 5 | 6 | 示例 1: 7 | 8 | 输入: 9 | 11110 10 | 11010 11 | 11000 12 | 00000 13 | 14 | 输出: 1 15 | 示例 2: 16 | 17 | 输入: 18 | 11000 19 | 11000 20 | 00100 21 | 00011 22 | 23 | 输出: 3 24 | 25 | 来源:力扣(LeetCode) 26 | 链接:https://leetcode-cn.com/problems/number-of-islands 27 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 28 | */ 29 | let numIslands = function (grid) { 30 | let count = 0 31 | for (let i = 0; i < grid.length; i++) { 32 | let row = grid[i] 33 | for (let j = 0; j < row.length; j++) { 34 | if (row[j] === '1') { 35 | dfs(grid, i, j) 36 | count++ 37 | } 38 | } 39 | } 40 | return count 41 | }; 42 | 43 | function dfs(grid, i, j) { 44 | let point = grid[i] && grid[i][j] 45 | if (point === '0' || point === undefined) { 46 | return 47 | } 48 | grid[i][j] = '0' 49 | 50 | dfs(grid, i - 1, j) // 上 51 | dfs(grid, i + 1, j) // 下 52 | dfs(grid, i, j - 1) // 左 53 | dfs(grid, i, j + 1) // 右 54 | } 55 | 56 | /** 57 | * 很经典的 dfs 问题,很棒很棒的思路,当找到一个点为1的时候,先记录数量加一。 58 | 59 | 然后对于每个点递归的去遍历自己和上下左右的节点,如果值为1就置为0再继续遍历上下左右,这样直到遇到某一边本身就为0的时候停止。 60 | 61 | 这样,一片 “值为1的岛屿” 就全部归零了。 62 | */ -------------------------------------------------------------------------------- /DFS问题/岛屿的周长-463.js: -------------------------------------------------------------------------------- 1 | // https://leetcode-cn.com/problems/island-perimeter 2 | /** 3 | * @param {number[][]} grid 4 | * @return {number} 5 | */ 6 | let islandPerimeter = function (grid) { 7 | let yLen = grid.length; 8 | if (!yLen) return 0; 9 | let xLen = grid[0].length; 10 | 11 | let perimeter = { value: 0 }; 12 | 13 | for (let y = 0; y < yLen; y++) { 14 | for (let x = 0; x < xLen; x++) { 15 | if (grid[y][x] === 1) { 16 | dfs(grid, y, x, perimeter); 17 | break; 18 | } 19 | } 20 | } 21 | 22 | return perimeter.value; 23 | }; 24 | 25 | function dfs(grid, y, x, perimeter) { 26 | let cell = grid[y][x]; 27 | if (cell === "COMPLETE") return; 28 | 29 | grid[y][x] = "COMPLETE"; 30 | 31 | let below = grid[y - 1] && grid[y - 1][x]; 32 | let upper = grid[y + 1] && grid[y + 1][x]; 33 | let left = grid[y][x - 1]; 34 | let right = grid[y][x + 1]; 35 | 36 | if (below) { 37 | dfs(grid, y - 1, x, perimeter); 38 | } else { 39 | perimeter.value++; 40 | } 41 | 42 | if (upper) { 43 | dfs(grid, y + 1, x, perimeter); 44 | } else { 45 | perimeter.value++; 46 | } 47 | 48 | if (left) { 49 | dfs(grid, y, x - 1, perimeter); 50 | } else { 51 | perimeter.value++; 52 | } 53 | 54 | if (right) { 55 | dfs(grid, y, x + 1, perimeter); 56 | } else { 57 | perimeter.value++; 58 | } 59 | } 60 | 61 | console.log( 62 | islandPerimeter([ 63 | [0, 1, 0, 0], 64 | [1, 1, 1, 0], 65 | [0, 1, 0, 0], 66 | [1, 1, 0, 0], 67 | ]) 68 | ); 69 | -------------------------------------------------------------------------------- /DFS问题/岛屿的最大面积-659.js: -------------------------------------------------------------------------------- 1 | // https://github.com/sl1673495/daily-plan/issues/18 2 | 3 | /** 4 | * @param {number[][]} grid 5 | * @return {number} 6 | */ 7 | let maxAreaOfIsland = function (grid) { 8 | let yLen = grid.length; 9 | if (!yLen) return grid; 10 | let xLen = grid[0].length; 11 | let max = 0; 12 | 13 | for (let y = 0; y < yLen; y++) { 14 | for (let x = 0; x < xLen; x++) { 15 | if (grid[y][x] === 1) { 16 | let countRef = { current: 0 }; 17 | dfs(grid, y, x, countRef); 18 | if (countRef.current > max) { 19 | max = countRef.current; 20 | } 21 | } 22 | } 23 | } 24 | return max; 25 | }; 26 | 27 | function dfs(grid, y, x, countRef) { 28 | if (!grid[y] || !grid[y][x] || grid[y][x] === 0 || grid[y][x] === "COMPLETE") { 29 | return; 30 | } 31 | 32 | if (grid[y][x] === 1) { 33 | grid[y][x] = "COMPLETE"; 34 | countRef.current++; 35 | } 36 | 37 | dfs(grid, y - 1, x, countRef); 38 | dfs(grid, y + 1, x, countRef); 39 | dfs(grid, y, x - 1, countRef); 40 | dfs(grid, y, x + 1, countRef); 41 | } -------------------------------------------------------------------------------- /DFS问题/被围绕的区域-循环版.js: -------------------------------------------------------------------------------- 1 | /** 2 | 给定一个二维的矩阵,包含 'X' 和 'O'(字母 O)。 3 | 4 | 找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。 5 | 6 | 示例: 7 | 8 | X X X X 9 | X O O X 10 | X X O X 11 | X O X X 12 | 运行你的函数后,矩阵变为: 13 | 14 | X X X X 15 | X X X X 16 | X X X X 17 | X O X X 18 | 解释: 19 | 20 | 被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。 21 | 22 | 来源:力扣(LeetCode) 23 | 链接:https://leetcode-cn.com/problems/surrounded-regions 24 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 25 | */ 26 | 27 | /** 28 | * @param {character[][]} board 29 | * @return {void} Do not return anything, modify board in-place instead. 30 | */ 31 | 32 | function genKey(i, j) { 33 | return `${i}-${j}`; 34 | } 35 | 36 | let solve = function (board) { 37 | const notXMap = {}; 38 | for (let i = 0; i < board.length; i++) { 39 | let row = board[i]; 40 | for (let j = 0; j < row.length; j++) { 41 | let cell = row[j]; 42 | if (cell === "O" && !notXMap[genKey(i, j)]) { 43 | dfsIsValid(board, i, j, notXMap); 44 | } 45 | } 46 | } 47 | return board; 48 | }; 49 | 50 | function getBoradValue(board, i, j) { 51 | let row = board[i]; 52 | return row && row[j]; 53 | } 54 | 55 | function dfsIsValid(board, currentI, currnetJ, notXMap) { 56 | let queue = []; // 本次dfs走过的路径 57 | let thisTimeFailed = { current: false }; 58 | let thisTimeHasHandledMap = {}; 59 | let checkQueue = [[currentI, currnetJ]]; 60 | 61 | while (checkQueue.length) { 62 | const head = checkQueue.shift(); 63 | // 临时替换 标记为可替换节点 64 | const [i, j] = head; 65 | let row = board[i]; 66 | let cell = row && board[i][j]; 67 | 68 | // 已经在map中记录处理过的 整个链路都不可能为X 69 | if (notXMap[genKey(i, j)]) { 70 | continue; 71 | } 72 | 73 | // 本次路径失败 74 | if (!cell) { 75 | thisTimeFailed.current = true; 76 | continue; 77 | } 78 | 79 | // 为X说明这一边符合条件 80 | if (cell === "X") continue; 81 | 82 | // 推到路径队列里,等到遍历完成决定该保留O还是换成X 83 | queue.push([i, j]); 84 | 85 | if (!thisTimeHasHandledMap[genKey(i - 1, j)] && !notXMap[genKey(i - 1, j)]) { 86 | thisTimeHasHandledMap[genKey(i - 1, j)] = true; 87 | checkQueue.push([i - 1, j]) 88 | }; 89 | if (!thisTimeHasHandledMap[genKey(i + 1, j)] && !notXMap[genKey(i + 1, j)]) { 90 | thisTimeHasHandledMap[genKey(i + 1, j)] = true; 91 | checkQueue.push([i + 1, j]) 92 | }; 93 | if (!thisTimeHasHandledMap[genKey(i, j - 1)] && !notXMap[genKey(i, j - 1)]) { 94 | thisTimeHasHandledMap[genKey(i, j - 1)] = true; 95 | checkQueue.push([i, j - 1]) 96 | }; 97 | if (!thisTimeHasHandledMap[genKey(i, j + 1)] && !notXMap[genKey(i, j + 1)]) { 98 | thisTimeHasHandledMap[genKey(i, j + 1)] = true; 99 | checkQueue.push([i, j + 1]); 100 | } 101 | } 102 | 103 | // 循环结束后 104 | // 如果本次循环遇到出界的情况 则全部记录在map中 后续遍历不再走这些格子 105 | // 否则说明是被围绕的区域 赋值为X 106 | if (queue.length) { 107 | queue.forEach(([i, j]) => { 108 | // 失败了 恢复O 109 | if (thisTimeFailed.current) { 110 | // 记录在map中 一定不会是X 111 | notXMap[genKey(i, j)] = true; 112 | } else { 113 | board[i][j] = "X"; 114 | } 115 | }); 116 | } 117 | } 118 | 119 | console.log( 120 | solve([ 121 | ["X", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O"], 122 | ["O", "X", "O", "O", "O", "O", "X", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "X", "X"], 123 | ["O", "O", "O", "O", "O", "O", "O", "O", "X", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "X"], 124 | ["O", "O", "X", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "X", "O"], 125 | ["O", "O", "O", "O", "O", "X", "O", "O", "O", "O", "X", "O", "O", "O", "O", "O", "X", "O", "O", "X"], 126 | ["X", "O", "O", "O", "X", "O", "O", "O", "O", "O", "X", "O", "X", "O", "X", "O", "X", "O", "X", "O"], 127 | ["O", "O", "O", "O", "X", "O", "O", "X", "O", "O", "O", "O", "O", "X", "O", "O", "X", "O", "O", "O"], 128 | ["X", "O", "O", "O", "X", "X", "X", "O", "X", "O", "O", "O", "O", "X", "X", "O", "X", "O", "O", "O"], 129 | ["O", "O", "O", "O", "O", "X", "X", "X", "X", "O", "O", "O", "O", "X", "O", "O", "X", "O", "O", "O"], 130 | ["X", "O", "O", "O", "O", "X", "O", "O", "O", "O", "O", "O", "X", "X", "O", "O", "X", "O", "O", "X"], 131 | ["O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "X", "O", "O", "X", "O", "O", "O", "X", "O", "X"], 132 | ["O", "O", "O", "O", "X", "O", "X", "O", "O", "X", "X", "O", "O", "O", "O", "O", "X", "O", "O", "O"], 133 | ["X", "X", "O", "O", "O", "O", "O", "X", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O"], 134 | ["O", "X", "O", "X", "O", "O", "O", "X", "O", "X", "O", "O", "O", "X", "O", "X", "O", "X", "O", "O"], 135 | ["O", "O", "X", "O", "O", "O", "O", "O", "O", "O", "X", "O", "O", "O", "O", "O", "X", "O", "X", "O"], 136 | ["X", "X", "O", "O", "O", "O", "O", "O", "O", "O", "X", "O", "X", "X", "O", "O", "O", "X", "O", "O"], 137 | ["O", "O", "X", "O", "O", "O", "O", "O", "O", "O", "X", "O", "O", "X", "O", "X", "O", "X", "O", "O"], 138 | ["O", "O", "O", "X", "O", "O", "O", "O", "O", "X", "X", "X", "O", "O", "X", "O", "O", "O", "X", "O"], 139 | ["O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O", "O"], 140 | ["X", "O", "O", "O", "O", "X", "O", "O", "O", "X", "X", "O", "O", "X", "O", "X", "O", "X", "O", "O"], 141 | ]) 142 | ); 143 | -------------------------------------------------------------------------------- /DFS问题/被围绕的区域-正解版.js: -------------------------------------------------------------------------------- 1 | // https://github.com/sl1673495/daily-plan/issues/16 2 | 3 | let solve = function (board) { 4 | if (board.length == 0) return null; 5 | 6 | for (let y = 0; y < board.length; y++) { 7 | for (let x = 0; x < board[0].length; x++) { 8 | if (board[y][x] == "O" && (y == 0 || y == board.length - 1 || x == 0 || x == board[0].length - 1)) { 9 | dfs(board, y, x); 10 | } 11 | } 12 | } 13 | 14 | for (let y = 0; y < board.length; y++) { 15 | for (let x = 0; x < board[0].length; x++) { 16 | if (board[y][x] == "W") { 17 | board[y][x] = "O"; 18 | } else { 19 | board[y][x] = "X"; 20 | } 21 | } 22 | } 23 | 24 | return board; 25 | }; 26 | 27 | function dfs(board, y, x) { 28 | if (y < 0 || x < 0 || y >= board.length || x >= board[0].length || board[y][x] == "X" || board[y][x] == "W") { 29 | return; 30 | } 31 | board[y][x] = "W"; 32 | dfs(board, y + 1, x); 33 | dfs(board, y - 1, x); 34 | dfs(board, y, x + 1); 35 | dfs(board, y, x - 1); 36 | return; 37 | } 38 | 39 | console.log( 40 | solve([ 41 | ["X", "X", "X", "X"], 42 | ["X", "O", "O", "X"], 43 | ["X", "X", "O", "X"], 44 | ["X", "O", "X", "X"], 45 | ]) 46 | ); 47 | -------------------------------------------------------------------------------- /DFS问题/被围绕的区域.js: -------------------------------------------------------------------------------- 1 | /** 2 | 给定一个二维的矩阵,包含 'X' 和 'O'(字母 O)。 3 | 4 | 找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。 5 | 6 | 示例: 7 | 8 | X X X X 9 | X O O X 10 | X X O X 11 | X O X X 12 | 运行你的函数后,矩阵变为: 13 | 14 | X X X X 15 | X X X X 16 | X X X X 17 | X O X X 18 | 解释: 19 | 20 | 被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。 21 | 22 | 来源:力扣(LeetCode) 23 | 链接:https://leetcode-cn.com/problems/surrounded-regions 24 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 25 | */ 26 | 27 | /** 28 | * @param {character[][]} board 29 | * @return {void} Do not return anything, modify board in-place instead. 30 | */ 31 | 32 | function genKey(i, j) { 33 | return `${i}-${j}`; 34 | } 35 | 36 | let solve = function (board) { 37 | const notXMap = {}; 38 | for (let i = 0; i < board.length; i++) { 39 | let row = board[i]; 40 | for (let j = 0; j < row.length; j++) { 41 | let cell = row[j]; 42 | if (cell === "O") { 43 | let queue = []; // 本次dfs走过的路径 44 | let thisDfsFailed = { current: false }; 45 | dfsIsValid(board, i, j, notXMap, queue, thisDfsFailed); 46 | } 47 | } 48 | } 49 | for (let i = 0; i < board.length; i++) { 50 | let row = board[i]; 51 | for (let j = 0; j < row.length; j++) { 52 | let cell = row[j]; 53 | if (cell === "O" || cell === "SYMBOL") { 54 | // 如果是X 55 | if (!notXMap[genKey(i, j)]) { 56 | board[i][j] = "X"; 57 | } 58 | } 59 | } 60 | } 61 | return board; 62 | }; 63 | 64 | function dfsIsValid(board, i, j, notXMap, queue, thisDfsFailed) { 65 | // 临时替换 标记为可替换节点 66 | let row = board[i]; 67 | let cell = row && board[i][j]; 68 | 69 | // 已经在map中记录处理过的 整个链路都不可能为X 70 | if (notXMap[genKey(i, j)]) { 71 | return; 72 | } 73 | 74 | // 本次路径失败 75 | if (!cell) { 76 | thisDfsFailed.current = true; 77 | return; 78 | } 79 | 80 | // 为X说明这一边符合条件 81 | if (cell === "X") return; 82 | // 为SYMBOL说明之前已经被临时标记过 继续检查别的边 83 | if (cell === "SYMBOL") return; 84 | 85 | board[i][j] = "SYMBOL"; 86 | // 这里才去记录路径,排除上面不需要处理的格子。 87 | queue.push({ i, j }); 88 | 89 | // 为O的话,进一步DFS,直到遇到「边界(X、SYMBOL、undefined)」 90 | dfsIsValid(board, i - 1, j, notXMap, queue, thisDfsFailed); // 上 91 | dfsIsValid(board, i + 1, j, notXMap, queue, thisDfsFailed); // 下 92 | dfsIsValid(board, i, j - 1, notXMap, queue, thisDfsFailed); // 左 93 | dfsIsValid(board, i, j + 1, notXMap, queue, thisDfsFailed); // 右 94 | 95 | // 如果DFS结束,queue的值没有被清空,说明这是符合条件的 96 | if (queue.length) { 97 | queue.forEach(({ i, j }) => { 98 | // 失败了 恢复O 99 | if (thisDfsFailed.current) { 100 | board[i][j] = "O"; 101 | // 记录在map中 一定不会是X 102 | notXMap[genKey(i, j)] = true; 103 | } 104 | }); 105 | } 106 | } 107 | 108 | console.log( 109 | solve([ 110 | ["O", "X", "O", "O", "X", "X", "X", "O", "O", "O", "O", "O", "X", "O", "O", "O", "O", "X", "O", "X"], 111 | ["X", "O", "X", "O", "O", "X", "X", "O", "O", "X", "O", "X", "O", "X", "O", "X", "X", "O", "O", "O"], 112 | ["O", "X", "O", "O", "O", "X", "X", "X", "X", "O", "O", "O", "O", "O", "X", "X", "X", "X", "O", "X"], 113 | ["X", "X", "O", "O", "O", "X", "X", "O", "O", "O", "X", "X", "X", "O", "O", "X", "O", "X", "X", "O"], 114 | ["O", "X", "O", "X", "X", "O", "X", "O", "O", "O", "X", "O", "O", "X", "O", "O", "O", "O", "O", "X"], 115 | ["X", "O", "O", "X", "O", "X", "O", "O", "O", "X", "X", "O", "X", "O", "O", "X", "O", "O", "O", "O"], 116 | ["X", "O", "O", "O", "X", "X", "O", "O", "O", "O", "O", "X", "O", "O", "X", "O", "O", "O", "O", "X"], 117 | ["X", "O", "O", "O", "X", "O", "X", "X", "X", "O", "X", "O", "X", "X", "X", "X", "O", "O", "O", "X"], 118 | ["X", "O", "O", "X", "O", "O", "O", "X", "O", "O", "O", "O", "O", "O", "O", "O", "O", "X", "O", "X"], 119 | ["O", "O", "O", "X", "O", "X", "X", "X", "X", "X", "X", "X", "X", "X", "O", "O", "O", "O", "X", "O"], 120 | ["X", "O", "X", "O", "X", "O", "O", "X", "X", "X", "O", "X", "X", "O", "O", "X", "X", "O", "O", "O"], 121 | ["O", "X", "O", "O", "X", "O", "O", "O", "O", "O", "O", "X", "X", "X", "X", "O", "O", "O", "X", "O"], 122 | ["X", "O", "O", "O", "X", "X", "X", "O", "X", "O", "O", "O", "X", "O", "X", "O", "X", "O", "O", "X"], 123 | ["O", "O", "O", "O", "X", "O", "X", "X", "O", "X", "O", "X", "O", "X", "X", "X", "X", "O", "O", "O"], 124 | ["O", "X", "X", "O", "O", "O", "O", "X", "O", "O", "X", "X", "X", "O", "O", "X", "X", "O", "X", "O"], 125 | ["X", "O", "X", "X", "X", "X", "X", "X", "O", "X", "X", "O", "X", "O", "O", "X", "O", "O", "O", "X"], 126 | ["X", "O", "O", "O", "X", "O", "X", "O", "O", "X", "O", "X", "O", "O", "X", "O", "O", "X", "X", "X"], 127 | ["O", "O", "X", "O", "O", "O", "O", "X", "O", "O", "X", "X", "O", "X", "X", "X", "O", "O", "O", "O"], 128 | ["O", "O", "X", "O", "O", "O", "O", "O", "O", "X", "X", "O", "X", "O", "X", "O", "O", "O", "X", "X"], 129 | ["X", "O", "O", "O", "X", "O", "X", "X", "X", "O", "O", "X", "O", "X", "O", "X", "X", "O", "O", "O"], 130 | ]) 131 | ); 132 | 133 | //输入 134 | [ 135 | ["O", "X", "O", "O", "X", "X", "X", "O", "O", "O", "O", "O", "X", "O", "O", "O", "O", "X", "O", "X"], 136 | ["X", "O", "X", "O", "O", "X", "X", "O", "O", "X", "O", "X", "O", "X", "O", "X", "X", "O", "O", "O"], 137 | ["O", "X", "O", "O", "O", "X", "X", "X", "X", "O", "O", "O", "O", "O", "X", "X", "X", "X", "O", "X"], 138 | ["X", "X", "O", "O", "O", "X", "X", "O", "O", "O", "X", "X", "X", "O", "O", "X", "O", "X", "X", "O"], 139 | ["O", "X", "O", "X", "X", "O", "X", "O", "O", "O", "X", "O", "O", "X", "O", "O", "O", "O", "O", "X"], 140 | ["X", "O", "O", "X", "O", "X", "O", "O", "O", "X", "X", "O", "X", "O", "O", "X", "O", "O", "O", "O"], 141 | ["X", "O", "O", "O", "X", "X", "O", "O", "O", "O", "O", "X", "O", "O", "X", "O", "O", "O", "O", "X"], 142 | ["X", "O", "O", "O", "X", "O", "X", "X", "X", "O", "X", "O", "X", "X", "X", "X", "O", "O", "O", "X"], 143 | ["X", "O", "O", "X", "O", "O", "O", "X", "O", "O", "O", "O", "O", "O", "O", "O", "O", "X", "O", "X"], 144 | ["O", "O", "O", "X", "O", "X", "X", "X", "X", "X", "X", "X", "X", "X", "O", "O", "O", "O", "X", "O"], 145 | ["X", "O", "X", "O", "X", "O", "O", "X", "X", "X", "O", "X", "X", "O", "O", "X", "X", "O", "O", "O"], 146 | ["O", "X", "O", "O", "X", "O", "O", "O", "O", "O", "O", "X", "X", "X", "X", "O", "O", "O", "X", "O"], 147 | ["X", "O", "O", "O", "X", "X", "X", "O", "X", "O", "O", "O", "X", "O", "X", "O", "X", "O", "O", "X"], 148 | ["O", "O", "O", "O", "X", "O", "X", "X", "O", "X", "O", "X", "O", "X", "X", "X", "X", "O", "O", "O"], 149 | ["O", "X", "X", "O", "O", "O", "O", "X", "O", "O", "X", "X", "X", "O", "O", "X", "X", "O", "X", "O"], 150 | ["X", "O", "X", "X", "X", "X", "X", "X", "O", "X", "X", "O", "X", "O", "O", "X", "O", "O", "O", "X"], 151 | ["X", "O", "O", "O", "X", "O", "X", "O", "O", "X", "O", "X", "O", "O", "X", "O", "O", "X", "X", "X"], 152 | ["O", "O", "X", "O", "O", "O", "O", "X", "O", "O", "X", "X", "O", "X", "X", "X", "O", "O", "O", "O"], 153 | ["O", "O", "X", "O", "O", "O", "O", "O", "O", "X", "X", "O", "X", "O", "X", "O", "O", "O", "X", "X"], 154 | ["X", "O", "O", "O", "X", "O", "X", "X", "X", "O", "O", "X", "O", "X", "O", "X", "X", "O", "O", "O"], 155 | ]; 156 | 157 | // 期望 158 | [ 159 | ["O", "X", "O", "O", "X", "X", "X", "O", "O", "O", "O", "O", "X", "O", "O", "O", "O", "X", "O", "X"], 160 | ["X", "X", "X", "O", "O", "X", "X", "O", "O", "X", "O", "X", "O", "X", "O", "X", "X", "O", "O", "O"], 161 | ["O", "X", "O", "O", "O", "X", "X", "X", "X", "O", "O", "O", "O", "O", "X", "X", "X", "X", "O", "X"], 162 | ["X", "X", "O", "O", "O", "X", "X", "O", "O", "O", "X", "X", "X", "O", "O", "X", "O", "X", "X", "O"], 163 | ["O", "X", "O", "X", "X", "X", "X", "O", "O", "O", "X", "X", "X", "X", "O", "O", "O", "O", "O", "X"], 164 | ["X", "O", "O", "X", "X", "X", "O", "O", "O", "X", "X", "X", "X", "O", "O", "X", "O", "O", "O", "O"], 165 | ["X", "O", "O", "O", "X", "X", "O", "O", "O", "O", "O", "X", "O", "O", "X", "O", "O", "O", "O", "X"], 166 | ["X", "O", "O", "O", "X", "X", "X", "X", "X", "O", "X", "O", "X", "X", "X", "X", "O", "O", "O", "X"], 167 | ["X", "O", "O", "X", "X", "X", "X", "X", "O", "O", "O", "O", "O", "O", "O", "O", "O", "X", "O", "X"], 168 | ["O", "O", "O", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "O", "O", "O", "O", "X", "O"], 169 | ["X", "O", "X", "O", "X", "X", "X", "X", "X", "X", "X", "X", "X", "O", "O", "X", "X", "O", "O", "O"], 170 | ["O", "X", "O", "O", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "O", "O", "O", "X", "O"], 171 | ["X", "O", "O", "O", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "O", "X", "O", "O", "X"], 172 | ["O", "O", "O", "O", "X", "O", "X", "X", "O", "X", "X", "X", "X", "X", "X", "X", "X", "O", "O", "O"], 173 | ["O", "X", "X", "O", "O", "O", "O", "X", "O", "O", "X", "X", "X", "X", "X", "X", "X", "O", "X", "O"], 174 | ["X", "O", "X", "X", "X", "X", "X", "X", "O", "X", "X", "X", "X", "X", "X", "X", "O", "O", "O", "X"], 175 | ["X", "O", "O", "O", "X", "O", "X", "O", "O", "X", "X", "X", "X", "X", "X", "O", "O", "X", "X", "X"], 176 | ["O", "O", "X", "O", "O", "O", "O", "X", "O", "O", "X", "X", "X", "X", "X", "X", "O", "O", "O", "O"], 177 | ["O", "O", "X", "O", "O", "O", "O", "O", "O", "X", "X", "X", "X", "X", "X", "O", "O", "O", "X", "X"], 178 | ["X", "O", "O", "O", "X", "O", "X", "X", "X", "O", "O", "X", "O", "X", "O", "X", "X", "O", "O", "O"], 179 | ]; 180 | 181 | // 这个递归解法思路是对的,但是在最后一个测试用例爆栈了。 -------------------------------------------------------------------------------- /LRU缓存.js: -------------------------------------------------------------------------------- 1 | class DoubleNode { 2 | constructor(key, val) { 3 | this.key = key 4 | this.val = val 5 | 6 | this.prev = null 7 | this.next = null 8 | } 9 | } 10 | 11 | class LRUCache { 12 | constructor(max) { 13 | this.max = max 14 | this.map = new Map() 15 | 16 | this.head = null 17 | this.tail = null 18 | } 19 | 20 | get(key) { 21 | const node = this.map.get(key) 22 | if (!node) { 23 | return -1 24 | } else { 25 | const res = node.val 26 | this.remove(node) 27 | this.appendHead(node) 28 | return res 29 | } 30 | } 31 | 32 | put(key, value) { 33 | let node = this.map.get(key) 34 | // 有这个缓存 35 | if (node) { 36 | node.val = value 37 | // 新加入的 放在最前面 38 | this.remove(node) 39 | this.appendHead(node) 40 | } else { 41 | // 没有这个缓存 42 | node = new DoubleNode(key, value) 43 | // 如果超出容量了 删除最后一个 再放到头部 44 | if (this.map.size >= this.max) { 45 | this.map.delete(this.tail.key) 46 | this.remove(this.tail) 47 | this.appendHead(node) 48 | this.map.set(key, node) 49 | } else { 50 | // 未超出容量 就直接放到头部 51 | this.appendHead(node) 52 | this.map.set(key, node) 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * 把头部指针的改变成新的node 59 | * @param {DoubleNode} node 60 | */ 61 | appendHead(node) { 62 | if (this.head === null) { 63 | this.head = this.tail = node 64 | } else { 65 | node.next = this.head 66 | this.head.prev = node 67 | this.head = node 68 | } 69 | } 70 | 71 | /** 72 | * 删除某个节点 73 | * @param {DoubleNode} node 74 | */ 75 | remove(node) { 76 | if (this.head === this.tail) { 77 | this.head = this.tail = null 78 | } else { 79 | // 删除头部 80 | if (this.head === node) { 81 | this.head = this.head.next 82 | node.next = null 83 | } else if (this.tail === node) { 84 | this.tail = this.tail.prev 85 | this.tail.next = null 86 | node.prev = null 87 | } else { 88 | node.prev.next = node.next 89 | node.next.prev = node.prev 90 | node.prev = node.next = null 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to leetcode-javascript 👋

2 |

3 | 4 | License: MIT 5 | 6 |

7 | 8 | > 力扣的题解记录(JavaScript) 9 | 10 | ## 关于我 11 | 大家好,我是 ssh,现在在字节跳动的 Web Infra 担任前端工程师,微信:**[sshsunlight](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/017d568dc1d14cd883cc3238350a39ec~tplv-k3u1fbpfcp-watermark.image)**,欢迎找我交个朋友。 12 | 13 | 一些算法相关的资料,我放在「前端从进阶到入院」公众号里了,回复「资料」即可获取。 14 | 15 | ![qrcode_for_gh_d2b31290dd8b_258](https://user-images.githubusercontent.com/23615778/134800856-9a44fa9a-4f1b-4884-a0b6-b58c5f3331df.jpg) 16 | 17 | ## 调试 18 | 19 | 提供了 .vscode 配置文件,在 vscode 中选择「小爬虫」图标,点击启动程序,即可启动断点调试。 20 | 21 | ## 思路 22 | 23 | 思路会记录在本仓库的 Issues 中,按照 label 进行分类。比如想查看 「DFS」 分类下的问题,那么选择标签进行筛选即可。 24 | 25 | ## 目录 26 | 27 | 28 | ### 例题详解 29 | 30 | [最接近的三数之和-16](https://github.com/sl1673495/leetcode-javascript/issues/115) 31 | 32 | [分发饼干-455](https://github.com/sl1673495/leetcode-javascript/issues/88) 33 | 34 | [N皇后-51](https://github.com/sl1673495/leetcode-javascript/issues/78) 35 | 36 | [单词搜索-79](https://github.com/sl1673495/leetcode-javascript/issues/77) 37 | 38 | [二进制手表-401](https://github.com/sl1673495/leetcode-javascript/issues/76) 39 | 40 | [电话号码的字母组合-17](https://github.com/sl1673495/leetcode-javascript/issues/65) 41 | 42 | [二叉树的所有路径-257](https://github.com/sl1673495/leetcode-javascript/issues/59) 43 | 44 | [路径总和-112](https://github.com/sl1673495/leetcode-javascript/issues/57) 45 | 46 | [两两交换链表中的节点-24](https://github.com/sl1673495/leetcode-javascript/issues/51) 47 | 48 | [有效的括号-20](https://github.com/sl1673495/leetcode-javascript/issues/48) 49 | 50 | [无重复字符的最长子串-3](https://github.com/sl1673495/leetcode-javascript/issues/42) 51 | 52 | [二分查找-704](https://github.com/sl1673495/leetcode-javascript/issues/23) 53 | 54 | ### 递归与回溯 55 | 56 | [跳水板-面试题 16.11 ](https://github.com/sl1673495/leetcode-javascript/issues/118) 57 | 58 | [顺次数-1291](https://github.com/sl1673495/leetcode-javascript/issues/116) 59 | 60 | [螺旋矩阵 II-59](https://github.com/sl1673495/leetcode-javascript/issues/113) 61 | 62 | [螺旋矩阵-54](https://github.com/sl1673495/leetcode-javascript/issues/112) 63 | 64 | [矩阵置零-73](https://github.com/sl1673495/leetcode-javascript/issues/111) 65 | 66 | [不同路径 III-980](https://github.com/sl1673495/leetcode-javascript/issues/107) 67 | 68 | [字母大小写全排列-784](https://github.com/sl1673495/leetcode-javascript/issues/106) 69 | 70 | [黄金矿工-1219](https://github.com/sl1673495/leetcode-javascript/issues/105) 71 | 72 | [有重复字符串的排列组合-面试题 08.08](https://github.com/sl1673495/leetcode-javascript/issues/104) 73 | 74 | [单词搜索 II-212](https://github.com/sl1673495/leetcode-javascript/issues/92) 75 | 76 | [解数独-37](https://github.com/sl1673495/leetcode-javascript/issues/79) 77 | 78 | [N皇后-51](https://github.com/sl1673495/leetcode-javascript/issues/78) 79 | 80 | [单词搜索-79](https://github.com/sl1673495/leetcode-javascript/issues/77) 81 | 82 | [二进制手表-401](https://github.com/sl1673495/leetcode-javascript/issues/76) 83 | 84 | [子集 II-90](https://github.com/sl1673495/leetcode-javascript/issues/75) 85 | 86 | [ 组合总和 III-216](https://github.com/sl1673495/leetcode-javascript/issues/74) 87 | 88 | [组合总和 II-40](https://github.com/sl1673495/leetcode-javascript/issues/73) 89 | 90 | [组合总和-39](https://github.com/sl1673495/leetcode-javascript/issues/72) 91 | 92 | [子集-78](https://github.com/sl1673495/leetcode-javascript/issues/71) 93 | 94 | [组合-77](https://github.com/sl1673495/leetcode-javascript/issues/70) 95 | 96 | [全排列 II-47](https://github.com/sl1673495/leetcode-javascript/issues/69) 97 | 98 | [全排列-46](https://github.com/sl1673495/leetcode-javascript/issues/68) 99 | 100 | [分割回文串-131](https://github.com/sl1673495/leetcode-javascript/issues/67) 101 | 102 | [复原IP地址-93](https://github.com/sl1673495/leetcode-javascript/issues/66) 103 | 104 | [电话号码的字母组合-17](https://github.com/sl1673495/leetcode-javascript/issues/65) 105 | 106 | [括号生成-22](https://github.com/sl1673495/leetcode-javascript/issues/31) 107 | 108 | ### 动态规划 109 | 110 | [最长的斐波那契子序列的长度-873](https://github.com/sl1673495/leetcode-javascript/issues/117) 111 | 112 | [最长重复子数组-718](https://github.com/sl1673495/leetcode-javascript/issues/114) 113 | 114 | [下降路径最小和-931](https://github.com/sl1673495/leetcode-javascript/issues/108) 115 | 116 | [最大正方形-221](https://github.com/sl1673495/leetcode-javascript/issues/101) 117 | 118 | [恢复空格-面试题 17.13](https://github.com/sl1673495/leetcode-javascript/issues/100) 119 | 120 | [最长单词-面试题 17.15](https://github.com/sl1673495/leetcode-javascript/issues/99) 121 | 122 | [单词拆分 II-140](https://github.com/sl1673495/leetcode-javascript/issues/95) 123 | 124 | [单词拆分-139](https://github.com/sl1673495/leetcode-javascript/issues/93) 125 | 126 | [最长回文子串-5](https://github.com/sl1673495/leetcode-javascript/issues/91) 127 | 128 | [无重叠区间-435](https://github.com/sl1673495/leetcode-javascript/issues/90) 129 | 130 | [目标和-494](https://github.com/sl1673495/leetcode-javascript/issues/87) 131 | 132 | [一和零-474](https://github.com/sl1673495/leetcode-javascript/issues/86) 133 | 134 | [最长公共子序列-1143](https://github.com/sl1673495/leetcode-javascript/issues/85) 135 | 136 | [摆动序列-376](https://github.com/sl1673495/leetcode-javascript/issues/84) 137 | 138 | [最长上升子序列-300](https://github.com/sl1673495/leetcode-javascript/issues/83) 139 | 140 | [最长等差数列-1027](https://github.com/sl1673495/leetcode-javascript/issues/82) 141 | 142 | [解码方法-91](https://github.com/sl1673495/leetcode-javascript/issues/81) 143 | 144 | [三角形最小路径和-120](https://github.com/sl1673495/leetcode-javascript/issues/80) 145 | 146 | [最小路径和-64](https://github.com/sl1673495/leetcode-javascript/issues/34) 147 | 148 | [括号生成-22](https://github.com/sl1673495/leetcode-javascript/issues/31) 149 | 150 | [爬楼梯-70](https://github.com/sl1673495/leetcode-javascript/issues/22) 151 | 152 | [买卖股票的最佳时机-121](https://github.com/sl1673495/leetcode-javascript/issues/19) 153 | 154 | ### 双指针 155 | 156 | [最接近的三数之和-16](https://github.com/sl1673495/leetcode-javascript/issues/115) 157 | 158 | [通过删除字母匹配到字典里最长单词-524](https://github.com/sl1673495/leetcode-javascript/issues/98) 159 | 160 | [搜索二维矩阵 II-240](https://github.com/sl1673495/leetcode-javascript/issues/96) 161 | 162 | [判断子序列-392](https://github.com/sl1673495/leetcode-javascript/issues/89) 163 | 164 | [分发饼干-455](https://github.com/sl1673495/leetcode-javascript/issues/88) 165 | 166 | [验证回文串-125](https://github.com/sl1673495/leetcode-javascript/issues/33) 167 | 168 | [两数之和 II - 输入有序数组-167](https://github.com/sl1673495/leetcode-javascript/issues/32) 169 | 170 | [合并两个有序数组-88](https://github.com/sl1673495/leetcode-javascript/issues/29) 171 | 172 | [移动零-283](https://github.com/sl1673495/leetcode-javascript/issues/26) 173 | 174 | ### 前缀和 175 | 176 | [和为K的子数组-560](https://github.com/sl1673495/leetcode-javascript/issues/110) 177 | 178 | ### 位运算 179 | 180 | [找不同-389](https://github.com/sl1673495/leetcode-javascript/issues/109) 181 | 182 | ### 查找表 183 | 184 | [找不同-389](https://github.com/sl1673495/leetcode-javascript/issues/109) 185 | 186 | [两个数组的交集 II-350](https://github.com/sl1673495/leetcode-javascript/issues/37) 187 | 188 | ### BFS 189 | 190 | [跳跃游戏 IV-1345](https://github.com/sl1673495/leetcode-javascript/issues/103) 191 | 192 | [跳跃游戏 III-1306](https://github.com/sl1673495/leetcode-javascript/issues/102) 193 | 194 | [二叉树的最小深度-111](https://github.com/sl1673495/leetcode-javascript/issues/54) 195 | 196 | [二叉树的最大深度-104](https://github.com/sl1673495/leetcode-javascript/issues/53) 197 | 198 | [二叉树的右视图-199](https://github.com/sl1673495/leetcode-javascript/issues/52) 199 | 200 | [二叉树的层序遍历-102](https://github.com/sl1673495/leetcode-javascript/issues/30) 201 | 202 | [相同的树-100](https://github.com/sl1673495/leetcode-javascript/issues/21) 203 | 204 | ### 排序 205 | 206 | [最长单词-面试题 17.15](https://github.com/sl1673495/leetcode-javascript/issues/99) 207 | 208 | [通过删除字母匹配到字典里最长单词-524](https://github.com/sl1673495/leetcode-javascript/issues/98) 209 | 210 | [快速排序](https://github.com/sl1673495/leetcode-javascript/issues/41) 211 | 212 | [颜色分类-75](https://github.com/sl1673495/leetcode-javascript/issues/28) 213 | 214 | ### 链表 215 | 216 | [移除链表元素-203](https://github.com/sl1673495/leetcode-javascript/issues/97) 217 | 218 | [两数相加-3](https://github.com/sl1673495/leetcode-javascript/issues/94) 219 | 220 | [两两交换链表中的节点-24](https://github.com/sl1673495/leetcode-javascript/issues/51) 221 | 222 | [删除链表的倒数第N个节点-19](https://github.com/sl1673495/leetcode-javascript/issues/46) 223 | 224 | [删除链表的节点-面试题18](https://github.com/sl1673495/leetcode-javascript/issues/40) 225 | 226 | [反转链表II-92](https://github.com/sl1673495/leetcode-javascript/issues/39) 227 | 228 | [反转链表 206](https://github.com/sl1673495/leetcode-javascript/issues/38) 229 | 230 | ### 贪心算法 231 | 232 | [判断子序列-392](https://github.com/sl1673495/leetcode-javascript/issues/89) 233 | 234 | [分发饼干-455](https://github.com/sl1673495/leetcode-javascript/issues/88) 235 | 236 | [买卖股票的最佳时机 II-122](https://github.com/sl1673495/leetcode-javascript/issues/20) 237 | 238 | ### DFS 239 | 240 | [二叉树的最近公共祖先-236](https://github.com/sl1673495/leetcode-javascript/issues/64) 241 | 242 | [将有序数组转换为二叉搜索树](https://github.com/sl1673495/leetcode-javascript/issues/63) 243 | 244 | [删除二叉搜索树中的节点-450](https://github.com/sl1673495/leetcode-javascript/issues/62) 245 | 246 | [路径总和 III-437](https://github.com/sl1673495/leetcode-javascript/issues/61) 247 | 248 | [求根到叶子节点数字之和-129](https://github.com/sl1673495/leetcode-javascript/issues/60) 249 | 250 | [二叉树的所有路径-257](https://github.com/sl1673495/leetcode-javascript/issues/59) 251 | 252 | [左叶子之和-404](https://github.com/sl1673495/leetcode-javascript/issues/58) 253 | 254 | [路径总和-112](https://github.com/sl1673495/leetcode-javascript/issues/57) 255 | 256 | [平衡二叉树-110](https://github.com/sl1673495/leetcode-javascript/issues/56) 257 | 258 | [对称二叉树-101](https://github.com/sl1673495/leetcode-javascript/issues/55) 259 | 260 | [二叉树的最小深度-111](https://github.com/sl1673495/leetcode-javascript/issues/54) 261 | 262 | [二叉树的最大深度-104](https://github.com/sl1673495/leetcode-javascript/issues/53) 263 | 264 | [二叉树的层序遍历-102](https://github.com/sl1673495/leetcode-javascript/issues/30) 265 | 266 | [路径总和 II-113](https://github.com/sl1673495/leetcode-javascript/issues/27) 267 | 268 | [相同的树-100](https://github.com/sl1673495/leetcode-javascript/issues/21) 269 | 270 | ### 二叉树 271 | 272 | [二叉树的最近公共祖先-236](https://github.com/sl1673495/leetcode-javascript/issues/64) 273 | 274 | [将有序数组转换为二叉搜索树](https://github.com/sl1673495/leetcode-javascript/issues/63) 275 | 276 | [删除二叉搜索树中的节点-450](https://github.com/sl1673495/leetcode-javascript/issues/62) 277 | 278 | [路径总和 III-437](https://github.com/sl1673495/leetcode-javascript/issues/61) 279 | 280 | [求根到叶子节点数字之和-129](https://github.com/sl1673495/leetcode-javascript/issues/60) 281 | 282 | [二叉树的所有路径-257](https://github.com/sl1673495/leetcode-javascript/issues/59) 283 | 284 | [左叶子之和-404](https://github.com/sl1673495/leetcode-javascript/issues/58) 285 | 286 | [路径总和-112](https://github.com/sl1673495/leetcode-javascript/issues/57) 287 | 288 | [平衡二叉树-110](https://github.com/sl1673495/leetcode-javascript/issues/56) 289 | 290 | [对称二叉树-101](https://github.com/sl1673495/leetcode-javascript/issues/55) 291 | 292 | [二叉树的最小深度-111](https://github.com/sl1673495/leetcode-javascript/issues/54) 293 | 294 | [二叉树的最大深度-104](https://github.com/sl1673495/leetcode-javascript/issues/53) 295 | 296 | [二叉树的右视图-199](https://github.com/sl1673495/leetcode-javascript/issues/52) 297 | 298 | [二叉树的前序遍历-144](https://github.com/sl1673495/leetcode-javascript/issues/50) 299 | 300 | [二叉树的层序遍历-102](https://github.com/sl1673495/leetcode-javascript/issues/30) 301 | 302 | [路径总和 II-113](https://github.com/sl1673495/leetcode-javascript/issues/27) 303 | 304 | [相同的树-100](https://github.com/sl1673495/leetcode-javascript/issues/21) 305 | 306 | ### 栈和队列 307 | 308 | [二叉树的右视图-199](https://github.com/sl1673495/leetcode-javascript/issues/52) 309 | 310 | [二叉树的前序遍历-144](https://github.com/sl1673495/leetcode-javascript/issues/50) 311 | 312 | [简化路径-71](https://github.com/sl1673495/leetcode-javascript/issues/49) 313 | 314 | [有效的括号-20](https://github.com/sl1673495/leetcode-javascript/issues/48) 315 | 316 | [逆波兰表达式求值-150](https://github.com/sl1673495/leetcode-javascript/issues/47) 317 | 318 | ### 滑动窗口 319 | 320 | [滑动窗口的最大值-239](https://github.com/sl1673495/leetcode-javascript/issues/45) 321 | 322 | [找到字符串中所有字母异位词-438](https://github.com/sl1673495/leetcode-javascript/issues/44) 323 | 324 | [最小覆盖子串-76](https://github.com/sl1673495/leetcode-javascript/issues/43) 325 | 326 | [无重复字符的最长子串-3](https://github.com/sl1673495/leetcode-javascript/issues/42) 327 | 328 | [长度最小的子数组-209](https://github.com/sl1673495/leetcode-javascript/issues/36) 329 | 330 | ### 数据结构 331 | 332 | [LRU 缓存机制-146](https://github.com/sl1673495/leetcode-javascript/issues/35) 333 | 334 | ### 二分查找 335 | 336 | [Pow(x, n)-50](https://github.com/sl1673495/leetcode-javascript/issues/25) 337 | 338 | [x 的平方根-69](https://github.com/sl1673495/leetcode-javascript/issues/24) 339 | 340 | [二分查找-704](https://github.com/sl1673495/leetcode-javascript/issues/23) 341 | 342 | ## Author 343 | 344 | 👤 **ssh** 345 | 346 | - Website: https://ssh-blog.now.sh 347 | - Github: [@sl1673495](https://github.com/sl1673495) 348 | 349 | ## 🤝 Contributing 350 | 351 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/sl1673495/leetcode-javascript/issues). 352 | 353 | ## Show your support 354 | 355 | Give a ⭐️ if this project helped you! 356 | 357 | --- 358 | 359 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ 360 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "glob": "^7.1.6" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /三数之和.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[][]} 4 | */ 5 | let twoSum = function(nums, target) { 6 | let map = new Map() 7 | let results = [] 8 | for (let i = 0; i < nums.length; i++) { 9 | let num = nums[i] 10 | let result = map.get(target - num) 11 | if (result !== undefined) { 12 | results.push([nums[result], num]) 13 | } 14 | map.set(num, i) 15 | } 16 | return results 17 | } 18 | 19 | let threeSum = function(nums) { 20 | nums.sort((a, b) => a - b) 21 | let set = new Set() 22 | let results = [] 23 | for (let i = 0; i < nums.length - 2; i++) { 24 | let find = twoSum(nums.slice(i + 1), -nums[i]) 25 | if (find) { 26 | find.forEach((arr) => { 27 | if (!set.has(arr.join(''))) { 28 | results.push([nums[i], ...arr]) 29 | } 30 | set.add(arr.join('')) 31 | }) 32 | } 33 | } 34 | return results 35 | } 36 | 37 | console.log(threeSum([-1,0,1,2,-1,-4])) 38 | 39 | /** 40 | * 利用两数之和的变形来求解,循环判断第三个数的负数是否能和另外两个数相加得到。 41 | */ -------------------------------------------------------------------------------- /两个数组的交集II-350.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums1 3 | * @param {number[]} nums2 4 | * @return {number[]} 5 | */ 6 | let intersect = function (nums1, nums2) { 7 | let map1 = makeCountMap(nums1) 8 | let map2 = makeCountMap(nums2) 9 | let res = [] 10 | for (let num of map1.keys()) { 11 | const count1 = map1.get(num) 12 | const count2 = map2.get(num) 13 | 14 | if (count2) { 15 | const pushCount = Math.min(count1, count2) 16 | for (let i = 0; i < pushCount; i++) { 17 | res.push(num) 18 | } 19 | } 20 | } 21 | return res 22 | } 23 | 24 | function makeCountMap(nums) { 25 | let map = new Map() 26 | for (let i = 0; i < nums.length; i++) { 27 | let num = nums[i] 28 | let count = map.get(num) 29 | if (count) { 30 | map.set(num, count + 1) 31 | } else { 32 | map.set(num, 1) 33 | } 34 | } 35 | return map 36 | } 37 | -------------------------------------------------------------------------------- /二分查找/x 的平方根-69.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} x 3 | * @return {number} 4 | */ 5 | let mySqrt = function (x) { 6 | let left = 0; 7 | let right = x; 8 | let ans = -1 9 | while (left <= right) { 10 | let mid = Math.round((left + right) / 2); 11 | let product = mid * mid; 12 | if (product < x) { 13 | ans = mid 14 | left = mid + 1; 15 | } else if (product > x) { 16 | right = mid - 1; 17 | } else if (product === x) { 18 | return mid; 19 | } 20 | } 21 | 22 | return ans 23 | }; 24 | 25 | console.log(mySqrt(200)); 26 | -------------------------------------------------------------------------------- /二分查找/二分查找.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 二分搜索 从一个数组中搜索一个确定的数 3 | */ 4 | 5 | function binarySearch(arr, n, target) { 6 | // 在[l...r]的范围里寻找target 7 | let l = 0; 8 | let r = n - 1; 9 | while (l <= r) { 10 | let mid = Math.round((l + r) / 2); 11 | 12 | if (arr[mid] === target) { 13 | return mid; 14 | } 15 | 16 | if (target > arr[mid]) { 17 | l = mid + 1; // 在[mid+1...r]的范围里寻找target 18 | } else { 19 | r = mid - 1; 20 | } 21 | } 22 | return -1; 23 | } 24 | 25 | console.log(console.log(binarySearch([1, 2, 3, 4, 5], 5, 2))); 26 | -------------------------------------------------------------------------------- /二叉树/二叉搜索树中第K小的元素.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val, left, right) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.left = (left===undefined ? null : left) 6 | * this.right = (right===undefined ? null : right) 7 | * } 8 | */ 9 | /** 10 | * @param {TreeNode} root 11 | * @param {number} k 12 | * @return {number} 13 | */ 14 | var kthSmallest = function (root, k) { 15 | let count = 0 16 | let finded 17 | 18 | let dfs = (node) => { 19 | if (!node) { 20 | return 21 | } 22 | dfs(node.left) 23 | count++ 24 | if (count === k) { 25 | finded = node.val 26 | return 27 | } 28 | dfs(node.right) 29 | } 30 | 31 | dfs(root) 32 | 33 | return finded 34 | } 35 | -------------------------------------------------------------------------------- /二叉树/二叉树的右视图-199.js: -------------------------------------------------------------------------------- 1 | let rightSideView = function (root) { 2 | if (!root) return [] 3 | let queue = [root] 4 | let res = [] 5 | while (queue.length) { 6 | let len = queue.length 7 | let last 8 | for (let i = 0; i < len; i++) { 9 | let node = queue.shift() 10 | if (node.left) { 11 | queue.push(node.left) 12 | } 13 | if (node.right) { 14 | queue.push(node.right) 15 | } 16 | if (node.val !== undefined) { 17 | last = node.val 18 | } 19 | } 20 | res.push(last) 21 | } 22 | return res 23 | } 24 | -------------------------------------------------------------------------------- /二叉树/二叉树的层次遍历 II-107.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | 9 | let TreeNode = require('../工具/二叉树.js') 10 | 11 | /** 12 | * @param {TreeNode} root 13 | * @return {number[][]} 14 | */ 15 | var levelOrderBottom = function (root) { 16 | let res = [] 17 | let dfs = (node, level = 0) => { 18 | if (!node) return 19 | 20 | if (!res[level]) { 21 | res[level] = [] 22 | } 23 | 24 | dfs(node.left, level + 1) 25 | dfs(node.right, level + 1) 26 | 27 | res[level].push(node.val) 28 | } 29 | 30 | dfs(root) 31 | return res.reverse() 32 | }; 33 | 34 | var t = new TreeNode(3) 35 | t.left = new TreeNode(9) 36 | t.right = new TreeNode(20) 37 | t.right.left = new TreeNode(15) 38 | t.right.right = new TreeNode(7) 39 | 40 | console.log(levelOrderBottom(t)) -------------------------------------------------------------------------------- /二叉树/二叉树的所有路径-257.js: -------------------------------------------------------------------------------- 1 | let binaryTreePaths = function (root) { 2 | let res = [] 3 | let dfs = (node, path = "") => { 4 | if (!node) { 5 | return 6 | } 7 | 8 | let newPath = path ? `${path}->${node.val}` : `${node.val}` 9 | if (!node.left && !node.right) { 10 | res.push(newPath) 11 | } 12 | 13 | dfs(node.left, newPath) 14 | dfs(node.right, newPath) 15 | } 16 | dfs(root) 17 | return res 18 | } 19 | -------------------------------------------------------------------------------- /二叉树/二叉树的最大深度-104.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode id=104 lang=javascript 3 | * 4 | * [104] Maximum Depth of Binary Tree 5 | */ 6 | 7 | // @lc code=start 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val, left, right) { 11 | * this.val = (val===undefined ? 0 : val) 12 | * this.left = (left===undefined ? null : left) 13 | * this.right = (right===undefined ? null : right) 14 | * } 15 | */ 16 | /** 17 | * @param {TreeNode} root 18 | * @return {number} 19 | */ 20 | let maxDepth = function (root) { 21 | let max = 0 22 | let helper = (node, depth) => { 23 | if (!node) return 24 | max = Math.max(max, depth) 25 | if (node.left) { 26 | helper(node.left, depth + 1) 27 | } 28 | if (node.right) { 29 | helper(node.right, depth + 1) 30 | } 31 | } 32 | helper(root, 1) 33 | return max 34 | } 35 | // @lc code=end 36 | -------------------------------------------------------------------------------- /二叉树/二叉树的最小深度-111.js: -------------------------------------------------------------------------------- 1 | let minDepth = function (root) { 2 | if (!root) return 0 3 | 4 | let depth = 0 5 | let queue = [root] 6 | 7 | while (queue.length) { 8 | depth++ 9 | let len = queue.length 10 | while (len--) { 11 | let node = queue.shift() 12 | 13 | let left = node.left 14 | let right = node.right 15 | if (!left && !right) { 16 | return depth 17 | } 18 | 19 | if (left) { 20 | queue.push(left) 21 | } 22 | if (right) { 23 | queue.push(right) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /二叉树/二叉树的最近公共祖先.js: -------------------------------------------------------------------------------- 1 | let T = require("../工具/二叉树") 2 | 3 | let lowestCommonAncestor = function (root, p, q) { 4 | let findAndCreate = (node, target, depth) => { 5 | if (node !== target) { 6 | let findInLeft 7 | if (node.left) { 8 | node.left.parent = node 9 | findInLeft = findAndCreate(node.left, target, depth + 1) 10 | } 11 | 12 | if (findInLeft) { 13 | return findInLeft 14 | } else { 15 | if (node.right) { 16 | node.right.parent = node 17 | return findAndCreate(node.right, target, depth + 1) 18 | } 19 | } 20 | } else { 21 | return { 22 | depth, 23 | node, 24 | } 25 | } 26 | } 27 | 28 | let findP = findAndCreate(root, p, 0) || {} 29 | let findQ = findAndCreate(root, q, 0) || {} 30 | 31 | let cur = findP.depth > findQ.depth ? findQ.node : findP.node 32 | 33 | while (!(isAncestor(cur, p) && isAncestor(cur, q))) { 34 | cur = cur.parent 35 | } 36 | 37 | return cur 38 | } 39 | 40 | function isAncestor(node, target) { 41 | if (!node) { 42 | return false 43 | } 44 | if (node !== target) { 45 | return !!(isAncestor(node.left, target) || isAncestor(node.right, target)) 46 | } else { 47 | return true 48 | } 49 | } 50 | 51 | let l 52 | let r 53 | let t = new T(3) 54 | t.left = l = new T(5) 55 | t.right = r = new T(1) 56 | 57 | console.log(lowestCommonAncestor(t, l, r)) 58 | -------------------------------------------------------------------------------- /二叉树/删除二叉搜索树中的节点-450.js: -------------------------------------------------------------------------------- 1 | let deleteNode = function (root, key) { 2 | let findNodePos = (node, key) => { 3 | if (!node) { 4 | return false 5 | } 6 | if (node.left && node.left.val === key) { 7 | return { 8 | parent: node, 9 | pos: "left", 10 | } 11 | } else if (node.right && node.right.val === key) { 12 | return { 13 | parent: node, 14 | pos: "right", 15 | } 16 | } else { 17 | return findNodePos(node.left, key) || findNodePos(node.right, key) 18 | } 19 | } 20 | 21 | let findLastLeft = (node) => { 22 | if (!node.left) { 23 | return node 24 | } 25 | return findLastLeft(node.left) 26 | } 27 | 28 | let virtual = new TreeNode() 29 | virtual.left = root 30 | 31 | let finded = findNodePos(virtual, key) 32 | if (finded) { 33 | let { parent, pos } = finded 34 | let target = parent[pos] 35 | let targetLeft = target.left 36 | let targetRight = target.right 37 | 38 | if (!targetLeft && !targetRight) { 39 | parent[pos] = null 40 | } else if (!targetRight) { 41 | parent[pos] = targetLeft 42 | } else if (!targetLeft) { 43 | parent[pos] = targetRight 44 | } else { 45 | parent[pos] = targetRight 46 | let lastLeft = findLastLeft(targetRight) 47 | lastLeft.left = targetLeft 48 | } 49 | } 50 | 51 | return virtual.left 52 | } -------------------------------------------------------------------------------- /二叉树/对称二叉树-101.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {boolean} 11 | */ 12 | var isSymmetric = function (root) { 13 | if (!root) return true 14 | let helper = (left, right) => { 15 | if (!left && !right) { 16 | return true 17 | } 18 | if (!left || !right) { 19 | return false 20 | } 21 | if (left.val === right.val) { 22 | return helper(left.left, right.right) && helper(left.right, right.left) 23 | } else { 24 | return false 25 | } 26 | } 27 | return helper(root, root) 28 | } 29 | -------------------------------------------------------------------------------- /二叉树/将有序数组转为二叉搜索树.js: -------------------------------------------------------------------------------- 1 | let sortedArrayToBST = function (nums) { 2 | let n = nums.length 3 | if (!n) { 4 | return null 5 | } 6 | let mid = Math.floor(n / 2) 7 | let root = new TreeNode(nums[mid]) 8 | 9 | root.left = sortedArrayToBST(nums.slice(0, mid)) 10 | root.right = sortedArrayToBST(nums.slice(mid + 1, n)) 11 | 12 | return root 13 | }; -------------------------------------------------------------------------------- /二叉树/左叶子之和-404.js: -------------------------------------------------------------------------------- 1 | let sumOfLeftLeaves = function (root) { 2 | let sum = 0 3 | 4 | let dfs = (node) => { 5 | if (!node) return 6 | 7 | if (isLeaf(node.left)) { 8 | sum += node.left.val 9 | } 10 | 11 | dfs(node.left) 12 | dfs(node.right) 13 | } 14 | 15 | dfs(root) 16 | 17 | return sum 18 | } 19 | 20 | function isLeaf(node) { 21 | return !!node && !node.left && !node.right 22 | } 23 | -------------------------------------------------------------------------------- /二叉树/平衡二叉树-110.js: -------------------------------------------------------------------------------- 1 | let isBalanced = function (root) { 2 | if (!root) { 3 | return true 4 | } 5 | 6 | let isSonBalnaced = 7 | Math.abs(getHeight(root.left) - getHeight(root.right)) <= 1 8 | 9 | return isSonBalnaced && isBalanced(root.left) && isBalanced(root.right) 10 | } 11 | 12 | function getHeight(node) { 13 | if (!node) return 0 14 | return Math.max(getHeight(node.left), getHeight(node.right)) + 1 15 | } 16 | -------------------------------------------------------------------------------- /二叉树/求根到叶子节点数字之和-129.js: -------------------------------------------------------------------------------- 1 | let sumNumbers = function (root) { 2 | let paths = [] 3 | 4 | let dfs = (node, path) => { 5 | if (!node) { 6 | return 7 | } 8 | 9 | let newPath = `${path}${node.val}` 10 | if (!node.left && !node.right) { 11 | paths.push(newPath) 12 | return 13 | } 14 | 15 | dfs(node.left, newPath) 16 | dfs(node.right, newPath) 17 | } 18 | 19 | dfs(root, "") 20 | return paths.reduce((total, val) => { 21 | return total + Number(val) 22 | }, 0) 23 | } 24 | -------------------------------------------------------------------------------- /二叉树/路径总和 II-113.js: -------------------------------------------------------------------------------- 1 | let TreeNode = require('../工具/二叉树.js') 2 | 3 | var pathSum = function (root, sum) { 4 | let res = []; 5 | let search = function (node, paths) { 6 | if (!node.val) return 7 | paths.push(node.val); 8 | if (node.left) { 9 | search(node.left, paths); 10 | } 11 | if (node.right) { 12 | search(node.right, paths); 13 | } 14 | if (!node.left && !node.right) { 15 | if (sumVals(paths) === sum) { 16 | res.push(paths.slice()); 17 | } 18 | } 19 | paths.pop(); 20 | }; 21 | search(root, []); 22 | return res; 23 | }; 24 | 25 | function sumVals(nodes) { 26 | return nodes.reduce((prev, val) => { 27 | prev += val; 28 | return prev; 29 | }, 0); 30 | } 31 | 32 | 33 | var t = new TreeNode(5) 34 | t.left = new TreeNode(4) 35 | t.left.left = new TreeNode(11) 36 | t.left.left.left = new TreeNode(7) 37 | t.left.left.right = new TreeNode(2) 38 | 39 | t.right = new TreeNode(8) 40 | t.right.left = new TreeNode(13) 41 | t.right.right = new TreeNode(4) 42 | t.right.right.left = new TreeNode(5) 43 | t.right.right.right = new TreeNode(1) 44 | 45 | console.log(pathSum(new TreeNode(), 22)) 46 | -------------------------------------------------------------------------------- /二叉树/路径总和 III-437.js: -------------------------------------------------------------------------------- 1 | let pathSum = function (root, sum) { 2 | if (!root) return 0 3 | return ( 4 | countSum(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum) 5 | ) 6 | } 7 | 8 | let countSum = (node, sum) => { 9 | let count = 0 10 | let dfs = (node, target) => { 11 | if (!node) return 12 | 13 | if (node.val === target) { 14 | count += 1 15 | } 16 | 17 | dfs(node.left, target - node.val) 18 | dfs(node.right, target - node.val) 19 | } 20 | dfs(node, sum) 21 | return count 22 | } 23 | -------------------------------------------------------------------------------- /二叉树/路径总和-112.js: -------------------------------------------------------------------------------- 1 | let hasPathSum = function (root, sum) { 2 | if (!root) { 3 | return false 4 | } 5 | // 叶子节点 判断当前的值是否等于 sum 即可 6 | if (!root.left && !root.right) { 7 | return root.val === sum 8 | } 9 | 10 | return ( 11 | hasPathSum(root.left, sum - root.val) || 12 | hasPathSum(root.right, sum - root.val) 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /二叉树的前中后序遍历.js: -------------------------------------------------------------------------------- 1 | const binaryTree = { 2 | val: "A", 3 | left: { 4 | val: "B", 5 | left: { 6 | val: "D" 7 | }, 8 | right: { 9 | val: "E" 10 | } 11 | }, 12 | right: { 13 | val: "C", 14 | right: { 15 | val: "F" 16 | } 17 | } 18 | }; 19 | 20 | /** 21 | * 前序遍历 访问顺序为 根节点 -> 左节点 -> 右节点 22 | */ 23 | function preorder(root) { 24 | // 递归边界,root 为空 25 | if (!root) { 26 | return; 27 | } 28 | 29 | // 输出当前遍历的结点值 30 | console.log("当前遍历的结点值是:", root.val); 31 | // 递归遍历左子树 32 | preorder(root.left); 33 | // 递归遍历右子树 34 | preorder(root.right); 35 | } 36 | 37 | /** 38 | * 中序遍历 访问顺序为 左节点 -> 根节点 -> 右节点 39 | */ 40 | function inorder(root) { 41 | // 递归边界,root 为空 42 | if (!root) { 43 | return; 44 | } 45 | 46 | // 递归遍历左子树 47 | inorder(root.left); 48 | // 输出当前遍历的结点值 49 | console.log("当前遍历的结点值是:", root.val); 50 | // 递归遍历右子树 51 | inorder(root.right); 52 | } 53 | 54 | /** 55 | * 后序遍历 访问顺序为 左节点 -> 右节点 -> 根节点 56 | */ 57 | function postorder(root) { 58 | // 递归边界,root 为空 59 | if (!root) { 60 | return; 61 | } 62 | 63 | // 递归遍历左子树 64 | postorder(root.left); 65 | // 递归遍历右子树 66 | postorder(root.right); 67 | // 输出当前遍历的结点值 68 | console.log("当前遍历的结点值是:", root.val); 69 | } 70 | 71 | console.log("前序遍历"); 72 | preorder(binaryTree); 73 | 74 | console.log("中序遍历"); 75 | inorder(binaryTree); 76 | 77 | console.log("后序遍历") 78 | postorder(binaryTree) 79 | -------------------------------------------------------------------------------- /位运算/找不同-389.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string} t 4 | * @return {character} 5 | */ 6 | let findTheDifference = function (s, t) { 7 | let rest = t.charCodeAt(t.length - 1) 8 | for (let i = 0; i < s.length; i++) { 9 | let charS = s[i] 10 | let charT = t[i] 11 | rest ^= charS.charCodeAt(0) 12 | rest ^= charT.charCodeAt(0) 13 | } 14 | return String.fromCharCode(rest) 15 | } 16 | -------------------------------------------------------------------------------- /前K个高频元素.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | 347. 前 K 个高频元素 4 | 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 5 | 6 | 示例 1: 7 | 8 | 输入: nums = [1,1,1,2,2,3], k = 2 9 | 输出: [1,2] 10 | 示例 2: 11 | 12 | 输入: nums = [1], k = 1 13 | 输出: [1] 14 | 说明: 15 | 16 | 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。 17 | 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。 18 | */ 19 | 20 | let topKFrequent = function(nums, k) { 21 | let map = new Map() 22 | for (let i = 0; i < nums.length; i++) { 23 | let num = nums[i] 24 | let count = map.get(num) 25 | if (!count) { 26 | map.set(num, 1) 27 | } else { 28 | map.set(num, count + 1) 29 | } 30 | } 31 | let sorted = Array.from(map.entries()).sort((a, b) => b[1] - a[1]) 32 | let result = [] 33 | for (let i = 0; i < k; i++) { 34 | result.push(sorted[i][0]) 35 | } 36 | return result 37 | } 38 | console.log(topKFrequent([1, 2, 3, 3, 3, 4, 4, 4, 4, 5, 1, 2], 2)) 39 | -------------------------------------------------------------------------------- /动态规划/01背包-DP版.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {number[]} w 物品的重量集合 4 | * @param {number[]} v 物品的价值集合 5 | * @param {number} C 背包容量 6 | */ 7 | let knapsack01 = function (w, v, C) { 8 | let n = w.length; 9 | if (n === 0) return 0; 10 | 11 | // 构建二维数组dp表 12 | // x轴代表背包容量 y轴代表考虑的物品情况 13 | // 第一行只考虑一种物品(基准情况) 14 | // 第二行考虑一和二两种(通过拿取二和不拿二,再去组合第一行的最佳情况来求最大值) 15 | // 第三行以此类推 16 | let memo = new Array(n); 17 | for (let i = 0; i < memo.length; i++) { 18 | memo[i] = new Array(C + 1).fill(0); 19 | } 20 | 21 | // 基础情况 背包在各个容量的情况下 只考虑第一个物品时的最优解 22 | for (let j = 0; j <= C; j++) { 23 | memo[0][j] = j >= w[0] ? v[0] : 0; 24 | } 25 | 26 | for (let i = 1; i < n; i++) { 27 | for (let j = 0; j <= C; j++) { 28 | let weight = w[i]; 29 | let restWeight = j - weight; 30 | // 有足够容量的情况下 选择当前的的物品 并且用剩余的重量去找前面几个物品组合的最优解 31 | let pickNow = j >= weight ? v[i] + memo[i - 1][restWeight] : 0; 32 | 33 | // 另一种选择 这个物品不放进背包了 直接求用这个背包容量组合前面几种物品的最优解 34 | let pickPrev = memo[i - 1][j]; 35 | 36 | memo[i][j] = Math.max(pickNow, pickPrev); 37 | } 38 | } 39 | 40 | return memo[n - 1][C]; 41 | }; 42 | 43 | console.log(knapsack01([1, 2, 3], [6, 10, 12], 5)); 44 | -------------------------------------------------------------------------------- /动态规划/01背包-递归版.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {number[]} w 物品的重量集合 4 | * @param {number[]} v 物品的价值集合 5 | * @param {number} C 背包容量 6 | */ 7 | function knapsack01(w, v, C) { 8 | let n = w.length - 1; 9 | 10 | return bestValue(w, v, n, C); 11 | } 12 | 13 | // 用 [0...index] 的物品 14 | // 填充容积为c的背包的最大价值 15 | function bestValue(w, v, index, c) { 16 | if (index < 0 || c <= 0) return 0; 17 | 18 | let max = bestValue(w, v, index - 1, c); 19 | 20 | // 装背包之前需要先判断这个当前背包还可以容纳下这个物品 21 | if (c >= w[index]) { 22 | max = Math.max( 23 | // 不装进背包 24 | max, 25 | // 装进背包 26 | v[index] + bestValue(w, v, index - 1, c - w[index]) 27 | ); 28 | } 29 | 30 | return max; 31 | } 32 | 33 | console.log(knapsack01([1, 2, 3], [6, 10, 12], 5)); 34 | -------------------------------------------------------------------------------- /动态规划/一和零-474.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[]} strs 3 | * @param {number} m 4 | * @param {number} n 5 | * @return {number} 6 | */ 7 | let findMaxForm = function (strs, m, n) { 8 | let sl = strs.length 9 | if (!sl) { 10 | return 0 11 | } 12 | 13 | let dp = [] 14 | 15 | for (let i = 0; i <= m; i++) { 16 | dp[i] = [] 17 | for (let j = 0; j <= n; j++) { 18 | dp[i][j] = [] 19 | for (let s = 0; s < sl; s++) { 20 | let str = strs[s] 21 | let [strM, strN] = countMAndN(str) 22 | 23 | let pickOnlyPrev = dp[i][j][s - 1] || 0 24 | let pickCurAndPrev = 0 25 | if (i >= strM && j >= strN) { 26 | pickCurAndPrev = 1 + (dp[i - strM][j - strN][s - 1] || 0) 27 | } 28 | 29 | dp[i][j][s] = Math.max(pickCurAndPrev, pickOnlyPrev) 30 | } 31 | } 32 | } 33 | return dp[m][n][sl - 1] 34 | } 35 | 36 | function countMAndN(str) { 37 | let m = 0 38 | let n = 0 39 | for (let i = 0; i < str.length; i++) { 40 | if (str[i] === "0") { 41 | m++ 42 | } else { 43 | n++ 44 | } 45 | } 46 | return [m, n] 47 | } 48 | 49 | console.log(findMaxForm(["10", "0", "1"], 1, 1)) 50 | -------------------------------------------------------------------------------- /动态规划/三角形的最小路径和-120.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} triangle 3 | * @return {number} 4 | */ 5 | let minimumTotal = function (triangle) { 6 | let tryMin = (level, lastIndex, prevSum) => { 7 | if (level === triangle.length) { 8 | return prevSum 9 | } 10 | 11 | let row = triangle[level] 12 | let cur = row[lastIndex] 13 | let curNext = row[lastIndex + 1] 14 | 15 | let selected = tryMin(level + 1, lastIndex, prevSum + cur) 16 | if (curNext !== undefined) { 17 | selected = Math.min( 18 | selected, 19 | tryMin(level + 1, lastIndex + 1, prevSum + curNext) 20 | ) 21 | } 22 | return selected 23 | } 24 | 25 | return tryMin(0, 0, 0) 26 | } 27 | 28 | minimumTotal([[2], [3, 4], [6, 5, 7], [4, 1, 8, 3]]) 29 | -------------------------------------------------------------------------------- /动态规划/下降路径最小和-931.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} A 3 | * @return {number} 4 | */ 5 | let minFallingPathSum = function (A) { 6 | let n = A.length 7 | 8 | let dp = [] 9 | for (let i = 0; i < n; i++) { 10 | dp[i] = [] 11 | } 12 | 13 | for (let j = 0; j < n; j++) { 14 | dp[n - 1][j] = A[n - 1][j] 15 | } 16 | 17 | for (let i = n - 2; i >= 0; i--) { 18 | for (let j = 0; j < n; j++) { 19 | dp[i][j] = Infinity 20 | let left = j - 1 21 | let right = j + 1 22 | let mid = j 23 | let nextRowIndexes = [left, mid, right] 24 | for (let nextRowIndex of nextRowIndexes) { 25 | if (nextRowIndex >= 0 && nextRowIndex < n) { 26 | dp[i][j] = Math.min(dp[i][j], A[i][j] + dp[i + 1][nextRowIndex]) 27 | } 28 | } 29 | } 30 | } 31 | 32 | // 第一行的最小值 可以确定整体的最小路径 33 | return Math.min(...dp[0]) 34 | } 35 | -------------------------------------------------------------------------------- /动态规划/乘积最大子数组-152.js: -------------------------------------------------------------------------------- 1 | let maxProduct = function (nums) { 2 | let dp = []; 3 | let n = nums.length; 4 | 5 | let last = nums[n - 1]; 6 | dp[n - 1] = { 7 | max: last, 8 | min: last, 9 | }; 10 | 11 | for (i = nums.length - 2; i >= 0; i--) { 12 | let num = nums[i]; 13 | let withNextMin = num * dp[i + 1].min; 14 | let withNextMax = num * dp[i + 1].max; 15 | let withoutNext = num; 16 | dp[i] = { 17 | max: Math.max(withoutNext, withNextMin, withNextMax), 18 | min: Math.min(withoutNext, withNextMin, withNextMax), 19 | }; 20 | } 21 | 22 | return Math.max(...dp.map(({ max }) => max)); 23 | }; 24 | -------------------------------------------------------------------------------- /动态规划/买卖股票的最佳时机.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * 循环版 5 | * @param {number[]} prices 6 | * @return {number} 7 | */ 8 | let maxProfit = function(prices) { 9 | let max = 0 10 | for (let i = 1; i < prices.length; i++) { 11 | for (let j = 0; j < i; j++) { 12 | let price = prices[j] 13 | let sale = prices[i] - price 14 | max = Math.max(max, sale) 15 | } 16 | } 17 | 18 | return max 19 | }; 20 | 21 | 22 | /** 23 | * DP版 24 | */ 25 | let maxProfit = function (prices) { 26 | let n = prices.length 27 | if (!n || n === 1) return 0 28 | 29 | // 最大收益 30 | let prevMax = 0 31 | // 最小价格 32 | let prevMin = prices[0] 33 | 34 | for (let i = 1; i < n; i++) { 35 | let price = prices[i] 36 | 37 | prevMax = Math.max(price - prevMin, prevMax) 38 | prevMin = Math.min(price, prevMin) 39 | } 40 | 41 | return prevMax 42 | }; 43 | 44 | console.log(maxProfit([1, 2])) -------------------------------------------------------------------------------- /动态规划/使用最小花费爬楼梯-746.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} cost 3 | * @return {number} 4 | */ 5 | let minCostClimbingStairs = function (cost) { 6 | let dp = []; 7 | 8 | for (let i = cost.length - 1; i >= 0; i--) { 9 | let oneStep = cost[i] + (dp[i + 1] || 0); 10 | let twoStep = cost[i] + (dp[i + 2] || 0); 11 | 12 | dp[i] = Math.min(oneStep, twoStep); 13 | } 14 | 15 | return Math.min(dp[0], dp[1]); 16 | }; -------------------------------------------------------------------------------- /动态规划/分割等和子集-416.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {boolean} 4 | */ 5 | let canPartition = function (nums) { 6 | let n = nums.length 7 | 8 | let sum = nums.reduce((a, b) => a + b); 9 | 10 | let target = sum / 2; 11 | 12 | // 数据不是整数 直接return 13 | if (Math.ceil(target) !== target) { 14 | return false; 15 | } 16 | 17 | let dp = new Array(n); 18 | for (let i = 0; i < dp.length; i++) { 19 | dp[i] = new Array(target + 1).fill(false); 20 | } 21 | 22 | // 列代表可以选择去凑数的数值 23 | for (let i = 0; i < dp.length; i++) { 24 | // 行代表是否可以凑到这个数字j 25 | for (let j = 0; j <= target; j++) { 26 | // 不用当前数,直接选择前一行的结果 27 | let pickPrev = (dp[i - 1] ? dp[i - 1][j]: false) || false 28 | 29 | // 拿出当前数,并且从前一行里找其他的值能否凑成剩下的值 30 | let pickCurrentAndPrev = (dp[i - 1] ? dp[i - 1][j - nums[i]]: false) || false 31 | 32 | // 只拿的值直接去凑目标值 33 | let pickCurrent = j === nums[i] 34 | 35 | // 任意一者满足 即可理解成 「i下标的值」配合「i下标之前的数值」 可以一起凑成目标值 36 | let can = ( 37 | pickPrev || 38 | pickCurrent|| 39 | pickCurrentAndPrev 40 | ) 41 | 42 | dp[i][j] = can 43 | 44 | // 只要任意一行的 target 列满足条件 即可认为有「子数组」可以凑成目标值 直接返回 true 45 | if ((j === target) && can) { 46 | return true 47 | } 48 | } 49 | } 50 | return dp[n - 1][target] 51 | }; -------------------------------------------------------------------------------- /动态规划/单词拆分 II.js: -------------------------------------------------------------------------------- 1 | let wordBreak = function (s, wordDict) { 2 | let uniqSChars = uniq(s.split("")) 3 | let uniqWordDictChars = uniq(wordDict.join("")) 4 | if (uniqSChars.length !== uniqWordDictChars.length) { 5 | return false 6 | } 7 | 8 | let n = s.length 9 | if (!n) { 10 | return [] 11 | } 12 | 13 | let wordSet = new Set(wordDict) 14 | let dp = [] 15 | dp[0] = [""] 16 | 17 | for (let i = 1; i <= n; i++) { 18 | let res = [] 19 | for (let j = i; j >= 0; j--) { 20 | let word = s.slice(j, i) 21 | if (wordSet.has(word)) { 22 | if (dp[j] && dp[j].length) { 23 | for (let prev of dp[j]) { 24 | res.push(prev ? prev + " " + word : word) 25 | } 26 | } 27 | } 28 | } 29 | dp[i] = res 30 | } 31 | 32 | return dp[n] 33 | } 34 | 35 | function uniq(arr) { 36 | return Array.from(new Set(arr)) 37 | } 38 | -------------------------------------------------------------------------------- /动态规划/单词拆分-139.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string[]} wordDict 4 | * @return {boolean} 5 | */ 6 | let wordBreak = function (s, wordDict) { 7 | let n = s.length 8 | if (!n) return true 9 | 10 | let wordSet = new Set(wordDict) 11 | let dp = [] 12 | dp[0] = true 13 | 14 | for (let i = 0; i <= n; i++) { 15 | for (let j = i; j >= 0; j--) { 16 | let word = s.slice(j, i) 17 | if (wordSet.has(word) && dp[j]) { 18 | dp[i] = true 19 | break 20 | } 21 | } 22 | } 23 | 24 | return !!dp[n] 25 | } 26 | -------------------------------------------------------------------------------- /动态规划/可获得的最大点数-1423.js: -------------------------------------------------------------------------------- 1 | // 力扣超时 卡在第26个用例 2 | let maxScore = function (cardPoints, k) { 3 | let n = cardPoints.length 4 | 5 | let prevTimeChunk = [] 6 | for (let i = 0; i < n; i++) { 7 | for (let j = i; j < n; j++) { 8 | if (!prevTimeChunk[i]) { 9 | prevTimeChunk[i] = [] 10 | } 11 | prevTimeChunk[i][j] = 0 12 | } 13 | } 14 | 15 | let currentTimeChunk = [] 16 | 17 | for (let time = 1; time <= k; time++) { 18 | for (let i = n - 1; i >= 0; i--) { 19 | for (let j = i; j < n; j++) { 20 | if (!currentTimeChunk[i]) { 21 | currentTimeChunk[i] = [] 22 | } 23 | 24 | // 只剩一个可选 有次数的情况下就选这一项 否则为0 25 | if (i === j) { 26 | currentTimeChunk[i][j] = time > 0 ? cardPoints[i] : 0 27 | } 28 | 29 | let pickHead = cardPoints[i] 30 | let pickTail = cardPoints[j] 31 | 32 | currentTimeChunk[i][j] = Math.max( 33 | pickHead + (prevTimeChunk[i + 1] ? prevTimeChunk[i + 1][j] || 0 : 0), 34 | pickTail + (prevTimeChunk[i][j - 1] || 0) 35 | ) 36 | } 37 | } 38 | prevTimeChunk = currentTimeChunk 39 | currentTimeChunk = [] 40 | } 41 | 42 | return prevTimeChunk[0][n - 1] 43 | } 44 | 45 | console.log(maxScore([1,79,80,1,1,1,200,1], 3)) 46 | -------------------------------------------------------------------------------- /动态规划/完全平方数-279.js: -------------------------------------------------------------------------------- 1 | // https://github.com/sl1673495/leetcode-javascript/issues/9 2 | 3 | /** 4 | * @param {number} n 5 | * @return {number} 6 | */ 7 | let numSquares = function (n) { 8 | let dp = []; 9 | 10 | // 求0就假设为0次 11 | dp[0] = 0; 12 | 13 | for (let i = 1; i <= n; i++) { 14 | let j = 1; 15 | // 初始化为Infinity 这样后面任意一个小值都可以覆盖它 16 | let min = Infinity; 17 | while (true) { 18 | // 用 i 减去不断递增的平方数 j * j 19 | let prev = i - j * j; 20 | if (prev < 0) { 21 | break; 22 | } 23 | 24 | // 假设i = 10、j = 1 实际上就是在求dp[10 - 1] + 1 25 | // 也就是凑成 9 的最小次数 再加上 1(也就是 1 这个平方数的次数) 26 | min = Math.min(min, dp[prev] + 1); 27 | j++; 28 | } 29 | dp[i] = min === Infinity ? 0 : min; 30 | } 31 | 32 | return dp[n]; 33 | }; 34 | -------------------------------------------------------------------------------- /动态规划/恢复空格-面试题 17.13.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[]} dictionary 3 | * @param {string} sentence 4 | * @return {number} 5 | */ 6 | let respace = function (dictionary, sentence) { 7 | let n = sentence.length 8 | let dp = [0] 9 | for (let i = 1; i <= n; i++) { 10 | let min = dp[i - 1] + 1 11 | for (let word of dictionary) { 12 | if (sentence.substring(i - word.length, i) === word) { 13 | min = Math.min(min, dp[i - word.length]) 14 | } 15 | } 16 | dp[i] = min 17 | } 18 | return dp[n] 19 | } 20 | -------------------------------------------------------------------------------- /动态规划/打家劫舍-198.js: -------------------------------------------------------------------------------- 1 | let assert = require("assert"); 2 | 3 | /** 4 | * 打家劫舍 递归版 5 | * @param {number[]} nums 6 | * @return {number} 7 | */ 8 | let robRecurision = function (nums) { 9 | // 考虑从 index 开始到 nums - 1 为止 10 | // 打劫的最高价值 11 | let tryRob = function (nums, index) { 12 | // 超出边界了 13 | if (index >= nums.length) { 14 | return 0; 15 | } 16 | 17 | let max = 0; 18 | // 从 index,...n 分别选作起点开始抢劫 19 | // 求出各个值作为起点的最优解 20 | for (let i = index; i < nums.length; i++) { 21 | let value = nums[i]; 22 | 23 | // 不能打劫邻舍 所以要 +2 24 | max = Math.max(max, value + tryRob(nums, i + 2)); 25 | } 26 | 27 | return max; 28 | }; 29 | 30 | return tryRob(nums, 0); 31 | }; 32 | 33 | assert(robRecurision([1, 2, 3, 1]) === 4); 34 | assert(robRecurision([2, 7, 9, 3, 1]) === 12); 35 | 36 | /** 37 | * 打家劫舍-递归版2 38 | * 把打劫细分为 39 | * 40 | * 1. 打劫当前房子 那么下次就要从start + 2开始 41 | * 2. 打劫下一个房子 那么就直接从start + 1开始 42 | * 43 | * 求这两者间的最大值 44 | * @param {*} nums 45 | */ 46 | let robRecurision2 = function (nums) { 47 | let memo = []; 48 | function tryRob(nums, start) { 49 | let memorized = memo[start]; 50 | if (memorized) return memorized; 51 | 52 | if (start > nums.length - 1) return 0; 53 | 54 | let robNow = nums[start] + tryRob(nums, start + 2); 55 | let robNext = tryRob(nums, start + 1); 56 | 57 | let best = Math.max(robNext, robNow); 58 | memo[start] = best; 59 | 60 | return best; 61 | } 62 | 63 | return tryRob(nums, 0); 64 | }; 65 | 66 | assert(robRecurision2([1, 2, 3, 1]) === 4); 67 | assert(robRecurision2([2, 7, 9, 3, 1]) === 12); 68 | 69 | /** 70 | * 打家劫舍 DP版 71 | * @param {number[]} nums 72 | * @return {number} 73 | */ 74 | let rob = function (nums) { 75 | if (!nums.length) { 76 | return 0; 77 | } 78 | let dp = []; 79 | 80 | for (let i = nums.length - 1; i >= 0; i--) { 81 | let max = 0; 82 | for (let index = i; index < nums.length; index++) { 83 | let value = nums[index]; 84 | max = Math.max(max, value + (dp[index + 2] || 0)); 85 | } 86 | dp[i] = max; 87 | } 88 | 89 | return dp[0]; 90 | }; 91 | 92 | assert(rob([1, 2, 3, 1]) === 4); 93 | assert(rob([2, 7, 9, 3, 1]) === 12); 94 | 95 | /** 96 | * 打家劫舍 DP版2 97 | * @param {number[]} nums 98 | * @return {number} 99 | */ 100 | let rob2 = function (nums) { 101 | if (!nums.length) { 102 | return 0; 103 | } 104 | let dp = []; 105 | 106 | for (let i = nums.length - 1; i >= 0; i--) { 107 | let robNow = nums[i] + (dp[i + 2] || 0) 108 | let robNext = dp[i + 1] || 0 109 | 110 | dp[i] = Math.max(robNow, robNext) 111 | } 112 | 113 | return dp[0]; 114 | }; 115 | 116 | 117 | console.log(rob2([1, 10, 3, 1, 5])); 118 | -------------------------------------------------------------------------------- /动态规划/打家劫舍III-337.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | const TreeNode = require("../工具/二叉树"); 9 | 10 | /** 11 | * 65 / 124 个通过测试用例 12 | */ 13 | 14 | /** 15 | * @param {TreeNode} root 16 | * @return {number} 17 | */ 18 | /** 19 | * @param {TreeNode} root 20 | * @return {number} 21 | */ 22 | let memo = new WeakMap() 23 | let rob = function (root) { 24 | if (!root) { 25 | return 0; 26 | } 27 | 28 | let memorized = memo.get(root) 29 | if (memorized) { 30 | return memorized 31 | } 32 | 33 | let notRob = rob(root.left) + rob(root.right); 34 | let robNow = 35 | (root.val || 0) + 36 | (root.left ? rob(root.left.left) + rob(root.left.right) : 0) + 37 | (root.right ? rob(root.right.left) + rob(root.right.right) : 0); 38 | 39 | let max = Math.max(notRob, robNow); 40 | memo.set(root, max) 41 | return max; 42 | }; 43 | 44 | // 这种情况是不对的 应该计算出7 45 | let tree1 = new TreeNode(2); 46 | tree1.left = new TreeNode(1); 47 | tree1.right = new TreeNode(3); 48 | tree1.left.left = new TreeNode(null); 49 | tree1.left.right = new TreeNode(4); 50 | 51 | console.log(rob(tree1)); 52 | -------------------------------------------------------------------------------- /动态规划/找硬币-动态规划版.js: -------------------------------------------------------------------------------- 1 | const coinChange = (coins, targetAmount) => { 2 | // 初始化备忘录,用Infinity填满备忘录,Infinity说明该值不可以用硬币凑出来 3 | const dp = new Array(targetAmount + 1).fill(Infinity); 4 | 5 | // 设置初始条件为0 这一项无法用公式推导出来 6 | dp[0] = 0; 7 | 8 | for (let amount = 1; amount <= targetAmount; amount++) { 9 | for (let j = 0; j < coins.length; j++) { 10 | let coin = coins[j]; 11 | if (coin <= amount) { 12 | // 根据动态转移方程 求出当前面值需要的硬币数最小值 13 | dp[amount] = Math.min( 14 | dp[amount], 15 | // 比如目标15元 使用5元硬币 拆分为 dp(15 - 5) + 1 16 | dp[amount - coin] + 1 17 | ); 18 | } 19 | } 20 | } 21 | 22 | // 如果 `dp[amount] === Infinity`说明没有最优解返回-1,否则返回最优解 23 | return dp[targetAmount] === Infinity ? -1 : dp[targetAmount]; 24 | }; 25 | 26 | console.log(coinChange([1, 5, 11], 21)) 27 | -------------------------------------------------------------------------------- /动态规划/找硬币-记忆递归版.js: -------------------------------------------------------------------------------- 1 | function f(n) { 2 | function makeChange(amount) { 3 | if (amount <= 0) return 0; 4 | 5 | // 校验是否已经在备忘录中存在结果,如果存在返回即可 6 | if (cache[amount]) return cache[amount]; 7 | 8 | let min = Infinity; 9 | if (amount >= 1) { 10 | min = Math.min(makeChange(amount - 1) + 1, min); 11 | } 12 | 13 | if (amount >= 5) { 14 | min = Math.min(makeChange(amount - 5) + 1, min); 15 | } 16 | 17 | if (amount >= 11) { 18 | min = Math.min(makeChange(amount - 11) + 1, min); 19 | } 20 | 21 | return (cache[amount] = min); 22 | } 23 | // 备忘录 24 | const cache = []; 25 | return makeChange(n); 26 | } 27 | 28 | console.log(f(5000)); 29 | -------------------------------------------------------------------------------- /动态规划/找硬币-递归版.js: -------------------------------------------------------------------------------- 1 | // https://juejin.im/post/5e86d0ad6fb9a03c387f3342 2 | 3 | /** 4 | * 动态规划的前置思想,把问题拆分成子问题。 5 | * 比如f(15),可以拆分成min( 6 | * f(4) + 11 * 1, 7 | * f(10) + 5 * 1, 8 | * f(14) + 1 * 1 9 | * ) 10 | * 然后这里的f(4)、f(10)、f(14) 进一步按照这个步骤值拆分。 11 | * 这种做法的缺点是容易爆栈。 12 | * @param {number} n 13 | */ 14 | function f(n) { 15 | if (n === 0) return 0; 16 | let min = Infinity; 17 | if (n >= 1) { 18 | min = Math.min(f(n - 1) + 1, min); 19 | } 20 | 21 | if (n >= 5) { 22 | min = Math.min(f(n - 5) + 1, min); 23 | } 24 | 25 | if (n >= 11) { 26 | min = Math.min(f(n - 11) + 1, min); 27 | } 28 | 29 | return min; 30 | } 31 | 32 | console.log(f(50)); // 3 33 | -------------------------------------------------------------------------------- /动态规划/括号生成-22.js: -------------------------------------------------------------------------------- 1 | let generateParenthesis = function (n) { 2 | let dp = [] 3 | dp[0] = [''] 4 | dp[1] = ['()'] 5 | 6 | for (let i = 2; i <= n; i++) { 7 | let res = [] 8 | for (let j = 0; j <= i - 1; j++) { 9 | let inners = dp[j] 10 | let outers = dp[i - 1 - j] 11 | 12 | for (let inner of inners) { 13 | for (let outer of outers) { 14 | res.push(`(${inner})${outer}`) 15 | } 16 | } 17 | } 18 | dp[i] = res 19 | } 20 | return dp[n] 21 | }; 22 | 23 | console.log(generateParenthesis(4)) -------------------------------------------------------------------------------- /动态规划/斐波那契数列-509.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} N 3 | * @return {number} 4 | */ 5 | let fib = function (N) { 6 | let prev = 1n 7 | let prevPrev = 1n 8 | 9 | for (let i = 2; i <= N; i++) { 10 | let current = prev + prevPrev 11 | prevPrev = prev 12 | prev = current 13 | } 14 | 15 | return prev 16 | }; 17 | 18 | // let fib = function (N) { 19 | // let dp = [] 20 | 21 | // dp[0] = 0n; 22 | // dp[1] = 1n; 23 | 24 | // for (let i = 2; i <= N; i++) { 25 | // dp[i] = dp[i - 1] + dp[i - 2]; 26 | // } 27 | 28 | // return dp[N]; 29 | // }; 30 | 31 | // let fib = (function () { 32 | // let memo = new Map(); 33 | // return function (n) { 34 | // // 优先从记忆里取 找到就直接 return 35 | // // 否则又要进入下面的递归逻辑 非常耗时 36 | // let memorized = memo.get(n); 37 | // if (memorized) { 38 | // return memorized; 39 | // } 40 | 41 | // if (n == 1 || n == 2) { 42 | // return 1; 43 | // } 44 | 45 | // let f1 = fib(n - 1) 46 | // let f2 = fib(n - 2) 47 | 48 | // // 记忆下来 49 | // memo.set(n - 1, f1) 50 | // memo.set(n - 2, f2) 51 | 52 | // return f1 + f2 53 | // }; 54 | // })(); 55 | 56 | console.log(fib(100000)); 57 | -------------------------------------------------------------------------------- /动态规划/无重叠区间-435.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} intervals 3 | * @return {number} 4 | */ 5 | let eraseOverlapIntervals = function (intervals) { 6 | let n = intervals.length 7 | if (!n) { 8 | return 0 9 | } 10 | 11 | // 按照起始点排序 12 | intervals.sort((a, b) => a[0] - b[0]) 13 | 14 | // dp[i] 表示从 [0, i] 能构成的最长的无重叠区间的个数 15 | let dp = [] 16 | dp[0] = 1 17 | 18 | for (let i = 1; i < n; i++) { 19 | let max = 1 20 | let [curStart] = intervals[i] 21 | for (let j = 0; j < i; j++) { 22 | let [prevStart, prevEnd] = intervals[j] 23 | if (prevEnd <= curStart) { 24 | max = Math.max(max, dp[j] + 1) 25 | } 26 | } 27 | dp[i] = max 28 | } 29 | 30 | return n - Math.max(...dp) 31 | }; -------------------------------------------------------------------------------- /动态规划/最大子序和-53.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | let maxSubArray = function(nums) { 6 | let n = nums.length; 7 | let dp = []; 8 | 9 | dp[n - 1] = nums[n - 1]; 10 | 11 | for (let i = n - 2; i >= 0; i--) { 12 | let pickSelf = nums[i]; 13 | let pickWithNext = pickSelf + dp[i + 1]; 14 | dp[i] = Math.max(pickSelf, pickWithNext); 15 | } 16 | 17 | return Math.max(...dp); 18 | }; -------------------------------------------------------------------------------- /动态规划/最大正方形-221.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {character[][]} matrix 3 | * @return {number} 4 | */ 5 | let maximalSquare = function (matrix) { 6 | let maxY = matrix.length 7 | if (!maxY) return 0 8 | let maxX = matrix[0].length 9 | 10 | let dp = [] 11 | let max = 0 12 | 13 | let dpBasic = (y, x) => { 14 | if (matrix[y][x] === "1") { 15 | max = 1 16 | dp[y][x] = 1 17 | } else { 18 | dp[y][x] = 0 19 | } 20 | } 21 | for (let y = 0; y < maxY; y++) { 22 | dp[y] = [] 23 | dpBasic(y, 0) 24 | } 25 | for (let x = 1; x < maxX; x++) { 26 | dpBasic(0, x) 27 | } 28 | 29 | for (let y = 1; y < maxY; y++) { 30 | for (let x = 1; x < maxX; x++) { 31 | let val = matrix[y][x] 32 | if (val === "0") { 33 | dp[y][x] = 0 34 | } else { 35 | let left = dp[y][x - 1] 36 | let top = dp[y - 1][x] 37 | let leftTop = dp[y - 1][x - 1] 38 | dp[y][x] = Math.min(left, top, leftTop) + 1 39 | max = Math.max(max, dp[y][x]) 40 | } 41 | } 42 | } 43 | return max * max 44 | } 45 | -------------------------------------------------------------------------------- /动态规划/最小路径和-64.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} grid 3 | * @return {number} 4 | */ 5 | let minPathSum = function (grid) { 6 | let y = grid.length 7 | if (!y) { 8 | return 0 9 | } 10 | let x = grid[0].length 11 | 12 | let dp = [] 13 | for (let i = 0; i < y; i++) { 14 | dp[i] = [] 15 | } 16 | 17 | dp[0][0] = grid[0][0] 18 | 19 | // 第一行的基础状态 记得加上左边格子的值 20 | for (let j = 1; j < x; j++) { 21 | dp[0][j] = grid[0][j] + dp[0][j - 1] 22 | } 23 | 24 | // 第一列的基础状态 加上上方格子的最优解即可 25 | for (let i = 1; i < y; i++) { 26 | dp[i][0] = grid[i][0] + dp[i - 1][0] 27 | } 28 | 29 | // 开始求左上往右下求解 30 | for (let i = 1; i < grid.length; i++) { 31 | for (let j = 1; j < grid[i].length; j++) { 32 | let cur = grid[i][j] 33 | let fromUp = cur + (dp[i - 1][j] !== undefined ? dp[i - 1][j]: Infinity) 34 | let fromLeft = cur + (dp[i][j - 1] !== undefined ? dp[i][j - 1]: Infinity) 35 | 36 | dp[i][j] = Math.min( 37 | fromUp, 38 | fromLeft 39 | ) 40 | } 41 | } 42 | return dp[y - 1][x - 1] 43 | }; -------------------------------------------------------------------------------- /动态规划/最长上升子序列-300.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | let lengthOfLIS = function (nums) { 6 | let dp = [] 7 | let n = nums.length 8 | if (!n) { 9 | return 0 10 | } 11 | 12 | dp[0] = 1 13 | for (let i = 1; i < n; i++) { 14 | let num = nums[i] 15 | let max = 1 16 | // j 从 [0, i) 依次求出可以和 i 组成的最长上升子序列 17 | for (let j = 0; j < i; j++) { 18 | let prevNum = nums[j] 19 | if (num > prevNum) { 20 | // 循环中不断更新 max 值 21 | max = Math.max(max, dp[j] + 1) 22 | } 23 | } 24 | dp[i] = max 25 | } 26 | 27 | return Math.max(...dp) 28 | } 29 | -------------------------------------------------------------------------------- /动态规划/最长公共子序列-1143.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} text1 3 | * @param {string} text2 4 | * @return {number} 5 | */ 6 | let longestCommonSubsequence = function (text1, text2) { 7 | let n1 = text1.length 8 | let n2 = text2.length 9 | 10 | let dp = [] 11 | 12 | for (let i1 = 0; i1 <= n1; i1++) { 13 | dp[i1] = [] 14 | dp[i1][0] = 0 15 | } 16 | dp[0] = Array(n2 + 1).fill(0) 17 | 18 | for (let i1 = 1; i1 <= n1; i1++) { 19 | for (let i2 = 1; i2 <= n2; i2++) { 20 | let str1 = text1[i1 - 1] 21 | let str2 = text2[i2 - 1] 22 | 23 | if (str1 === str2) { 24 | dp[i1][i2] = 1 + dp[i1 - 1][i2 - 1] 25 | }else { 26 | dp[i1][i2] = Math.max(dp[i1 - 1][i2], dp[i1][i2 - 1]) 27 | } 28 | } 29 | } 30 | 31 | return dp[n1][n2] 32 | }; -------------------------------------------------------------------------------- /动态规划/最长单词-面试题 17.15.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string[]} wordDict 4 | * @return {boolean} 5 | */ 6 | let wordBreak = function (s, wordDict) { 7 | let n = s.length 8 | if (!n) return true 9 | 10 | let wordSet = new Set(wordDict) 11 | let dp = [] 12 | dp[0] = true 13 | 14 | for (let i = 0; i <= n; i++) { 15 | for (let j = i; j >= 0; j--) { 16 | let word = s.slice(j, i) 17 | if (wordSet.has(word) && dp[j]) { 18 | dp[i] = true 19 | break 20 | } 21 | } 22 | } 23 | 24 | return !!dp[n] 25 | } 26 | /** 27 | * @param {string[]} words 28 | * @return {string} 29 | */ 30 | let longestWord = function (words) { 31 | // 先长度降序 后字典序升序 排序 32 | words.sort((a, b) => { 33 | let diff = b.length - a.length 34 | if (diff !== 0) { 35 | return diff 36 | } else { 37 | return a < b ? -1 : 1 38 | } 39 | }) 40 | words = Array.from(new Set(words)) 41 | for (let i = 0; i < words.length; i++) { 42 | let word = words[i] 43 | let rest = words.slice(0, i).concat(words.slice(i + 1)) 44 | if (wordBreak(word, rest)) { 45 | return word 46 | } 47 | } 48 | return "" 49 | } -------------------------------------------------------------------------------- /动态规划/最长回文子串-5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 动态规划 3 | * @param {string} s 4 | * @return {string} 5 | */ 6 | // let longestPalindrome = function (s) { 7 | // let n = s.length 8 | // if (n < 2) { 9 | // return s 10 | // } 11 | 12 | // let dp = [] 13 | // for (let i = 0; i < n; i++) { 14 | // dp[i] = [] 15 | // dp[i][i] = true 16 | // } 17 | 18 | // let max = 0 19 | // let begin = 0 20 | // for (let j = 1; j < n; j++) { 21 | // for (let i = 0; i < j; i++) { 22 | // if (s[j] !== s[i]) { 23 | // dp[i][j] = false 24 | // } else { 25 | // let indent = dp[i + 1][j - 1] 26 | // if (indent === undefined || indent === true) { 27 | // dp[i][j] = true 28 | // }else { 29 | // dp[i][j] = false 30 | // } 31 | // } 32 | 33 | // if (dp[i][j] === true && j - i > max) { 34 | // max = j - i 35 | // begin = i 36 | // } 37 | // } 38 | // } 39 | // console.log('dp', dp) 40 | // return s.substr(begin, max + 1) 41 | // } 42 | 43 | /** 44 | * 中心扩散法 45 | * @param {string} s 46 | * @return {string} 47 | */ 48 | let longestPalindrome = function (s) { 49 | let n = s.length 50 | if (n < 2) { 51 | return s 52 | } 53 | 54 | let begin = 0 55 | let max = 1 56 | 57 | let spread = (start, end) => { 58 | while (s[start] === s[end] && start >= 0 && end < n) { 59 | let len = end - start + 1 60 | if (len > max) { 61 | max = len 62 | begin = start 63 | } 64 | start-- 65 | end++ 66 | } 67 | } 68 | 69 | for (let mid = 0; mid < n; mid++) { 70 | spread(mid, mid) 71 | spread(mid, mid + 1) 72 | } 73 | 74 | return s.substr(begin, max) 75 | } 76 | 77 | console.log(longestPalindrome("babad")) 78 | -------------------------------------------------------------------------------- /动态规划/最长重复子数组-718.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} A 3 | * @param {number[]} B 4 | * @return {number} 5 | */ 6 | let findLength = function (A, B) { 7 | let dp = [] 8 | let al = A.length 9 | let bl = B.length 10 | 11 | for (let i = 0; i <= al; i++) { 12 | dp[i] = [] 13 | for (let j = 0; j <= bl; j++) { 14 | dp[i][j] = 0 15 | } 16 | } 17 | 18 | let max = 0 19 | for (let i = al - 1; i >= 0; i--) { 20 | for (let j = bl - 1; j >= 0; j--) { 21 | let a = A[i] 22 | let b = B[j] 23 | 24 | if (a === b) { 25 | dp[i][j] = dp[i + 1][j + 1] + 1 26 | max = Math.max(max, dp[i][j]) 27 | } else { 28 | dp[i][j] = 0 29 | } 30 | } 31 | } 32 | return max 33 | } 34 | -------------------------------------------------------------------------------- /动态规划/爬楼梯-70.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 3 | 4 | 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 5 | 6 | 注意:给定 n 是一个正整数。 7 | 8 | 示例 1: 9 | 10 | 输入: 2 11 | 输出: 2 12 | 解释: 有两种方法可以爬到楼顶。 13 | 1. 1 阶 + 1 阶 14 | 2. 2 阶 15 | 16 | 17 | 来源:力扣(LeetCode) 18 | 链接:https://leetcode-cn.com/problems/climbing-stairs 19 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 20 | */ 21 | 22 | /** 23 | * 爬n阶楼梯,可以拆分为 24 | * 爬1阶加爬f(n-1)阶 + 爬2阶加爬f(n-2)阶 的方式。 25 | * @param {number} n 26 | * @return {number} 27 | */ 28 | let climbStairs = function (n) { 29 | let dp = new Array(n + 1).fill(0); 30 | 31 | // 初始条件 爬一阶只有一种方式 32 | dp[1] = 1; 33 | dp[2] = 2 34 | 35 | for (let i = 3; i <= n; i++) { 36 | dp[i] = dp[i - 1] + dp[i - 2]; 37 | } 38 | 39 | return dp[n]; 40 | }; 41 | 42 | console.log(climbStairs(3)) 43 | -------------------------------------------------------------------------------- /动态规划/目标和-494.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} S 4 | * @return {number} 5 | */ 6 | let findTargetSumWays = function (nums, S) { 7 | let ns = nums.length 8 | if (!ns) { 9 | return 0 10 | } 11 | let min = nums.reduce((sum, cur) => sum - cur, 0) 12 | let max = nums.reduce((sum, cur) => sum + cur, 0) 13 | 14 | let dp = [] 15 | for (let n = 0; n < ns; n++) { 16 | dp[n] = [] 17 | } 18 | 19 | // 基础状态 20 | for (let s = min; s <= max; s++) { 21 | let num = nums[0] 22 | let pickPositive = s === num ? 1 : 0 23 | // 选负数形态 24 | let pickNegative = -s === num ? 1 : 0 25 | dp[0][s] = pickPositive + pickNegative 26 | } 27 | 28 | for (let n = 1; n < ns; n++) { 29 | for (let s = min; s <= max; s++) { 30 | let num = nums[n] 31 | // 选正数形态 32 | let pickPositive = dp[n - 1][s - num] || 0 33 | // 选负数形态 34 | let pickNegative = dp[n - 1][s + num] || 0 35 | dp[n][s] = pickNegative + pickPositive 36 | } 37 | } 38 | return dp[ns - 1][S] || 0 39 | } 40 | -------------------------------------------------------------------------------- /动态规划/零钱兑换II-518.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} amount 3 | * @param {number[]} coins 4 | * @return {number} 5 | */ 6 | let change = function (amount, coins) { 7 | let dp = new Array(amount + 1).fill(0); 8 | 9 | dp[0] = 1; 10 | 11 | for (let coin of coins) { 12 | for (let i = 1; i <= amount; i++) { 13 | if (i >= coin) { 14 | dp[i] += dp[i - coin]; 15 | } 16 | } 17 | } 18 | 19 | return dp[amount]; 20 | }; 21 | 22 | console.log(change(5, [1, 2, 5])); 23 | -------------------------------------------------------------------------------- /动态规划/面试题08.11.硬币.js: -------------------------------------------------------------------------------- 1 | let coins = [1, 5, 10, 25]; 2 | let waysToChange = function (n) { 3 | let cl = coins.length; 4 | if (n === 0) return 0; 5 | 6 | let dp = new Array(cl); 7 | for (let i = 0; i < cl; i++) { 8 | dp[i] = new Array(n + 1); 9 | } 10 | 11 | for (let i = 0; i < cl; i++) { 12 | dp[i][0] = 1 13 | } 14 | 15 | for (let i = 0; i < dp.length; i++) { 16 | for (let j = 1; j <= n; j++) { 17 | let coin = coins[i]; 18 | let dpPrev = dp[i - 1]; 19 | // 考虑不用当前硬币 20 | let pickPrev = dpPrev ? dpPrev[j] : 0; 21 | 22 | // 考虑加上当前硬币 23 | let pickCurrentAndPrev = 0; 24 | if (j >= coin) { 25 | // 用了当前的硬币以后 剩余的面值 26 | let rest = j - coin; 27 | let pickRest = dp[i][rest]; 28 | if (pickRest > 0) { 29 | // 这个方式数其实就是凑剩余硬币的方式数 30 | // 比如 以硬币5和面值10来说 31 | // 拿出了5 发现剩余面值是5 32 | // 凑剩余面值5的情况是 5 + 11111 33 | // 所以拿出5来凑的方式是1种 34 | pickCurrentAndPrev = pickRest; 35 | } 36 | } 37 | 38 | dp[i][j] = (pickPrev + pickCurrentAndPrev) % 1000000007; 39 | } 40 | } 41 | 42 | return dp[cl - 1][n]; 43 | }; 44 | 45 | console.log(waysToChange(61)); // 73 46 | -------------------------------------------------------------------------------- /单值二叉树.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {TreeNode} root 3 | * @return {boolean} 4 | */ 5 | let isUnivalTree = function(root) { 6 | let values = [] 7 | dfs(root, values) 8 | let first = values[0] 9 | return values.every(val => val === first) 10 | }; 11 | 12 | function dfs(node, values) { 13 | if (!node) return 14 | values.push(node.val) 15 | dfs(node.left, values) 16 | dfs(node.right, values) 17 | } 18 | 19 | 20 | 21 | let TreeNode = require('./工具/二叉树.js') 22 | 23 | let treeNode = new TreeNode(1) 24 | 25 | treeNode.left = new TreeNode(1) 26 | treeNode.right = new TreeNode(1) 27 | 28 | treeNode.left.left = new TreeNode(1) 29 | 30 | treeNode.left.right = new TreeNode(2) 31 | 32 | 33 | console.log(isUnivalTree(treeNode)) -------------------------------------------------------------------------------- /双指针/删除排序数组中的重复项-26.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | let removeDuplicates = function (nums) { 6 | // 快指针 7 | let i = 0; 8 | // 慢指针 9 | let j = 0; 10 | 11 | while (i < nums.length) { 12 | let fast = nums[i]; 13 | let slot = nums[j]; 14 | 15 | // 快慢不相等,说明找到了一个新的值 16 | // 把慢指针的位置更新,并且赋值成新的值,继续等待下一个新值。 17 | if (fast !== slot) { 18 | j++; 19 | nums[j] = fast; 20 | } 21 | i++; 22 | } 23 | 24 | console.log(nums); 25 | 26 | return j + 1; 27 | }; 28 | 29 | console.log(removeDuplicates([0, 0, 1, 1, 1, 2, 2, 3, 3, 4])); 30 | -------------------------------------------------------------------------------- /双指针/合并两个有序数组-88.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=88 lang=javascript 3 | * 4 | * [88] 合并两个有序数组 5 | */ 6 | 7 | // @lc code=start 8 | /** 9 | * @param {number[]} nums1 10 | * @param {number} m 11 | * @param {number[]} nums2 12 | * @param {number} n 13 | * @return {void} Do not return anything, modify nums1 in-place instead. 14 | */ 15 | let merge = function (arr1, m, arr2, n) { 16 | // 两个指针指向数组非空位置的末尾 17 | let i = m - 1; 18 | let j = n - 1; 19 | // 第三个指针指向第一个数组的末尾 填充数据 20 | let k = arr1.length - 1; 21 | 22 | while (i >= 0 && j >= 0) { 23 | let num1 = arr1[i]; 24 | let num2 = arr2[j]; 25 | 26 | if (num1 > num2) { 27 | arr1[k] = num1; 28 | i--; 29 | } else { 30 | arr1[k] = num2; 31 | j--; 32 | } 33 | k--; 34 | } 35 | 36 | while (j >= 0) { 37 | arr1[k] = arr2[j]; 38 | j--; 39 | k--; 40 | } 41 | }; 42 | // @lc code=end 43 | -------------------------------------------------------------------------------- /双指针/搜索二维矩阵 II-240.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} matrix 3 | * @param {number} target 4 | * @return {boolean} 5 | */ 6 | let searchMatrix = function (matrix, target) { 7 | let y = matrix.length 8 | if (!y) return false 9 | let x = matrix[0].length 10 | 11 | let row = y - 1 12 | let column = 0 13 | while (row >= 0 && column < x) { 14 | let val = matrix[row][column] 15 | if (val > target) { 16 | row-- 17 | } else if (val < target) { 18 | column++ 19 | } else if (val === target) { 20 | return true 21 | } 22 | } 23 | return false 24 | }; -------------------------------------------------------------------------------- /双指针/最接近的三数之和-16.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} target 4 | * @return {number} 5 | */ 6 | let threeSumClosest = function (nums, target) { 7 | let n = nums.length 8 | if (n === 3) { 9 | return getSum(nums) 10 | } 11 | // 先升序排序 此为解题的前置条件 12 | nums.sort((a, b) => a - b) 13 | 14 | let min = Infinity // 和 target 的最小差 15 | let res 16 | 17 | // 从左往右依次尝试定一个基础指针 右边至少再保留两位 否则无法凑成3个 18 | for (let i = 0; i <= nums.length - 3; i++) { 19 | let basic = nums[i] 20 | let left = i + 1 // 左指针先从 i 右侧的第一位开始尝试 21 | let right = n - 1 // 右指针先从数组最后一项开始尝试 22 | 23 | while (left < right) { 24 | let sum = basic + nums[left] + nums[right] // 三数求和 25 | // 更新最小差 26 | let diff = Math.abs(sum - target) 27 | if (diff < min) { 28 | min = diff 29 | res = sum 30 | } 31 | if (sum < target) { 32 | // 求出的和如果小于目标值的话 可以尝试把左指针右移 扩大值 33 | left++ 34 | } else if (sum > target) { 35 | // 反之则右指针左移 36 | right-- 37 | } else { 38 | // 相等的话 差就为0 一定是答案 39 | return sum 40 | } 41 | } 42 | } 43 | 44 | return res 45 | } 46 | 47 | function getSum(nums) { 48 | return nums.reduce((total, cur) => total + cur, 0) 49 | } 50 | -------------------------------------------------------------------------------- /双指针/通过删除字母匹配到字典里最长单词-524.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string[]} d 4 | * @return {string} 5 | */ 6 | let findLongestWord = function (s, d) { 7 | let n = d.length 8 | let points = Array(n).fill(-1) 9 | 10 | let find = "" 11 | for (let i = 0; i < s.length; i++) { 12 | let char = s[i] 13 | for (let j = 0; j < n; j++) { 14 | let targetChar = d[j][points[j] + 1] 15 | if (char === targetChar) { 16 | points[j]++ 17 | let word = d[j] 18 | let wl = d[j].length 19 | if (points[j] === wl - 1) { 20 | let fl = find.length 21 | if (wl > fl || (wl === fl && word < find)) { 22 | find = word 23 | } 24 | } 25 | } 26 | } 27 | } 28 | 29 | return find 30 | } -------------------------------------------------------------------------------- /可被K整除的子数组.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 暴力解 超时了 3 | * @param {number[]} A 4 | * @param {number} K 5 | * @return {number} 6 | */ 7 | var subarraysDivByK = function (A, K) { 8 | let prevPrefix = [] 9 | let currentPrefix = [] 10 | let count = 0 11 | for (let i = A.length - 1; i >= 0; i--) { 12 | let num = A[i] 13 | judge(num) 14 | for (let prev of prevPrefix) { 15 | let sum = prev + num 16 | judge(sum) 17 | } 18 | prevPrefix = currentPrefix 19 | currentPrefix = [] 20 | } 21 | 22 | function judge(num) { 23 | if (num % K === 0 || num === 0) { 24 | count++ 25 | } 26 | currentPrefix.push(num) 27 | } 28 | 29 | return count 30 | } 31 | 32 | console.log(subarraysDivByK([4, 5, 0, -2, -3, 1], 5)) 33 | -------------------------------------------------------------------------------- /合并两个有序数组.js: -------------------------------------------------------------------------------- 1 | /** 2 | 88. 合并两个有序数组 3 | 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 num1 成为一个有序数组。 4 | 5 | 6 | 7 | 说明: 8 | 9 | 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 10 | 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 11 | 12 | 13 | 示例: 14 | 15 | 输入: 16 | nums1 = [1,2,10,0,0,0], m = 3 17 | nums2 = [2,5,6,7], n = 3 18 | 19 | 输出: [1,2,2,3,5,6] 20 | */ 21 | 22 | /** 23 | * @param {number[]} nums1 24 | * @param {number} m 25 | * @param {number[]} nums2 26 | * @param {number} n 27 | * @return {void} Do not return anything, modify nums1 in-place instead. 28 | */ 29 | const merge = function(nums1, m, nums2, n) { 30 | let i = m - 1 31 | let j = n - 1 32 | // 尾部指针 33 | let k = nums1.length - 1 34 | while (j >= 0) { 35 | let n1 = nums1[i] 36 | let n2 = nums2[j] 37 | 38 | if (n1 > n2) { 39 | nums1[k] = n1 40 | k-- 41 | i-- 42 | } else { 43 | nums1[k] = n2 44 | k-- 45 | j-- 46 | } 47 | } 48 | } 49 | 50 | let a = [1, 2, 10, 0, 0, 0] 51 | merge(a, 3, [2, 5, 6, 7], 4) 52 | 53 | console.log(a) 54 | 55 | /** 56 | [1,2,10,0,0,0,0] 57 | ↑ 58 | [2,5,6,7] 59 | ↑ 60 | 61 | [1,2,10,0,0,0,+10] 62 | ↑ 63 | [2,5,6,7] 64 | ↑ 65 | 66 | [1,2,10,0,0,+7,10] 67 | ↑ 68 | [2,5,6,7] 69 | ↑ 70 | 71 | [1,2,10,0,+6,7,10] 72 | ↑ 73 | [2,5,6,7] 74 | ↑ 75 | 76 | [1,2,0,+5,6,7,10] 77 | ↑ 78 | [2,5,6,7] 79 | ↑ 80 | 81 | [1,2,+2,5,6,7,10] 82 | ↑ 83 | [2,5,6,7] 84 | 指针移动完毕 85 | */ 86 | 87 | /** 88 | * @param {number[]} nums1 89 | * @param {number} m 90 | * @param {number[]} nums2 91 | * @param {number} n 92 | * @return {void} Do not return anything, modify nums1 in-place instead. 93 | */ 94 | const merge2 = function(nums1, m, nums2, n) { 95 | // 初始化两个指针的指向,初始化 nums1 尾部索引k 96 | let i = m - 1, j = n - 1, k = m + n - 1 97 | // 当两个数组都没遍历完时,指针同步移动 98 | while(i >= 0 && j >= 0) { 99 | // 取较大的值,从末尾往前填补 100 | if(nums1[i] >= nums2[j]) { 101 | nums1[k] = nums1[i] 102 | i-- 103 | k-- 104 | } else { 105 | nums1[k] = nums2[j] 106 | j-- 107 | k-- 108 | } 109 | } 110 | 111 | // nums2 留下的情况,特殊处理一下 112 | while(j>=0) { 113 | nums1[k] = nums2[j] 114 | k-- 115 | j-- 116 | } 117 | }; 118 | 119 | let a2= [1, 2, 0, 0, 0, 0] 120 | merge(a2, 2, [2, 5, 6], 4) 121 | 122 | console.log(a2) -------------------------------------------------------------------------------- /合并两个有序链表.js: -------------------------------------------------------------------------------- 1 | function ListNode(val) { 2 | this.val = val 3 | this.next = null 4 | } 5 | 6 | let node1 = new ListNode(1) 7 | node1.next = new ListNode(2) 8 | node1.next.next = new ListNode(4) 9 | 10 | let node2 = new ListNode(1) 11 | node2.next = new ListNode(3) 12 | node2.next.next = new ListNode(4) 13 | /** 14 | * @param {ListNode} l1 15 | * @param {ListNode} l2 16 | * @return {ListNode} 17 | */ 18 | let mergeTwoLists = function(l1, l2) { 19 | let arr = [] 20 | if (!l1 && !l2) { 21 | return null 22 | } 23 | while (l1 || l2) { 24 | let runL1 = () => { 25 | arr.push(l1.val) 26 | l1 = l1.next 27 | } 28 | let runL2 = () => { 29 | arr.push(l2.val) 30 | l2 = l2.next 31 | } 32 | if (!l1) { 33 | runL2() 34 | } else if (!l2) { 35 | runL1() 36 | } else if (l1.val > l2.val) { 37 | runL2() 38 | } else { 39 | runL1() 40 | } 41 | } 42 | return arrToList(arr) 43 | } 44 | 45 | function arrToList(arr) { 46 | let i = 0 47 | let r = new ListNode() 48 | let head = r 49 | while (i < arr.length) { 50 | head.val = arr[i] 51 | if (++i < arr.length) { 52 | head.next = new ListNode() 53 | head = head.next 54 | } 55 | } 56 | return r 57 | } 58 | 59 | console.log(JSON.stringify(mergeTwoLists(node1, node2))) 60 | -------------------------------------------------------------------------------- /回文子串-647.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | let countSubstrings = function (s) { 6 | let n = s.length 7 | if (n < 2) { 8 | return n 9 | } 10 | 11 | let count = 0 12 | 13 | let spread = (start, end) => { 14 | while (s[start] === s[end] && start >= 0 && end < n) { 15 | start-- 16 | end++ 17 | count++ 18 | } 19 | } 20 | 21 | for (let mid = 0; mid < n; mid++) { 22 | spread(mid, mid) 23 | spread(mid, mid + 1) 24 | } 25 | 26 | return count 27 | } 28 | 29 | console.log(countSubstrings("a")) 30 | -------------------------------------------------------------------------------- /字符串字符最短路径.js: -------------------------------------------------------------------------------- 1 | /** 2 | 给定一个字符串 S 和一个字符 C。返回一个代表字符串 S 中每个字符到字符串 S 中的字符 C 的最短距离的数组。 3 | 4 | 示例 1: 5 | 6 | 输入: S = "loveleetcode", C = 'e' 7 | 输出: [3, 2, 1, 0, 1, 0, 0, 1, 2, 2, 1, 0] 8 | 说明: 9 | 10 | 字符串 S 的长度范围为 [1, 10000]。 11 | C 是一个单字符,且保证是字符串 S 里的字符。 12 | S 和 C 中的所有字母均为小写字母。 13 | */ 14 | 15 | /** 16 | * @param {string} S 17 | * @param {character} C 18 | * @return {number[]} 19 | */ 20 | let shortestToChar = function(S, C) { 21 | let sl = S.length 22 | 23 | if (sl === 0) { 24 | return [] 25 | } 26 | if (sl.length > 10000) { 27 | S = S.substr(0, 10000) 28 | } 29 | let pos = [] 30 | let res = [] 31 | // 先走一次循环 存储每个C值的位置 便于比对 32 | for (let i = 0; i < sl; i++) { 33 | if (S[i] === C) { 34 | pos.push(i) 35 | } 36 | } 37 | 38 | for (let i = 0; i < sl; i++) { 39 | // 如果这个字符就是C 距离就是0 40 | let char = S[i] 41 | if (char === C) { 42 | res.push(0) 43 | } else { 44 | // 定一个最短距离变量 45 | let shortest = sl 46 | // 循环C位置的数组 找出距离最短的 注意绝对值 47 | for (let j = 0; j < pos.length; j++) { 48 | let diff = pos[j] - i 49 | diff = diff > 0 ? diff : Math.abs(diff) 50 | if (diff < shortest) { 51 | shortest = diff 52 | } 53 | } 54 | res.push(shortest) 55 | } 56 | } 57 | return res 58 | } 59 | -------------------------------------------------------------------------------- /工具/二叉树.js: -------------------------------------------------------------------------------- 1 | class TreeNode { 2 | constructor(val) { 3 | this.val = val 4 | this.left = null 5 | this.right = null 6 | } 7 | } 8 | 9 | module.exports = TreeNode -------------------------------------------------------------------------------- /工具/交换.js: -------------------------------------------------------------------------------- 1 | function swap(arr, i, j) { 2 | let temp = arr[i]; 3 | arr[i] = arr[j]; 4 | arr[j] = temp; 5 | }; 6 | 7 | module.exports = swap -------------------------------------------------------------------------------- /工具/排序速度.js: -------------------------------------------------------------------------------- 1 | const glob = require("glob") 2 | const path = require("path") 3 | const swap = require("./交换") 4 | const random = require("./随机值") 5 | 6 | function sortTest(sortFns, source, desc) { 7 | console.log(desc) 8 | const table = {} 9 | 10 | if (typeof source === "function") { 11 | source = source() 12 | } 13 | 14 | sortFns.forEach((fn) => { 15 | const copy = source.slice() 16 | const start = new Date().getTime() 17 | 18 | try { 19 | fn(copy) 20 | } catch (e) { 21 | return (table[fn.sortName] = { 22 | 结果: "程序异常", 23 | 原因: e.message, 24 | }) 25 | } 26 | 27 | const end = new Date().getTime() 28 | const time = end - start 29 | const timeStr = `${time}ms` 30 | const success = isSorted(copy, source) 31 | 32 | table[fn.sortName] = { 33 | 耗时: timeStr, 34 | 数据长度: source.length, 35 | 结果: success ? "成功" : "失败", 36 | } 37 | }) 38 | 39 | console.table(table) 40 | } 41 | 42 | function getRandomArray(count) { 43 | const arr = [] 44 | for (let i = 0; i < count; i++) { 45 | arr.push(Math.floor(i * Math.random() * 10)) 46 | } 47 | return arr 48 | } 49 | 50 | function getNearlyArray(count, swapTime) { 51 | const arr = [] 52 | for (let i = 0; i < count; i++) { 53 | arr.push(i) 54 | } 55 | 56 | for (let i = 0; i < swapTime; i++) { 57 | const x = Math.floor(Math.random() * count) 58 | const y = Math.floor(Math.random() * count) 59 | swap(arr, x, y) 60 | } 61 | 62 | return arr 63 | } 64 | 65 | function getRangedArray(count, min, max) { 66 | const arr = [] 67 | for (let i = 0; i < count; i++) { 68 | arr.push(random(min, max)) 69 | } 70 | return arr 71 | } 72 | 73 | function isSorted(target, source) { 74 | return ( 75 | target.toString() === 76 | source 77 | .slice() 78 | .sort((a, b) => a - b) 79 | .toString() 80 | ) 81 | } 82 | 83 | glob("排序/*.js", (err, result) => { 84 | if (err) throw err 85 | const sortFunctions = result 86 | .map((p) => require(path.resolve(p))) 87 | .filter(Boolean) 88 | sortTest(sortFunctions, () => getRandomArray(10000), "普通数组排序") 89 | sortTest(sortFunctions, () => getNearlyArray(10000), "近似数组排序") 90 | sortTest(sortFunctions, () => getRangedArray(500), "大量重复值元素排序") 91 | }) 92 | -------------------------------------------------------------------------------- /工具/链表.js: -------------------------------------------------------------------------------- 1 | function ListNode(val) { 2 | this.val = val 3 | this.next = null 4 | } 5 | 6 | /** 7 | * 数组 -> 链表 8 | * @param {number[]} vals 9 | */ 10 | function makeListNode(vals) { 11 | let head = new ListNode(vals[0]) 12 | let i = 1 13 | let cur = head 14 | while (i < vals.length) { 15 | let val = vals[i] 16 | cur.next = new ListNode(val) 17 | cur = cur.next 18 | i++ 19 | } 20 | return head 21 | } 22 | 23 | module.exports = ListNode 24 | 25 | module.exports.makeListNode = makeListNode 26 | -------------------------------------------------------------------------------- /工具/随机值.js: -------------------------------------------------------------------------------- 1 | function random(low, high) { 2 | return Math.round(Math.random() * (high - low)) + low 3 | } 4 | 5 | module.exports = random 6 | -------------------------------------------------------------------------------- /广度优先遍历和深度优先遍历.js: -------------------------------------------------------------------------------- 1 | const data = [ 2 | { 3 | name: "1", 4 | children: [ 5 | { 6 | name: "1-1", 7 | }, 8 | { 9 | name: "1-2", 10 | children: [ 11 | { 12 | name: "1-2-1", 13 | }, 14 | { 15 | name: "1-2-2", 16 | }, 17 | ], 18 | }, 19 | { 20 | name: "1-3", 21 | children: [ 22 | { 23 | name: "1-3-1", 24 | }, 25 | { 26 | name: "1-3-2", 27 | }, 28 | ], 29 | }, 30 | ], 31 | }, 32 | ] 33 | 34 | /** 35 | * bfs需要借助一个队列,在访问平级节点的时候遇到节点有子节点的话,先全部把子节点推入队列。 36 | * 这样下一轮bfs的时候,就会按顺序先平级执行这个队列,在这个过程中重复上一步对子节点的收集。 37 | */ 38 | function bfs(tree) { 39 | let queue = [] 40 | tree.forEach((node) => { 41 | console.log(node.name) 42 | if (node.children) { 43 | queue = queue.concat(node.children) 44 | } 45 | }) 46 | if (queue.length) { 47 | bfs(queue) 48 | } 49 | } 50 | 51 | /** 52 | * dfs就是一路向下访问,遇到节点有children的情况就直接递归下去,而先不管同级的其他节点。 53 | */ 54 | function dfs(tree) { 55 | tree.forEach((node) => { 56 | console.log(node.name) 57 | if (node.children) { 58 | dfs(node.children) 59 | } 60 | }) 61 | } 62 | 63 | console.log("bfs") 64 | bfs(data) 65 | console.log("dfs") 66 | dfs(data) 67 | -------------------------------------------------------------------------------- /归并排序.js: -------------------------------------------------------------------------------- 1 | function mergeSort(arr) { 2 | let l = arr.length; 3 | if (l === 1) return arr; 4 | let m = Math.round(l / 2); 5 | let left = arr.slice(0, m); 6 | let right = arr.slice(m); 7 | return merge(mergeSort(left), mergeSort(right)); 8 | } 9 | 10 | function merge(arr1, arr2) { 11 | let l1 = arr1.length; 12 | let l2 = arr2.length; 13 | let i1 = 0; 14 | let i2 = 0; 15 | let r = []; 16 | while (i1 < l1 && i2 < l2) { 17 | let item1 = arr1[i1]; 18 | let item2 = arr2[i2]; 19 | if (item1 < item2) { 20 | r.push(item1); 21 | i1++; 22 | } else { 23 | r.push(item2); 24 | i2++; 25 | } 26 | } 27 | 28 | while (i1 < l1) { 29 | r.push(arr1[i1++]); 30 | } 31 | 32 | while (i2 < l2) { 33 | r.push(arr2[i2++]); 34 | } 35 | 36 | return r; 37 | } 38 | 39 | console.log(mergeSort([1, 4, 2, 3, 5, 2, 7, 3, 17, 25, 19])) -------------------------------------------------------------------------------- /找出子字符串第一次出现的位置-28.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} haystack 3 | * @param {string} needle 4 | * @return {number} 5 | */ 6 | let strStr = function(haystack, needle) { 7 | if (needle === '') return 0 8 | 9 | let start 10 | for (let i = 0; i < haystack.length - needle.length + 1; i++) { 11 | for (let j = 0; j < needle.length; j++) { 12 | let hIndex = i + j 13 | if (hIndex > haystack.length) { 14 | return -1 15 | } 16 | if (needle[j] === haystack[i + j]) { 17 | if (j === 0) { 18 | start = i 19 | } 20 | if (j === needle.length - 1) { 21 | return start 22 | } 23 | }else { 24 | break 25 | } 26 | } 27 | } 28 | return -1 29 | }; 30 | 31 | console.log(strStr("hello", "lo")) -------------------------------------------------------------------------------- /排序/冒泡排序.js: -------------------------------------------------------------------------------- 1 | const swap = require("../工具/交换") 2 | /** 3 | * 冒泡排序 4 | * @param {number[]} arr 5 | */ 6 | function bubbleSort(arr) { 7 | let n = arr.length 8 | if (n <= 1) return arr 9 | 10 | for (let i = 0; i < n; i++) { 11 | let flag = false 12 | // 从前往后冒泡 所以已经处理过的就不用再访问了 13 | // 并且由于每次遍历会访问j+1项,等于提前遍历了后一项 14 | // 所以这里的终止条件可以是n - i再减去1 15 | for (let j = 0; j < n - 1 - i; j++) { 16 | if (arr[j] > arr[j + 1]) { 17 | swap(arr, j, j + 1) 18 | flag = true 19 | } 20 | } 21 | // 如果这次循环都没有数字被交换 说明已经是排序好的数组 22 | if (!flag) return arr 23 | } 24 | return arr 25 | } 26 | 27 | bubbleSort.sortName = "冒泡排序" 28 | 29 | // module.exports = bubbleSort 30 | -------------------------------------------------------------------------------- /排序/快速排序-三路.js: -------------------------------------------------------------------------------- 1 | const swap = require("../工具/交换") 2 | const random = require("../工具/随机值") 3 | 4 | /** 5 | * 三路快速排序 6 | * 将 arr[l...r] 分为 < v, === v, > v三部分 7 | * 之后递归的对 < v, > v 两部分三路快排 8 | * @param {number[]} arr 9 | */ 10 | function quickSort(arr) { 11 | _quickSort(arr, 0, arr.length - 1) 12 | return arr 13 | } 14 | 15 | /** 16 | * 对 arr[l...r] 部分进行快速排序 17 | * @param {number[]} arr 18 | * @param {number} l 左边界 19 | * @param {number} r 右边界 20 | */ 21 | function _quickSort(arr, l, r) { 22 | if (l >= r) { 23 | return 24 | } 25 | let [p, q] = partition(arr, l, r) 26 | _quickSort(arr, l, p) 27 | _quickSort(arr, q, r) 28 | } 29 | 30 | /** 31 | * 对 arr[l...r] 部分进行快速排序 32 | * @param {number[]} arr 33 | * @param {number} l 左边界 34 | * @param {number} r 右边界 35 | * @returns {number} 返回索引值p,使得arr[l...p-1] < arr[p] < arr[p+1...r] 36 | */ 37 | function partition(arr, left, right) { 38 | // 取一个基准值 取随机值 39 | let rand = random(left, right) 40 | swap(arr, left, rand) 41 | let pivot = arr[left] 42 | 43 | // 三路 注意看注释里的区间 44 | let lt = left // arr[left + 1...lt] < v 45 | let gt = right + 1 // arr[gt...r] > v 46 | let index = left + 1 // arr[lt + 1...index) === v 47 | 48 | while (index < gt) { 49 | let num = arr[index] 50 | if (num < pivot) { 51 | swap(arr, index, lt + 1) 52 | lt++ 53 | index++ 54 | } else if (num > pivot) { 55 | swap(arr, index, gt - 1) 56 | gt-- 57 | } else if (num === pivot) { 58 | index++ 59 | } 60 | } 61 | swap(arr, left, lt) 62 | 63 | return [lt - 1, gt] 64 | } 65 | 66 | quickSort.sortName = "快速排序(三路)" 67 | 68 | module.exports = quickSort 69 | -------------------------------------------------------------------------------- /排序/快速排序-空间.js: -------------------------------------------------------------------------------- 1 | function quickSort(arr) { 2 | if (arr.length === 1 || arr.length === 0) { 3 | return arr 4 | } 5 | const left = [] 6 | 7 | const right = [] 8 | const ref = arr[0] 9 | 10 | for (let i = 1; i < arr.length; i++) { 11 | let num = arr[i] 12 | if (num < ref) { 13 | left.push(num) 14 | } else { 15 | right.push(num) 16 | } 17 | } 18 | 19 | return [...quickSort(left), ref, ...quickSort(right)] 20 | } 21 | 22 | quickSort.sortName = "快速排序(空间版)" 23 | 24 | // module.exports = quickSort 25 | -------------------------------------------------------------------------------- /排序/快速排序.js: -------------------------------------------------------------------------------- 1 | const swap = require("../工具/交换") 2 | const random = require("../工具/随机值") 3 | 4 | /** 5 | * 6 | * @param {number[]} arr 7 | */ 8 | function quickSort(arr) { 9 | _quickSort(arr, 0, arr.length - 1) 10 | return arr 11 | } 12 | 13 | /** 14 | * 对 arr[l...r] 部分进行快速排序 15 | * @param {number[]} arr 16 | * @param {number} l 左边界 17 | * @param {number} r 右边界 18 | */ 19 | function _quickSort(arr, l, r) { 20 | if (l >= r) { 21 | return 22 | } 23 | let p = partition(arr, l, r) 24 | _quickSort(arr, l, p - 1) 25 | _quickSort(arr, p + 1, r) 26 | } 27 | 28 | /** 29 | * 对 arr[l...r] 部分进行快速排序 30 | * @param {number[]} arr 31 | * @param {number} l 左边界 32 | * @param {number} r 右边界 33 | * @returns {number} 返回索引值p,使得arr[l...p-1] < arr[p] < arr[p+1...r] 34 | */ 35 | function partition(arr, left, right) { 36 | // 取一个基准值 取随机值 37 | let rand = random(left, right) 38 | swap(arr, left, rand) 39 | let pivot = arr[left] 40 | 41 | // arr[left+1...index] < pivot, arr[index+1...i) > pivot 42 | let index = left 43 | for (let i = left + 1; i <= right; i++) { 44 | let num = arr[i] 45 | if (num < pivot) { 46 | // 如果当前值小于基准值的话,就交换到index + 1的位置去。 47 | // 扩充了index的范围 [index...], pivot, [...right] 48 | swap(arr, index + 1, i) 49 | index++ 50 | } 51 | } 52 | 53 | swap(arr, left, index) 54 | return index 55 | } 56 | 57 | quickSort.sortName = "快速排序" 58 | 59 | // module.exports = quickSort 60 | -------------------------------------------------------------------------------- /排序/选择排序.js: -------------------------------------------------------------------------------- 1 | const swap = require("../工具/交换") 2 | 3 | function selectSort(arr) { 4 | for (let i = 0; i < arr.length; i++) { 5 | let min = i 6 | for (let j = i + 1; j < arr.length; j++) { 7 | if (arr[j] < arr[min]) { 8 | min = j 9 | } 10 | } 11 | swap(arr, i, min) 12 | } 13 | return arr 14 | } 15 | 16 | selectSort.sortName = "选择排序" 17 | 18 | // module.exports = selectSort 19 | -------------------------------------------------------------------------------- /最长公共前缀.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[]} strs 3 | * @return {string} 4 | */ 5 | let longestCommonPrefix = function (strs) { 6 | let point = 0 7 | let common = "" 8 | let shortestStr 9 | let getShortestStr = false 10 | 11 | if (strs.length === 1) { 12 | return strs[0] 13 | } 14 | if (strs.length === 0) { 15 | return '' 16 | } 17 | 18 | while (1) { 19 | for (let i = 0; i < strs.length; i++) { 20 | let str = strs[i] 21 | if (i > 0 && str[point] !== strs[i - 1][point]) { 22 | return common 23 | } 24 | 25 | // 寻找最短的字符串 26 | if (!getShortestStr) { 27 | if (!shortestStr) { 28 | shortestStr = str 29 | } else if (str.length < shortestStr.length) { 30 | shortestStr = str 31 | } 32 | } 33 | } 34 | 35 | common += strs[0][point] || '' 36 | point++ 37 | if (point > shortestStr.length) { 38 | return common 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /有序数组求交集.js: -------------------------------------------------------------------------------- 1 | let a = [] 2 | 3 | for (let index = 0; index < 500; index++) { 4 | a.push(i) 5 | } 6 | 7 | for (let i = 16; i < 10000; i++) { 8 | a.push(i) 9 | } 10 | 11 | let b = [] 12 | 13 | for (let index = 0; index < 500; index++) { 14 | b.push(i) 15 | } 16 | 17 | for (let i = 10001; i < 50000; i++) { 18 | b.push(i) 19 | } 20 | 21 | function mapIntersection(arr1, arr2) { 22 | console.time() 23 | let map = new Map() 24 | for (let i = 0; i < arr1.length; i++) { 25 | map.set(arr1[i], true) 26 | } 27 | 28 | let result = [] 29 | for (let i = 0; i < arr2.length; i++) { 30 | let val = arr2[i] 31 | if (map.get(val)) { 32 | result.push(val) 33 | } 34 | } 35 | console.timeEnd() 36 | return result 37 | } 38 | 39 | console.log("mapIntersection", mapIntersection(a, b)) 40 | 41 | function pointIntersection(arr1, arr2) { 42 | console.time() 43 | let i = 0 44 | let j = 0 45 | 46 | let l1 = arr1.length 47 | let l2 = arr2.length 48 | 49 | let result = [] 50 | while (i < l1 && j < l2) { 51 | let val1 = arr1[i] 52 | let val2 = arr2[j] 53 | let val1Last = arr1[i - 1] 54 | let val2Last = arr2[j - 1] 55 | 56 | if (val1 > val2) { 57 | i++ 58 | } 59 | 60 | if (val1 === val2) { 61 | result.push(val1) 62 | i++ 63 | j++ 64 | } 65 | 66 | if (val1 < val2) { 67 | j++ 68 | } 69 | 70 | if (val1 > val2Last || val2 > val1Last) { 71 | console.timeEnd() 72 | return result 73 | } 74 | } 75 | console.timeEnd() 76 | return result 77 | } 78 | 79 | console.log('pointIntersection', pointIntersection(a, b)) -------------------------------------------------------------------------------- /有序数组的单个元素-540.js: -------------------------------------------------------------------------------- 1 | /** 2 | 540. 有序数组中的单一元素 3 | 给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。 4 | 5 | 示例 1: 6 | 7 | 输入: [1,1,2,3,3,4,4,8,8] 8 | 输出: 2 9 | 示例 2: 10 | 11 | 输入: [3,3,7,7,10,11,11] 12 | 输出: 10 13 | 注意: 您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。 14 | */ 15 | 16 | function singleNonDuplicate(nums) { 17 | let len = nums.length 18 | 19 | let lo = 0 20 | let hi = len - 1 21 | 22 | while (lo < hi) { 23 | let mid = lo + Math.floor((hi - lo) / 2) 24 | let midVal = nums[mid] 25 | 26 | // 被mid后的数组长度是奇数还是偶数 27 | let halvesAreEven = (hi - mid) % 2 == 0; 28 | if (midVal === nums[mid - 1]) { 29 | if (halvesAreEven) { 30 | // [0, 1, 1, 2, 2] 31 | // mid 32 | // 显然左边是奇数 [0] hi是mid往左两位 33 | hi = mid - 2 34 | }else { 35 | // [0, 0, 1, 1, 2, 2, 3] 36 | // mid 37 | // 显然右边是奇数 [2, 2, 3] lo是mid往右一位 38 | lo = mid + 1 39 | } 40 | 41 | }else if (midVal === nums[mid + 1]) { 42 | if (halvesAreEven) { 43 | // [0, 0, 1, 1, 2] 44 | // mid 45 | // 右边是奇数 [2] lo是mid右移动两位 46 | lo = mid + 2 47 | }else { 48 | // [0, 0, 1, 2, 2, 3, 3] 49 | // mid 50 | // 左边是奇数 [0, 0, 1] hi是mid左移一位 51 | hi = mid - 1 52 | } 53 | } else { 54 | return midVal 55 | } 56 | } 57 | return nums[lo] 58 | } 59 | 60 | 61 | console.log(singleNonDuplicate([3,3,7,7,10,11,11])) -------------------------------------------------------------------------------- /有序矩阵中第K小的元素-378.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} matrix 3 | * @param {number} k 4 | * @return {number} 5 | */ 6 | let kthSmallest = function (matrix, k) { 7 | let maxY = matrix.length 8 | let sorted = [] 9 | let points = [] 10 | for (let i = 0; i < maxY; i++) { 11 | points[i] = 0 12 | } 13 | 14 | while (sorted.length < k) { 15 | let min = Infinity 16 | let minY 17 | for (let y = 0; y < maxY; y++) { 18 | let point = points[y] 19 | let num = matrix[y][point] 20 | if (num < min) { 21 | min = num 22 | minY = y 23 | } 24 | } 25 | sorted.push(min) 26 | // 选中最小项的x指针右移 27 | points[minY]++ 28 | } 29 | 30 | return sorted[sorted.length - 1] 31 | }; -------------------------------------------------------------------------------- /标题分组.js: -------------------------------------------------------------------------------- 1 | /** 2 | 需要输出 3 | [{ 4 | "name": "h3" 5 | }, { 6 | "name": "h2", 7 | "child": [{ 8 | "name": "h3" 9 | }] 10 | }, { 11 | "name": "h1", 12 | "child": [{ 13 | "name": "h2", 14 | "child": [{ 15 | "name": "h3" 16 | }, { 17 | "name": "h3" 18 | }] 19 | }, { 20 | "name": "h2", 21 | "child": [{ 22 | "name": "h3" 23 | }] 24 | }] 25 | }, { 26 | "name": "h1", 27 | "child": [{ 28 | "name": "h2", 29 | "child": [{ 30 | "name": "h4" 31 | }] 32 | }, { 33 | "name": "h2", 34 | "child": [{ 35 | "name": "h3" 36 | }] 37 | }] 38 | }] 39 | */ 40 | 41 | let list = [ 42 | 'h3', 43 | 'h2', 'h3', 44 | 'h1', 'h2', 'h3', 'h3', 'h2', 'h3', 45 | 'h1', 'h2', 'h4', 'h2', 'h3' 46 | ] 47 | 48 | function makeTree(arr) { 49 | let tree = []; 50 | let max = Infinity 51 | let prev = {}; 52 | 53 | arr.forEach((title) => { 54 | let level = Number(title[1]); 55 | if (level <= max) { 56 | let node = { 57 | name: title, 58 | }; 59 | tree.push(node); 60 | prev.level = level; 61 | prev.node = node; 62 | max = level 63 | } else if (level === prev.level) { 64 | // 等级相同的话 上级节点的child继续增加子节点 65 | prev = prev.prev; 66 | pushNodeAndAdvanceQueue(); 67 | } else if (level > prev.level) { 68 | pushNodeAndAdvanceQueue(); 69 | } else { 70 | while (level <= prev.level) { 71 | // 向上回溯,找到平级的再上一层node 72 | prev = prev.prev; 73 | } 74 | pushNodeAndAdvanceQueue(); 75 | } 76 | 77 | function pushNodeAndAdvanceQueue() { 78 | let node = { 79 | name: title, 80 | }; 81 | if (!prev.node.child) { 82 | let child = []; 83 | prev.node.child = child; 84 | } 85 | prev.node.child.push(node); 86 | let next = { 87 | level, 88 | node, 89 | prev, 90 | }; 91 | prev = next; 92 | } 93 | }); 94 | 95 | return tree; 96 | } 97 | 98 | console.log(makeTree(list)); 99 | 100 | /** 101 | * 思路 102 | * 103 | * 利用链表来向上回溯合适的父节点 104 | * 利用max变量记录当前分组的最大标题,如果比max还大,就需要新开一个分组了。 105 | */ 106 | -------------------------------------------------------------------------------- /栈和队列/二叉树的前序遍历-144.js: -------------------------------------------------------------------------------- 1 | const TreeNode = require("../工具/二叉树") 2 | 3 | /** 4 | * @param {TreeNode} root 5 | * @return {number[]} 6 | */ 7 | let preorderTraversal = function (root) { 8 | let res = [] 9 | let stack = [ 10 | { 11 | type: "go", 12 | node: root, 13 | }, 14 | ] 15 | 16 | while (stack.length) { 17 | let { type, node } = stack.pop() 18 | 19 | if (!node) continue 20 | 21 | if (type === "print") { 22 | res.push(node.val) 23 | } 24 | 25 | if (type === "go") { 26 | stack.push({ type: "print", node }) 27 | 28 | if (node.right) { 29 | stack.push({ type: "go", node: node.right }) 30 | } 31 | 32 | if (node.left) { 33 | stack.push({ type: "go", node: node.left }) 34 | } 35 | } 36 | } 37 | 38 | return res 39 | } 40 | 41 | const tree = new TreeNode(1) 42 | tree.left = new TreeNode(4) 43 | tree.left.left = new TreeNode(5) 44 | tree.right = new TreeNode(6) 45 | tree.right.right = new TreeNode(7) 46 | 47 | console.log(preorderTraversal(tree)) 48 | -------------------------------------------------------------------------------- /栈和队列/从上到下打印二叉树III-面试题32.js: -------------------------------------------------------------------------------- 1 | const TreeNode = require("../工具/二叉树") 2 | 3 | let levelOrder = function (root) { 4 | let queue = [root] 5 | let res = [] 6 | if (!root) return res 7 | let level = 0 8 | while (queue.length) { 9 | level++ 10 | let subRes = [] 11 | let len = queue.length 12 | let shouldReverse = level % 2 === 0 13 | 14 | for (let i = 0; i < len; i++) { 15 | let node = queue.shift() 16 | subRes.push(node.val) 17 | if (node.left) { 18 | queue.push(node.left) 19 | } 20 | if (node.right) { 21 | queue.push(node.right) 22 | } 23 | } 24 | // 偶数行 把结果子数组reverse即可 25 | if (shouldReverse) { 26 | subRes.reverse() 27 | } 28 | res.push(subRes) 29 | } 30 | return res 31 | } 32 | 33 | let tree = new TreeNode(1) 34 | tree.left = new TreeNode(2) 35 | tree.left.left = new TreeNode(4) 36 | tree.right = new TreeNode(3) 37 | tree.right.right = new TreeNode(5) 38 | 39 | console.log(levelOrder(tree)) 40 | -------------------------------------------------------------------------------- /栈和队列/有效的括号-20.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {boolean} 4 | */ 5 | let isValid = function (s) { 6 | let sl = s.length 7 | if (sl % 2 !== 0) return false 8 | let leftToRight = { 9 | "{": "}", 10 | "[": "]", 11 | "(": ")", 12 | } 13 | // 建立一个反向的 value -> key 映射表 14 | let rightToLeft = createReversedMap(leftToRight) 15 | // 用来匹配左右括号的栈 16 | let stack = [] 17 | 18 | for (let i = 0; i < s.length; i++) { 19 | let bracket = s[i] 20 | // 左括号 放进栈中 21 | if (leftToRight[bracket]) { 22 | stack.push(bracket) 23 | } else { 24 | let needLeftBracket = rightToLeft[bracket] 25 | // 左右括号都不是 直接失败 26 | if (!needLeftBracket) { 27 | return false 28 | } 29 | 30 | // 栈中取出最后一个括号 如果不是需要的那个左括号 就失败 31 | let lastBracket = stack.pop() 32 | if (needLeftBracket !== lastBracket) { 33 | return false 34 | } 35 | } 36 | } 37 | 38 | if (stack.length) { 39 | return false 40 | } 41 | return true 42 | } 43 | 44 | function createReversedMap(map) { 45 | return Object.keys(map).reduce((prev, key) => { 46 | const value = map[key] 47 | prev[value] = key 48 | return prev 49 | }, {}) 50 | } 51 | 52 | console.log(isValid('({})[()]')) -------------------------------------------------------------------------------- /栈和队列/简化路径-71.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} path 3 | * @return {string} 4 | */ 5 | let simplifyPath = function (path) { 6 | let tokens = path.split("/") 7 | let stack = [] 8 | 9 | for (let index = 0; index < tokens.length; index++) { 10 | let token = tokens[index] 11 | if (token === "..") { 12 | if (stack.length > 0) { 13 | stack.pop() 14 | } 15 | } else if (!(token === '') && !(token === '.')) { 16 | stack.push(token) 17 | } 18 | } 19 | 20 | let res = '/' 21 | 22 | for (let token of stack) { 23 | res += `${token}/` 24 | } 25 | 26 | if (res !== '/') { 27 | res = res.substr(0, res.length - 1) 28 | } 29 | 30 | return res 31 | } 32 | 33 | console.log(simplifyPath("/home/")) 34 | -------------------------------------------------------------------------------- /栈和队列/逆波兰表达式求值-150.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[]} tokens 3 | * @return {number} 4 | */ 5 | let opMap = { 6 | "+": (a, b) => b + a, 7 | "-": (a, b) => b - a, 8 | "*": (a, b) => b * a, 9 | "/": (a, b) => parseInt(b / a, 10), 10 | } 11 | 12 | let evalRPN = function (tokens) { 13 | let stack = [] 14 | for (let token of tokens) { 15 | let op = opMap[token] 16 | if (op) { 17 | let a = parseInt(stack.pop()) 18 | let b = parseInt(stack.pop()) 19 | let res = op(a, b) 20 | stack.push(res) 21 | } else { 22 | stack.push(token) 23 | } 24 | } 25 | return stack[0] 26 | } 27 | 28 | console.log(evalRPN(["-8","23","8","-","9","23","-","-","*","33","-8","/","+","38","-14","-","-","-7","32","-19","-","11","+","+","+","14","22","-","-","27","-9","-","+","31","+","-12","-11","-","-","14","+","30","+","37","30","-","+","-9","+","7","-","37","+","-5","13","/","-","19","-2","-19","12","+","-","23","+","-","-19","-","+","6","+","-17","+","17","+","5","36","+","-10","+","+","23","-8","-","-","18","-","31","-16","-","+","34","+","-6","+","24","-","22","-","-8","-","28","+","-12","+","39","28","-7","+","+","-14","5","+","5","+","10","+","+","+","-18","*","10","+","-5","11","-","6","+","-","-12","31","+","+","30","29","-","-","39","+","13","-8","-5","+","-","26","19","-","*","-","10","-","-20","5","+","+","0","-","28","-","19","/","28","+","-18","-","28","20","+","-5","-19","+","+","-","-12","-","3","-","6","-15","+","4","-","-","38","+","-9","-","38","-","12","-20","-","10","5","-15","-","-","-","+","-11","+","5","+","2","-","28","+","-9","-11","-","+","37","-","-17","31","-","2","+","+","-16","-12","-","-","12","+","34","-","15","+","8","+","17","-","2","-","33","+","-5","+","14","+","29","-","33","23","+","26","30","-","+","+","39","+","9","24","-","-","20","15","+","-","24","+","37","-","30","-1","-","+","34","+","-13","-","23","15","-","-","-5","-8","8","30","35","-9","22","+","-","-","36","-1","+","5","-","-","+","25","-","+","27","-","16","+","+","+","39","-","15","-","-3","+","5","-6","-","+","-6","-15","-7","-","+","/","13","-","18","+","4","+","29","+","-17","0","-6","-20","-17","+","12","-","+","-","+","+","-10","22","+","+","-11","-","-2","38","-","-","-6","+","0","-","-10","+","-4","-10","+","-","0","-","31","30","-","37","5","+","+","+","-15","+","38","4","-","-16","-17","+","+","+","38","-","27","-19","/","12","+","/"])) 29 | -------------------------------------------------------------------------------- /格雷编码.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @return {number[]} 4 | */ 5 | let grayCode = function(n) { 6 | // 用来算输入为n的格雷编码序列 7 | let make = n => { 8 | if (n === 1) { 9 | return ["0", "1"] 10 | } else { 11 | let prev = make(n - 1) 12 | let result = [] 13 | let max = Math.pow(2, n) - 1 14 | for (let i = 0, len = prev.length; i < len; i++) { 15 | result[i] = `0${prev[i]}` 16 | result[max - i] = `1${prev[i]}` 17 | } 18 | return result 19 | } 20 | } 21 | return make(n).map(v => parseInt(v, 2)) 22 | } 23 | console.log(grayCode(3)) 24 | -------------------------------------------------------------------------------- /滑动窗口/找到字符串中所有字母异位词-438.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string} p 4 | * @return {number[]} 5 | */ 6 | let findAnagrams = function (s, p) { 7 | let targetMap = makeCountMap(p) 8 | let sl = s.length 9 | let pl = p.length 10 | // [left,...right] 滑动窗口 11 | let left = 0 12 | let right = pl - 1 13 | let windowMap = makeCountMap(s.substring(left, right + 1)) 14 | let res = [] 15 | 16 | while (left <= sl - pl && right < sl) { 17 | if (isAnagrams(windowMap, targetMap)) { 18 | res.push(left) 19 | } 20 | windowMap[s[left]]-- 21 | right++ 22 | left++ 23 | addCountToMap(windowMap, s[right]) 24 | } 25 | 26 | return res 27 | } 28 | 29 | let isAnagrams = function (windowMap, targetMap) { 30 | let targetKeys = Object.keys(targetMap) 31 | for (let targetKey of targetKeys) { 32 | if ( 33 | !windowMap[targetKey] || 34 | windowMap[targetKey] !== targetMap[targetKey] 35 | ) { 36 | return false 37 | } 38 | } 39 | return true 40 | } 41 | 42 | function addCountToMap(map, str) { 43 | if (!map[str]) { 44 | map[str] = 1 45 | } else { 46 | map[str]++ 47 | } 48 | } 49 | 50 | function makeCountMap(strs) { 51 | let map = {} 52 | for (let i = 0; i < strs.length; i++) { 53 | let letter = strs[i] 54 | addCountToMap(map, letter) 55 | } 56 | return map 57 | } 58 | 59 | console.log(findAnagrams("abab", "ab")) 60 | -------------------------------------------------------------------------------- /滑动窗口/无重复字符的最长子串-3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | let lengthOfLongestSubstring = function (str) { 6 | let n = str.length 7 | // 滑动窗口为s[left...right] 8 | let left = 0 9 | let right = -1 10 | let freqMap = {} // 记录当前子串中下标对应的出现频率 11 | let max = 0 // 找到的满足条件子串的最长长度 12 | 13 | while (left < n) { 14 | let nextLetter = str[right + 1] 15 | if (!freqMap[nextLetter] && nextLetter !== undefined) { 16 | freqMap[nextLetter] = 1 17 | right++ 18 | } else { 19 | freqMap[str[left]] = 0 20 | left++ 21 | } 22 | max = Math.max(max, right - left + 1) 23 | } 24 | 25 | return max 26 | } 27 | 28 | console.log(lengthOfLongestSubstring("pwwkew")) -------------------------------------------------------------------------------- /滑动窗口/最小覆盖子串.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string} t 4 | * @return {string} 5 | */ 6 | let minWindow = function (s, t) { 7 | // 先制定目标 根据t字符串统计出每个字符应该出现的个数 8 | let targetMap = makeCountMap(t) 9 | 10 | let sl = s.length 11 | let tl = t.length 12 | let left = 0 // 左边界 13 | let right = -1 // 右边界 14 | let countMap = {} // 当前窗口子串中 每个字符出现的次数 15 | let min = "" // 当前计算出的最小子串 16 | 17 | // 循环终止条件是两者有一者超出边界 18 | while (left <= sl - tl && right <= sl) { 19 | // 和 targetMap 对比出现次数 确定是否满足条件 20 | let isValid = true 21 | Object.keys(targetMap).forEach((key) => { 22 | let targetCount = targetMap[key] 23 | let count = countMap[key] 24 | if (!count || count < targetCount) { 25 | isValid = false 26 | } 27 | }) 28 | 29 | if (isValid) { 30 | // 如果满足 记录当前的子串 并且左边界右移 31 | let currentValidLength = right - left + 1 32 | if (currentValidLength < min.length || min === "") { 33 | min = s.substring(left, right + 1) 34 | } 35 | // 也要把map里对应的项去掉 36 | countMap[s[left]]-- 37 | left++ 38 | } else { 39 | // 否则右边界右移 40 | addCountToMap(countMap, s[right + 1]) 41 | right++ 42 | } 43 | } 44 | 45 | return min 46 | } 47 | 48 | function addCountToMap(map, str) { 49 | if (!map[str]) { 50 | map[str] = 1 51 | } else { 52 | map[str]++ 53 | } 54 | } 55 | 56 | function makeCountMap(strs) { 57 | let map = {} 58 | for (let i = 0; i < strs.length; i++) { 59 | let letter = strs[i] 60 | addCountToMap(map, letter) 61 | } 62 | return map 63 | } 64 | 65 | console.log(minWindow("aa", "a")) 66 | -------------------------------------------------------------------------------- /滑动窗口/滑动窗口的最大值-面试题59 - I.js: -------------------------------------------------------------------------------- 1 | let maxSlidingWindow = function (nums, k) { 2 | if (k === 0 || !nums.length) { 3 | return [] 4 | } 5 | let left = 0 6 | let right = k - 1 7 | let res = [findMax(nums, left, right)] 8 | 9 | while (right < nums.length - 1) { 10 | right++ 11 | left++ 12 | res.push(findMax(nums, left, right)) 13 | } 14 | 15 | return res 16 | } 17 | 18 | function findMax(nums, left, right) { 19 | let max = -Infinity 20 | for (let i = left; i <= right; i++) { 21 | max = Math.max(max, nums[i]) 22 | } 23 | return max 24 | } -------------------------------------------------------------------------------- /滑动窗口/长度最小的子数组-209.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} s 3 | * @param {number[]} nums 4 | * @return {number} 5 | */ 6 | let minSubArrayLen = function (s, nums) { 7 | let n = nums.length 8 | // 定义[i,...j]滑动窗口 取这个窗口里的和 9 | let i = 0 10 | let j = -1 11 | 12 | let sum = 0 13 | let res = Infinity 14 | 15 | while (i < n) { 16 | if (sum < s) { 17 | sum += nums[++j] 18 | } else { 19 | sum -= nums[i] 20 | i++ 21 | } 22 | 23 | if (sum >= s) { 24 | res = Math.min(res, j - i + 1) 25 | } 26 | } 27 | return res === Infinity ? 0 : res 28 | } 29 | 30 | console.log(minSubArrayLen(7, [2, 3, 1, 2, 4, 3])) 31 | -------------------------------------------------------------------------------- /种花问题.js: -------------------------------------------------------------------------------- 1 | /** 2 | 605. 种花问题 3 | 假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。 4 | 5 | 给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。 6 | 7 | 示例 1: 8 | 9 | 输入: flowerbed = [1,0,0,0,1], n = 1 10 | 输出: True 11 | 示例 2: 12 | 13 | 输入: flowerbed = [1,0,0,0,1], n = 2 14 | 输出: False 15 | 注意: 16 | 17 | 数组内已种好的花不会违反种植规则。 18 | 输入的数组长度范围为 [1, 20000]。 19 | n 是非负整数,且不会超过输入数组的大小。 20 | */ 21 | /** 22 | * @param {number[]} flowerbed 23 | * @param {number} n 24 | * @return {boolean} 25 | */ 26 | let canPlaceFlowers = function(flowerbed, n) { 27 | // empty代表连续命中0的次数 命中3次就说明要回退一个种花 28 | let empty = 1 29 | let count = 0 30 | for (let i = 0; i < flowerbed.length; i++) { 31 | let has = flowerbed[i] 32 | if (!has) { 33 | empty++ 34 | // 连续三次空白 35 | if (empty === 3) { 36 | // 回退一格种花 37 | flowerbed[i - 1] = 1 38 | count++ 39 | // 因为这次没命中 所以又从1开始算 40 | empty = 1 41 | if (count >= n) { 42 | return true 43 | } 44 | } 45 | if (i === flowerbed.length - 1 && empty === 2) { 46 | count++ 47 | } 48 | } else { 49 | empty = 0 50 | } 51 | } 52 | return count >= n 53 | } 54 | 55 | console.log(canPlaceFlowers([1, 0, 0, 0, 1, 0, 0], 2)) 56 | -------------------------------------------------------------------------------- /移动零-283.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 3 | 4 | 示例: 5 | 6 | 输入: [0,1,0,3,12] 7 | 输出: [1,3,12,0,0] 8 | 说明: 9 | 10 | 必须在原数组上操作,不能拷贝额外的数组。 11 | 尽量减少操作次数。 12 | 13 | 来源:力扣(LeetCode) 14 | 链接:https://leetcode-cn.com/problems/move-zeroes 15 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 16 | */ 17 | 18 | /** 19 | [0, 1, 0, 3, 12] 20 | i 21 | j 22 | [0, 1, 0, 3, 12] 23 | i 24 | j 25 | i位置不等于0 i和j交换 j++ 26 | 27 | [1, 0, 0, 3, 12] 28 | i 29 | j 30 | i位置等于0 继续前进 31 | [1, 3, 0, 0, 12] 32 | i 33 | j 34 | i位置不等于0 i和j交换 j++ 35 | [1, 3, 12, 0, 0] 36 | i 37 | j 38 | i位置不等于0 i和j交换 j++ 39 | 40 | 此时数组的0全部在最右边了 41 | */ 42 | 43 | /** 44 | * @param {number[]} nums 45 | * @return {void} Do not return anything, modify nums in-place instead. 46 | */ 47 | let moveZeroes = function(nums) { 48 | let j = 0 49 | for (let i = 0; i < nums.length; i++) { 50 | if (nums[i] !== 0) { 51 | let temp = nums[j] 52 | nums[j] = nums[i] 53 | nums[i] = temp 54 | j++ 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /移除元素.js: -------------------------------------------------------------------------------- 1 | /** 2 | 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 3 | 4 | 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 5 | 6 | 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 7 | 8 |   9 | 10 | 示例 1: 11 | 12 | 给定 nums = [3,2,2,3], val = 3, 13 | 14 | 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 15 | 16 | 你不需要考虑数组中超出新长度后面的元素。 17 | 示例 2: 18 | 19 | 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 20 | 21 | 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。 22 | 23 | 注意这五个元素可为任意顺序。 24 | 25 | 你不需要考虑数组中超出新长度后面的元素。 26 | 27 | 来源:力扣(LeetCode) 28 | 链接:https://leetcode-cn.com/problems/remove-element 29 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 30 | */ 31 | 32 | let removeElement = function (nums, val) { 33 | let i = 0; 34 | let j = 0; 35 | 36 | while (i < nums.length) { 37 | let num = nums[i]; 38 | if (num !== val) { 39 | nums[j] = num; 40 | j++; 41 | } 42 | 43 | i++; 44 | } 45 | 46 | return j; 47 | }; 48 | 49 | removeElement([3, 2, 2, 3], 3); 50 | 51 | /** 52 | * 这题用的是双指针法,慢指针指向待替换的位置,等到读取到非目标值的时候,就替换到那个位置,然后把慢指针前进。 53 | */ 54 | -------------------------------------------------------------------------------- /翻转二叉树.js: -------------------------------------------------------------------------------- 1 | /** 2 | 翻转一棵二叉树。 3 | 4 | 示例: 5 | 6 | 输入: 7 | 8 | 4 9 | / \ 10 | 2 7 11 | / \ / \ 12 | 1 3 6 9 13 | 输出: 14 | 15 | 4 16 | / \ 17 | 7 2 18 | / \ / \ 19 | 9 6 3 1 20 | 备注: 21 | 这个问题是受到 Max Howell 的 原问题 启发的 : 22 | 23 | 谷歌:我们90%的工程师使用您编写的软件(Homebrew),但是您却无法在面试时在白板上写出翻转二叉树这道题,这太糟糕了。 24 | */ 25 | 26 | /** 27 | * @param {*} root 28 | * 遍历法,先循环找出所有需要翻转的treeNode,然后遍历翻转即可 29 | */ 30 | let invertTree = function(root) { 31 | let queue = []; 32 | function traverse(tree) { 33 | if (!tree) return; 34 | queue.push(tree); 35 | traverse(tree.left); 36 | traverse(tree.right); 37 | } 38 | traverse(root); 39 | 40 | while (queue.length) { 41 | let node = queue.pop(); 42 | let temp = node.right; 43 | node.right = node.left; 44 | node.left = temp; 45 | } 46 | return root; 47 | }; 48 | -------------------------------------------------------------------------------- /贪心算法/分发饼干-455.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} g 3 | * @param {number[]} s 4 | * @return {number} 5 | */ 6 | let findContentChildren = function (g, s) { 7 | g.sort((a, b) => a - b) 8 | s.sort((a, b) => a - b) 9 | 10 | let i = 0 11 | let j = 0 12 | 13 | let count = 0 14 | while (j < s.length && i < g.length) { 15 | let need = g[i] 16 | let cookie = s[j] 17 | 18 | if (cookie >= need) { 19 | count++ 20 | i++ 21 | j++ 22 | } else { 23 | j++ 24 | } 25 | } 26 | 27 | return count 28 | } 29 | -------------------------------------------------------------------------------- /贪心算法/判断子序列-392.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string} t 4 | * @return {boolean} 5 | */ 6 | let isSubsequence = function (s, t) { 7 | let i = 0 8 | let sl = s.length 9 | if (!sl) { 10 | return true 11 | } 12 | 13 | for (let j = 0; j < t.length; j++) { 14 | let target = s[i] 15 | let cur = t[j] 16 | if (cur === target) { 17 | i++ 18 | if (i === sl) { 19 | return true 20 | } 21 | } 22 | } 23 | 24 | return false 25 | } 26 | /** 27 | * @param {string} s 28 | * @param {string} t 29 | * @return {boolean} 30 | */ 31 | let isSubsequence = function (s, t) { 32 | let sl = s.length 33 | if (!sl) { 34 | return true 35 | } 36 | 37 | let i = 0 38 | for (let j = 0; j < t.length; j++) { 39 | let target = s[i] 40 | let cur = t[j] 41 | if (cur === target) { 42 | i++ 43 | if (i === sl) { 44 | return true 45 | } 46 | } 47 | } 48 | 49 | return false 50 | } 51 | -------------------------------------------------------------------------------- /递归与回溯/N 皇后-51.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @return {string[][]} 4 | */ 5 | let solveNQueens = function (n) { 6 | let res = [] 7 | 8 | // 已摆放皇后的的列下标 9 | let columns = [] 10 | // 已摆放皇后的对角线1下标 左下 -> 右上 11 | // 计算某个坐标是否在这个对角线的方式是「行下标 + 列下标」是否相等 12 | let dia1 = [] 13 | // 已摆放皇后的对角线2下标 左上 -> 右下 14 | // 计算某个坐标是否在这个对角线的方式是「行下标 - 列下标」是否相等 15 | let dia2 = [] 16 | 17 | // 尝试在一个n皇后问题中 摆放第index行内的皇后位置 18 | let putQueen = (rowIndex, row) => { 19 | if (rowIndex === n) { 20 | res.push(generateBoard(row)) 21 | return 22 | } 23 | 24 | // 尝试摆第index行的皇后 尝试[0, n-1]列 25 | for (let columnIndex = 0; columnIndex < n; columnIndex++) { 26 | // 在列上不冲突 27 | let columnNotConflict = !columns[columnIndex] 28 | // 在对角线1上不冲突 29 | let dia1NotConflict = !dia1[rowIndex + columnIndex] 30 | // 在对角线2上不冲突 31 | let dia2NotConflict = !dia2[rowIndex - columnIndex] 32 | 33 | if (columnNotConflict && dia1NotConflict && dia2NotConflict) { 34 | 35 | columns[columnIndex] = true 36 | dia1[rowIndex + columnIndex] = true 37 | dia2[rowIndex - columnIndex] = true 38 | 39 | putQueen(rowIndex + 1, row.concat(columnIndex)) 40 | 41 | columns[columnIndex] = false 42 | dia1[rowIndex + columnIndex] = false 43 | dia2[rowIndex - columnIndex] = false 44 | } 45 | } 46 | } 47 | 48 | putQueen(0, []) 49 | 50 | return res 51 | } 52 | 53 | function generateBoard(row) { 54 | let n = row.length 55 | let res = [] 56 | for(let y = 0; y < n; y++) { 57 | let cur = '' 58 | for (let x = 0; x < n; x++) { 59 | if (x === row[y]) { 60 | cur += 'Q' 61 | }else { 62 | cur += '.' 63 | } 64 | } 65 | res.push(cur) 66 | } 67 | return res 68 | } 69 | 70 | console.log(solveNQueens(4)) 71 | -------------------------------------------------------------------------------- /递归与回溯/不同路径 III-980.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} grid 3 | * @return {number} 4 | */ 5 | let uniquePathsIII = function (grid) { 6 | let maxY = grid.length 7 | if (!maxY) return 0 8 | let maxX = grid[0].length 9 | 10 | let validCellsCount = 0 11 | let entry 12 | let visited = [] 13 | for (let y = 0; y < maxY; y++) { 14 | visited[y] = [] 15 | for (let x = 0; x < maxX; x++) { 16 | let val = grid[y][x] 17 | if (val === 0) { 18 | validCellsCount++ 19 | } 20 | if (val === 1) { 21 | entry = [y, x] 22 | } 23 | } 24 | } 25 | 26 | let isValid = (y, x) => { 27 | return ( 28 | y >= 0 && 29 | y < maxY && 30 | x >= 0 && 31 | x < maxX && 32 | !visited[y][x] && 33 | grid[y][x] !== -1 34 | ) 35 | } 36 | 37 | let dirs = [ 38 | [-1, 0], 39 | [1, 0], 40 | [0, -1], 41 | [0, 1], 42 | ] 43 | let res = 0 44 | 45 | let dfs = (y, x, passCount) => { 46 | let val = grid[y][x] 47 | if (val === 2) { 48 | if (passCount === validCellsCount) { 49 | res++ 50 | } 51 | return 52 | } else if (val === 0) { 53 | passCount += 1 54 | } 55 | 56 | for (let [diffY, diffX] of dirs) { 57 | let nextY = y + diffY 58 | let nextX = x + diffX 59 | if (isValid(nextY, nextX)) { 60 | visited[nextY][nextX] = true 61 | dfs(nextY, nextX, passCount) 62 | visited[nextY][nextX] = false 63 | } 64 | } 65 | } 66 | 67 | let [entryY, entryX] = entry 68 | visited[entryY][entryX] = true 69 | dfs(entryY, entryX, 0) 70 | 71 | return res 72 | } 73 | -------------------------------------------------------------------------------- /递归与回溯/二进制手表-401.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} num 3 | * @return {string[]} 4 | */ 5 | let HOURS = [1, 2, 4, 8] 6 | let MINUTES = [1, 2, 4, 8, 16, 32] 7 | 8 | let readBinaryWatch = function (num) { 9 | let res = [] 10 | 11 | let combine = (arr, num) => { 12 | if (num === 0) { 13 | return [0] 14 | } 15 | let res = [] 16 | let helper = (start, prevCount, prevSum) => { 17 | if (prevCount === num) { 18 | res.push(prevSum) 19 | return 20 | } 21 | 22 | for (let i = start; i < arr.length; i++) { 23 | let cur = arr[i] 24 | helper(i + 1, prevCount + 1, prevSum + cur) 25 | } 26 | } 27 | helper(0, 0, 0) 28 | return res 29 | } 30 | 31 | for (let i = 0; i <= num; i++) { 32 | let hours = combine(HOURS, i) 33 | let minutes = combine(MINUTES, num - i) 34 | 35 | for (let hour of hours) { 36 | if (hour > 11) continue 37 | for (let minute of minutes) { 38 | if (minute > 59) { 39 | continue 40 | } 41 | res.push(`${hour}:${padLeft(minute)}`) 42 | } 43 | } 44 | } 45 | return res 46 | } 47 | 48 | function padLeft(num) { 49 | let str = num.toString() 50 | if (str.length === 1) { 51 | str = `0${str}` 52 | } 53 | return str 54 | } -------------------------------------------------------------------------------- /递归与回溯/全排列 II-47.js: -------------------------------------------------------------------------------- 1 | 2 | let uniqSymbol = 'X' 3 | 4 | let permuteUnique = function (nums) { 5 | let n = nums.length 6 | if (n === 1) { 7 | return [nums] 8 | } 9 | let permuteSet = (nums) => { 10 | let n = nums.length 11 | if (n === 0) { 12 | return new Set() 13 | } 14 | if (n === 1) { 15 | return new Set(nums) 16 | } 17 | 18 | let res = new Set() 19 | for (let i = 0; i < n; i++) { 20 | let use = nums[i] 21 | if (use === undefined) { 22 | continue 23 | } 24 | let rest = nums.slice(0, i).concat(nums.slice(i + 1, n)) 25 | let restPermuteds = permuteSet(rest) 26 | restPermuteds.forEach((restPermuted) => { 27 | res.add(`${use}${uniqSymbol}${restPermuted}`) 28 | }) 29 | } 30 | 31 | return res 32 | } 33 | 34 | let permuted = permuteSet(nums) 35 | 36 | return Array.from(permuted).map((val) => val.split(uniqSymbol).map(Number)) 37 | } 38 | 39 | console.log(permuteUnique([-1,2,-1,2,1,-1,2,1])) 40 | -------------------------------------------------------------------------------- /递归与回溯/全排列-46.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[][]} 4 | */ 5 | let permute = function (nums) { 6 | let n = nums.length 7 | if (n === 1) { 8 | return [nums] 9 | } 10 | 11 | let res = [] 12 | for (let i = 0; i < n; i++) { 13 | let use = nums[i] 14 | let rest = nums.slice(0, i).concat(nums.slice(i + 1, n)) 15 | let restPermuteds = permute(rest) 16 | for (let restPermuted of restPermuteds) { 17 | res.push(restPermuted.concat(use)) 18 | } 19 | } 20 | 21 | return res 22 | } 23 | 24 | console.log(permute([1, 2, 3])) 25 | -------------------------------------------------------------------------------- /递归与回溯/分割回文串-131.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {string[][]} 4 | */ 5 | 6 | let partition = function (s) { 7 | let n = s.length 8 | let ret = [] 9 | let find = function (start, prev) { 10 | // 最少分割一个字符 最多分割到末尾前一位 11 | for (let i = 1; i <= n; i++) { 12 | let end = start + i 13 | let cur = s.substring(start, end) 14 | if (cur) { 15 | let res = prev.concat(cur) 16 | if (isPalindrome(cur)) { 17 | if (end === n) { 18 | ret.push(res) 19 | } else { 20 | find(start + i, res) 21 | } 22 | } 23 | } 24 | } 25 | } 26 | find(0, []) 27 | return ret 28 | } 29 | 30 | function isPalindrome(s) { 31 | if (!s) { 32 | return false 33 | } 34 | let i = 0 35 | let j = s.length - 1 36 | 37 | while (i < j) { 38 | let head = s[i] 39 | let tail = s[j] 40 | 41 | if (head !== tail) { 42 | return false 43 | } else { 44 | i++ 45 | j-- 46 | } 47 | } 48 | return true 49 | } 50 | 51 | console.log(partition("aab")) 52 | -------------------------------------------------------------------------------- /递归与回溯/单词搜索 II-212.Trie版本.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Initialize your data structure here. 3 | */ 4 | var Trie = function () { 5 | this.root = new TrieNode() 6 | } 7 | 8 | var TrieNode = function () { 9 | this.children = new Map() 10 | this.isEnd = false 11 | } 12 | 13 | /** 14 | * Inserts a word into the trie. 15 | * @param {string} word 16 | * @return {void} 17 | */ 18 | Trie.prototype.insert = function (word) { 19 | let node = this.root 20 | 21 | for (let i = 0; i < word.length; i++) { 22 | let { children } = node 23 | let trieNode = children.get(word[i]) 24 | if (!trieNode) { 25 | trieNode = new TrieNode() 26 | children.set(word[i], trieNode) 27 | } 28 | node = trieNode 29 | 30 | if (i === word.length - 1) { 31 | node.isEnd = true 32 | node.word = word 33 | } 34 | } 35 | } 36 | 37 | let dirs = [ 38 | [0, 1], 39 | [0, -1], 40 | [-1, 0], 41 | [1, 0], 42 | ] 43 | /** 44 | * @param {character[][]} board 45 | * @param {string[]} words 46 | * @return {string[]} 47 | */ 48 | let findWords = function (board, words) { 49 | let maxY = board.length 50 | if (!maxY) return [] 51 | let maxX = board[0].length 52 | 53 | let rootTrie = new Trie() 54 | for (let word of words) { 55 | rootTrie.insert(word) 56 | } 57 | 58 | // 记录已访问过的二维下标 59 | let visited = [] 60 | for (let y = 0; y < maxY; y++) { 61 | visited[y] = [] 62 | } 63 | 64 | let isValid = (x, y) => { 65 | return x >= 0 && x < maxX && y >= 0 && y < maxY && !visited[y][x] 66 | } 67 | 68 | // 返回结果 69 | let res = [] 70 | 71 | let dfs = (x, y, trie) => { 72 | let char = board[y][x] 73 | let children = trie.children 74 | let nextTrie = children && children.get(char) 75 | if (nextTrie) { 76 | if (nextTrie.word) { 77 | res.push(nextTrie.word) 78 | nextTrie.word = null 79 | } else { 80 | visited[y][x] = true 81 | for (let dir of dirs) { 82 | let [offsetY, offsetX] = dir 83 | let nextY = y + offsetY 84 | let nextX = x + offsetX 85 | if (isValid(nextX, nextY)) { 86 | dfs(nextX, nextY, nextTrie) 87 | } 88 | } 89 | visited[y][x] = false 90 | } 91 | } 92 | } 93 | 94 | for (let y = 0; y < maxY; y++) { 95 | for (let x = 0; x < maxX; x++) { 96 | if (y === 1 && x === 0) debugger 97 | dfs(x, y, rootTrie.root) 98 | } 99 | } 100 | 101 | return Array.from(new Set(res)) 102 | } 103 | 104 | console.log( 105 | findWords( 106 | [ 107 | ["a", "b"], 108 | ["a", "a"], 109 | ], 110 | ["aaba"] 111 | ) 112 | ) 113 | -------------------------------------------------------------------------------- /递归与回溯/单词搜索 II-212.js: -------------------------------------------------------------------------------- 1 | let dirs = [ 2 | [0, 1], 3 | [0, -1], 4 | [-1, 0], 5 | [1, 0], 6 | ] 7 | /** 8 | * @param {character[][]} board 9 | * @param {string[]} words 10 | * @return {string[]} 11 | */ 12 | let findWords = function (board, words) { 13 | let maxY = board.length 14 | if (!maxY) return [] 15 | let maxX = board[0].length 16 | 17 | // 记录已访问过的二维下标 18 | let visited = [] 19 | for (let y = 0; y < maxY; y++) { 20 | visited[y] = [] 21 | } 22 | 23 | let isValid = (x, y) => { 24 | return x >= 0 && x < maxX && y >= 0 && y < maxY && !visited[y][x] 25 | } 26 | 27 | // 返回结果 28 | let res = [] 29 | 30 | let dfs = (x, y, word, index) => { 31 | let char = board[y][x] 32 | let targetChar = word[index] 33 | if (char === targetChar) { 34 | if (index === word.length - 1) { 35 | res.push(word) 36 | } else { 37 | visited[y][x] = true 38 | for (let dir of dirs) { 39 | let [offsetY, offsetX] = dir 40 | let nextY = y + offsetY 41 | let nextX = x + offsetX 42 | if (isValid(nextX, nextY)) { 43 | dfs(nextX, nextY, word, index + 1) 44 | } 45 | } 46 | visited[y][x] = false 47 | } 48 | } 49 | } 50 | 51 | let prefixMap = new Map() 52 | for (let y = 0; y < maxY; y++) { 53 | for (let x = 0; x < maxX; x++) { 54 | let prefix = board[y][x] 55 | let pos = prefixMap.get(prefix) 56 | if (!pos) { 57 | prefixMap.set(prefix, [[y, x]]) 58 | } else { 59 | prefixMap.set(prefix, [...pos, [y, x]]) 60 | } 61 | } 62 | } 63 | 64 | let find = (word) => { 65 | let head = word[0] 66 | let pos = prefixMap.get(head) 67 | if (pos) { 68 | pos.forEach(([y, x]) => { 69 | dfs(x, y, word, 0) 70 | }) 71 | } 72 | } 73 | 74 | words.forEach(find) 75 | 76 | return Array.from(new Set(res)) 77 | } 78 | 79 | console.log( 80 | findWords( 81 | [ 82 | ["a", "b"], 83 | ["a", "a"], 84 | ], 85 | ["aba", "baa", "bab", "aaab", "aaa", "aaaa", "aaba"] 86 | ) 87 | ) 88 | -------------------------------------------------------------------------------- /递归与回溯/单词搜索-79.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {character[][]} board 3 | * @param {string} word 4 | * @return {boolean} 5 | */ 6 | let directions = [[-1, 0], [1, 0], [0, -1], [0, 1]] // 左 右 上 下 7 | 8 | let exist = function (board, word) { 9 | let maxY = board.length 10 | if (!maxY) return false 11 | let maxX = board[0].length 12 | 13 | // 二维数组记录已访问过的元素 14 | let visited = new Array(maxY) 15 | for (let y = 0; y < visited.length; y++) { 16 | visited[y] = new Array(maxX) 17 | } 18 | 19 | let inArea = (x, y) => { 20 | return x >= 0 && x < maxX && y >= 0 && y < maxY 21 | } 22 | 23 | let search = (startX, startY, wordIndex) => { 24 | // 当前起始字符不匹配 直接失败 25 | let curCell = board[startY][startX] 26 | let curChar = word[wordIndex] 27 | if (curCell !== curChar) { 28 | return false 29 | } 30 | 31 | // 如果递归到最后一位字符 就直接返回最后一位字符是否匹配成功 32 | if (wordIndex === word.length - 1) { 33 | return curChar === curChar 34 | } 35 | 36 | // 进一步递归 先记录为已访问元素 防止递归的时候重复访问 37 | visited[startY][startX] = true 38 | 39 | for (let direction of directions) { 40 | let [x, y] = direction 41 | let nextX = startX + x 42 | let nextY = startY + y 43 | 44 | // 需要保证未越界且未被访问过 45 | if (inArea(nextX, nextY) && !visited[nextY][nextX]) { 46 | if (search(nextX, nextY, wordIndex + 1)) { 47 | return true 48 | } 49 | } 50 | } 51 | // 重置已访问标记位 52 | visited[startY][startX] = false 53 | } 54 | 55 | for (let y = 0; y < maxY; y++) { 56 | for (let x = 0; x < maxX; x++) { 57 | if (search(x, y, 0)) { 58 | return true 59 | } 60 | } 61 | } 62 | 63 | return false 64 | }; -------------------------------------------------------------------------------- /递归与回溯/和为K的子数组-560.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} k 4 | * @return {number} 5 | */ 6 | let subarraySum = function (nums, k) { 7 | let n = nums.length 8 | if (!n) { 9 | return 0 10 | } 11 | 12 | let res = 0 13 | for (let i = 0; i < n; i++) { 14 | let total = 0 15 | for (let j = i; j < n; j++) { 16 | total += nums[j] 17 | if (total === k) { 18 | res += 1 19 | } 20 | } 21 | } 22 | return res 23 | }; -------------------------------------------------------------------------------- /递归与回溯/复原ip地址-93.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {string[]} 4 | */ 5 | let restoreIpAddresses = function (s) { 6 | let res = [] 7 | let findPos = (start, prev, used) => { 8 | if (used === 3) { 9 | let rest = s.substr(start) 10 | // 点全部用光后 剩余字符依然是一个合格的ip chunk 11 | // 就视为一个答案 放入数组 12 | if (isValidChunk(rest)) { 13 | res.push(prev.concat(rest).join(".")) 14 | } 15 | return 16 | } 17 | 18 | for (let i = 1; i <= 3; i++) { 19 | let end = start + i 20 | let cur = s.substring(start, end) 21 | if (isValidChunk(cur)) { 22 | findPos(end, prev.concat(cur), used + 1) 23 | } 24 | } 25 | } 26 | 27 | findPos(0, [], 0) 28 | 29 | return res 30 | } 31 | 32 | function isValidChunk(str) { 33 | let strLen = str.length 34 | if (strLen === 0) { 35 | return false 36 | } 37 | // 开头是0的话 只能整个字符串只有一位0才行 38 | if (str[0] === "0") { 39 | return strLen === 1 40 | } 41 | let num = Number(str) 42 | return num <= 255 43 | } 44 | -------------------------------------------------------------------------------- /递归与回溯/子集 II-90.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[][]} 4 | */ 5 | var subsetsWithDup = function (nums) { 6 | let n = nums.length 7 | let res = [] 8 | if (!n) { 9 | return res 10 | } 11 | 12 | nums.sort() 13 | 14 | let used = {} 15 | 16 | let helper = (start, prev, target) => { 17 | if (prev.length === target) { 18 | let key = genKey(prev) 19 | if (!used[key]) { 20 | res.push(prev) 21 | used[key] = true 22 | } 23 | return 24 | } 25 | 26 | for (let i = start; i < n; i++) { 27 | let rest = n - i 28 | let need = target - prev.length 29 | if (rest < need) { 30 | continue 31 | } 32 | helper(i + 1, prev.concat(nums[i]), target) 33 | } 34 | } 35 | 36 | for (let i = 1; i <= n; i++) { 37 | helper(0, [], i) 38 | } 39 | 40 | return [[], ...res] 41 | }; 42 | 43 | function genKey(arr) { 44 | return arr.join('~') 45 | } -------------------------------------------------------------------------------- /递归与回溯/子集-78.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[][]} 4 | */ 5 | let subsets = function (nums) { 6 | let res = [] 7 | let n = nums.length 8 | if (n === 0) { 9 | return res 10 | } 11 | 12 | let helper = (start, prev, targetLength) => { 13 | if (start > n) { 14 | return 15 | } 16 | if (prev.length === targetLength) { 17 | res.push(prev) 18 | return 19 | } 20 | 21 | for (let i = start; i < n; i++) { 22 | let cur = nums[i] 23 | helper(i + 1, prev.concat(cur), targetLength) 24 | } 25 | } 26 | 27 | for (let j = 1; j <= nums.length; j++) { 28 | helper(0, [], j) 29 | } 30 | 31 | return [[], ...res] 32 | } 33 | -------------------------------------------------------------------------------- /递归与回溯/字母大小写全排列-784.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} S 3 | * @return {string[]} 4 | */ 5 | let letterCasePermutation = function (S) { 6 | let res = [] 7 | 8 | let helper = (prev, rest) => { 9 | if (prev.length === S.length) { 10 | res.push(prev) 11 | return 12 | } 13 | 14 | let char = rest[0] 15 | let word1 = prev + char 16 | let nextRest = rest.substring(1) 17 | 18 | if (!isNaN(Number(char))) { 19 | helper(word1, nextRest) 20 | return 21 | } else { 22 | let upperChar = char.toUpperCase() 23 | let char2 = upperChar === char ? char.toLowerCase() : upperChar 24 | let word2 = prev + char2 25 | 26 | helper(word1, nextRest) 27 | helper(word2, nextRest) 28 | } 29 | } 30 | 31 | helper('', S) 32 | 33 | return res 34 | }; -------------------------------------------------------------------------------- /递归与回溯/字母迭代组合器-1286.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} characters 3 | * @param {number} combinationLength 4 | */ 5 | let CombinationIterator = function (characters, combinationLength) { 6 | let cl = characters.length 7 | let res = [] 8 | let helper = (start, prev) => { 9 | let pl = prev.length 10 | for (let i = start; i < cl; i++) { 11 | let rest = cl - i 12 | let diff = combinationLength - pl 13 | if (diff > rest) { 14 | return 15 | } 16 | let next = prev + characters[i] 17 | if (next.length == combinationLength) { 18 | res.push(next) 19 | } else { 20 | helper(i + 1, next) 21 | } 22 | } 23 | } 24 | helper(0, "") 25 | this.res = res.sort((a, b) => (a > b ? 1 : -1)) 26 | this.point = 0 27 | } 28 | 29 | /** 30 | * @return {string} 31 | */ 32 | CombinationIterator.prototype.next = function () { 33 | if (!this.hasNext()) { 34 | return undefined 35 | } 36 | let val = this.res[this.point++] 37 | return val 38 | } 39 | 40 | /** 41 | * @return {boolean} 42 | */ 43 | CombinationIterator.prototype.hasNext = function () { 44 | return this.point !== this.res.length 45 | } 46 | 47 | /** 48 | * Your CombinationIterator object will be instantiated and called as such: 49 | * var obj = new CombinationIterator(characters, combinationLength) 50 | * var param_1 = obj.next() 51 | * var param_2 = obj.hasNext() 52 | */ 53 | -------------------------------------------------------------------------------- /递归与回溯/括号-面试题 08.09.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @return {string[]} 4 | */ 5 | let generateParenthesis = function (n) { 6 | let res = [] 7 | 8 | let helper = (left, right, prev) => { 9 | if (left < 0 || right < 0 || right < left) { 10 | return 11 | } 12 | if (left === 0 && right === 0) { 13 | res.push(prev) 14 | return 15 | } 16 | 17 | helper(left - 1, right, prev + "(") 18 | helper(left, right - 1, prev + ")") 19 | } 20 | 21 | helper(n, n, '') 22 | return res 23 | }; -------------------------------------------------------------------------------- /递归与回溯/活字印刷-1079.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} tiles 3 | * @return {number} 4 | */ 5 | let numTilePossibilities = function (tiles) { 6 | let res = new Set() 7 | 8 | let helper = (prev, rest) => { 9 | if (prev.length > 0) { 10 | res.add(prev) 11 | } 12 | 13 | for (let i = 0; i < rest.length; i++) { 14 | let char = rest[i] 15 | let cur = prev + char 16 | helper(cur, rest.substring(0, i) + rest.substring(i + 1)) 17 | } 18 | } 19 | 20 | helper('', tiles) 21 | 22 | return res.size 23 | }; -------------------------------------------------------------------------------- /递归与回溯/电话号码的字母组合-17.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} digits 3 | * @return {string[]} 4 | */ 5 | let letterMap = [ 6 | " ", //0 7 | "", //1 8 | "abc", //2 9 | "def", //3 10 | "ghi", //4 11 | "jkl", //5 12 | "mno", //6 13 | "pqrs", //7 14 | "tuv", //8 15 | "wxyz", //9 16 | ] 17 | 18 | let letterCombinations = function (digits) { 19 | let res = [] 20 | 21 | if (digits === "") { 22 | return res 23 | } 24 | 25 | /** 26 | * 27 | * @param {number} index 当前处理到的下标位置 28 | * @param {string} str 当前已经凑成的字符串 29 | */ 30 | let findCombinations = (index, str) => { 31 | if (digits.length === index) { 32 | res.push(str) 33 | return 34 | } 35 | 36 | let char = digits[index] // 数字 37 | let letters = letterMap[Number(char)] // 数字对应的字母 38 | 39 | for (let i = 0; i < letters.length; i++) { 40 | let letter = letters[i] 41 | findCombinations(index + 1, `${str}${letter}`) 42 | } 43 | } 44 | 45 | findCombinations(0, "") 46 | 47 | return res 48 | } 49 | -------------------------------------------------------------------------------- /递归与回溯/矩阵置零-73.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} matrix 3 | * @return {void} Do not return anything, modify matrix in-place instead. 4 | */ 5 | let setZeroes = function (matrix) { 6 | let maxY = matrix.length 7 | if (!maxY) return 8 | let maxX = matrix[0].length 9 | 10 | let handledRows = [] 11 | let handledColumns = [] 12 | 13 | let zeros = [] 14 | for (let y = 0; y < maxY; y++) { 15 | for (let x = 0; x < maxX; x++) { 16 | let val = matrix[y][x] 17 | if (val === 0) { 18 | zeros.push([y, x]) 19 | } 20 | } 21 | } 22 | 23 | for (let [y, x] of zeros) { 24 | if (!handledRows[x]) { 25 | for (let i = 0; i < maxY; i++) { 26 | matrix[i][x] = 0 27 | } 28 | handledRows[x] = true 29 | } 30 | if (!handledColumns[y]) { 31 | for (let i = 0; i < maxX; i++) { 32 | matrix[y][i] = 0 33 | } 34 | handledColumns[y] = true 35 | } 36 | } 37 | }; -------------------------------------------------------------------------------- /递归与回溯/组合-77.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @param {number} k 4 | * @return {number[][]} 5 | */ 6 | let combine = function (n, k) { 7 | let ret = [] 8 | 9 | let helper = (start, prev) => { 10 | let len = prev.length 11 | if (len === k) { 12 | ret.push(prev) 13 | return 14 | } 15 | 16 | if (start > n) { 17 | return 18 | } 19 | 20 | // 还有 rest 个位置待填补 21 | let rest = k - prev.length 22 | for (let i = start; i <= n; i++) { 23 | if (n - i + 1 < rest) { 24 | continue 25 | } 26 | helper(i + 1, prev.concat(i)) 27 | } 28 | } 29 | helper(1, []) 30 | return ret 31 | } 32 | 33 | console.log(combine(4, 2)) 34 | -------------------------------------------------------------------------------- /递归与回溯/组合总和-39.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} candidates 3 | * @param {number} target 4 | * @return {number[][]} 5 | */ 6 | let combinationSum = function (candidates, target) { 7 | let res = [] 8 | 9 | let helper = (start, prevSum, prevArr) => { 10 | // 由于全是正整数 所以一旦和大于目标值了 直接结束本次递归即可。 11 | if (prevSum > target) { 12 | return 13 | } 14 | // 目标值达成 15 | if (prevSum === target) { 16 | res.push(prevArr) 17 | return 18 | } 19 | 20 | for (let i = start; i < candidates.length; i++) { 21 | // 这里还是继续从start本身开始 因为多个重复值是允许的 22 | let cur = candidates[i] 23 | let sum = prevSum + cur 24 | let arr = prevArr.concat(cur) 25 | helper(i, sum, arr) 26 | } 27 | } 28 | 29 | helper(0, 0, []) 30 | 31 | return res 32 | } -------------------------------------------------------------------------------- /递归与回溯/组合总和-40.js: -------------------------------------------------------------------------------- 1 | let genKey = (arr) => arr.join("~") 2 | /** 3 | * @param {number[]} candidates 4 | * @param {number} target 5 | * @return {number[][]} 6 | */ 7 | let combinationSum2 = function (candidates, target) { 8 | let res = [] 9 | 10 | if (!candidates.length) { 11 | return res 12 | } 13 | 14 | candidates.sort() 15 | 16 | let used = {} 17 | 18 | let helper = (start, prevSum, prevArr) => { 19 | // 由于全是正整数 所以一旦和大于目标值了 直接结束本次递归即可。 20 | if (prevSum > target) { 21 | return 22 | } 23 | // 目标值达成 24 | if (prevSum === target) { 25 | let key = genKey(prevArr) 26 | if (!used[key]) { 27 | res.push(prevArr) 28 | used[key] = true 29 | } 30 | return 31 | } 32 | 33 | for (let i = start; i < candidates.length; i++) { 34 | // 这里还是继续从start本身开始 因为多个重复值是允许的 35 | let cur = candidates[i] 36 | let sum = prevSum + cur 37 | let arr = prevArr.concat(cur) 38 | helper(i + 1, sum, arr) 39 | } 40 | } 41 | 42 | helper(0, 0, []) 43 | 44 | return res 45 | } 46 | 47 | console.log(combinationSum2([2, 1, 2, 1, 3], 6)) 48 | -------------------------------------------------------------------------------- /递归与回溯/螺旋矩阵-54.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} matrix 3 | * @return {number[]} 4 | */ 5 | let spiralOrder = function (matrix) { 6 | let maxY = matrix.length 7 | if (!maxY) return [] 8 | let maxX = matrix[0].length 9 | 10 | // 记录一个 visited 数组 11 | // 按照 右 -> 下 -> 左 -> 上 的方向不断前进 12 | // 直到遇到边界或者已经访问过的元素 则切换成下一个方向 13 | let dirs = [ 14 | [0, 1], // 右 15 | [1, 0], // 下 16 | [0, -1], // 左 17 | [-1, 0], // 上 18 | ] 19 | 20 | let currentDirIndex = 0 21 | 22 | let visited = [] 23 | for (let y = 0; y < maxY; y++) { 24 | visited[y] = [] 25 | } 26 | let isValid = (y, x) => { 27 | return y >= 0 && y < maxY && x >= 0 && x < maxX && !visited[y][x] 28 | } 29 | 30 | let targetLength = maxY * maxX 31 | let res = [] 32 | 33 | let helper = (y, x) => { 34 | let val = matrix[y][x] 35 | res.push(val) 36 | 37 | if (res.length === targetLength) { 38 | return res 39 | } 40 | 41 | visited[y][x] = true 42 | let [diffY, diffX] = dirs[currentDirIndex % 4] 43 | let nextY = y + diffY 44 | let nextX = x + diffX 45 | if (!isValid(nextY, nextX)) { 46 | [diffY, diffX] = dirs[++currentDirIndex % 4] 47 | nextY = y + diffY 48 | nextX = x + diffX 49 | } 50 | helper(nextY, nextX) 51 | } 52 | 53 | helper(0, 0) 54 | 55 | return res 56 | }; -------------------------------------------------------------------------------- /递归与回溯/解数独-37.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {character[][]} board 3 | * @return {void} Do not return anything, modify board in-place instead. 4 | */ 5 | let solveSudoku = function (board) { 6 | let rows = initTwoDimensionalArray(9) 7 | let columns = initTwoDimensionalArray(9) 8 | let grids = initTwoDimensionalArray(3) 9 | 10 | // 待处理下标队列 第一次扫描的时候记录下来 11 | let pending = [] 12 | 13 | for (let y = 0; y < 9; y++) { 14 | for (let x = 0; x < 9; x++) { 15 | let cell = board[y][x] 16 | if (cell === ".") { 17 | // 待填充的数独格子 记录在队列中 18 | pending.push([x, y]) 19 | continue 20 | } 21 | // 记录下当前下标 22 | recordCell(x, y, cell) 23 | } 24 | } 25 | 26 | let helper = (startPendingIndex) => { 27 | if (startPendingIndex === pending.length) { 28 | return true 29 | } 30 | 31 | let [x, y] = pending[startPendingIndex] 32 | 33 | for (let i = 1; i <= 9; i++) { 34 | let cur = i.toString() 35 | if (isValid(x, y, cur)) { 36 | board[y][x] = cur 37 | recordCell(x, y, cur) 38 | if (helper(startPendingIndex + 1)) { 39 | return true 40 | } else { 41 | board[y][x] = "." 42 | restoreCell(x, y, cur) 43 | } 44 | } 45 | } 46 | } 47 | 48 | helper(0) 49 | 50 | function recordCell(x, y, cell) { 51 | rows[y][cell] = true 52 | columns[x][cell] = true 53 | let [gridX, gridY] = findGridIndex(x, y) 54 | if (!grids[gridY][gridX]) { 55 | grids[gridY][gridX] = new Map() 56 | } 57 | grids[gridY][gridX].set(cell, true) 58 | } 59 | 60 | function restoreCell(x, y, cell) { 61 | rows[y][cell] = false 62 | columns[x][cell] = false 63 | let [gridX, gridY] = findGridIndex(x, y) 64 | grids[gridY][gridX].set(cell, false) 65 | } 66 | 67 | function isValid(x, y, cell) { 68 | let isYConflict = rows[y][cell] 69 | let isXConflict = columns[x][cell] 70 | let [gridX, gridY] = findGridIndex(x, y) 71 | let grid = grids[gridY][gridX] 72 | let isGridConflict = grid && grid.get(cell) 73 | return !isYConflict && !isXConflict && !isGridConflict 74 | } 75 | } 76 | 77 | function initTwoDimensionalArray(length) { 78 | let ret = [] 79 | for (let i = 0; i < length; i++) { 80 | ret.push([]) 81 | } 82 | return ret 83 | } 84 | 85 | function findGridIndex(x, y) { 86 | return [Math.floor(x / 3), Math.floor(y / 3)] 87 | } 88 | -------------------------------------------------------------------------------- /递归与回溯/跳水板-面试题 16.11.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} shorter 3 | * @param {number} longer 4 | * @param {number} k 5 | * @return {number[]} 6 | */ 7 | let divingBoard = function (shorter, longer, k) { 8 | if (k === 0) { 9 | return [] 10 | } 11 | if (shorter === longer) { 12 | return [k * shorter] 13 | } 14 | 15 | let res = [] 16 | for (let i = 0; i <= k; i++) { 17 | let longCount = i 18 | let shortCount = k - i 19 | res.push(shortCount * shorter + longCount * longer) 20 | } 21 | 22 | return res 23 | }; -------------------------------------------------------------------------------- /递归与回溯/递归矩阵-59.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @return {number[][]} 4 | */ 5 | let generateMatrix = function (n) { 6 | // 记录一个 visited 数组 7 | // 按照 右 -> 下 -> 左 -> 上 的方向不断前进 8 | // 直到遇到边界或者已经访问过的元素 则切换成下一个方向 9 | let dirs = [ 10 | [0, 1], // 右 11 | [1, 0], // 下 12 | [0, -1], // 左 13 | [-1, 0], // 上 14 | ] 15 | 16 | let currentDirIndex = 0 17 | 18 | let visited = [] 19 | for (let y = 0; y < n; y++) { 20 | visited[y] = [] 21 | } 22 | let isValid = (y, x) => { 23 | return y >= 0 && y < n && x >= 0 && x < n && !visited[y][x] 24 | } 25 | 26 | let targetLength = n * n 27 | let res = [] 28 | for (let y = 0; y < n; y++) { 29 | res[y] = [] 30 | } 31 | 32 | let helper = (y, x, num) => { 33 | res[y][x] = num 34 | 35 | if (num === targetLength) { 36 | return res 37 | } 38 | 39 | visited[y][x] = true 40 | let [diffY, diffX] = dirs[currentDirIndex % 4] 41 | let nextY = y + diffY 42 | let nextX = x + diffX 43 | if (!isValid(nextY, nextX)) { 44 | ;[diffY, diffX] = dirs[++currentDirIndex % 4] 45 | nextY = y + diffY 46 | nextX = x + diffX 47 | } 48 | helper(nextY, nextX, num + 1) 49 | } 50 | 51 | helper(0, 0, 1) 52 | 53 | return res 54 | } 55 | -------------------------------------------------------------------------------- /递归与回溯/顺次数-1291.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} low 3 | * @param {number} high 4 | * @return {number[]} 5 | */ 6 | let sequentialDigits = function (low, high) { 7 | let lowLen = low.toString().length 8 | let highLen = high.toString().length 9 | let lens = [] 10 | for (let i = lowLen; i <= highLen; i++) { 11 | lens.push(i) 12 | } 13 | 14 | let res = [] 15 | for (let i = 0; i < lens.length; i++) { 16 | let len = lens[i] 17 | for (let start = 1; start <= 10 - len; start++) { 18 | let num = start 19 | 20 | for (let n = start + 1; n < start + len; n++) { 21 | num = 10 * num + n 22 | } 23 | if (num <= high && num >= low) { 24 | res.push(num) 25 | } 26 | } 27 | } 28 | 29 | return res 30 | } -------------------------------------------------------------------------------- /递归与回溯/黄金矿工-1219.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} grid 3 | * @return {number} 4 | */ 5 | let getMaximumGold = function (grid) { 6 | let maxY = grid.length 7 | if (maxY === 0) { 8 | return 0 9 | } 10 | let maxX = grid[0].length 11 | 12 | let visited = [] 13 | for (let y = 0; y < maxY; y++) { 14 | visited[y] = [] 15 | } 16 | 17 | let dirs = [ 18 | [1, 0], 19 | [-1, 0], 20 | [0, -1], 21 | [0, 1], 22 | ] 23 | 24 | // 验证是否能走入这个格子 25 | // 1. 范围不能越界 26 | // 2. 本轮递归中未访问过 27 | // 3. 格子的值不能为 0 28 | let isValid = (y, x) => { 29 | return ( 30 | y >= 0 && 31 | y < maxY && 32 | x >= 0 && 33 | x < maxX && 34 | grid[y][x] !== 0 && 35 | !visited[y][x] 36 | ) 37 | } 38 | 39 | let maxGold = 0 40 | let helper = (y, x, prevGold) => { 41 | let val = grid[y][x] 42 | let curGold = prevGold + val 43 | if (curGold === 0) { 44 | return 45 | } 46 | 47 | for (let dir of dirs) { 48 | let [diffY, diffX] = dir 49 | let nextY = y + diffY 50 | let nextX = x + diffX 51 | if (isValid(nextY, nextX)) { 52 | visited[y][x] = true 53 | helper(nextY, nextX, curGold) 54 | visited[y][x] = false 55 | } else { 56 | // 走到尽头或者不符合条件的了 57 | maxGold = Math.max(maxGold, curGold) 58 | } 59 | } 60 | } 61 | 62 | for (let y = 0; y < maxY; y++) { 63 | for (let x = 0; x < maxX; x++) { 64 | helper(y, x, 0) 65 | } 66 | } 67 | 68 | return maxGold 69 | } 70 | -------------------------------------------------------------------------------- /链表/两两交换链表中的节点-24.js: -------------------------------------------------------------------------------- 1 | const ListNode = require("../工具/链表") 2 | 3 | let swapPairs = function (head) { 4 | let helper = function (node) { 5 | let tempNext = node.next 6 | if (tempNext) { 7 | let tempNextNext = node.next.next 8 | node.next.next = node 9 | if (tempNextNext) { 10 | node.next = helper(tempNextNext) 11 | }else { 12 | node.next = null 13 | } 14 | } 15 | return tempNext 16 | } 17 | 18 | let res = helper(head) 19 | 20 | return res 21 | } 22 | 23 | let node = new ListNode(1) 24 | node.next = new ListNode(2) 25 | node.next.next = new ListNode(3) 26 | node.next.next.next = new ListNode(4) 27 | 28 | console.log(swapPairs(node)) 29 | -------------------------------------------------------------------------------- /链表/两数相加 II-445.js: -------------------------------------------------------------------------------- 1 | var reverseList = function (head) { 2 | if (!head) return null 3 | let res = null 4 | let dfs = function (node) { 5 | if (node.next) { 6 | dfs(node.next) 7 | node.next.next = node 8 | } else { 9 | res = node 10 | } 11 | } 12 | 13 | dfs(head) 14 | 15 | head.next = null 16 | 17 | return res 18 | }; 19 | 20 | var addTwoNumbers = function (l1, l2) { 21 | l1 = reverseList(l1) 22 | l2 = reverseList(l2) 23 | 24 | let i = 0 25 | let root = new ListNode() 26 | let cur = root 27 | let plus = false 28 | 29 | let traverse = (node1, node2) => { 30 | let isDouble = !!node2 31 | while (isDouble ? (node1 && node2) : node1) { 32 | cur.next = new ListNode() 33 | cur = cur.next 34 | 35 | let sum = node1.val + (plus ? 1 : 0) 36 | if (isDouble) { 37 | sum += node2.val 38 | } 39 | 40 | if (sum >= 10) { 41 | sum %= 10 42 | plus = true 43 | } else { 44 | plus = false 45 | } 46 | cur.val = sum 47 | 48 | node1 = node1.next 49 | if (isDouble) { 50 | node2 = node2.next 51 | } 52 | } 53 | 54 | if (node1) { 55 | traverse(node1) 56 | } 57 | if (node2) { 58 | traverse(node2) 59 | } 60 | } 61 | 62 | traverse(l1, l2) 63 | 64 | if (plus) { 65 | cur.next = new ListNode(1) 66 | } 67 | 68 | return reverseList(root.next) 69 | }; 70 | -------------------------------------------------------------------------------- /链表/两数相加-3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} l1 10 | * @param {ListNode} l2 11 | * @return {ListNode} 12 | */ 13 | let addTwoNumbers = function (l1, l2) { 14 | let i = 0 15 | let root = new ListNode() 16 | let cur = root 17 | let plus = false 18 | 19 | let traverse = (node1, node2) => { 20 | let isDouble = !!node2 21 | while (isDouble ? node1 && node2 : node1) { 22 | cur.next = new ListNode() 23 | cur = cur.next 24 | 25 | let sum = node1.val + (plus ? 1 : 0) 26 | if (isDouble) { 27 | sum += node2.val 28 | } 29 | 30 | if (sum >= 10) { 31 | sum %= 10 32 | plus = true 33 | } else { 34 | plus = false 35 | } 36 | cur.val = sum 37 | 38 | node1 = node1.next 39 | if (isDouble) { 40 | node2 = node2.next 41 | } 42 | } 43 | 44 | if (node1) { 45 | traverse(node1) 46 | } 47 | if (node2) { 48 | traverse(node2) 49 | } 50 | } 51 | 52 | traverse(l1, l2) 53 | 54 | if (plus) { 55 | cur.next = new ListNode(1) 56 | } 57 | 58 | return root.next 59 | } 60 | -------------------------------------------------------------------------------- /链表/删除链表中的节点-面试题18.js: -------------------------------------------------------------------------------- 1 | var deleteNode = function (head, val) { 2 | let virtual = { 3 | next: head, 4 | } 5 | let cur = virtual 6 | while (cur) { 7 | if (cur.next) { 8 | if (cur.next.val === val) { 9 | cur.next = cur.next.next 10 | } 11 | } 12 | cur = cur.next 13 | } 14 | return virtual.next 15 | } 16 | -------------------------------------------------------------------------------- /链表/删除链表的倒数第N个节点-19.js: -------------------------------------------------------------------------------- 1 | const { makeListNode } = require('../工具/链表') 2 | /** 3 | * Definition for singly-linked list. 4 | * function ListNode(val) { 5 | * this.val = val; 6 | * this.next = null; 7 | * } 8 | */ 9 | /** 10 | * @param {ListNode} head 11 | * @param {number} n 12 | * @return {ListNode} 13 | */ 14 | let removeNthFromEnd = function (head, n) { 15 | let node = head 16 | let nodes = [] 17 | do { 18 | nodes.push(node) 19 | node = node.next 20 | } while (node) 21 | 22 | const l = nodes.length 23 | const index = l - n 24 | const targetNode = nodes[index] 25 | const prevNode = nodes[index - 1] 26 | if (prevNode) { 27 | prevNode.next = targetNode.next 28 | } 29 | 30 | const tempNext = targetNode.next 31 | targetNode.next = null 32 | 33 | 34 | if (targetNode === head) { 35 | return tempNext 36 | } 37 | return head 38 | }; 39 | 40 | const node = makeListNode([1, 2, 3, 4, 5]) 41 | console.log(removeNthFromEnd(node, 5)) -------------------------------------------------------------------------------- /链表/反转链表-206.js: -------------------------------------------------------------------------------- 1 | function reverse(head) { 2 | let prev = null 3 | let cur = head 4 | while (cur) { 5 | let next = cur.next 6 | cur.next = prev 7 | 8 | prev = cur 9 | cur = next 10 | } 11 | // 返回反转后的头节点 12 | return prev 13 | } 14 | -------------------------------------------------------------------------------- /链表/反转链表II-92.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | 9 | const ListNode = require("../工具/链表") 10 | /** 11 | * @param {ListNode} head 12 | * @param {number} m 13 | * @param {number} n 14 | * @return {ListNode} 15 | */ 16 | 17 | let reverseBetween = function (head, m, n) { 18 | let i = 1 19 | let sliceStartPrev = null 20 | let sliceStart = null 21 | let sliceEnd = null 22 | let cur = head 23 | 24 | // 记录切分起点的前一个节点,和切分终点的后一个节点 25 | while (i <= n) { 26 | if (i === m - 1) { 27 | sliceStartPrev = cur 28 | } 29 | if (i === m) { 30 | sliceStart = cur 31 | } 32 | if (i === n) { 33 | sliceEnd = cur 34 | } 35 | cur = cur.next 36 | i++ 37 | } 38 | 39 | let sliceEndNext = sliceEnd.next 40 | // 切断切分终点的next 防止反转的时候反转过头 41 | sliceEnd.next = null 42 | 43 | const { head: slicedHead, tail: slicedTail } = reverse(sliceStart) 44 | if (sliceStartPrev) { 45 | // 如果需要反转的部分有前一个节点 那么只需要在中间动手脚 原样返回head节点即可 46 | sliceStartPrev.next = slicedHead 47 | } else { 48 | // 这里需要注意的是 如果没有sliceStartPrev 说明是从第一个节点就开始反转的 49 | // 那么我们需要手动调整head为反转后的head 50 | head = slicedHead 51 | } 52 | slicedTail.next = sliceEndNext 53 | 54 | return head 55 | } 56 | 57 | function reverse(head) { 58 | let prev = null 59 | let cur = head 60 | while (cur) { 61 | let next = cur.next 62 | cur.next = prev 63 | 64 | prev = cur 65 | cur = next 66 | } 67 | // 返回反转后的头尾节点 68 | return { head: prev, tail: head } 69 | } 70 | 71 | let node = new ListNode(3) 72 | node.next = new ListNode(5) 73 | 74 | console.log(JSON.stringify(reverseBetween(node, 1, 1))) 75 | -------------------------------------------------------------------------------- /链表/移除链表元素-203.js: -------------------------------------------------------------------------------- 1 | let removeElements = function (head, val) { 2 | let root = new ListNode() 3 | root.next = head 4 | let cur = root 5 | while (cur) { 6 | let next = cur.next 7 | if (!next) { 8 | break 9 | } 10 | let nextVal = next.val 11 | if (nextVal === val) { 12 | cur.next = cur.next.next 13 | } else { 14 | cur = cur.next 15 | } 16 | } 17 | return root.next 18 | } 19 | -------------------------------------------------------------------------------- /验证回文字符串2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {boolean} 4 | */ 5 | let validPalindrome = function (s) { 6 | let i = 0; 7 | let j = s.length - 1; 8 | 9 | // 两个指针往中间缩进 10 | while (i < j && s[i] === s[j]) { 11 | i++; 12 | j--; 13 | } 14 | 15 | // 遇到相对位置不相等了 判断删除一位后的情况 16 | if (isPalindrome(i + 1, j)) { 17 | return true; 18 | } 19 | if (isPalindrome(i, j - 1)) { 20 | return true; 21 | } 22 | 23 | // 工具方法,用于判断字符串是否回文 24 | function isPalindrome(st, ed) { 25 | while (st < ed) { 26 | if (s[st] !== s[ed]) { 27 | return false; 28 | } 29 | st++; 30 | ed--; 31 | } 32 | return true; 33 | } 34 | 35 | // 这样都不满足 那就不符合要求了 36 | return false; 37 | }; 38 | -------------------------------------------------------------------------------- /验证子序列.js: -------------------------------------------------------------------------------- 1 | /** 2 | 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 3 | 4 | 你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。 5 | 6 | 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。 7 | 8 | 示例 1: 9 | s = "abc", t = "ahbgdc" 10 | 返回 true. 11 | */ 12 | 13 | // let isSubsequence = function(s, t) { 14 | // let lastIndex = -1 15 | // let sliced = t 16 | // let slicedCount = 0 17 | // for (let i = 0; i < s.length; i++) { 18 | // let finded = sliced.indexOf(s[i]) 19 | // if (finded + slicedCount > lastIndex) { 20 | // lastIndex = finded + slicedCount 21 | // sliced = sliced.substr(finded + 1) 22 | // slicedCount += finded + 1 23 | // continue 24 | // }else { 25 | // return false 26 | // } 27 | // } 28 | // return true 29 | // } 30 | 31 | // 这种是速度最快的 利用indexOf的第二个参数做起点 32 | let isSubsequence = function(s, t) { 33 | let start=0 34 | for(let i=0; i < s.length; i++) { 35 | let index = t.indexOf(s[i], start) 36 | if(index===-1) return false 37 | start=index+1 38 | } 39 | return true 40 | }; 41 | 42 | let a = "leeeeetcode" 43 | let b = 'leeeeeeeeeeccccctcccoddddeeee' 44 | console.log(isSubsequence(a, b)) -------------------------------------------------------------------------------- /验证平衡二叉树.js: -------------------------------------------------------------------------------- 1 | let isValidBST = function(root) { 2 | function helper(node, lower, upper) { 3 | // 遇到空节点 直接返回true 4 | if (!node) return true 5 | 6 | let val = node.val 7 | // 如果当前的值比下边界还小 就失败 8 | if (lower !== null && val <= lower) return false 9 | // 如果当前的值比上边界还大 就失败 10 | if (upper !== null && val >= upper) return false 11 | 12 | // 以此检测右子树 分别以自身到上一次的上边界 作为范围 13 | if (node.right && !helper(node.right, val, upper)) return false 14 | // 以此检测左子树 分别以下边界到自身的值 作为范围 15 | // 也就是说 左边的子树里不能有任何超出这个范围的值 16 | if (node.left && !helper(node.left, lower, val)) return false 17 | 18 | return true 19 | } 20 | return helper(root, null, null) 21 | } 22 | 23 | console.log(isValidBST({ 24 | val: 5, 25 | left: { 26 | val: 1, 27 | right: { 28 | val: 6, 29 | left: { 30 | val: 2 31 | } 32 | } 33 | }, 34 | }) 35 | ) --------------------------------------------------------------------------------