├── .gitignore ├── 1. Two Sum.md ├── 100. Same Tree.md ├── 101. Symmetric Tree.md ├── 1022. Sum of Root To Leaf Binary Numbers.md ├── 1025. Divisor Game.md ├── 104. Maximum Depth of Binary Tree.md ├── 105. Construct Binary Tree from Preorder and Inorder Traversal.md ├── 106. Construct Binary Tree from Inorder and Postorder Traversal.md ├── 107. Binary Tree Level Order Traversal II.md ├── 108. Convert Sorted Array to Binary Search Tree.md ├── 110. Balanced Binary Tree.md ├── 111. Minimum Depth of Binary Tree.md ├── 112. Path Sum.md ├── 113. Path Sum II.md ├── 116. Populating Next Right Pointers in Each Node.md ├── 118. Pascal's Triangle.md ├── 119. Pascal's Triangle II.md ├── 121. Best Time to Buy and Sell Stock.md ├── 122. Best Time to Buy and Sell Stock II.md ├── 125. Valid Palindrome.md ├── 13. Roman to Integer.md ├── 136. Single Number.md ├── 14. Longest Common Prefix.md ├── 141. Linked List Cycle.md ├── 15. 3Sum.md ├── 155. Min Stack.md ├── 160. Intersection of Two Linked Lists.md ├── 167. Two Sum II - Input array is sorted.md ├── 169. Majority Element.md ├── 198. House Robber.md ├── 20. Valid Parentheses.md ├── 203. Remove Linked List Elements.md ├── 206. Reverse Linked List.md ├── 21. Merge Two Sorted Lists.md ├── 226. Invert Binary Tree.md ├── 234. Palindrome Linked List.md ├── 235. Lowest Common Ancestor of a Binary Search Tree.md ├── 257. Binary Tree Paths.md ├── 26. Remove Duplicates from Sorted Array.md ├── 27. Remove Element.md ├── 283. Move Zeroes.md ├── 344. Reverse String.md ├── 345. Reverse Vowels of a String.md ├── 349. Intersection of Two Arrays.md ├── 350. Intersection of Two Arrays II.md ├── 392. Is Subsequence.md ├── 394. Decode String.md ├── 404. Sum of Left Leaves.md ├── 429. N-ary Tree Level Order Traversal.md ├── 501. Find Mode in Binary Search Tree.md ├── 53. Maximum Subarray.md ├── 530. Minimum Absolute Difference in BST.md ├── 532. K-diff Pairs in an Array.md ├── 538. Convert BST to Greater Tree.md ├── 543. Diameter of Binary Tree.md ├── 559. Maximum Depth of N-ary Tree.md ├── 563. Binary Tree Tilt.md ├── 572. Subtree of Another Tree.md ├── 589. N-ary Tree Preorder Traversal.md ├── 590. N-ary Tree Postorder Traversal.md ├── 606. Construct String from Binary Tree.md ├── 617. Merge Two Binary Trees.md ├── 63. Unique Paths II.md ├── 637. Average of Levels in Binary Tree.md ├── 653. Two Sum IV - Input is a BST.md ├── 669. Trim a Binary Search Tree.md ├── 67. Add Binary.md ├── 671. Second Minimum Node In a Binary Tree.md ├── 687. Longest Univalue Path.md ├── 69. Sqrt(x).md ├── 7. Reverse Integer.md ├── 70. Climbing Stairs.md ├── 700. Search in a Binary Search Tree.md ├── 783. Minimum Distance Between BST Nodes.md ├── 83. Remove Duplicates from Sorted List.md ├── 844. Backspace String Compare.md ├── 872. Leaf-Similar Trees.md ├── 876. Middle of the Linked List.md ├── 88. Merge Sorted Array.md ├── 897. Increasing Order Search Tree.md ├── 9. Palindrome Number.md ├── 925. Long Pressed Name.md ├── 938. Range Sum of BST.md ├── 94. Binary Tree Inorder Traversal.md ├── 965. Univalued Binary Tree.md ├── 977. Squares of a Sorted Array.md ├── 98. Validate Binary Search Tree.md ├── 993. Cousins in Binary Tree.md ├── LICENSE ├── README.md ├── index.js ├── index.ts ├── package-lock.json ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | /node_modules -------------------------------------------------------------------------------- /1. Two Sum.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 4 | 5 | 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 6 | 7 | 来源:力扣(LeetCode) 8 | 链接:https://leetcode-cn.com/problems/two-sum 9 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 10 | 11 | # 解法 12 | 13 | ```javascript 14 | const twoSum = function(nums, target) { 15 | let map = {}; 16 | let res; 17 | nums.some((item, index) => { 18 | if (map[target - item] !== undefined) { 19 | res = [index, map[target - item]] 20 | return true; 21 | } 22 | map[item] = index; 23 | }); 24 | return res; 25 | }; 26 | 27 | console.log(twoSum([3, 3], 9)); 28 | ``` 29 | 30 | # 分析 31 | 32 | 可以使用双重遍历和哈希表来实现 33 | 34 | * 双重遍历:时间复杂度为 *O*(*n*2),空间复杂度为 O(1) 35 | * 哈希表:时间复杂度为 O(n) ,空间复杂度为 O(n) 36 | 37 | 由于双重遍历复杂度较高,这里使用哈希表来实现 38 | 39 | 新建一个对象作为哈希表,每次遍历数组元素时将元素作为哈希表的键,数组下标作为值 40 | 41 | 同时每次遍历时,先判断 是否存在与哈希表中,若存在则直接返回当前元素下标和 在哈希表中对应的下标,作为最终的结果 42 | 43 | 同时由于是边遍历边往哈希表放入键值对,所以比生成完整的哈希表再遍历效率更高(即遍历 2 次,复杂度: 2 O(n)) 44 | 45 | Ps:因为考虑到以下情况 [3,3] ,所以先进行判断,再往哈希表放入一个元素 -------------------------------------------------------------------------------- /100. Same Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定两个二叉树,编写一个函数来检验它们是否相同。 5 | 6 | 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 7 | 8 | 示例 1: 9 | 10 | ```javascript 11 | 输入: 1 1 12 | / \ / \ 13 | 2 3 2 3 14 | 15 | [1,2,3] [1,2,3] 16 | 17 | 输出: true 18 | ``` 19 | 20 | 示例 2: 21 | 22 | ```javascript 23 | 输入: 1 1 24 | / \ 25 | 2 2 26 | 27 | [1,2] [1,null,2] 28 | 29 | 输出: false 30 | ``` 31 | 32 | 33 | 34 | 示例 3: 35 | 36 | ```javascript 37 | 输入: 1 1 38 | / \ / \ 39 | 2 1 1 2 40 | 41 | [1,2,1], [1,1,2] 42 | 43 | 输出: false 44 | ``` 45 | 46 | 来源:力扣(LeetCode) 47 | 链接:https://leetcode-cn.com/problems/same-tree 48 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 49 | 50 | # 解法 51 | 52 | ```javascript 53 | var isSameTree = function(p, q) { 54 | if (p && !q) return false; 55 | if (!p && q) return false; 56 | if (!p && !q) return true; 57 | 58 | if (q.val !== p.val) return false; 59 | if (!isSameTree(p.left, q.left)) { 60 | return false; 61 | } 62 | return isSameTree(p.right, q.right); 63 | }; 64 | ``` 65 | 66 | # 分析 67 | 68 | 经典的树的先序遍历,遍历每一个节点,当节点不同时直接返回 false,同时开始向上弹出调用栈,最终直接返回 false 69 | 70 | 71 | 72 | 73 | 74 | # 解法2 75 | 76 | ```javascript 77 | const check = (p, q) => { 78 | if (p && !q) return false; 79 | if (!p && q) return false; 80 | if (!p && !q) return true; 81 | return q.val === p.val; 82 | }; 83 | 84 | var isSameTree = function(p, q) { 85 | let queueP = [p]; 86 | let queueQ = [q]; 87 | while (queueP.length) { 88 | let nodeP = queueP.shift(); 89 | let nodeQ = queueQ.shift(); 90 | if (!check(nodeP, nodeQ)) return false; 91 | // 当 nodeP 为 null 时,直接下一个循环 92 | // 否则 nodeP.left 会报错 93 | if (!nodeP) continue; 94 | // 如果 p,q 的左子节点不想等 95 | // 则没有必要继续对比直接返回 false(贴近最优解) 96 | if (check(nodeP.left, nodeQ.left)) { 97 | queueP.push(nodeP.left); 98 | queueQ.push(nodeQ.left); 99 | } else { 100 | return false; 101 | } 102 | if (check(nodeP.right, nodeQ.right)) { 103 | queueP.push(nodeP.right); 104 | queueQ.push(nodeQ.right); 105 | } else { 106 | return false; 107 | } 108 | } 109 | return true; 110 | }; 111 | 112 | console.log(isSameTree(tree, tree2)); 113 | 114 | ``` 115 | 116 | # 分析 117 | 118 | 经典的广度遍历,通过声明 queueP,queueQ两个队列,放入各自的根节点,随后同时出列进行判断,当值不想等时直接返回 false,当值相等时,将各自左右节点推入队列继续进行循环,直到队列清空 119 | -------------------------------------------------------------------------------- /101. Symmetric Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉树,检查它是否是镜像对称的。 5 | 6 | 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 7 | 8 | ```javascript 9 | 1 10 | / \ 11 | 2 2 12 | / \ / \ 13 | 3 4 4 3 14 | ``` 15 | 16 | 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 17 | 18 | ```javascript 19 | 1 20 | / \ 21 | 2 2 22 | \ \ 23 | 3 3 24 | ``` 25 | 26 | 说明: 27 | 28 | 如果你可以运用递归和迭代两种方法解决这个问题,会很加分。 29 | 30 | 来源:力扣(LeetCode) 31 | 链接:https://leetcode-cn.com/problems/symmetric-tree 32 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 33 | 34 | # 解法 35 | 36 | ```javascript 37 | var isSameTree = function(p, q) { 38 | if (p && !q) return false; 39 | if (!p && q) return false; 40 | if (!p && !q) return true; 41 | if (p.val !== q.val) return false; 42 | if (!isSameTree(p.left, q.right)) return false; 43 | return isSameTree(p.right, q.left); 44 | }; 45 | var isSymmetric = function(root) { 46 | if (!root) return true; 47 | return isSameTree(root.left, root.right); 48 | }; 49 | console.log(isSymmetric(root)); 50 | ``` 51 | 52 | # 分析 53 | 54 | 和第 100 题寻找相同的树的解法1相同,通过递归的先序遍历(深度遍历)依次比对两棵树的节点,将每次对比的参数改为 55 | 56 | - 左节点的左子节点和右节点的右子节点 57 | - 右节点的左子节点和左节点的右子节点 58 | 59 | # 解法2 60 | 61 | ```javascript 62 | 63 | const check = (p, q) => { 64 | if (p && !q) return false; 65 | if (!p && q) return false; 66 | if (!p && !q) return true; 67 | 68 | return q.val === p.val; 69 | }; 70 | 71 | var isSameTree = function(p, q) { 72 | let queueP = [p]; 73 | let queueQ = [q]; 74 | while (queueP.length) { 75 | let nodeP = queueP.shift(); 76 | let nodeQ = queueQ.shift(); 77 | if (!check(nodeP, nodeQ)) return false; 78 | // 当 nodeP 为 null 时,直接下一个循环 79 | // 否则 nodeP.left 会报错 80 | if (!nodeP) continue; 81 | // 如果 p,q 的左子节点不想等 82 | // 则没有必要继续对比直接返回 false(贴近最优解) 83 | if (check(nodeP.left, nodeQ.right)) { 84 | queueP.push(nodeP.left); 85 | queueQ.push(nodeQ.right); 86 | } else { 87 | return false; 88 | } 89 | if (check(nodeP.right, nodeQ.left)) { 90 | queueP.push(nodeP.right); 91 | queueQ.push(nodeQ.left); 92 | } else { 93 | return false; 94 | } 95 | } 96 | return true; 97 | }; 98 | var isSymmetric = function(root) { 99 | if (!root) return true; 100 | return isSameTree(root.left, root.right); 101 | }; 102 | ``` 103 | 104 | # 分析2 105 | 106 | 和第 100 题寻找相同的树的解法2相同,通过维护两个队列,将参数的左右节点分别作为 100 题解法的左右参数,将每次对比的参数改为 107 | 108 | * 左节点的左子节点和右节点的右子节点 109 | 110 | * 右节点的左子节点和左节点的右子节点 111 | 112 | 同时将相应节点推入队列即可 113 | -------------------------------------------------------------------------------- /1022. Sum of Root To Leaf Binary Numbers.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给出一棵二叉树,其上每个结点的值都是 0 或 1 。每一条从根到叶的路径都代表一个从最高有效位开始的二进制数。例如,如果路径为 0 -> 1 -> 1 -> 0 -> 1,那么它表示二进制数 01101,也就是 13 。 5 | 6 | 对树上的每一片叶子,我们都要找出从根到该叶子的路径所表示的数字。 7 | 8 | 以 10^9 + 7 为模,返回这些数字之和。 9 | 10 | 示例: 11 | 12 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/04/05/sum-of-root-to-leaf-binary-numbers.png) 13 | 14 | 输入:[1,0,1,0,1,0,1] 15 | 输出:22 16 | 解释:(100) + (101) + (110) + (111) = 4 + 5 + 6 + 7 = 22 17 | 18 | 19 | 提示: 20 | 21 | 树中的结点数介于 1 和 1000 之间。 22 | node.val 为 0 或 1 。 23 | 24 | 来源:力扣(LeetCode) 25 | 链接:https://leetcode-cn.com/problems/sum-of-root-to-leaf-binary-numbers 26 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 27 | 28 | # 解法 29 | 30 | ```javascript 31 | const isLeaf = node => !node.left && !node.right; 32 | 33 | var sumRootToLeaf = function(root) { 34 | let res = 0; 35 | const traverse = (node, arr = []) => { 36 | if (!node) return; 37 | arr = [...arr]; 38 | if (isLeaf(node)) { 39 | res += parseInt(arr.join("") + node.val, 2); 40 | } else { 41 | arr.push(node.val); 42 | } 43 | traverse(node.left, arr); 44 | traverse(node.right, arr); 45 | }; 46 | traverse(root); 47 | return res; 48 | }; 49 | ``` 50 | 51 | # 分析 52 | 53 | 通过递归调用栈 + 新建数组,保存之前经过的父节点,然后当是子节点时将数组的元素取出求值即可 54 | 55 | Todo: `有一种高效的位运算方法,等以后二进制了解更深入考虑修改算法` 56 | 57 | -------------------------------------------------------------------------------- /1025. Divisor Game.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。 5 | 6 | 最初,黑板上有一个数字 N 。在每个玩家的回合,玩家需要执行以下操作: 7 | 8 | 选出任一 x,满足 0 < x < N 且 N % x == 0 。 9 | 用 N - x 替换黑板上的数字 N 。 10 | 如果玩家无法执行这些操作,就会输掉游戏。 11 | 12 | 只有在爱丽丝在游戏中取得胜利时才返回 True,否则返回 false。假设两个玩家都以最佳状态参与游戏。 13 | 14 | 15 | 16 | 示例 1: 17 | 18 | 输入:2 19 | 输出:true 20 | 解释:爱丽丝选择 1,鲍勃无法进行操作。 21 | 示例 2: 22 | 23 | 输入:3 24 | 输出:false 25 | 解释:爱丽丝选择 1,鲍勃也选择 1,然后爱丽丝无法进行操作。 26 | 27 | 28 | 提示: 29 | 30 | 1 <= N <= 1000 31 | 32 | 来源:力扣(LeetCode) 33 | 链接:https://leetcode-cn.com/problems/divisor-game 34 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 35 | 36 | # 解法 37 | 38 | ```javascript 39 | var divisorGame = function(N) { 40 | return N % 2 === 0 41 | }; 42 | ``` 43 | 44 | # 分析 45 | 46 | 这其实是一个数学问题,所以直接抛出最终结论的解法,即当 N 是一个偶数,那 先手的 alice 就一定会赢(假设智商都相同且足够聪明),当 N 是一个奇数,则 alice 一定会输 47 | 48 | 先考虑最终结果,当 N = 2 时,轮到的那个人一定胜利,因为那个人将 N - 1 还给另外一个人,另一个人拿到 1 就输了(前提规则) 49 | 50 | 因此当 alice 拿到一个偶数时,每次将其 -1,将奇数给 bob,就一定会赢,因为拿到奇数的 bob 只能完成两个操作 51 | 52 | * 将这个奇数 - 1,还给 alice 一个偶数 53 | * 将奇数除以约数,**但奇数的约数一定为奇数**,所以最终也会给 alice 一个偶数 54 | 55 | **所以还给 alice 的一定是一个偶数**,此时 alice 再将偶数 - 1 还给 bob ,当 N = 2 时,bob 就输了 56 | 57 | 即拿到的 N 如果是偶数就赢了,奇数就输了 58 | 59 | # 解法2 60 | 61 | ```javascript 62 | var divisorGame = function(N) { 63 | let dp = []; 64 | dp[1] = false; 65 | dp[2] = true; 66 | for (let i = 3; i <= N; i++) { 67 | dp[i] = false; 68 | for (let j = 0; j < i; j++) { 69 | if (i % j !== 0) continue; 70 | if (dp[i - j] === false) { 71 | dp[i] = true; 72 | } 73 | } 74 | } 75 | return dp[N]; 76 | }; 77 | ``` 78 | 79 | # 分析 80 | 81 | 动态规划解法,按照常规解法,先求每个节点的 dp 值,dp 值为一个布尔值 82 | 83 | 拿 dp[4] 即 N = 4 举例 84 | 85 | dp[4] 的值依赖于 dp[2] 和 dp[3],因为当 N = 4 时,alice 可能会做以下操作 86 | 87 | * x = 1,将 3 (4 - 1) 给 bob 88 | * x = 2,将 2(4 - 2)给 bob 89 | 90 | 同时 bob 的选择结果和 alice 是相反的,也就是说如果 dp[3]/dp[2] 为 false,那么 dp[4] 就为 true 91 | 92 | 另外由于 alice 足够聪明,所以只有当 dp[3] 和 dp[2] 中的值都为 true 时,dp[4] 才为 false,否则如果 dp[3]/dp[2] 中有一个为 false, Alice 一定会选择那个让 bob 为 false 的值,这样 alice 的结果就会为 true 93 | 94 | 即 f(x) = ![f( x - a) ,f( x - b), f(x - c), f(x - d), ...].every(Boolean) 95 | 96 | (**a,b,c,d 为所有 x 的约数**) 97 | 98 | # 解法3 99 | 100 | ```javascript 101 | var divisorGame = function(N) { 102 | let dp = []; 103 | dp[1] = false; 104 | dp[2] = true; 105 | for (let i = 3; i <= N; i++) { 106 | dp[i] = !dp 107 | .slice(0, i) 108 | .filter((item, index) => i % (i - index) === 0) 109 | .every(Boolean); 110 | } 111 | return dp[N]; 112 | }; 113 | ``` 114 | 115 | # 分析 116 | 117 | ES5 更加简洁的写法,中间的 filter 是由约数反推过来的 -------------------------------------------------------------------------------- /104. Maximum Depth of Binary Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉树,找出其最大深度。 5 | 6 | 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 7 | 8 | 说明: 叶子节点是指没有子节点的节点。 9 | 10 | 示例: 11 | 给定二叉树 [3,9,20,null,null,15,7], 12 | 13 | ``` 14 | 3 15 | / \ 16 | 9 20 17 | / \ 18 | 15 7 19 | ``` 20 | 21 | 返回它的最大深度 3 。 22 | 23 | 来源:力扣(LeetCode) 24 | 链接:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree 25 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 26 | 27 | ## 示例 28 | 29 | ``` 30 | // 123 31 | // / \ 32 | // 75 788 33 | // / / \ 34 | // 26 364 845 35 | // / \ / 36 | // 9 35 141 37 | // / \ 38 | // 4 15 39 | // \ 40 | // 6 41 | // \ 42 | // 8 43 | ``` 44 | 45 | # 解法 46 | 47 | ```javascript 48 | var maxDepth = node => Math.max(maxDepth(node.left), maxDepth(node.right)) + 1; 49 | ``` 50 | 51 | # 分析 52 | 53 | 通过后序遍历,先遍历到树的最深处,在通过不断弹出调用栈,每次弹出返回值 + 1,随后取左子树和右子树的最大深度作为当前调用栈的返回值,最终返回值根节点,输出最大深度 54 | 55 | # 解法2 56 | 57 | ```javascript 58 | var maxDepth = root => { 59 | let depth = 0; 60 | if (!root) return depth; 61 | let queue = [root]; 62 | while (queue.length) { 63 | let length = queue.length; 64 | for (let i = 0; i < length; i++) { 65 | let node = queue.shift(); 66 | if (node.left) queue.push(node.left); 67 | if (node.right) queue.push(node.right); 68 | } 69 | depth++; 70 | } 71 | return depth; 72 | }; 73 | ``` 74 | 75 | # 分析 76 | 77 | 使用队列,避免了深度栈溢出的问题,定义一个最终的 depth 变量,并通过 for 循环当这一层的所有节点都遍历时,depth++ 78 | 79 | **每次 while 循环会遍历当前层所有的节点**,原理是通过 lenth 变量记录遍历节点数,并通过上一层推入到队列的节点数,生成下一次的 length 变量,每一次的 while 循环都会重新计算 下一层length 变量 80 | -------------------------------------------------------------------------------- /105. Construct Binary Tree from Preorder and Inorder Traversal.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 根据一棵树的前序遍历与中序遍历构造二叉树。 5 | 6 | 注意: 7 | 你可以假设树中没有重复的元素。 8 | 9 | 例如,给出 10 | 11 | 前序遍历 preorder = [3,9,20,15,7] 12 | 中序遍历 inorder = [9,3,15,20,7] 13 | 返回如下的二叉树: 14 | 15 | 3 16 | / \ 17 | 9 20 18 | / \ 19 | 15 7 20 | 来源:力扣(LeetCode) 21 | 链接:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal 22 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 23 | 24 | # 解法 25 | 26 | ```javascript 27 | /** 28 | * Definition for a binary tree node. 29 | * function TreeNode(val) { 30 | * this.val = val; 31 | * this.left = this.right = null; 32 | * } 33 | */ 34 | /** 35 | * @param {number[]} preorder 36 | * @param {number[]} inorder 37 | * @return {TreeNode} 38 | */ 39 | var buildTree = function(preorder, inorder) { 40 | if (!preorder.length) return null; 41 | let node = new TreeNode(preorder[0]); 42 | let point = inorder.indexOf(preorder[0]); 43 | let leftLen = inorder.slice(0, point).length; 44 | node.left = buildTree( 45 | preorder.slice(1, 1 + leftLen), 46 | inorder.slice(0, point) 47 | ); 48 | node.right = buildTree( 49 | preorder.slice(1 + leftLen, preorder.length), 50 | inorder.slice(point + 1, inorder.length) 51 | ); 52 | return node; 53 | }; 54 | ``` 55 | 56 | # 分析 57 | 58 | 根据评论区的结论,得出 59 | 60 | **前序/后序+中序序列可以唯一确定一棵二叉树** 61 | 62 | 解题思路如下: 63 | 64 | 1. 前序遍历数组的第一个元素一定是当前的元素的节点(如果是第一次执行,则为根节点) 65 | 66 | 2. 从中序数组中找到上述前序数组的第一个元素,以这个元素为基准,结合中序遍历的特点,可以推到出:**左边为所有左子树元素,右边为所有右子树元素** 67 | 68 | 3. 回到前序数组中,根据前序遍历的特点可以推到出:`preorder = [当前节点,左子树节点,右子树节点]` 69 | 70 | 4. 由于第二步确定了中序遍历中左右子树的数量,则结合第三步可以推到出前序遍历中左右子树的数量 71 | 72 | 5. 递归的将前序数组的左右子树和中序数组的左右子树建树 73 | 74 | 75 | 76 | preorder = [3,9,20,15,7] 77 | 78 | inorder = [9,3,15,20,7] 79 | 80 | 以题目为例,preorder 第一个元素 3 一定是当前节点(即根节点),然后从 inorder 中找元素3,并以它为标志点(point),前面为节点 3 的左子树,后面为右子树 81 | 82 | inorder 83 | 84 | * 左子树:[9] 85 | * 根:[3] 86 | * 右子树:[15,20,7] 87 | 88 | 由此可见左子树的数量为 1,右子树为 3,然后从 preorder 中分割 89 | 90 | preorder 91 | 92 | * 根:[3] 93 | * 左子树:[9] (长度为1) 94 | * 右子树:[20,15,7] (长度为3) 95 | 96 | 随后将 [9],[9] 和 [15,2-,7],[20,15,7] 分别作为创建左右节点的参数传入,递归的进行建树 97 | 98 | ```javascript 99 | node.left = buildTree([9],[9]); 100 | node.right = buildTree([15,2-,7],[20,15,7] ); 101 | ``` -------------------------------------------------------------------------------- /106. Construct Binary Tree from Inorder and Postorder Traversal.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 根据一棵树的中序遍历与后序遍历构造二叉树。 5 | 6 | 注意: 7 | 你可以假设树中没有重复的元素。 8 | 9 | 例如,给出 10 | 11 | 中序遍历 inorder = [9,3,15,20,7] 12 | 后序遍历 postorder = [9,15,7,20,3] 13 | 返回如下的二叉树: 14 | 15 | 3 16 | / \ 17 | 9 20 18 | / \ 19 | 15 7 20 | 21 | 22 | 来源:力扣(LeetCode) 23 | 链接:https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal 24 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 25 | 26 | # 解法 27 | 28 | ```javascript 29 | /** 30 | * Definition for a binary tree node. 31 | * function TreeNode(val) { 32 | * this.val = val; 33 | * this.left = this.right = null; 34 | * } 35 | */ 36 | /** 37 | * @param {number[]} inorder 38 | * @param {number[]} postorder 39 | * @return {TreeNode} 40 | */ 41 | var buildTree = function(inorder, postorder) { 42 | if (!inorder.length) return null; 43 | const point = inorder.indexOf(postorder[postorder.length - 1]); 44 | let node = new TreeNode(postorder[postorder.length - 1]); 45 | node.left = buildTree(inorder.slice(0, point), postorder.slice(0, point)); 46 | node.right = buildTree( 47 | inorder.slice(point + 1, inorder.length), 48 | postorder.slice(point, postorder.length - 1) 49 | ); 50 | return node; 51 | }; 52 | 53 | ``` 54 | 55 | # 分析 56 | 57 | 和 105 题解法相同,只不过一个由前序遍历变成后序遍历 58 | 59 | 1. 所以我们需要将后序遍历数组的末尾元素作为当前节点,然后从中序数组中找到此元素,元素左边为中序遍历的左子树数组,右边为右子树数组 60 | 61 | 2. 从后序数组的头部截取左子树相同长度的数组,得到左子树后序遍历的数组 62 | 63 | 3. 后序数组去除上一步左子树的长度,去除末尾根元素,剩下的元素为右子树后序遍历的数组 64 | 4. 将1,2,3步获得的中序数组左右子树数组和后序数组左右子树数组分别作为左右子树的参数传入下个递归即可 65 | 66 | 例子和 105 相同 -------------------------------------------------------------------------------- /107. Binary Tree Level Order Traversal II.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) 5 | 6 | 例如: 7 | 给定二叉树 [3,9,20,null,null,15,7], 8 | 9 | 3 10 | / \ 11 | 9 20 12 | / \ 13 | 15 7 14 | 15 | 返回其自底向上的层次遍历为: 16 | 17 | ``` 18 | [ 19 | [15,7], 20 | [9,20], 21 | [3] 22 | ] 23 | ``` 24 | 25 | 来源:力扣(LeetCode) 26 | 链接:https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii 27 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 28 | 29 | ## 示例 30 | 31 | ``` 32 | // 123 33 | // / \ 34 | // 75 788 35 | // / / \ 36 | // 26 364 845 37 | // / \ / 38 | // 9 35 141 39 | // / \ 40 | // 4 15 41 | // \ 42 | // 6 43 | // \ 44 | // 8 45 | ``` 46 | 47 | # 解法1 48 | 49 | ```javascript 50 | var levelOrderBottom = root => { 51 | if (!root) return []; 52 | let res = []; 53 | const next = (node, res, depth) => { 54 | if (!node) return; 55 | res[depth] || (res[depth] = []); 56 | res[depth].push(node.data); 57 | next(node.left, res, depth + 1); 58 | next(node.right, res, depth + 1); 59 | }; 60 | next(root, res, 0); 61 | return res.reverse(); 62 | }; 63 | ``` 64 | 65 | # 分析 66 | 67 | 深度遍历(先序遍历)解法,每次遍历定义一个深度 depth 变量,每一层的变量放入同一个数组的同一个下标中,通过调用栈来控制 depth (在节点 8 的时候,深度为7,在节点 35 的时候深度为 4) 68 | 69 | # 解法2 70 | 71 | ```javascript 72 | var levelOrderBottom = function(root) { 73 | let res = []; 74 | if (!root) return res; 75 | let queue = [root]; 76 | while (queue.length) { 77 | let tempArr = []; 78 | let length = queue.length; 79 | for (let i = 0; i < length; i++) { 80 | let node = queue.shift(); 81 | tempArr.push(node.val); 82 | if (node.left) queue.push(node.left); 83 | if (node.right) queue.push(node.right); 84 | } 85 | res.push(tempArr); 86 | } 87 | return res.reverse(); 88 | }; 89 | ``` 90 | 91 | # 分析 92 | 93 | 广度遍历解法,通过 for 循环,可以在每次的 while 循环中导出着一层**所有**的节点,原理是通过一个 length 变量记录当前层数节点的个数,并且在队列入队的时候不会马上进行下一次 while 循环的出列,而是将节点个数保存为 length 变量 94 | 95 | 最后通过 reverse 来反转数组,达到从底至上的输出 96 | -------------------------------------------------------------------------------- /108. Convert Sorted Array to Binary Search Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 5 | 6 | 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。 7 | 8 | 示例: 9 | 10 | 给定有序数组: [-10,-3,0,5,9], 11 | 12 | 一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树: 13 | 14 | 0 15 | / \ 16 | -3 9 17 | / / 18 | -10 5 19 | 来源:力扣(LeetCode) 20 | 链接:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree 21 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处 22 | 23 | # 解法 24 | 25 | ```javascript 26 | var sortedArrayToBST = function(nums) { 27 | if (nums.length <= 1) return nums[0] == null ? null : new TreeNode(nums[0]); 28 | let midIndex = Math.floor(nums.length / 2); 29 | let midItem = nums[midIndex]; 30 | let node = new TreeNode(); 31 | node.val = midItem; 32 | node.left = sortedArrayToBST(nums.slice(0, midIndex)); 33 | node.right = sortedArrayToBST(nums.slice(midIndex + 1, nums.length)); 34 | return node; 35 | }; 36 | ``` 37 | 38 | # 分析 39 | 40 | 递归的解法,由于是顺序数组,每次取中点,将中点作为节点的值,中点左边数组作为左子树,右边作为右子树,依次递归从上到下构建整棵树 41 | 42 | # 解法2 43 | 44 | ```javascript 45 | var sortedArrayToBST = function(nums) { 46 | if (!nums.length) return null; 47 | let node = new TreeNode(null); 48 | let queue = [ 49 | { 50 | start: 0, 51 | end: nums.length, 52 | node 53 | } 54 | ]; 55 | while (queue.length) { 56 | let item = queue.shift(); 57 | let mid = Math.floor((item.start + item.end) / 2); 58 | item.node.val = nums[mid]; 59 | 60 | if (item.start < mid) { 61 | let leftNode = (item.node.left = new TreeNode(null)); 62 | queue.push({ 63 | start: item.start, 64 | end: mid, 65 | node: leftNode 66 | }); 67 | } 68 | if (mid + 1 < item.end) { 69 | let rightNode = (item.node.right = new TreeNode(null)); 70 | queue.push({ 71 | start: mid + 1, 72 | end: item.end, 73 | node: rightNode 74 | }); 75 | } 76 | } 77 | return node; 78 | }; 79 | ``` 80 | 81 | # 分析 82 | 83 | 广度遍历解法,维护一个队列,队列中的元素包含**起止点**和当前节点,两者关系为,起止点为当前节点其下的所有子节点的值 84 | 85 | 每次将起止点一分为二,并将中点作为当前节点的 val 值,然后将左右两个数组包裹成元素推入队列 86 | 87 | 四个要素:中点,起点,终点,当前树节点 88 | -------------------------------------------------------------------------------- /110. Balanced Binary Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉树,判断它是否是高度平衡的二叉树。 5 | 6 | 本题中,一棵高度平衡二叉树定义为: 7 | 8 | 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。 9 | 10 | 示例 1: 11 | 12 | 给定二叉树 [3,9,20,null,null,15,7] 13 | 14 | 3 15 | / \ 16 | 9 20 17 | / \ 18 | 15 7 19 | 20 | 返回 true 。 21 | 22 | 示例 2: 23 | 24 | 给定二叉树 [1,2,2,3,3,null,null,4,4] 25 | 26 | 1 27 | / \ 28 | 2 2 29 | / \ 30 | 3 3 31 | / \ 32 | 4 4 33 | 34 | 返回 false 。 35 | 36 | 来源:力扣(LeetCode) 37 | 链接:https://leetcode-cn.com/problems/balanced-binary-tree 38 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 39 | 40 | # 解法 41 | 42 | ```javascript 43 | var isBalanced = node => { 44 | let res = true; 45 | const helper = function(node) { 46 | if (!node) return 0; 47 | let left = helper(node.right) + 1; 48 | let right = helper(node.left) + 1; 49 | if (Math.abs(left - right) > 1) { 50 | res = false; 51 | } 52 | return Math.max(left, right); 53 | }; 54 | helper(node); 55 | return res; 56 | }; 57 | 58 | ``` 59 | 60 | # 分析 61 | 62 | **从下往上的**递归解法,基于**树的最大深度**的扩展,递为向下遍历节点,归为向上计算深度 63 | 64 | 同时在求深度的时候,分别对左右子树的最大深度做差并求出绝对值,一旦绝对值超过1,则最终结果为 false,由于是递归求每个节点的左右子树,所以当都符合条件时为高度平衡树 65 | 66 | # 解法2 67 | 68 | ```javascript 69 | const maxDepth = node => 70 | node ? Math.max(maxDepth(node.left), maxDepth(node.right)) + 1 : 0; 71 | 72 | var isBalanced = node => 73 | !node || 74 | (Math.abs(maxDepth(node.left) - maxDepth(node.right)) <= 1 && 75 | isBalanced(node.left) && 76 | isBalanced(node.right)); 77 | ``` 78 | 79 | # 分析 80 | 81 | 从上往下的递归解法,属于暴力解法,每次从上到下遍历树的节点,并对节点的左右子树的最大深度求值 ,时间复杂度为 O(n^2) ,此解法存在大量重复遍历节点,所以**性能没有前者好** 82 | -------------------------------------------------------------------------------- /111. Minimum Depth of Binary Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉树,找出其最小深度。 5 | 6 | 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 7 | 8 | 说明: 叶子节点是指没有子节点的节点。 9 | 10 | 示例: 11 | 12 | 给定二叉树 [3,9,20,null,null,15,7], 13 | 14 | 3 15 | / \ 16 | 9 20 17 | / \ 18 | 15 7 19 | 20 | 返回它的最小深度 2. 21 | 22 | 来源:力扣(LeetCode) 23 | 链接:https://leetcode-cn.com/problems/minimum-depth-of-binary-tree 24 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 25 | 26 | # 解法 27 | 28 | ```javascript 29 | var minDepth = function(root) { 30 | if (!root) return 0; 31 | let left, right; 32 | if (root.left) { 33 | left = minDepth(root.left); 34 | } else { 35 | return minDepth(root.right) + 1; 36 | } 37 | 38 | if (root.right) { 39 | right = minDepth(root.right); 40 | } else { 41 | right = left; 42 | } 43 | return Math.min(left, right) + 1; 44 | }; 45 | ``` 46 | 47 | # 分析 48 | 49 | 深度遍历版本,和二叉树最大深度的区别在于,当一个节点只有一个子节点时,需要直接返回第二个子节点,而当节点含有两个子节点时,则继续递归向下遍历 50 | 51 | 同时考虑更好的解法,当节点只有一个子节点时 52 | 53 | 如果没有左节点,则先计算右节点深度,并返回 54 | 55 | 如果没有右节点,则直接返回之前计算过的左节点深度 56 | 57 | # 解法2 58 | 59 | ```javascript 60 | var minDepth = function(root) { 61 | if (!root) return 0; 62 | let depth = 0; 63 | let queue = []; 64 | let cur = root; 65 | queue.push(cur); 66 | while (queue.length) { 67 | let length = queue.length; 68 | depth++; 69 | for (let i = 0; i < length; i++) { 70 | cur = queue.shift(); 71 | if (!cur.left && !cur.right) return depth; 72 | if (cur.left) queue.push(cur.left); 73 | if (cur.right) queue.push(cur.right); 74 | } 75 | } 76 | }; 77 | ``` 78 | 79 | # 分析 80 | 81 | 广度遍历版本,逐层遍历二叉树,**当某一层的某个节点没有左右子节点时**,即是叶子节点,直接返回当前层数,性能比深度遍历更好 82 | -------------------------------------------------------------------------------- /112. Path Sum.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。 5 | 6 | 说明: 叶子节点是指没有子节点的节点。 7 | 8 | 示例: 9 | 给定如下二叉树,以及目标和 sum = 22, 10 | 11 | 5 12 | / \ 13 | 4 8 14 | / / \ 15 | 11 13 4 16 | / \ \ 17 | 7 2 1 18 | 返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。 19 | 20 | 来源:力扣(LeetCode) 21 | 链接:https://leetcode-cn.com/problems/path-sum 22 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 23 | 24 | # 解法 25 | 26 | ```javascript 27 | var hasPathSum = function(root, sum) { 28 | if (!root) return false; 29 | if (root.val === sum && (!root.left && !root.right)) { 30 | return true; 31 | } 32 | if (root.left && hasPathSum(root.left, sum - root.val)) { 33 | return true; 34 | } 35 | if (root.right && hasPathSum(root.right, sum - root.val)) { 36 | return true; 37 | } 38 | return false; 39 | }; 40 | ``` 41 | 42 | # 分析 43 | 44 | 递归版本,从顶向下开始递归,每次递归时判断 sum 是否和当前节点的值相同,如果相同且当前节点是叶子节点,则返回 true 45 | 46 | 否则递归向下遍历,同时让 sum 减去当前节点的值 47 | 48 | # 解法2 49 | 50 | ```javascript 51 | var hasPathSum = function(root, sum) { 52 | if (!root) return false; 53 | let queue = []; 54 | queue.push({ 55 | node: root, 56 | sum 57 | }); 58 | while (queue.length) { 59 | let length = queue.length; 60 | for (let i = 0; i < length; i++) { 61 | let { node, sum } = queue.shift(); 62 | if (sum - node.val === 0 && (!node.left && !node.right)) { 63 | return true; 64 | } 65 | if (node.left) queue.push({ node: node.left, sum: sum - node.val }); 66 | if (node.right) queue.push({ node: node.right, sum: sum - node.val }); 67 | } 68 | } 69 | return false; 70 | }; 71 | ``` 72 | 73 | # 分析 74 | 75 | 队列的循环版本,每次循环往队列中同时添加当前节点和当前 sum 值(sum 的计算规则和递归相同),当出队的 sum 和当前节点相同且节点是叶子节点时,返回 true,否则继续循环 76 | -------------------------------------------------------------------------------- /113. Path Sum II.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。 5 | 6 | 说明: 叶子节点是指没有子节点的节点。 7 | 8 | 示例: 9 | 给定如下二叉树,以及目标和 sum = 22, 10 | 11 | 5 12 | / \ 13 | 4 8 14 | / / \ 15 | 11 13 4 16 | / \ / \ 17 | 7 2 5 1 18 | 返回: 19 | 20 | [ 21 | [5,4,11,2], 22 | [5,8,4,5] 23 | ] 24 | 25 | 来源:力扣(LeetCode) 26 | 链接:https://leetcode-cn.com/problems/path-sum-ii 27 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 28 | 29 | # 解法 30 | 31 | ```javascript 32 | const isLeaf = node => !node.left && !node.right; 33 | 34 | var pathSum = function(root, sum) { 35 | let res = []; 36 | const traverse = (node, rest, path) => { 37 | if (!node) return; 38 | path = [...path, node.val]; 39 | if (rest - node.val === 0 && isLeaf(node)) { 40 | res.push(path); 41 | } 42 | traverse(node.left, rest - node.val, path); 43 | traverse(node.right, rest - node.val, path); 44 | }; 45 | 46 | traverse(root, sum, []); 47 | return res; 48 | }; 49 | ``` 50 | 51 | # 分析 52 | 53 | 深度遍历递归解法,从上往下遍历,每次遍历向下传入经过的路径 path ,减去当前节点后的 rest 值 54 | 55 | 当 rest 为 0 且当前节点是叶子节点时,将经过的路径推入 res 数组中 56 | 57 | # 解法2 58 | 59 | ```javascript 60 | var pathSum = function(root, sum) { 61 | let res = []; 62 | const traverse = (node, rest, path) => { 63 | if (!node) return null; 64 | path.push(node.val); 65 | 66 | if (rest - node.val === 0 && (!node.left && !node.right)) { 67 | res.push([...path]); // 往 res 中推入结果还是要拷贝一份 68 | } 69 | 70 | if (node.left || node.right) { 71 | traverse(node.left, rest - node.val, path); 72 | traverse(node.right, rest - node.val, path); 73 | } 74 | path.pop(); 75 | }; 76 | 77 | traverse(root, sum, []); 78 | return res; 79 | }; 80 | ``` 81 | 82 | # 分析 83 | 84 | 和解法1相同,区别在于解法1每次递归都生成一个新的数组 path,而解法2重复利用同一个数组 path 85 | 86 | 之所以每次生成一个新数组,是因为防止引用类型影响其他递归栈中的 path 值,而我们可以利用**回溯**,实现每次递归都是正确的 path 87 | 88 | 每次递归都将当前节点推入 path 数组,并在最后弹出当前节点,并处理**非叶子节点**的边缘情况即可 -------------------------------------------------------------------------------- /116. Populating Next Right Pointers in Each Node.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下: 5 | 6 | struct Node { 7 | int val; 8 | Node *left; 9 | Node *right; 10 | Node *next; 11 | } 12 | 填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。 13 | 14 | 初始状态下,所有 next 指针都被设置为 NULL。 15 | 16 | 示例: 17 | 18 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/02/15/116_sample.png) 19 | 20 | 输入:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":null,"right":null,"val":4},"next":null,"right":{"$id":"4","left":null,"next":null,"right":null,"val":5},"val":2},"next":null,"right":{"$id":"5","left":{"$id":"6","left":null,"next":null,"right":null,"val":6},"next":null,"right":{"$id":"7","left":null,"next":null,"right":null,"val":7},"val":3},"val":1} 21 | 22 | 输出:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":{"$id":"4","left":null,"next":{"$id":"5","left":null,"next":{"$id":"6","left":null,"next":null,"right":null,"val":7},"right":null,"val":6},"right":null,"val":5},"right":null,"val":4},"next":{"$id":"7","left":{"$ref":"5"},"next":null,"right":{"$ref":"6"},"val":3},"right":{"$ref":"4"},"val":2},"next":null,"right":{"$ref":"7"},"val":1} 23 | 24 | 解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。 25 | 26 | 27 | 提示: 28 | 29 | 你只能使用常量级额外空间。 30 | 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。 31 | 32 | 来源:力扣(LeetCode) 33 | 链接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node 34 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 35 | 36 | # 解法 37 | 38 | ```javascript 39 | var connect = root => { 40 | const traverse = node => { 41 | if (!node || !node.left) return; 42 | node.left.next = node.right; 43 | node.right.next = node.next && node.next.left; 44 | traverse(node.left); 45 | traverse(node.right); 46 | }; 47 | traverse(root); 48 | }; 49 | ``` 50 | 51 | # 分析 52 | 53 | 深度遍历解法,每次遍历涉及到当前节点和左右两个子节点,特殊点在于图中节点 5 链接到节点 6 的地方 54 | 55 | 这里先将左节点链接到右节点,然后利用**完美二叉树**的特点,在遍历到当前节点 2 时,将 2 的右节点(5)的 next 指向当前节点 next 属性的左节点 56 | 57 | 另外 BFS 会更加简单这里就不阐述了 -------------------------------------------------------------------------------- /118. Pascal's Triangle.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。 5 | 6 | ![img](https://upload.wikimedia.org/wikipedia/commons/0/0d/PascalTriangleAnimated2.gif) 7 | 8 | 在杨辉三角中,每个数是它左上方和右上方的数的和。 9 | 10 | 示例: 11 | 12 | 输入: 5 13 | 输出: 14 | [ 15 | [1], 16 | [1,1], 17 | [1,2,1], 18 | [1,3,3,1], 19 | [1,4,6,4,1] 20 | ] 21 | 22 | 来源:力扣(LeetCode) 23 | 链接:https://leetcode-cn.com/problems/pascals-triangle 24 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 25 | 26 | # 解法 27 | 28 | ```javascript 29 | var generate = function(numRows) { 30 | if (numRows === 0) return []; 31 | let dp = []; 32 | dp[0] = [1]; 33 | dp[1] = [1, 1]; 34 | for (let i = 2; i < numRows; i++) { 35 | dp[i] = [ 36 | 1, 37 | ...dp[i - 1] 38 | .map((item, index) => item + dp[i - 1][index + 1]) 39 | .slice(0, dp[i - 1].length - 1), 40 | 1 41 | ]; 42 | } 43 | return dp; 44 | }; 45 | ``` 46 | 47 | # 分析 48 | 49 | 动态规划解法,状态转移公式 50 | 51 | f(x) = [1, f(x-1)[0] + f(x - 1)[1], f(x-1)[1] + f(x - 1)[2], f(x-1)[2] + f(x - 1)[3], ...... , 1] 52 | 53 | 当前行数的开头和结尾都是 1,中间的值依赖于上个行数对应行相加的结果 54 | 55 | 同时排除 0,1 的情况 -------------------------------------------------------------------------------- /119. Pascal's Triangle II.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。 5 | 6 | ![img](https://upload.wikimedia.org/wikipedia/commons/0/0d/PascalTriangleAnimated2.gif) 7 | 8 | 在杨辉三角中,每个数是它左上方和右上方的数的和。 9 | 10 | 示例: 11 | 12 | 输入: 3 13 | 输出: [1,3,3,1] 14 | 进阶: 15 | 16 | 你可以优化你的算法到 O(k) 空间复杂度吗? 17 | 18 | 来源:力扣(LeetCode) 19 | 链接:https://leetcode-cn.com/problems/pascals-triangle-ii 20 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 21 | 22 | # 解法 23 | 24 | ```javascript 25 | var getRow = function(numRows) { 26 | if (numRows === 0) return [1]; 27 | if (numRows === 1) return [1, 1]; 28 | numRows = numRows + 1; 29 | let dp = [1, 1]; 30 | 31 | for (let i = 2; i < numRows; i++) { 32 | dp = [ 33 | 1, 34 | ...dp.map((item, index) => item + dp[index + 1]).slice(0, dp.length - 1), 35 | 1 36 | ]; 37 | } 38 | return dp; 39 | }; 40 | ``` 41 | 42 | # 分析 43 | 44 | 和杨辉三角解法相同,使用动态规划,区别在于使用变量保存上一个 dp 的结果,而非用一个数组保存(二维数组),进一步降低空间复杂度 -------------------------------------------------------------------------------- /121. Best Time to Buy and Sell Stock.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 5 | 6 | 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。 7 | 8 | 注意你不能在买入股票前卖出股票。 9 | 10 | 示例 1: 11 | 12 | 输入: [7,1,5,3,6,4] 13 | 输出: 5 14 | 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 15 | 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。 16 | 示例 2: 17 | 18 | 输入: [7,6,4,3,1] 19 | 输出: 0 20 | 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0 21 | 22 | 来源:力扣(LeetCode) 23 | 链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock 24 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 25 | 26 | # 解法 27 | 28 | ```javascript 29 | 30 | ``` 31 | 32 | # 分析 33 | 34 | 35 | # 解法2 36 | 37 | ```javascript 38 | const maxProfit = arr => { 39 | let res = 0; 40 | let maxDistance = 0; 41 | for (let i = 0; i < arr.length; i++) { 42 | let num = i === 0 ? 0 : arr[i] - arr[i - 1]; 43 | maxDistance = Math.max(num, maxDistance + num); 44 | res = Math.max(res, maxDistance); 45 | } 46 | return res; 47 | }; 48 | ``` 49 | 50 | # 分析 51 | 52 | 动态规划解法,由于无法直接递推出当前天数和之前天数的关系,所以需要进行一些转换,这里可以将每个元素减去前一个元素,算出 53 | 54 | -------------------------------------------------------------------------------- /122. Best Time to Buy and Sell Stock II.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 5 | 6 | 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 7 | 8 | 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 9 | 10 | 示例 1: 11 | 12 | ``` 13 | 输入: [7,1,5,3,6,4] 14 | 输出: 7 15 | 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 16 | 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 17 | ``` 18 | 19 | 示例 2: 20 | 21 | ``` 22 | 输入: [1,2,3,4,5] 23 | 输出: 4 24 | 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 25 | 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 26 | 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 27 | ``` 28 | 29 | 示例 3: 30 | 31 | ``` 32 | 输入: [7,6,4,3,1] 33 | 输出: 0 34 | 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 35 | ``` 36 | 37 | 来源:力扣(LeetCode) 38 | 链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii 39 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 40 | 41 | # 解法 42 | 43 | ```javascript 44 | var maxProfit = function(prices) { 45 | let cur = 0; 46 | let next = 1; 47 | let res = 0; 48 | while (next < prices.length - 1) { 49 | if (prices[cur] < prices[next] && prices[next] < prices[next + 1]) { 50 | next++; 51 | } else if (prices[cur] < prices[next] && prices[next] >= prices[next + 1]) { 52 | res += prices[next] - prices[cur]; 53 | cur = next; 54 | next++; 55 | } else { 56 | cur++; 57 | next++; 58 | } 59 | } 60 | if (prices[next] > prices[cur]) { 61 | res += prices[next] - prices[cur]; 62 | } 63 | return res; 64 | }; 65 | 66 | ``` 67 | 68 | # 分析 69 | 70 | 考虑将股票走势变为一个折线图,只要求出每个波谷和波峰之间的距离差即可 71 | 72 | ![](https://pic.leetcode-cn.com/d447f96d20d1cfded20a5d08993b3658ed08e295ecc9aea300ad5e3f4466e0fe-file_1555699515174) 73 | 74 | 定义一个 cur 和 next 指针,**cur 指向波谷,next 指向波峰**,每次 res 加上波峰和波谷差即可 -------------------------------------------------------------------------------- /125. Valid Palindrome.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 5 | 6 | 说明:本题中,我们将空字符串定义为有效的回文串。 7 | 8 | 示例 1: 9 | 10 | 输入: "A man, a plan, a canal: Panama" 11 | 输出: true 12 | 示例 2: 13 | 14 | 输入: "race a car" 15 | 输出: false 16 | 17 | 来源:力扣(LeetCode) 18 | 链接:https://leetcode-cn.com/problems/valid-palindrome 19 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 20 | 21 | # 解法 22 | 23 | ```javascript 24 | var isPalindrome = function(s) { 25 | let p = 0; 26 | let q = s.length - 1; 27 | while (p <= q) { 28 | if (!s[p].match(/[a-zA-Z]/)) { 29 | p++; 30 | continue; 31 | } 32 | if (!s[q].match(/[a-zA-Z]/)) { 33 | q--; 34 | continue; 35 | } 36 | if (s[p].toLowerCase() === s[q].toLowerCase()) { 37 | p++; 38 | q--; 39 | } else { 40 | return false; 41 | } 42 | } 43 | return true; 44 | }; 45 | ``` 46 | 47 | # 分析 48 | 49 | 定义两个指针 p,q,从头尾开始遍历,遇到非法字符(非英文字母)就再往前/后一位 50 | 51 | 当两个指针对应的字符相同时(不区分大小写),两个指针各往中间一位,直到重合返回 true,否则一旦字符不同返回 false 52 | 53 | # 解法2 54 | 55 | 利用栈的结构,反向输出,但是不在双指针的考察范围内,且比较简单,只提供思路 56 | 57 | -------------------------------------------------------------------------------- /13. Roman to Integer.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 5 | 6 | 字符 数值 7 | I 1 8 | V 5 9 | X 10 10 | L 50 11 | C 100 12 | D 500 13 | M 1000 14 | 例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。 15 | 16 | 通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况: 17 | 18 | I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。 19 | X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 20 | C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。 21 | 给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。 22 | 23 | 示例 1: 24 | 25 | 输入: "III" 26 | 输出: 3 27 | 示例 2: 28 | 29 | 输入: "IV" 30 | 输出: 4 31 | 示例 3: 32 | 33 | 输入: "IX" 34 | 输出: 9 35 | 示例 4: 36 | 37 | 输入: "LVIII" 38 | 输出: 58 39 | 解释: L = 50, V= 5, III = 3. 40 | 示例 5: 41 | 42 | 输入: "MCMXCIV" 43 | 输出: 1994 44 | 解释: M = 1000, CM = 900, XC = 90, IV = 4. 45 | 46 | 来源:力扣(LeetCode) 47 | 链接:https://leetcode-cn.com/problems/roman-to-integer 48 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 49 | 50 | # 解法 51 | 52 | ```javascript 53 | 54 | /** 55 | * @param {string} s 56 | * @return {number} 57 | */ 58 | var romanToInt = function(s) { 59 | let res = 0 60 | const map = { 61 | I : 1, 62 | IV: 4, 63 | V: 5, 64 | IX: 9, 65 | X: 10, 66 | XL: 40, 67 | L: 50, 68 | XC: 90, 69 | C: 100, 70 | CD: 400, 71 | D: 500, 72 | CM: 900, 73 | M: 1000 74 | }; 75 | for (let i = 0; i < s.length; i++) { 76 | if(map[s[i] + s[i+1]]){ 77 | res += map[s[i] + s[i+1]] 78 | // 多进一位,加上默认的 i++ ,也就是会向前进 2 位,因为跳过了 2 个数字 79 | i++ 80 | }else{ 81 | res += map[s[i]] 82 | } 83 | } 84 | return res 85 | }; 86 | ``` 87 | 88 | # 分析 89 | 90 | 首先列出所有罗马数字组成的可能,放入哈希表中 91 | 92 | 声明一个累加的遍历 res,之后根据观察哈希表得出,只可能存在一位或两位的罗马数字的**组合**,所以在遍历参数时,遇到两位的罗马数字取出对应的值,随后将遍历的指针向后多移动一位(即两位) 93 | 94 | -------------------------------------------------------------------------------- /136. Single Number.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 5 | 6 | 说明: 7 | 8 | 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 9 | 10 | 示例 1: 11 | 12 | 输入: [2,2,1] 13 | 输出: 1 14 | 示例 2: 15 | 16 | 输入: [4,1,2,1,2] 17 | 输出: 4 18 | 19 | 来源:力扣(LeetCode) 20 | 链接:https://leetcode-cn.com/problems/single-number 21 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 22 | 23 | # 解法 24 | 25 | ```javascript 26 | var singleNumber = function(nums) { 27 | nums.sort((a, b) => a - b); 28 | let p = 0; 29 | while (p < nums.length) { 30 | if (nums[p] !== nums[p + 1]) { 31 | return nums[p]; 32 | } 33 | p = p + 2; 34 | } 35 | }; 36 | ``` 37 | 38 | # 分析 39 | 40 | 排序解法,空间复杂度 O(1),还有一种是哈希表解法,但空间复杂度为 O(n) -------------------------------------------------------------------------------- /14. Longest Common Prefix.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 编写一个函数来查找字符串数组中的最长公共前缀。 5 | 6 | 如果不存在公共前缀,返回空字符串 ""。 7 | 8 | 示例 1: 9 | 10 | 输入: ["flower","flow","flight"] 11 | 输出: "fl" 12 | 示例 2: 13 | 14 | 输入: ["dog","racecar","car"] 15 | 输出: "" 16 | 解释: 输入不存在公共前缀。 17 | 说明: 18 | 19 | 所有输入只包含小写字母 a-z 。 20 | 21 | 来源:力扣(LeetCode) 22 | 链接:https://leetcode-cn.com/problems/longest-common-prefix 23 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 24 | 25 | # 解法 26 | 27 | ```javascript 28 | var longestCommonPrefix = function(strs) { 29 | if (!strs.length) return ""; 30 | let ans = ""; 31 | for (let i = 0; i < strs[0].length; i++) { 32 | let res = strs.every(str => str[i] === strs[0][i]); 33 | if (res) { 34 | ans += strs[0][i]; 35 | } else { 36 | break; 37 | } 38 | } 39 | return ans; 40 | }; 41 | 42 | console.log(longestCommonPrefix(["aca", "cba"])); 43 | ``` 44 | 45 | # 分析 46 | 47 | 先排除没有元素的边缘情况 48 | 49 | 每次遍历数组每个字符串元素的第 i 个字符,同时判断,如果全部都相等,则记录当前的字符,然后继续遍历每个字符串元素的第 i+1 个字符,当有一个字符不相等时,返回之前记录的结果 -------------------------------------------------------------------------------- /141. Linked List Cycle.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个链表,判断链表中是否有环。 5 | 6 | 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。 7 | 8 | 示例 1: 9 | 10 | 输入:head = [3,2,0,-4], pos = 1 11 | 输出:true 12 | 解释:链表中有一个环,其尾部连接到第二个节点。 13 | 14 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist.png) 15 | 16 | 17 | 示例 2: 18 | 19 | 输入:head = [1,2], pos = 0 20 | 输出:true 21 | 解释:链表中有一个环,其尾部连接到第一个节点。 22 | 23 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist_test2.png) 24 | 25 | 26 | 示例 3: 27 | 28 | 输入:head = [1], pos = -1 29 | 输出:false 30 | 解释:链表中没有环。 31 | 32 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist_test3.png) 33 | 34 | 35 | 进阶: 36 | 37 | 你能用 O(1)(即,常量)内存解决此问题吗? 38 | 39 | 来源:力扣(LeetCode) 40 | 链接:https://leetcode-cn.com/problems/linked-list-cycle 41 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 42 | 43 | # 解法 44 | 45 | ```javascript 46 | var hasCycle = function(head) { 47 | let set = new Set(); 48 | while (head) { 49 | if (set.has(head)) return true; 50 | set.add(head); 51 | head = head.next; 52 | } 53 | return false; 54 | }; 55 | ``` 56 | 57 | # 分析 58 | 59 | 哈希表解法,从头遍历链表,将链表节点记录在哈希表中 (Set) 60 | 61 | 每次遍历时去哈希表中尝试找对应节点,如果找到说明链表成环 62 | 63 | **但是使用哈希表空间复杂度为O(n)** 64 | 65 | # 解法2 66 | 67 | ```javascript 68 | var hasCycle = function(head) { 69 | if (!head) return false; 70 | let p = head; 71 | let q = head.next; 72 | while (q) { 73 | if (p === q) return true; 74 | p = p.next; 75 | q = q.next && q.next.next; 76 | } 77 | return false; 78 | }; 79 | ``` 80 | 81 | # 分析 82 | 83 | 双指针解法,定义一个快指针,一个慢指针,慢指针一次前进一格,快指针一次前进两格 84 | 85 | > **思路** 86 | > 87 | > 想象一下,两名运动员以不同的速度在环形赛道上跑步会发生什么? 88 | 89 | 原理是,如果是环形链表,那快指针一定能在某个时刻“追上”慢指针,即快指针和慢指针重合 90 | 91 | 相反如果不是环形链表,那快指针会遍历完毕,此时返回 false 92 | 93 | 相比于哈希表,空间复杂度为 O(1) 94 | 95 | 至于时间复杂度,当是**环形链表**时,快指针和慢指针始终会重合,并且一定在**环形中重合**,在重合时,快指针可能已经在环形部分循环了很多次 96 | 97 | ![image-20191014154705154](/Users/zhouhaolei/Library/Application Support/typora-user-images/image-20191014154705154.png) 98 | 99 | -------------------------------------------------------------------------------- /15. 3Sum.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。 5 | 6 | 注意:答案中不可以包含重复的三元组。 7 | 8 | 例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 9 | 10 | 满足要求的三元组集合为: 11 | [ 12 | [-1, 0, 1], 13 | [-1, -1, 2] 14 | ] 15 | 16 | 来源:力扣(LeetCode) 17 | 链接:https://leetcode-cn.com/problems/3sum 18 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 19 | 20 | # 解法 21 | 22 | ```javascript 23 | var threeSum = function(nums) { 24 | nums.sort((a, b) => a - b); 25 | const res = []; 26 | for (let i = 0; i < nums.length; ) { 27 | if (nums[i] > 0) break; 28 | let start = i + 1; 29 | let end = nums.length - 1; 30 | while (start < end) { 31 | const target = -nums[i] 32 | if (nums[start] + nums[end] < target) { 33 | start++; 34 | } else if (nums[start] + nums[end] > target) { 35 | end--; 36 | } else { 37 | res.push([nums[i], nums[start], nums[end]]); 38 | while (nums[start] === nums[start + 1]) { 39 | start++; 40 | } 41 | while (nums[end] === nums[end - 1]) { 42 | end--; 43 | } 44 | start++; 45 | end--; 46 | } 47 | } 48 | while (nums[i] === nums[i + 1]) { 49 | i++; 50 | } 51 | i++; 52 | } 53 | return res; 54 | }; 55 | ``` 56 | 57 | # 分析 58 | 59 | 双指针解法,时间复杂度为 O(n^2) (排序:O(NlogN) + 遍历和双指针:O(n^2)) 60 | 61 | 首先对数组进行排序,这样才能使用双指针 62 | 63 | 之后遍历数组每个元素,固定遍历的元素,由于是找出三个元素和为 0,当第一个元素被固定时,问题变成了 64 | 65 | `在一个顺序数组中找到两个和为 -固定元素(target) 的值` 66 | 67 | 顺序数组可以用双指针,建立两个头尾指针向中间遍历,当大于 target 时尾部向前,反之头部向后,找到元素时将头尾指针指向的元素和固定元素放入 res 数组即可 68 | 69 | 这里还有个需要注意的是,当遍历或者头尾指针找到 target 再次移动时,需要跳过重复元素(不能给数组去重,因为可能出现 -1,-1,2 这种情况) 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /155. Min Stack.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。 5 | 6 | push(x) -- 将元素 x 推入栈中。 7 | pop() -- 删除栈顶的元素。 8 | top() -- 获取栈顶元素。 9 | getMin() -- 检索栈中的最小元素。 10 | 示例: 11 | 12 | MinStack minStack = new MinStack(); 13 | minStack.push(-2); 14 | minStack.push(0); 15 | minStack.push(-3); 16 | minStack.getMin(); --> 返回 -3. 17 | minStack.pop(); 18 | minStack.top(); --> 返回 0. 19 | minStack.getMin(); --> 返回 -2. 20 | 21 | 来源:力扣(LeetCode) 22 | 链接:https://leetcode-cn.com/problems/min-stack 23 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 24 | 25 | # 解法 26 | 27 | ```javascript 28 | /** 29 | * initialize your data structure here. 30 | */ 31 | var MinStack = function() { 32 | this.extraStack = []; 33 | this.stack = []; 34 | }; 35 | 36 | /** 37 | * @param {number} x 38 | * @return {void} 39 | */ 40 | MinStack.prototype.push = function(x) { 41 | this.stack.push(x); 42 | if ( 43 | !this.extraStack.length || 44 | x <= this.extraStack[this.extraStack.length - 1] 45 | ) { 46 | this.extraStack.push(x); 47 | } 48 | }; 49 | 50 | /** 51 | * @return {void} 52 | */ 53 | MinStack.prototype.pop = function() { 54 | if (this.stack.pop() === this.extraStack[this.extraStack.length - 1]) { 55 | this.extraStack.pop(); 56 | } 57 | }; 58 | 59 | /** 60 | * @return {number} 61 | */ 62 | MinStack.prototype.top = function() { 63 | return this.stack[this.stack.length - 1]; 64 | }; 65 | 66 | /** 67 | * @return {number} 68 | */ 69 | MinStack.prototype.getMin = function() { 70 | return this.extraStack[this.extraStack.length - 1] 71 | }; 72 | ``` 73 | 74 | # 分析 75 | 76 | 利用一个额外的辅助栈,由于同为栈,所以**辅助栈的顺序和默认的栈是相同的**,当给默认栈推入的元素小于辅助栈栈顶的元素,则往辅助栈也推入这个元素 77 | 78 | 所以辅助栈的元素最终会从小到大排列,当获取最小值时去辅助栈栈顶的元素即可 79 | 80 | # 解法2 81 | 82 | ```javascript 83 | class Node { 84 | constructor(val) { 85 | this.val = val; 86 | this.next = null; 87 | this.min = null; 88 | } 89 | } 90 | 91 | /** 92 | * initialize your data structure here. 93 | */ 94 | var MinStack = function() { 95 | this.list = null; 96 | this.stack = []; 97 | }; 98 | 99 | /** 100 | * @param {number} x 101 | * @return {void} 102 | */ 103 | MinStack.prototype.push = function(x) { 104 | this.stack.push(x); 105 | if (!this.list) { 106 | this.list = new Node(x); 107 | this.list.min = x; 108 | } else { 109 | const node = new Node(x); 110 | node.min = Math.min(this.list.min, x); 111 | node.next = this.list; 112 | this.list = node; 113 | } 114 | }; 115 | 116 | /** 117 | * @return {void} 118 | */ 119 | MinStack.prototype.pop = function() { 120 | this.stack.pop(); 121 | this.list = this.list.next; 122 | }; 123 | 124 | /** 125 | * @return {number} 126 | */ 127 | MinStack.prototype.top = function() { 128 | return this.stack[this.stack.length - 1]; 129 | }; 130 | 131 | /** 132 | * @return {number} 133 | */ 134 | MinStack.prototype.getMin = function() { 135 | return this.list ? this.list.min : null; 136 | }; 137 | ``` 138 | 139 | # 分析 140 | 141 | **定义一个新的链表结构**,每个链表添加一个额外属性 min,保存以当前**链表为止**的最小数字 142 | 143 | 每次往栈中添加元素,需要再当前链表的**头部**也添加当前元素,同时将指针往**前**移动(这样不断往链表头部添加元素,当调用 pop 方法时,只要让链表向后移动即可,否则需要使用双向链表) 144 | 145 | 当调用 getMin 时,只要返回当前链表元素的 min 属性即可 146 | 147 | -------------------------------------------------------------------------------- /160. Intersection of Two Linked Lists.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 编写一个程序,找到两个单链表相交的起始节点。 5 | 6 | 如下面的两个链表: 7 | 8 | [![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/160_statement.png)](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/160_statement.png) 9 | 10 | 在节点 c1 开始相交。 11 | 12 | 13 | 14 | 示例 1: 15 | 16 | [![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/160_example_1.png)](https://assets.leetcode.com/uploads/2018/12/13/160_example_1.png) 17 | 18 | 输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 19 | 输出:Reference of the node with value = 8 20 | 输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 21 | 22 | 23 | 示例 2: 24 | 25 | [![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/160_example_2.png)](https://assets.leetcode.com/uploads/2018/12/13/160_example_2.png) 26 | 27 | 输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 28 | 输出:Reference of the node with value = 2 29 | 输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。 30 | 31 | 32 | 示例 3: 33 | 34 | [![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/160_example_3.png)](https://assets.leetcode.com/uploads/2018/12/13/160_example_3.png) 35 | 36 | 输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 37 | 输出:null 38 | 输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。 39 | 解释:这两个链表不相交,因此返回 null。 40 | 41 | 42 | 注意: 43 | 44 | 如果两个链表没有交点,返回 null. 45 | 在返回结果后,两个链表仍须保持原有的结构。 46 | 可假定整个链表结构中没有循环。 47 | 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。 48 | 49 | 来源:力扣(LeetCode) 50 | 链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists 51 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 52 | 53 | # 解法 54 | 55 | ```javascript 56 | var getIntersectionNode = function(headA, headB) { 57 | let set = new Set(); 58 | let cur = headA; 59 | while (cur) { 60 | set.add(cur); 61 | cur = cur.next; 62 | } 63 | cur = headB; 64 | while (cur) { 65 | if (set.has(cur)) { 66 | return cur; 67 | } 68 | cur = cur.next; 69 | } 70 | }; 71 | ``` 72 | 73 | # 分析 74 | 75 | 声明一个哈希表,然后遍历 headA 链表,将所有节点(非节点值)保存在哈希表中,空间复杂度为 O(n) 76 | 77 | 然后遍历 headB ,当当前遍历的节点存在于哈希表中,那这个节点就是相交节点 78 | 79 | `之所以可以这样做是因为题目的前提:两个链表相交后不会有分叉,由于引用类型的特点即使值相同也不会是相同节点,只有真正的相交节点才会存在于哈希表中` 80 | 81 | # 解法2 82 | 83 | ```javascript 84 | var getIntersectionNode = function(headA, headB) { 85 | if (!headA || !headB) return null; 86 | 87 | let curA = headA; 88 | let curB = headB; 89 | 90 | let switchA = false; 91 | let switchB = false; 92 | 93 | if (curA === curB) { 94 | return curA; 95 | } 96 | while (true) { 97 | curA = curA.next; 98 | curB = curB.next; 99 | if (!curA && !switchA) { 100 | switchA = true; 101 | curA = headB; 102 | } else if (!curA && switchA) { 103 | return null; 104 | } 105 | if (!curB && !switchB) { 106 | switchB = true; 107 | curB = headA; 108 | } else if (!curB && switchB) { 109 | return null; 110 | } 111 | if (curA === curB) { 112 | return curA; 113 | } 114 | } 115 | }; 116 | ``` 117 | 118 | # 分析 119 | 120 | https://leetcode-cn.com/problems/intersection-of-two-linked-lists/solution/xiang-jiao-lian-biao-by-leetcode/ 121 | 122 | 双指针解法,通过 curA 和 curB 两个指针,从头开始遍历链表,当 curA (curB) 遍历到尾部时,使它指向 curB (curA),并继续进行遍历,当 curA 和 curB 重合时,重合点即相交点 123 | 124 | 空间复杂度 O(1) 125 | 126 | 这里额外定义了一个开关,防止 curA 和 curB 始终不相交造成的死循环(不知道可不可以优化) 127 | 128 | -------------------------------------------------------------------------------- /167. Two Sum II - Input array is sorted.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。 5 | 6 | 函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。 7 | 8 | 说明: 9 | 10 | 返回的下标值(index1 和 index2)不是从零开始的。 11 | 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。 12 | 示例: 13 | 14 | 输入: numbers = [2, 7, 11, 15], target = 9 15 | 输出: [1,2] 16 | 解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。 17 | 18 | 来源:力扣(LeetCode) 19 | 链接:https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted 20 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 21 | 22 | # 解法 23 | 24 | ```javascript 25 | var twoSum = function(numbers, target) { 26 | let p = 0; 27 | let q = numbers.length - 1; 28 | while (p < q) { 29 | let res = numbers[p] + numbers[q]; 30 | if (res === target) { 31 | return [p + 1, q + 1]; 32 | } else if (res > target) { 33 | q--; 34 | } else if (res < target) { 35 | p++; 36 | } 37 | } 38 | return false; 39 | }; 40 | ``` 41 | 42 | # 分析 43 | 44 | 双指针解法,由于是**顺序数组**,所以可以不用哈希表,实现 O(1) 空间复杂度的操作 45 | 46 | 定义头尾两个指针,由于**唯一解**,所以两个指针往中间查找,当两个指针指向的值大于目标值,则尾指针(指向较大值的指针)向前移动,反之头指针向后,直到找到目标元素 47 | 48 | -------------------------------------------------------------------------------- /169. Majority Element.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。 5 | 6 | 你可以假设数组是非空的,并且给定的数组总是存在众数。 7 | 8 | 示例 1: 9 | 10 | 输入: [3,2,3] 11 | 输出: 3 12 | 示例 2: 13 | 14 | 输入: [2,2,1,1,1,2,2] 15 | 输出: 2 16 | 17 | 来源:力扣(LeetCode) 18 | 链接:https://leetcode-cn.com/problems/majority-element 19 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 20 | 21 | # 解法 22 | 23 | ```javascript 24 | var majorityElement = function(nums) { 25 | nums = nums.sort(); 26 | return nums[~~(nums.length / 2)] 27 | }; 28 | ``` 29 | 30 | # 分析 31 | 32 | 由于是众数,即众数组中出现次数**大于** `⌊ n/2 ⌋` 的元素,所以将数组排序,输出中间元素即可 33 | 34 | # 解法2 35 | 36 | ```javascript 37 | var majorityElement = function(nums) { 38 | let map = {}; 39 | for (let i = 0; i < nums.length; i++) { 40 | if (!map[nums[i]]) { 41 | map[nums[i]] = 0; 42 | } 43 | map[nums[i]] += 1; 44 | } 45 | let maxSum = Object.keys(map).reduce( 46 | (pre, cur) => (map[cur] > pre ? map[cur] : pre), 47 | 0 48 | ); 49 | return Number(Object.keys(map).filter(item => map[item] === maxSum)[0]); 50 | }; 51 | ``` 52 | 53 | # 分析 54 | 55 | 哈希表解法,道理我都懂,为啥遍历这么多次速度还比排序快-.- 56 | 57 | ![image.png](https://i.loli.net/2019/11/16/AcslYjMOvzTeLha.png) -------------------------------------------------------------------------------- /198. House Robber.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 5 | 6 | 给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。 7 | 8 | 示例 1: 9 | 10 | 输入: [1,2,3,1] 11 | 输出: 4 12 | 解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 13 | 偷窃到的最高金额 = 1 + 3 = 4 。 14 | 示例 2: 15 | 16 | 输入: [2,7,9,3,1] 17 | 输出: 12 18 | 解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 19 | 偷窃到的最高金额 = 2 + 9 + 1 = 12 。 20 | 21 | 来源:力扣(LeetCode) 22 | 链接:https://leetcode-cn.com/problems/house-robber 23 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 24 | 25 | # 解法 26 | 27 | ```javascript 28 | var rob = function(nums) { 29 | const func = n => { 30 | if (n === -1) return 0; 31 | if (n === -2) return 0; 32 | return Math.max(func(n - 2) + nums[n], func(n - 1)); 33 | }; 34 | return func(nums.length - 1); 35 | }; 36 | ``` 37 | 38 | # 分析 39 | 40 | 递归解法,**缺点容易栈溢出**,考虑小偷偷每一个房屋的收益,即数组每一个点的收益,可以得出以下结论 41 | 42 | * 不偷前一个房屋,偷当前房屋 43 | * 偷前一个房屋,不偷当前房屋 44 | 45 | 我们要求这两种情况的最大收益,转为公式 46 | 47 | ` F(x) = Math.max( F(x - 1),F(x - 2) + nums[i] )` 48 | 49 | 递归解法是从上往下遍历,会造成额外的遍历,但是相对容易理解 50 | 51 | 解法2 52 | 53 | ```javascript 54 | const isEven = i => (i & 1) === 0; 55 | var rob = function(nums) { 56 | let res = 0; 57 | let odd = 0; 58 | let even = 0; 59 | for (let i = 0; i < nums.length; i++) { 60 | if (isEven(i)) { 61 | odd = Math.max(nums[i] + odd, even); 62 | res = Math.max(res, odd); 63 | } else { 64 | even = Math.max(nums[i] + even, odd); 65 | res = Math.max(res, even); 66 | } 67 | } 68 | return res; 69 | }; 70 | ``` 71 | 72 | # 分析 73 | 74 | 动态规划解法,时间复杂度为 O(n) 75 | 76 | 通过两个变量 even,odd 保存 F(x- 1) 和 F(x - 2) 的值,使得空间复杂度为 O(1) 77 | 78 | 每次遍历,如果是当前是奇数,则更新 even 值,否则更新 odd 的值 79 | 80 | 最终从底往上求出最大值 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /20. Valid Parentheses.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 5 | 6 | 有效字符串需满足: 7 | 8 | 左括号必须用相同类型的右括号闭合。 9 | 左括号必须以正确的顺序闭合。 10 | 注意空字符串可被认为是有效字符串。 11 | 12 | 来源:力扣(LeetCode) 13 | 链接:https://leetcode-cn.com/problems/valid-parentheses 14 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 15 | 16 | # 解法 17 | 18 | ```javascript 19 | const map = { 20 | "{":"}", 21 | "[":"]", 22 | "(":")" 23 | } 24 | 25 | var isValid = function (s) { 26 | let stack = [] 27 | for (let i = 0; i < s.length; i++) { 28 | if(map[s[i]]){ 29 | stack.push(s[i]) 30 | }else{ 31 | let item = stack.pop() 32 | if( map[item] !== s[i]) return false 33 | } 34 | } 35 | return !stack.length 36 | }; 37 | 38 | console.log(isValid("()[]{}")) 39 | ``` 40 | 41 | # 分析 42 | 43 | 首先创建一个代表着括号对应开闭关系的哈希表,随后再创建一个栈,当是开括号时,将其推入栈,当是闭括号时,将栈顶元素弹出,判断栈顶元素在哈希表中是否和闭括号配对 44 | 45 | 栈可以帮助我们处理递归的情况,因为我们对内部可能存在多少嵌套一无所知,但我们只需将开括号存储在栈中,等待闭括号入栈即可 46 | 47 | 栈这种数据结构,还能用户判断 html 标签是否正确闭合 48 | 49 | `Vue 的编译器正是这么做的,同时还能根据确立父子关系,因为当闭合的标签匹配开始标签时,栈顶的第二个元素就是栈顶元素的父元素` 50 | -------------------------------------------------------------------------------- /203. Remove Linked List Elements.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 删除链表中等于给定值 ***val\*** 的所有节点。 5 | 6 | **示例:** 7 | 8 | ``` 9 | 输入: 1->2->6->3->4->5->6, val = 6 10 | 输出: 1->2->3->4->5 11 | ``` 12 | 13 | # 解法 14 | 15 | ```javascript 16 | var removeElements = function(head, val) { 17 | // 找到第一个非排除元素 18 | while (head && head.val === val) { 19 | head = head.next; 20 | } 21 | if (!head) return null; 22 | let slow = head; 23 | let fast = head.next; 24 | while (fast) { 25 | if (fast.val !== val) { 26 | slow.next = fast; 27 | slow = slow.next; 28 | } 29 | fast = fast.next; 30 | } 31 | // 最后是以排除元素结尾时,需要清除之后的所有排除元素 32 | slow.next = null; 33 | return head; 34 | }; 35 | ``` 36 | 37 | # 分析 38 | 39 | 定义快慢两个指针,slow 指向上个非排除元素的元素,fast 指向当前元素,当当前元素是排除元素时,fast 向后移动一位,直到指向非排除元素,此时将 slow 的下个元素指向当前 fast 的元素即可 40 | 41 | 同时考虑一些边界情况 42 | 43 | * 数组元素都是排除元素 44 | * 元素以排除元素结尾 45 | 46 | # 解法2 47 | 48 | ```javascript 49 | var removeElements = function(head, val) { 50 | let fake = new ListNode(val - 1); 51 | fake.next = head; 52 | let slow = fake; 53 | let fast = fake.next; 54 | while (fast) { 55 | if (fast.val !== val) { 56 | slow.next = cur; 57 | slow = slow.next; 58 | } 59 | fast = fast.next; 60 | } 61 | slow.next = null; 62 | return fake.next; 63 | }; 64 | ``` 65 | 66 | # 分析 67 | 68 | 通过向头部添加一个假的节点,使得原数组头部是排除元素的情况得以解决,相比于解法1,更加的简洁 69 | 70 | # 解法3 71 | 72 | ```javascript 73 | var removeElements = function(head, val) { 74 | if (!head) return null; 75 | head.next = removeElements(head.next, val); 76 | return head.val === val ? head.next : head; 77 | }; 78 | ``` 79 | 80 | # 分析 81 | 82 | 递归的解法,只能说.....妙啊 -------------------------------------------------------------------------------- /206. Reverse Linked List.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 反转一个单链表。 5 | 6 | 示例: 7 | 8 | 输入: 1->2->3->4->5->NULL 9 | 输出: 5->4->3->2->1->NULL 10 | 进阶: 11 | 你可以迭代或递归地反转链表。你能否用两种方法解决这道题? 12 | 13 | 来源:力扣(LeetCode) 14 | 链接:https://leetcode-cn.com/problems/reverse-linked-list 15 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 16 | 17 | # 解法 18 | 19 | ```javascript 20 | var reverseList = function(head) { 21 | if (!head.next) return head; 22 | let fast = head; 23 | let slow = null; 24 | while (fast) { 25 | let temp = fast.next; 26 | fast.next = slow; 27 | slow = fast; 28 | fast = temp; 29 | } 30 | return slow; 31 | }; 32 | ``` 33 | 34 | # 分析 35 | 36 | 循环版本,比之前那个反转单向链表更加简洁 37 | 38 | 定义 fast 和 slow 指针,fast 比 slow 快一个节点,并将 fast 的下个节点暂时存储(因为之后 fast 的 next 节点会被修改),然后将 fast 的 next 反转指向 slow 节点,再将 slow 和 fast 同时向前移动一个节点 39 | 40 | 这里并没有通过 next 移动,因为 next 指向已经被反转了,如果按照 next 移动指针会造成死循环,所以直接赋值对应节点 41 | 42 | # 解法2 43 | 44 | ```javascript 45 | const reverseList = function(node) { 46 | if (!node || !node.next) return node; 47 | let reversedNode = reverseList(node.next); 48 | node.next.next = node; 49 | node.next = null; 50 | return reversedNode; 51 | }; 52 | ``` 53 | 54 | # 分析 55 | 56 | 递归版本,首先递归到链表尾部,并保证当前节点的 next 存在(也就是到处第二个节点),然后将节点的 next 的 next 指向自身,再将 next 删除(不删除就变成双向链表了) 57 | 58 | 例如 1 -> 2 -> 3 -> 4 -> 5,递归一次结果为 1 -> 2 -> 3 -> 4 <- 5,随后依次弹出调用栈,从后往前修改链表指向 59 | 60 | 并且由于要返回反转后的链表,所以需要返回原链表的尾部节点 5,所以需要让递归始终返回尾部节点 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /21. Merge Two Sorted Lists.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 5 | 6 | 示例: 7 | 8 | 输入:1->2->4, 1->3->4 9 | 输出:1->1->2->3->4->4 10 | 11 | 来源:力扣(LeetCode) 12 | 链接:https://leetcode-cn.com/problems/merge-two-sorted-lists 13 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 14 | 15 | # 解法 16 | 17 | ```javascript 18 | function ListNode(val) { 19 | this.val = val; 20 | this.next = null; 21 | } 22 | ListNode.prototype.add = function(num) { 23 | let listNode = new ListNode(num); 24 | this.next = listNode; 25 | return listNode; 26 | }; 27 | let l1 = new ListNode(3); 28 | l1.add(9); 29 | 30 | let l2 = new ListNode(2); 31 | l1.add(5); 32 | 33 | const mergeTwoLists = function(l1, l2) { 34 | if (l1 == null) return l2; 35 | if (l2 == null) return l1; 36 | if (l1.val < l2.val) { 37 | l1.next = mergeTwoLists(l1.next, l2); 38 | return l2; 39 | } else { 40 | l2.next = mergeTwoLists(l1, l2.next); 41 | return l1; 42 | } 43 | }; 44 | ``` 45 | 46 | # 分析 47 | 48 | 最终的结果可能是以下两种情况 49 | 50 | merge 函数: 51 | 52 | l1[0] + merge(l1.next,l2) l1[0] < l2[0] 53 | 54 | l2[0] + merge(l1,l2.next) l1[0] >= l2[0] 55 | 56 | 当 l2 的第一个元素大于 l1 的第一个元素时,从 l1 开始合并链表,同时递归合并 l1 的下一个元素和 l2 57 | 58 | 反之当 l1 的第一个元素大于等于 l2 的第一个元素时候,从 l2 开始合并链表,递归合并 l2 的下个元素和 l1 59 | 60 | 当 l1 或者 l2 到达链表尾部时,表示其中一个链表已经被遍历结束,此时直接将另一个链表拼在当前链表的结尾即可,同时调用栈开始逐层弹出,组成最终的链表并返回 61 | 62 | ps: 可以使用最简单的链表结构开始检测代码 63 | 64 | l1: 1->8 65 | 66 | l2: 3 -> 5 -------------------------------------------------------------------------------- /226. Invert Binary Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 翻转一棵二叉树。 5 | 6 | 示例: 7 | 8 | 输入: 9 | 10 | 4 11 | / \ 12 | 2 7 13 | / \ / \ 14 | 1 3 6 9 15 | 16 | 输出: 17 | 18 | 4 19 | / \ 20 | 7 2 21 | / \ / \ 22 | 9 6 3 1 23 | 来源:力扣(LeetCode) 24 | 链接:https://leetcode-cn.com/problems/invert-binary-tree 25 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 26 | 27 | # 解法 28 | 29 | ```javascript 30 | var invertTree = function(root) { 31 | if (!root) return null; 32 | [root.left, root.right] = [root.right, root.left]; 33 | root.left = invertTree(root.left); 34 | root.right = invertTree(root.right); 35 | return root; 36 | }; 37 | ``` 38 | 39 | # 分析 40 | 41 | 递归解法,左节点等于右节点,同时递归向下遍历 42 | 43 | # 解法2 44 | 45 | ```javascript 46 | var invertTree = function(root) { 47 | if (!root) return null; 48 | let queue = []; 49 | queue.push(root); 50 | while (queue.length) { 51 | let node = queue.shift(); 52 | [node.left, node.right] = [node.right, node.left]; 53 | if (node.right) queue.push(node.right); 54 | if (node.left) queue.push(node.left); 55 | } 56 | return root; 57 | }; 58 | ``` 59 | 60 | # 分析 61 | 62 | 广度遍历解法 63 | -------------------------------------------------------------------------------- /234. Palindrome Linked List.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 请判断一个链表是否为回文链表。 5 | 6 | 示例 1: 7 | 8 | 输入: 1->2 9 | 输出: false 10 | 示例 2: 11 | 12 | 输入: 1->2->2->1 13 | 输出: true 14 | 进阶: 15 | 你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题? 16 | 17 | 来源:力扣(LeetCode) 18 | 链接:https://leetcode-cn.com/problems/palindrome-linked-list 19 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 20 | 21 | # 解法 22 | 23 | ```javascript 24 | var isPalindrome = function(head) { 25 | let arr = []; 26 | let cur = head; 27 | while (cur) { 28 | arr.push(cur.val); 29 | cur = cur.next; 30 | } 31 | let p = 0; 32 | let q = arr.length - 1; 33 | while (p < q) { 34 | if (arr[p] === arr[q]) { 35 | p++; 36 | q--; 37 | } else { 38 | return false; 39 | } 40 | } 41 | return true; 42 | }; 43 | ``` 44 | 45 | # 分析 46 | 47 | 双指针 + 额外数组版本,空间复杂度为 O(n),利用数组存储链表的所有值 48 | 49 | 并通过头尾两个指针,当 p 和 q 指向的元素相同则像向中间靠拢,若不同则返回 false,当 p q 重合时说明是一个对称的数组,即回文链表 50 | 51 | # 解法2 52 | 53 | ```javascript 54 | var isPalindrome = function(head) { 55 | if (!head || !head.next) return true; 56 | // 快慢指针确定链表中点 57 | let fast = head; 58 | let slow = head; 59 | while (fast.next && fast.next.next) { 60 | fast = fast.next.next; 61 | slow = slow.next; 62 | } 63 | // 奇偶判断 64 | let isOdd = !fast.next; 65 | let leftStart = slow; 66 | let rightStart = slow.next; 67 | 68 | // 反转链表 69 | let cur = head; 70 | let next = cur.next; 71 | while (cur !== leftStart) { 72 | let temp = cur; 73 | cur = next; 74 | if (next.next) { 75 | next = next.next; 76 | cur.next = temp; 77 | } else { 78 | cur.next = temp; 79 | break; 80 | } 81 | } 82 | head.next = null; 83 | 84 | if (isOdd) { 85 | leftStart = leftStart.next; 86 | } 87 | // 逐个判断 88 | while (leftStart) { 89 | if (leftStart.val !== rightStart.val) { 90 | return false; 91 | } 92 | leftStart = leftStart.next; 93 | rightStart = rightStart.next; 94 | } 95 | return true; 96 | }; 97 | ``` 98 | 99 | # 分析 100 | 101 | 双指针版本,不需要额外数组,空间复杂度为 O(n) 102 | 103 | 1. 定义快慢两个指针,快指针前进两格,最终当快指针遍历结束时,慢指针恰好在中点 104 | 105 | 2. 另外根据中点,创建 leftStart 和 rightStart 两个遍历的起始点,例如 [1,2,3,3,2,1],则 leftStart 为左边的 3,rightStart 为右边的 3 106 | 107 | 3. 然后再从头到 leftStart 处**反转链表** 108 | 4. 从两个起始点逐个向后判断,如果都相等则返回 true,否则返回 false 109 | 110 | `这里还需要对数组的长度进行判断,当奇数时中点再中心,偶数时再中心靠左` 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /235. Lowest Common Ancestor of a Binary Search Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 5 | 6 | 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” 7 | 8 | 例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5] 9 | 10 | ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/binarysearchtree_improved.png) 11 | 12 | 示例 1: 13 | 14 | ``` 15 | 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 16 | 输出: 6 17 | 解释: 节点 2 和节点 8 的最近公共祖先是 6。 18 | ``` 19 | 20 | 示例 2: 21 | 22 | ``` 23 | 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 24 | 输出: 2 25 | 解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。 26 | ``` 27 | 28 | 29 | 说明: 30 | 31 | 所有节点的值都是唯一的。 32 | p、q 为不同节点且均存在于给定的二叉搜索树中。 33 | 34 | 来源:力扣(LeetCode) 35 | 链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree 36 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 37 | 38 | # 解法 39 | 40 | ```javascript 41 | var lowestCommonAncestor = function(root, p, q) { 42 | let arr = []; 43 | let index = 0; 44 | const push = function(root, val) { 45 | arr.push(root); 46 | if (val < root.val) { 47 | if (root.left) push(root.left, val); 48 | } else if (val > root.val) { 49 | if (root.right) push(root.right, val); 50 | } else { 51 | return root; 52 | } 53 | }; 54 | const find = function(root, val) { 55 | if (arr[index] === root) { 56 | index++; 57 | } else { 58 | return arr[index - 1]; 59 | } 60 | if (val < root.val) { 61 | return find(root.left, val); 62 | } else if (val > root.val) { 63 | return find(root.right, val); 64 | } else { 65 | return root; 66 | } 67 | }; 68 | push(root, p); 69 | return find(root, q); 70 | }; 71 | ``` 72 | 73 | # 分析 74 | 75 | 通过先递归遍历第一个参数 p,同时声明一个数组,每次递归查找时,将经过的元素放入数组中 76 | 77 | 找到第一个参数 p 后,开始递归遍历第二个参数 q,在递归查找时,每次经过一个元素,就和数组中的元素作比较,如果相同证明声明它是共通的祖先元素(但是还不能证明是最近的组件元素),并且向后移动指针 78 | 79 | 一旦经过的元素和数组中的元素不相同,证明此时数组的**前一个元素**是 p,q 的最近祖先 80 | 81 | 82 | # 解法2 83 | 84 | ```javascript 85 | var lowestCommonAncestor = function(root, p, q) { 86 | if (p > root.val && q > root.val) { 87 | return lowestCommonAncestor(root.right, p, q); 88 | } 89 | if (p < root.val && q < root.val) { 90 | return lowestCommonAncestor(root.left, p, q); 91 | } 92 | return root; 93 | }; 94 | ``` 95 | 96 | # 分析 97 | 98 | 递归版本解法,核心思路是从上而下遍历二叉树,当 p,q 同时大于或同时小于当前遍历的二叉树节点,说明当前节点是 p,q 的公共祖先,但是并不是最近的公共祖先 99 | 100 | 在从上往下遍历的过程中,一旦发现 p 小于当前节点 (假设 p 大于 q),而 q 大于当前节点,则证明当前节点开始是分岔点,也就说当前节点是最近的公共祖先 101 | 102 | 或者当前节点等于 p 也说明它是公共祖先 103 | 104 | # 解法3 105 | 106 | ```javascript 107 | var lowestCommonAncestor = function(root, p, q) { 108 | let res = root; 109 | let stack = []; 110 | stack.push(res); 111 | while (stack.length) { 112 | let node = stack.pop(); 113 | if (p > node.val && q > node.val) { 114 | stack.push(node.right); 115 | } else if (p < node.val && q < node.val) { 116 | stack.push(node.left); 117 | } else { 118 | return node; 119 | } 120 | } 121 | }; 122 | ``` 123 | 124 | # 分析 125 | 126 | ​ 利用栈模拟递归的解法,核心原理同上 127 | -------------------------------------------------------------------------------- /257. Binary Tree Paths.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉树,返回所有从根节点到叶子节点的路径。 5 | 6 | 说明: 叶子节点是指没有子节点的节点。 7 | 8 | 示例: 9 | 10 | ``` 11 | 输入: 12 | 13 | 1 14 | / \ 15 | 2 3 16 | \ 17 | 5 18 | 19 | 输出: ["1->2->5", "1->3"] 20 | 21 | 解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3 22 | ``` 23 | 24 | 来源:力扣(LeetCode) 25 | 链接:https://leetcode-cn.com/problems/binary-tree-paths 26 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 27 | 28 | # 解法 29 | 30 | ```javascript 31 | const generatePath = (node, str, arr) => { 32 | if (!node) return; 33 | if (!str) { 34 | str += node.val; 35 | } else { 36 | str += "->" + node.val; 37 | } 38 | if (!node.left && !node.right) { 39 | arr.push(str); 40 | return; 41 | } 42 | generatePath(node.left, str, arr); 43 | generatePath(node.right, str, arr); 44 | }; 45 | 46 | var binaryTreePaths = function(root) { 47 | if (!root) return; 48 | let arr = []; 49 | generatePath(root, "", arr); 50 | return arr; 51 | }; 52 | ``` 53 | 54 | # 分析 55 | 56 | 递归解法,从上到下遍历树,每次遍历都记录加上当前路径并传递给下一层,当是叶子节点时,将经过的所有路径放到 arr 数组中 57 | 58 | # 解法2 59 | 60 | ```javascript 61 | var binaryTreePaths = function(root) { 62 | if (!root) return []; 63 | let arr = []; 64 | let queue = []; 65 | queue.push(root); 66 | while (queue.length) { 67 | let node = queue.shift(); 68 | let path = node.path ? node.path + "->" + node.val : String(node.val); 69 | if (!node.left && !node.right) { 70 | arr.push(path); 71 | } 72 | if (node.left) { 73 | node.left.path = path; 74 | queue.push(node.left); 75 | } 76 | if (node.right) { 77 | node.right.path = path; 78 | queue.push(node.right); 79 | } 80 | } 81 | return arr; 82 | }; 83 | ``` 84 | 85 | # 分析 86 | 87 | 广度遍历解法,每次入队时添加一个 path 属性,path 属性将当前节点之前所有经过的节点和当前的节点值拼接 88 | 89 | 然后当当前节点是子节点则直接放到 arr 数组中 90 | 91 | -------------------------------------------------------------------------------- /26. Remove Duplicates from Sorted Array.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 5 | 6 | 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 7 | 8 | 示例 1: 9 | 10 | > 给定数组 nums = [1,1,2], 11 | > 12 | > 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 13 | > 14 | > 你不需要考虑数组中超出新长度后面的元素。 15 | 16 | 示例 2: 17 | 18 | > 给定 nums = [0,0,1,1,1,2,2,3,3,4], 19 | > 20 | > 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 21 | > 22 | > 你不需要考虑数组中超出新长度后面的元素。 23 | 24 | 说明: 25 | 26 | 为什么返回数值是整数,但输出的答案是数组呢? 27 | 28 | 请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 29 | 30 | 你可以想象内部操作如下: 31 | 32 | ```java 33 | // nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 34 | int len = removeDuplicates(nums); 35 | 36 | // 在函数里修改输入数组对于调用者是可见的。 37 | // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 38 | for (int i = 0; i < len; i++) { 39 | print(nums[i]); 40 | } 41 | ``` 42 | 43 | 来源:力扣(LeetCode) 44 | 链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array 45 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 46 | 47 | # 解法 48 | 49 | ```javascript 50 | const removeDuplicates = nums => { 51 | let length = 1; 52 | for (let i = 1; i < nums.length; i++) { 53 | if (nums[i] === nums[i - 1]) { 54 | } else { 55 | nums[length] = nums[i]; 56 | length++; 57 | } 58 | } 59 | return length; 60 | }; 61 | 62 | let arr = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4]; 63 | 64 | console.log(removeDuplicates(arr)); 65 | 66 | console.log(arr); 67 | ``` 68 | 69 | # 分析 70 | 71 | 由于此题只需要**修改**数组前几位元素,去除重复的元素,并修改为非重复的元素,所以可以使用**取巧**的方法,即 72 | 73 | `不需要关心数组后面的元素,将前面的元素修改,并返回非重复元素的长度即可` 74 | 75 | 所以我们可以用过 length 变量记录非重复元素的数量,并且也可以通过它来定位下标来修改元素 76 | 77 | 当遇到非重复元素时 length ++,然后将当前的非重复元素赋值给当前数组 length 下标的元素(必须是 length 下标,而不是 i -1 下标,因为可能存在多个相同元素,此时需要被修改的元素就不是 i-1 的位置了) 78 | 79 | -------------------------------------------------------------------------------- /27. Remove Element.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。 5 | 6 | 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 7 | 8 | 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 9 | 10 | 示例 1: 11 | 12 | 13 | 14 | > 给定 nums = [3,2,2,3], val = 3, 15 | > 16 | > 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 17 | > 18 | > 你不需要考虑数组中超出新长度后面的元素。 19 | > 20 | > 来源:力扣(LeetCode) 21 | > 链接:https://leetcode-cn.com/problems/remove-element 22 | > 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 23 | 24 | 示例 2: 25 | 26 | > 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 27 | > 28 | > 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。 29 | > 30 | > 注意这五个元素可为任意顺序。 31 | > 32 | > 你不需要考虑数组中超出新长度后面的元素。 33 | > 34 | > 来源:力扣(LeetCode) 35 | > 链接:https://leetcode-cn.com/problems/remove-element 36 | > 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 37 | 38 | 说明: 39 | 40 | 为什么返回数值是整数,但输出的答案是数组呢? 41 | 42 | 请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 43 | 44 | 你可以想象内部操作如下: 45 | 46 | ```java 47 | // nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝 48 | int len = removeElement(nums, val); 49 | 50 | // 在函数里修改输入数组对于调用者是可见的。 51 | // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 52 | for (int i = 0; i < len; i++) { 53 | print(nums[i]); 54 | } 55 | ``` 56 | 57 | 58 | 59 | 来源:力扣(LeetCode) 60 | 链接:https://leetcode-cn.com/problems/remove-element 61 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 62 | 63 | # 解法 64 | 65 | ```javascript 66 | var removeElement = function(nums, val) { 67 | let temp = 0; 68 | for (let i = 0; i < nums.length; i++) { 69 | if (nums[i] !== val) { 70 | nums[temp] = nums[i]; 71 | temp++; 72 | } 73 | } 74 | return temp 75 | }; 76 | ``` 77 | 78 | # 分析 79 | 80 | 定义两个指针,一个“快”指针(i),一个“慢”指针(temp),每次遍历时,如果当前元素不等于排除的元素,快慢指针各加一,同时将快指针对应的值赋值给慢指针对应的值,否则如果遇到排除的元素,快指针加一,不会赋值,慢指针也不会改变 -------------------------------------------------------------------------------- /283. Move Zeroes.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 5 | 6 | 示例: 7 | 8 | 输入: [0,1,0,3,12] 9 | 输出: [1,3,12,0,0] 10 | 说明: 11 | 12 | 必须在原数组上操作,不能拷贝额外的数组。 13 | 尽量减少操作次数 14 | 15 | 来源:力扣(LeetCode) 16 | 链接:https://leetcode-cn.com/problems/move-zeroes 17 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 18 | 19 | # 解法 20 | 21 | ```javascript 22 | var moveZeroes = function(nums) { 23 | let slow = 0; 24 | for (let i = 0; i < nums.length; i++) { 25 | if (nums[i] === 0) continue; 26 | nums[slow++] = nums[i]; 27 | } 28 | for (let i = slow; i < nums.length; i++) { 29 | nums[i] = 0; 30 | } 31 | }; 32 | ``` 33 | 34 | # 分析 35 | 36 | 定义一个快指针和一个慢指针,慢指针指向非 0 的元素,当快指针遇到非 0 元素时,将慢指针指向的位置替换为快指针指向的非 0 元素 37 | 38 | 同时当快指针走完以后,慢指针到数组尾部的所有元素填充 0 即可 39 | 40 | 此时为原地算法,不需要额外的空间 41 | 42 | -------------------------------------------------------------------------------- /344. Reverse String.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 5 | 6 | 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 7 | 8 | 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 9 | 10 | 11 | 12 | 示例 1: 13 | 14 | 输入:["h","e","l","l","o"] 15 | 输出:["o","l","l","e","h"] 16 | 示例 2: 17 | 18 | 输入:["H","a","n","n","a","h"] 19 | 输出:["h","a","n","n","a","H"] 20 | 21 | 来源:力扣(LeetCode) 22 | 链接:https://leetcode-cn.com/problems/reverse-string 23 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 24 | 25 | # 解法 26 | 27 | ```javascript 28 | var reverseString = function(s) { 29 | let start = 0; 30 | let end = s.length - 1; 31 | while (start < end) { 32 | [s[start], s[end]] = [s[end], s[start]]; 33 | start++; 34 | end--; 35 | } 36 | return s 37 | }; 38 | ``` 39 | 40 | # 分析 41 | 42 | ​ 定义头尾两个指针,循环交换元素即可 43 | 44 | -------------------------------------------------------------------------------- /345. Reverse Vowels of a String.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 编写一个函数,以字符串作为输入,反转该字符串中的元音字母。 5 | 6 | 示例 1: 7 | 8 | 输入: "hello" 9 | 输出: "holle" 10 | 示例 2: 11 | 12 | 输入: "leetcode" 13 | 输出: "leotcede" 14 | 说明: 15 | 元音字母不包含字母"y"。 16 | 17 | 来源:力扣(LeetCode) 18 | 链接:https://leetcode-cn.com/problems/reverse-vowels-of-a-string 19 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 20 | 21 | # 解法 22 | 23 | ```javascript 24 | const WHITE_LIST = new Set(["a", "o", "e", "i", "o", "u","A","O","E","I","O","U"]); 25 | 26 | var reverseVowels = function(s) { 27 | s = s.split(""); 28 | let start = 0; 29 | let end = s.length - 1; 30 | while (start < end) { 31 | if (!WHITE_LIST.has(s[start])) { 32 | start++; 33 | continue; 34 | } 35 | if (!WHITE_LIST.has(s[end])) { 36 | end--; 37 | continue; 38 | } 39 | [s[start], s[end]] = [s[end], s[start]]; 40 | start++; 41 | end--; 42 | } 43 | return s.join(""); 44 | }; 45 | ``` 46 | 47 | # 分析 48 | 49 | 设置头尾双指针,同时添加白名单, 指针只可能停留在白名单列出的字符下面,循环并交换两个指针指向的字符即可 50 | -------------------------------------------------------------------------------- /349. Intersection of Two Arrays.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定两个数组,编写一个函数来计算它们的交集。 5 | 6 | 示例 1: 7 | 8 | 输入: nums1 = [1,2,2,1], nums2 = [2,2] 9 | 输出: [2] 10 | 示例 2: 11 | 12 | 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] 13 | 输出: [9,4] 14 | 说明: 15 | 16 | 输出结果中的每个元素一定是唯一的。 17 | 我们可以不考虑输出结果的顺序。 18 | 19 | 来源:力扣(LeetCode) 20 | 链接:https://leetcode-cn.com/problems/intersection-of-two-arrays 21 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 22 | 23 | # 解法 24 | 25 | ```javascript 26 | var intersection = function(nums1, nums2) { 27 | return [...new Set(nums2.filter(num => nums1.includes(num)))]; 28 | }; 29 | ``` 30 | 31 | # 分析 32 | 33 | 使用内建的 api 实现的差集 34 | 35 | # 解法2 36 | 37 | ```javascript 38 | var intersection = function(nums1, nums2) { 39 | nums1 = nums1.sort((a, b) => a - b); 40 | nums2 = nums2.sort((a, b) => a - b); 41 | let p, 42 | q, 43 | res = new Set(); 44 | p = q = 0; 45 | 46 | while (p < nums1.length && q < nums2.length) { 47 | if (nums1[p] === nums2[q]) { 48 | res.add(nums1[p]); 49 | p++; 50 | q++; 51 | } else if (nums1[p] > nums2[q]) { 52 | q++; 53 | } else { 54 | p++; 55 | } 56 | } 57 | return [...res]; 58 | } 59 | ``` 60 | 61 | # 分析 62 | 63 | 双指针解法,**首先需要对两个数组进行排序**,然后定义 p,q 两个指针 64 | 65 | 若 p,q 指向的元素相等则放入 res 数组中,否则小的那个向后移动一位,直到 nums1 遍历完毕 66 | 67 | -------------------------------------------------------------------------------- /350. Intersection of Two Arrays II.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定两个数组,编写一个函数来计算它们的交集。 5 | 6 | 示例 1: 7 | 8 | ``` 9 | 输入: nums1 = [1,2,2,1], nums2 = [2,2] 10 | 输出: [2,2] 11 | ``` 12 | 13 | 示例 2: 14 | 15 | ``` 16 | 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] 17 | 输出: [4,9] 18 | ``` 19 | 20 | 说明: 21 | 22 | 输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。 23 | 我们可以不考虑输出结果的顺序。 24 | 进阶: 25 | 26 | 如果给定的数组已经排好序呢?你将如何优化你的算法? 27 | 如果 nums1 的大小比 nums2 小很多,哪种方法更优? 28 | 如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办? 29 | 30 | 来源:力扣(LeetCode) 31 | 链接:https://leetcode-cn.com/problems/intersection-of-two-arrays-ii 32 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 33 | 34 | # 解法 35 | 36 | ```javascript 37 | var intersect = function(nums1, nums2) { 38 | nums1 = nums1.sort((a, b) => a - b); 39 | nums2 = nums2.sort((a, b) => a - b); 40 | let p, 41 | q, 42 | res = []; 43 | p = q = 0; 44 | while (p < nums1.length && q < nums2.length) { 45 | if (nums1[p] < nums2[q]) { 46 | p++; 47 | } else if (nums1[p] > nums2[q]) { 48 | q++; 49 | } else { 50 | res.push(nums1[p]); 51 | p++; 52 | q++; 53 | } 54 | } 55 | return res; 56 | }; 57 | ``` 58 | 59 | # 分析 60 | 61 | 和交集1的解法相同,使用数组替代 Set(因为不需要去重) 62 | 63 | 如果是有序数组性能会更加好,因为不需要主动排序 64 | 65 | # 解法2 66 | 67 | ```javascript 68 | var intersect = function(nums1, nums2) { 69 | let res = []; 70 | let map = {}; 71 | for (let i = 0; i < nums1.length; i++) { 72 | if (map[nums1[i]]) { 73 | map[nums1[i]] += 1; 74 | } else { 75 | map[nums1[i]] = 1; 76 | } 77 | } 78 | for (let i = 0; i < nums2.length; i++) { 79 | if (map[nums2[i]]) { 80 | res.push(nums2[i]); 81 | map[nums2[i]] -= 1; 82 | } 83 | if (map[nums2[i]] === 0) delete map[nums2[i]]; 84 | } 85 | return res; 86 | }; 87 | ``` 88 | 89 | # 分析 90 | 91 | 哈希表解法,遍历一遍 nums1 存入哈希表中,如果存在相同元素则值+1 92 | 93 | 生成 nums1 的哈希表后,遍历 nums2,如果当前元素存在再哈希表中,则放入嘴中数组中返回,同时哈希表中的元素值 -1 (为了防止 num2 的某个元素超过 nums1 中该元素的最大数量,导致“并集”的效果) 94 | 95 | -------------------------------------------------------------------------------- /392. Is Subsequence.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 5 | 6 | 你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。 7 | 8 | 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。 9 | 10 | 示例 1: 11 | s = "abc", t = "ahbgdc" 12 | 13 | 返回 true. 14 | 15 | 示例 2: 16 | s = "axc", t = "ahbgdc" 17 | 18 | 返回 false. 19 | 20 | 后续挑战 : 21 | 22 | 如果有大量输入的 S,称作S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码? 23 | 24 | 来源:力扣(LeetCode) 25 | 链接:https://leetcode-cn.com/problems/is-subsequence 26 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 27 | 28 | # 解法 29 | 30 | ```javascript 31 | var isSubsequence = function(s, t) { 32 | let j = 0; 33 | for (let i = 0; i < t.length; i++) { 34 | if (s[j] === t[i]) { 35 | j++; 36 | if (j === s.length) return true; 37 | } 38 | } 39 | return false; 40 | }; 41 | ``` 42 | 43 | # 分析 44 | 45 | 双指针解法,定义 i,j 两个指针,遍历 t(长字符串),当在 s 中匹配到和 t 相同的元素时,i 指针往后移动 46 | 47 | 当 i 指针移动到末尾时,返回 true,否则当 j 指针遍历完毕,返回 false 48 | 49 | -------------------------------------------------------------------------------- /394. Decode String.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个经过编码的字符串,返回它解码后的字符串。 5 | 6 | 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 7 | 8 | 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。 9 | 10 | 此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。 11 | 12 | 示例: 13 | 14 | s = "3[a]2[bc]", 返回 "aaabcbc". 15 | s = "3[a2[c]]", 返回 "accaccacc". 16 | s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef". 17 | 18 | 来源:力扣(LeetCode) 19 | 链接:https://leetcode-cn.com/problems/decode-string 20 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 21 | 22 | # 解法1 23 | 24 | ```javascript 25 | var decodeString = function(s) { 26 | let count = ""; 27 | let item = { 28 | count: "", 29 | value: "" 30 | }; 31 | let stack = []; 32 | stack.push(item); 33 | for (let i = 0; i < s.length; i++) { 34 | switch (true) { 35 | case !isNaN(Number(s[i])): 36 | count += s[i]; 37 | break; 38 | case s[i] === "[": 39 | item = { 40 | count, 41 | value: "" 42 | }; 43 | count = ""; 44 | stack.push(item); 45 | break; 46 | case s[i] === "]": 47 | item = stack.pop(); 48 | let str = item.value.repeat(Number(item.count)); 49 | item = stack[stack.length - 1]; 50 | item.value += str; 51 | break; 52 | default: 53 | item = stack[stack.length - 1]; 54 | item.value += s[i]; 55 | } 56 | } 57 | return stack.pop().value; 58 | }; 59 | ``` 60 | 61 | # 分析 62 | 63 | 循环 + 利用栈来管理层级,参数 s 中每一组 [] 对应的就是一层栈的元素,一个元素是一个对象,包含以下属性 64 | 65 | * Count:当前内元素重复的次数 66 | * value:当前层级内元素的字符串 67 | 68 | 当遇到 `[` 时,将一个新的元素推入栈中,同时将 [] 前的数字作为 count 属性赋值给元素 69 | 70 | 当遇到 `]` 时,弹出栈顶的元素,并将 `[` 和 `]` 中间的所有字符作为元素的 value 属性,并乘以 count 71 | 72 | -------------------------------------------------------------------------------- /404. Sum of Left Leaves.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 计算给定二叉树的所有左叶子之和。 5 | 6 | 示例: 7 | 8 | 3 9 | / \ 10 | 9 20 11 | / \ 12 | 15 7 13 | 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24 14 | 15 | 来源:力扣(LeetCode) 16 | 链接:https://leetcode-cn.com/problems/sum-of-left-leaves 17 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 18 | 19 | # 解法 20 | 21 | ```javascript 22 | const isLeaf = node => !node.left && !node.right; 23 | 24 | var sumOfLeftLeaves = function(root) { 25 | if (!root || isLeaf(root)) return 0; 26 | let count = 0; 27 | const sum = root => { 28 | if (!root) return 0; 29 | sum(root.left); 30 | if (root.right && !isLeaf(root.right)) { 31 | sum(root.right); 32 | } 33 | if (isLeaf(root)) { 34 | count += root.val; 35 | } 36 | }; 37 | sum(root); 38 | return count; 39 | }; 40 | ``` 41 | 42 | # 分析 43 | 44 | 递归的解法,**当当前节点的右子节点不是一个叶子节点时**,才进行递归 45 | 46 | 函数一开始排除了一些特殊情况,即树只有一个节点,则直接返回 0,因为没有左叶子节点 47 | 48 | # 解法2 49 | 50 | ```javascript 51 | 52 | const isLeaf = node => !node.left && !node.right; 53 | 54 | var sumOfLeftLeaves = function(root) { 55 | if (!root || isLeaf(root)) return 0; 56 | let count = 0; 57 | let queue = []; 58 | queue.push(root); 59 | while (queue.length) { 60 | let node = queue.shift(); 61 | if (isLeaf(node)) { 62 | count += node.val; 63 | } 64 | if (node.left) { 65 | queue.push(node.left); 66 | } 67 | if (node.right && !isLeaf(node.right)) { 68 | queue.push(node.right); 69 | } 70 | } 71 | return count; 72 | }; 73 | ``` 74 | 75 | # 分析 76 | 77 | 广度遍历的解法,和递归同理,**当节点的右子节点是叶子节点时**取消入队,其余情况都放入队列中 78 | 79 | 同时每次判断当前节点是否是叶子节点,如果是叶子节点,根据上述入队规则,**一定是左叶子节点**,累加即可 80 | 81 | -------------------------------------------------------------------------------- /429. N-ary Tree Level Order Traversal.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 5 | 6 | 给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。 7 | 8 | 例如,给定一个 3叉树 : 9 | 10 | ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/12/narytreeexample.png) 11 | 12 | 返回其层序遍历: 13 | 14 | ``` 15 | [ 16 | [1], 17 | [3,2,4], 18 | [5,6] 19 | ] 20 | ``` 21 | 22 | 23 | 说明: 24 | 25 | 树的深度不会超过 1000。 26 | 树的节点总数不会超过 5000。 27 | 28 | 来源:力扣(LeetCode) 29 | 链接:https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal 30 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 31 | 32 | # 解法 33 | 34 | ```javascript 35 | const traverseTree = (node, arr, depth = 0) => { 36 | if (!node) return; 37 | (arr[depth] || (arr[depth] = [])).push(node.val); 38 | for (let i = 0; i < node.children.length; i++) { 39 | func(node.children[i], arr, depth + 1); 40 | } 41 | }; 42 | 43 | var levelOrder = function(root) { 44 | let arr = []; 45 | if (!root) return arr; 46 | traverseTree(root, arr); 47 | return arr; 48 | }; 49 | ``` 50 | 51 | # 分析 52 | 53 | 递归解法,同 107 题 54 | 55 | # 解法2 56 | 57 | ```javascript 58 | var levelOrder = function(root) { 59 | if (!root) return []; 60 | let res = []; 61 | let queue = []; 62 | queue.push(root); 63 | while (queue.length) { 64 | let length = queue.length; 65 | let arr = []; 66 | for (let i = 0; i < length; i++) { 67 | let node = queue.shift(); 68 | if (!node) continue; 69 | arr.push(node.val); 70 | for (let i = 0; i < node.children.length; i++) { 71 | queue.push(node.children[i]); 72 | } 73 | } 74 | res.push(arr); 75 | } 76 | return res; 77 | }; 78 | ``` 79 | 80 | # 分析 81 | 82 | 广度解法 83 | 84 | -------------------------------------------------------------------------------- /501. Find Mode in Binary Search Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。 5 | 6 | 假定 BST 有如下定义: 7 | 8 | 结点左子树中所含结点的值小于等于当前结点的值 9 | 结点右子树中所含结点的值大于等于当前结点的值 10 | 左子树和右子树都是二叉搜索树 11 | 例如: 12 | 给定 BST [1,null,2,2], 13 | 14 | ``` 15 | 1 16 | \ 17 | 2 18 | / 19 | 2 20 | ``` 21 | 22 | 返回[2]. 23 | 24 | 提示:如果众数超过1个,不需考虑输出顺序 25 | 26 | 进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内) 27 | 28 | 来源:力扣(LeetCode) 29 | 链接:https://leetcode-cn.com/problems/find-mode-in-binary-search-tree 30 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 31 | 32 | 33 | 34 | # 解法 35 | 36 | ```javascript 37 | const traverse = (node, arr) => { 38 | if (!node) return; 39 | traverse(node.left, arr); 40 | arr.push(node.val); 41 | traverse(node.right, arr); 42 | }; 43 | 44 | var findMode = function(root) { 45 | let arr = []; 46 | if (!root) return arr; 47 | traverse(root, arr); 48 | let start = 0; 49 | let end = 0; 50 | let res = {}; 51 | // 获取数组众数 52 | while (end < arr.length) { 53 | let count = 0; 54 | while (arr[end] === arr[start]) { 55 | end++; 56 | count++; 57 | } 58 | if (Array.isArray(res[count])) { 59 | res[count].push(arr[start]); 60 | } else { 61 | res[count] = [arr[start]]; 62 | } 63 | start = end; 64 | } 65 | let keys = Object.keys(res); 66 | return res[keys[keys.length - 1]]; 67 | }; 68 | ``` 69 | 70 | # 分析 71 | 72 | 由于是二叉搜索树,所以中序遍历可以获得顺序数组,随后获取数组的众数即可 73 | -------------------------------------------------------------------------------- /53. Maximum Subarray.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 5 | 6 | 示例: 7 | 8 | 输入: [-2,1,-3,4,-1,2,1,-5,4], 9 | 输出: 6 10 | 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 11 | 进阶: 12 | 13 | 如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。 14 | 15 | 来源:力扣(LeetCode) 16 | 链接:https://leetcode-cn.com/problems/maximum-subarray 17 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 18 | 19 | # 解法 20 | 21 | ```javascript 22 | var maxSubArray = function(nums) { 23 | let res = nums[0]; 24 | let maxDistance = nums[0]; // f(nums[i - 1]) 25 | for (let i = 1; i < nums.length; i++) { 26 | maxDistance = Math.max(nums[i], maxDistance + nums[i]); // 计算出以当前节点结尾的最大值 f(nums[i]) 27 | res = Math.max(maxDistance, res); 28 | } 29 | return res; 30 | }; 31 | ``` 32 | 33 | # 分析 34 | 35 | 动态规划解法,**动态规划要求能够罗列出一个子序列的递推关系,并且动态规划往往是以数组某一个元素作为结束点向前递推的** 36 | 37 | 这里遍历数组的每个元素,将当前元素作为结束点向前递推,每次求出到这个元素为止的最大值 maxDistance,最终将所有元素的 maxDistance 的最大值输出即可 38 | 39 | # 解法2 40 | 41 | ```javascript 42 | var maxSubArray = function(nums) { 43 | // dps 保存每个元素的 dp,也就是以这个元素结尾的最大值 44 | // dp 是一个能够实现动态规划状态转移方程的节点,即有递推关系 45 | let dps = []; 46 | for (let i = 0; i < nums.length; i++) { 47 | dps[i] = Math.max( 48 | (dps[i - 1] == null ? 0 : dps[i - 1]) + nums[i], 49 | nums[i] 50 | ); 51 | } 52 | return dps.reduce((pre, cur) => Math.max(pre, cur), -Infinity); 53 | }; 54 | ``` 55 | 56 | # 分析 57 | 58 | 和解法1相同,但是更能体现动态规划的要点 59 | 60 | * 最优子结构(大问题的最优解(dp[i])由小问题的最优解(dp[i - 1])得出) 61 | * 无后效性(每个节点基于 dp[i - 1],至于 dp[i - 1] 如何算出来,当前步骤不关心,即**未来于过去无关**) 62 | 63 | 关系式为:f(X) = min( f(X-1) + nums[X] , nums[X]) 64 | 65 | X 为当前元素下标,如果 f(X-1) 为负债元素(< 0 ),则直接取当前元素作为当前元素的 f(X) -------------------------------------------------------------------------------- /530. Minimum Absolute Difference in BST.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个所有节点为非负值的二叉搜索树,求树中任意两节点的差的绝对值的最小值。 5 | 6 | 示例 : 7 | 8 | 输入: 9 | 10 | ``` 11 | 1 12 | \ 13 | 3 14 | / 15 | 2 16 | ``` 17 | 18 | 输出: 19 | 1 20 | 21 | 解释: 22 | 最小绝对差为1,其中 2 和 1 的差的绝对值为 1(或者 2 和 3)。 23 | 注意: 树中至少有2个节点。 24 | 25 | 来源:力扣(LeetCode) 26 | 链接:https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst 27 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 28 | 29 | # 解法 30 | 31 | ```javascript 32 | var getMinimumDifference = function(root) { 33 | let res; 34 | let pre; 35 | const traverse = node => { 36 | if (!node) return; 37 | traverse(node.left); 38 | 39 | if (pre == null) { 40 | pre = node.val; 41 | } else { 42 | if (res == null) { 43 | res = Math.abs(pre - node.val); 44 | } else { 45 | res = Math.min(res, Math.abs(pre - node.val)); 46 | } 47 | pre = node.val; 48 | } 49 | traverse(node.right); 50 | }; 51 | traverse(root); 52 | return res; 53 | }; 54 | ``` 55 | 56 | # 分析 57 | 58 | ​ 由于是二叉搜索树,所以中序遍历可以顺序得到二叉搜索树的每个元素,同时可以在每次遍历时就计算绝对值最小的两个元素 59 | 60 | -------------------------------------------------------------------------------- /532. K-diff Pairs in an Array.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个整数数组和一个整数 k, 你需要在数组里找到不同的 k-diff 数对。这里将 k-diff 数对定义为一个整数对 (i, j), 其中 i 和 j 都是数组中的数字,且两数之差的绝对值是 k. 5 | 6 | 示例 1: 7 | 8 | 输入: [3, 1, 4, 1, 5], k = 2 9 | 输出: 2 10 | 解释: 数组中有两个 2-diff 数对, (1, 3) 和 (3, 5)。 11 | 尽管数组中有两个1,但我们只应返回不同的数对的数量。 12 | 示例 2: 13 | 14 | 输入:[1, 2, 3, 4, 5], k = 1 15 | 输出: 4 16 | 解释: 数组中有四个 1-diff 数对, (1, 2), (2, 3), (3, 4) 和 (4, 5)。 17 | 示例 3: 18 | 19 | 输入: [1, 3, 1, 5, 4], k = 0 20 | 输出: 1 21 | 解释: 数组中只有一个 0-diff 数对,(1, 1)。 22 | 注意: 23 | 24 | 数对 (i, j) 和数对 (j, i) 被算作同一数对。 25 | 数组的长度不超过10,000。 26 | 所有输入的整数的范围在 [-1e7, 1e7]。 27 | 28 | 来源:力扣(LeetCode) 29 | 链接:https://leetcode-cn.com/problems/k-diff-pairs-in-an-array 30 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 31 | 32 | # 解法 33 | 34 | ```javascript 35 | let findPairs = function(nums, k) { 36 | if (k < 0) return 0; 37 | let map = {}; 38 | let count = 0; 39 | for (let i = 0; i < nums.length; i++) { 40 | if (map[nums[i]] == null) map[nums[i]] = 0; 41 | map[nums[i]]++; 42 | } 43 | 44 | Object.keys(map).forEach(key => { 45 | // 当 k = 0 时是特殊情况 46 | // 此时统计元素出现的次数即可 47 | if (k === 0) { 48 | if (map[key] > 1) count++; 49 | } else { 50 | // k 不为 0 时, 去哈希表中找对应节点即可 51 | if (map[Number(key) + k]) count++; 52 | } 53 | }); 54 | 55 | return count; 56 | }; 57 | 58 | console.log(findPairs([3, 1, 4, 1, 5], 2)); 59 | ``` 60 | 61 | # 分析 62 | 63 | 哈希表解法,将 nums 数组按照出现次数转为哈希表,然后遍历哈希表,分为两种情况 64 | 65 | * k=0 时,统计出现超过一次的元素,由于是哈希表所以不会重复统计 66 | * k 不为0 时,统计当前元素 + k 的值是否存在于哈希表中,这里虽然要求求 diff 也就是差值为 k 的元素,一般情况可能需要考虑 + k 和 -k 的情况,但是由于是哈希表,所以只考虑 + k 的情况(例如这里 3,5 的差为 2,由于是哈希表,只需遍历 3+2 的情况即可,不需要遍历 5-2,否则会重复遍历) 67 | 68 | # 解法2 69 | 70 | ```javascript 71 | let findPairs = function(nums, k) { 72 | if (k < 0) return 0; 73 | let count = 0; 74 | let map = {}; 75 | if (k === 0) { 76 | for (let i = 0; i < nums.length; i++) { 77 | if (map[nums[i]] == null) map[nums[i]] = 0; 78 | map[nums[i]]++; 79 | } 80 | Object.keys(map).forEach(key => { 81 | if (map[key] > 1) count++; 82 | }); 83 | return count; 84 | } 85 | 86 | nums.sort((a, b) => a - b); 87 | let left = 0; 88 | let right = 1; 89 | while (right < nums.length) { 90 | if (nums[right] - nums[left] > k) { 91 | left++; 92 | } else if (nums[right] - nums[left] < k) { 93 | right++; 94 | } else { 95 | // 判断是否和前者相同,防止出现重复的 diff 对 96 | // 例如 [1,1,2,2] 防止出现重复添加 97 | if (nums[left] !== nums[left - 1] && nums[right] !== nums[right - 1]) { 98 | count++; 99 | } 100 | left++; 101 | right++; 102 | } 103 | } 104 | 105 | return count; 106 | }; 107 | ``` 108 | 109 | # 分析 110 | 111 | 双指针解法,由于 k = 0 的特殊性,所以还是需要判断 k = 0 和 != 0 的情况 112 | 113 | * 当 k =0 时,和哈希表解法相同 114 | * 当 k != 0 时,定义 left,right 两个指针,每次判断 right 和 left 指向的元素的差值,并尝试找到差值为 k 时 left 和 right 的指向(即差值大于 k 时,left ++ ,否则 right ++),当差值 = k 时,还要判断 left 和 right ,是否还和前一个元素相同,防止出现重复添加的情况 -------------------------------------------------------------------------------- /538. Convert BST to Greater Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。 5 | 6 | 例如: 7 | 8 | ``` 9 | 输入: 二叉搜索树: 10 | 5 11 | / \ 12 | 2 13 13 | 14 | 输出: 转换为累加树: 15 | 18 16 | / \ 17 | 20 13 18 | ``` 19 | 20 | 来源:力扣(LeetCode) 21 | 链接:https://leetcode-cn.com/problems/convert-bst-to-greater-tree 22 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 23 | 24 | # 解法 25 | 26 | ```javascript 27 | var convertBST = function(root) { 28 | let num = 0; 29 | const traverse = node => { 30 | if (!node) return 0; 31 | traverse(node.right); 32 | node.val += num; 33 | num = node.val; 34 | traverse(node.left); 35 | }; 36 | traverse(root); 37 | return root; 38 | }; 39 | ``` 40 | 41 | # 分析 42 | 43 | ​ 核心原理是**反向中序遍历**(右 -> 中 -> 左),由于是二叉搜索树,所以中序遍历就是顺序数组,那反向中序遍历就是倒序数组 44 | 45 | 首先遍历右子树,同时将每次遍历的结果保存在 num 变量中缓存 46 | 47 | 当前节点的值 = 当前节点的值 + 右子树的值(num) 48 | 49 | 当前左节点的值 = 当前左节点的值 + 当前节点的值(sum) -------------------------------------------------------------------------------- /543. Diameter of Binary Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过根结点。 5 | 6 | 示例 : 7 | 给定二叉树 8 | 9 | 1 10 | / \ 11 | 2 3 12 | / \ 13 | 4 5 14 | 返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。 15 | 16 | 注意:两结点之间的路径长度是以它们之间边的数目表示。 17 | 18 | 来源:力扣(LeetCode) 19 | 链接:https://leetcode-cn.com/problems/diameter-of-binary-tree 20 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 21 | 22 | # 解法 23 | 24 | ```javascript 25 | var diameterOfBinaryTree = function(root) { 26 | if (!root) return 0; 27 | let max = 0; 28 | if (!root) return; 29 | const maxDepth = root => { 30 | if (!root) return 0; 31 | let left = maxDepth(root.left); 32 | let right = maxDepth(root.right); 33 | if (left + right > max) { 34 | max = left + right; 35 | } 36 | return Math.max(left, right) + 1; 37 | }; 38 | maxDepth(root); 39 | return max; 40 | }; 41 | ``` 42 | 43 | # 分析 44 | 45 | 核心要点:**二叉树的直径 = 左子树最大深度 + 右子树最大深度** 46 | 47 | 该问题就是在求二叉树最大深度的改版,深度遍历二叉树 48 | 49 | 递归的归时,**当前节点的左右子树之和就是当前节点的直径**,设置一个 max 变量每次遍历判断最大值即可 50 | 51 | -------------------------------------------------------------------------------- /559. Maximum Depth of N-ary Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个 N 叉树,找到其最大深度。 5 | 6 | 最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。 7 | 8 | 例如,给定一个 3叉树 : 9 | 10 | ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/12/narytreeexample.png) 11 | 12 | 我们应返回其最大深度,3。 13 | 14 | 说明: 15 | 16 | 树的深度不会超过 1000。 17 | 树的节点总不会超过 5000。 18 | 19 | 来源:力扣(LeetCode) 20 | 链接:https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree 21 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 22 | 23 | # 解法 24 | 25 | ```javascript 26 | var maxDepth = function(root) { 27 | if (!root) return 0; 28 | let res = 0; 29 | for (let i = 0; i < root.children.length; i++) { 30 | let depth = maxDepth(root.children[i]); 31 | if (res < depth) { 32 | res = depth; 33 | } 34 | } 35 | return res + 1; 36 | }; 37 | ``` 38 | 39 | # 分析 40 | 41 | 深度遍历解法,对应二叉树的最大深度,遍历所有子节点,将最大值赋值给 res,随后往上回溯时,每回溯一次 depth + 1 42 | 43 | # 解法2 44 | 45 | ```javascript 46 | var maxDepth = function(root) { 47 | if (!root) return 0; 48 | let res = 0; 49 | let queue = []; 50 | queue.push(root); 51 | while (queue.length) { 52 | let length = queue.length; 53 | for (let i = 0; i < length; i++) { 54 | let node = queue.shift(); 55 | if (node.children) { 56 | queue.push(...node.children); 57 | } 58 | } 59 | res += 1; 60 | } 61 | return res; 62 | }; 63 | ``` 64 | 65 | # 分析 66 | 67 | ​ 广度遍历解法,原理二叉树广度遍历 68 | 69 | -------------------------------------------------------------------------------- /563. Binary Tree Tilt.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉树,计算整个树的坡度。 5 | 6 | 一个树的节点的坡度定义即为,该节点左子树的结点之和和右子树结点之和的差的绝对值。空结点的的坡度是0。 7 | 8 | 整个树的坡度就是其所有节点的坡度之和。 9 | 10 | 示例: 11 | 12 | ``` 13 | 输入: 14 | 1 15 | / \ 16 | 2 3 17 | 输出: 1 18 | 解释: 19 | 结点的坡度 2 : 0 20 | 结点的坡度 3 : 0 21 | 结点的坡度 1 : |2-3| = 1 22 | 树的坡度 : 0 + 0 + 1 = 1 23 | ``` 24 | 25 | 注意: 26 | 27 | 任何子树的结点的和不会超过32位整数的范围。 28 | 坡度的值不会超过32位整数的范围。 29 | 30 | 来源:力扣(LeetCode) 31 | 链接:https://leetcode-cn.com/problems/binary-tree-tilt 32 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 33 | 34 | # 解法 35 | 36 | ```javascript 37 | const findTilt = root => { 38 | let res = 0; 39 | var traverse = function(root) { 40 | if (!root) return 0; 41 | let left = traverse(root.left); 42 | let right = traverse(root.right); 43 | res += Math.abs(left - right); 44 | return left + right + root.val; 45 | }; 46 | traverse(root); 47 | return res; 48 | }; 49 | ``` 50 | 51 | # 分析 52 | 53 | 深度遍历的解法,从上往下遍历,然后再从下往上回溯,回溯时计算左右子树的绝对值 54 | 55 | > 坡度 = (左子树之和 - 右子树之和 )的绝对值 56 | 57 | 所以还需要计算左右子树的和,只需要在每次递归时返回当前节点的左右子树 + 当前节点即可 -------------------------------------------------------------------------------- /572. Subtree of Another Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。 5 | 6 | 示例 1: 7 | 给定的树 s: 8 | 9 | 3 10 | / \ 11 | 4 5 12 | / \ 13 | 1 2 14 | 15 | 给定的树 t: 16 | 17 | ``` 18 | 4 19 | / \ 20 | 1 2 21 | ``` 22 | 23 | 返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。 24 | 25 | 示例 2: 26 | 给定的树 s: 27 | 28 | 3 29 | / \ 30 | 4 5 31 | / \ 32 | 1 2 33 | / 34 | 0 35 | 36 | 给定的树 t: 37 | 38 | ``` 39 | 4 40 | / \ 41 | 1 2 42 | ``` 43 | 44 | 返回 false。 45 | 46 | 来源:力扣(LeetCode) 47 | 链接:https://leetcode-cn.com/problems/subtree-of-another-tree 48 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 49 | 50 | # 解法 51 | 52 | ```javascript 53 | const traverse = (s, t) => { 54 | if (!s && !t) return true; 55 | if (!s) return false; 56 | if (!t) return false; 57 | if (s.val !== t.val) { 58 | return false; 59 | } 60 | return traverse(s.left, t.left) && traverse(s.right, t.right); 61 | }; 62 | 63 | var isSubtree = function(s, t) { 64 | if (!s) return false; 65 | if (s.val === t.val && traverse(s, t)) { 66 | return true; 67 | } else { 68 | return isSubtree(s.left, t) || isSubtree(s.right, t); 69 | } 70 | }; 71 | ``` 72 | 73 | # 分析 74 | 75 | 深度遍历解法,首先需要从 s 树中找到 t 的根节点,也就是两棵树遍历的起点 76 | 77 | 随后从这个起点开始,**同时**遍历两棵树,比对对应的两个节点的值,当两个节点**同时**遍历结束,且遍历的结果都相等时,代表 t 是 s 的子树,返回 true 并退出 78 | 79 | 如果从上述的起点遍历返回 false ,则继续向下遍历 s 树,尝试找到 t 子树 80 | 81 | # 解法2 82 | 83 | 84 | 85 | # 分析 86 | 87 | ​ 88 | 89 | -------------------------------------------------------------------------------- /589. N-ary Tree Preorder Traversal.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 5 | 6 | 给定一个 N 叉树,返回其节点值的*前序遍历*。 7 | 8 | 例如,给定一个 `3叉树` : 9 | 10 | 11 | 12 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/12/narytreeexample.png) 13 | 14 | 15 | 16 | 返回其前序遍历: `[1,3,5,6,2,4]`。 17 | 18 | # 解法 19 | 20 | ```javascript 21 | var preorder = function(root) { 22 | let res = []; 23 | const traverse = node => { 24 | if (!node) return; 25 | res.push(node.val); 26 | if (node.children) { 27 | node.children.forEach(traverse); 28 | } 29 | }; 30 | traverse(root); 31 | return res; 32 | }; 33 | ``` 34 | 35 | # 分析 36 | 37 | 深度遍历解法,同二叉树前序遍历 38 | 39 | # 解法2 40 | 41 | ```javascript 42 | var preorder = function(root) { 43 | let res = []; 44 | let stack = []; 45 | stack.push(root); 46 | while (stack.length) { 47 | let node = stack.pop(); 48 | if (!node) continue; 49 | res.push(node.val); 50 | if (!node.children) return; 51 | for (let i = node.children.length - 1; i >= 0; i--) { 52 | stack.push(node.children[i]); 53 | } 54 | } 55 | return res; 56 | } 57 | ``` 58 | 59 | # 分析 60 | 61 | 循环解法,使用 stack 模拟调用栈,使用循环代替递归,性能更好 62 | -------------------------------------------------------------------------------- /590. N-ary Tree Postorder Traversal.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个 N 叉树,返回其节点值的后序遍历。 5 | 6 | 例如,给定一个 3叉树 : 7 | 8 | 9 | 10 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/12/narytreeexample.png) 11 | 12 | 13 | 14 | 返回其后序遍历: [5,6,3,2,4,1]. 15 | 16 | 说明: 递归法很简单,你可以使用迭代法完成此题吗? 17 | 18 | 来源:力扣(LeetCode) 19 | 链接:https://leetcode-cn.com/problems/n-ary-tree-postorder-traversal 20 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 21 | 22 | # 解法 23 | 24 | ```javascript 25 | var postorder = node => { 26 | if (!node) return []; 27 | let res = []; 28 | let stack = [node]; 29 | while (stack.length) { 30 | let node = stack.pop(); 31 | let length = node.children.length; 32 | res.push(node.val); 33 | for (let i = 0; i < length; i++) { 34 | stack.push(node.children[i]); 35 | } 36 | } 37 | return res.reverse(); 38 | }; 39 | 40 | ``` 41 | 42 | # 分析 43 | 44 | 迭代版本,原理是**从右往左**遍历整颗 N 叉树 45 | 46 | 当当前节点含有子元素时,将当前节点弹出并放入 res 数组中,同时推入所有子元素 47 | 48 | 举例: 49 | 50 | 遍历上图的 N 叉树时,stack 栈中有 [2,3] 两个元素,会先将 3 弹出栈,再将 3 的子元素 6 推入栈,**这样只有当 3 这个子树的所有节点全部遍历完毕**,才会遍历 2 这个子树 51 | 52 | 同时由于栈的结构特点,需要将最终的结果进行一次**反转** 53 | 54 | `迭代原理同样应用与二叉树的后序遍历` 55 | 56 | -------------------------------------------------------------------------------- /606. Construct String from Binary Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 你需要采用前序遍历的方式,将一个二叉树转换成一个由括号和整数组成的字符串。 5 | 6 | 空节点则用一对空括号 "()" 表示。而且你需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。 7 | 8 | 示例 1: 9 | 10 | ``` 11 | 输入: 二叉树: [1,2,3,4] 12 | 1 13 | / \ 14 | 2 3 15 | / 16 | 4 17 | 18 | 输出: "1(2(4))(3)" 19 | 解释: 原本将是“1(2(4)())(3())”, 20 | 在你省略所有不必要的空括号对之后, 21 | 它将是“1(2(4))(3)”。 22 | ``` 23 | 24 | 示例 2: 25 | 26 | ``` 27 | 输入: 二叉树: [1,2,3,null,4] 28 | 1 29 | / \ 30 | 2 3 31 | \ 32 | 4 33 | 34 | 输出: "1(2()(4))(3)" 35 | 解释: 和第一个示例相似, 36 | 除了我们不能省略第一个对括号来中断输入和输出之间的一对一映射关系。 37 | ``` 38 | 39 | 来源:力扣(LeetCode) 40 | 链接:https://leetcode-cn.com/problems/construct-string-from-binary-tree 41 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 42 | 43 | # 解法 44 | 45 | ```javascript 46 | const isLeaf = node => !node.left && !node.right; 47 | 48 | var tree2str = function(node) { 49 | if (!node) return ""; 50 | if (isLeaf(node)) return String(node.val); 51 | return ( 52 | node.val + 53 | (node.left || node.right ? "(" : "") + 54 | tree2str(node.left) + 55 | (node.left || node.right ? ")" : "") + 56 | (node.left ? (node.right ? "(" : "") : "(") + 57 | tree2str(node.right) + 58 | (node.left ? (node.right ? ")" : "") : ")") 59 | ); 60 | }; 61 | 62 | ``` 63 | 64 | # 分析 65 | 66 | ​ 深度遍历,难点是括号的匹配,当左子树为空,右子树有值时,需要给左子树加上括号,其余情况省略括号 67 | -------------------------------------------------------------------------------- /617. Merge Two Binary Trees.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。 5 | 6 | 你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。 7 | 8 | 示例 1: 9 | 10 | ``` 11 | 输入: 12 | Tree 1 Tree 2 13 | 1 2 14 | / \ / \ 15 | 3 2 1 3 16 | / \ \ 17 | 5 4 7 18 | 输出: 19 | 合并后的树: 20 | 3 21 | / \ 22 | 4 5 23 | / \ \ 24 | 5 4 7 25 | ``` 26 | 27 | 注意: 合并必须从两个树的根节点开始。 28 | 29 | 来源:力扣(LeetCode) 30 | 链接:https://leetcode-cn.com/problems/merge-two-binary-trees 31 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 32 | 33 | # 解法 34 | 35 | ```javascript 36 | var mergeTrees = function(t1, t2) { 37 | if (!t1) return t2; 38 | if (!t2) return t1; 39 | t1.val += t2.val; 40 | t1.left = mergeTrees(t1.left, t2.left); 41 | t1.right = mergeTrees(t1.right, t2.right); 42 | return t1; 43 | }; 44 | ``` 45 | 46 | # 分析 47 | 48 | 深度遍历,考虑以下边缘情况 49 | 50 | * 当 t1 存在 t2 不存在,返回 t1 作为最终结果 51 | * 当 t2存在 t1 不存在,返回 t2 作为最终结果 52 | * 都不存在返回 null 53 | * 都存在则相加 54 | 55 | **尽可能返回有值的节点** 56 | 57 | -------------------------------------------------------------------------------- /63. Unique Paths II.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 5 | 6 | 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 7 | 8 | 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? 9 | 10 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/22/robot_maze.png) 11 | 12 | 网格中的障碍物和空位置分别用 1 和 0 来表示。 13 | 14 | 说明:m 和 n 的值均不超过 100。 15 | 16 | 示例 1: 17 | 18 | ``` 19 | 输入: 20 | [ 21 | [0,0,0], 22 | [0,1,0], 23 | [0,0,0] 24 | ] 25 | 输出: 2 26 | ``` 27 | 28 | 解释: 29 | 3x3 网格的正中间有一个障碍物。 30 | 从左上角到右下角一共有 2 条不同的路径: 31 | 32 | 1. 向右 -> 向右 -> 向下 -> 向下 33 | 2. 向下 -> 向下 -> 向右 -> 向右 34 | 35 | 来源:力扣(LeetCode) 36 | 链接:https://leetcode-cn.com/problems/unique-paths-ii 37 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 38 | 39 | # 解法 40 | 41 | ```javascript 42 | var uniquePathsWithObstacles = function(obstacleGrid) { 43 | if (!obstacleGrid[0] || obstacleGrid[0][0] === 1) return 0; 44 | let dp = [[1]]; 45 | // 只有一边的情况 46 | for (let i = 1; i < obstacleGrid[0].length; i++) { 47 | if (!dp[0]) { 48 | dp[0][i] = []; 49 | } 50 | dp[0][i] = obstacleGrid[0][i] === 1 ? 0 : dp[0][i - 1]; 51 | } 52 | for (let i = 1; i < obstacleGrid.length; i++) { 53 | if (!dp[i]) { 54 | dp[i] = []; 55 | } 56 | dp[i][0] = obstacleGrid[i][0] === 1 ? 0 : dp[i - 1][0]; 57 | } 58 | 59 | // 同时从上方和左方经过情况 60 | for (let m = 1; m < obstacleGrid[0].length; m++) { 61 | for (let n = 1; n < obstacleGrid.length; n++) { 62 | if (obstacleGrid[n][m] === 1) { 63 | dp[n][m] = 0; 64 | continue; 65 | } 66 | let pre1 = dp[n - 1][m] ? dp[n - 1][m] : 0; 67 | let pre2 = dp[n][m - 1] ? dp[n][m - 1] : 0; 68 | dp[n][m] = pre1 + pre2; 69 | } 70 | } 71 | return dp[dp.length - 1][dp[0].length - 1]; 72 | }; 73 | ``` 74 | 75 | # 分析 76 | 77 | 动态规划解法,从起点开始求出每个节点对应的 dp 值,最后节点的 dp 值即为终点 78 | 79 | 由于只可能在终点的上方和左方到达终点,所以状态转移公式如下 80 | 81 | ``` 82 | f(m,n) = f(m - 1,n) + f(m,n-1) m 为行数/ n 为列数 83 | ``` 84 | 85 | 同时考虑障碍物的情况,**当遇到障碍物时**,将当前节点的 dp 设为 0,即无论如何都不会走到当前节点,另外当当前节点的上方(左方)有障碍物时(dp 为 0 ),此时的 f(m,n) = f(m - 1,n) + 0,因为通过上方不可能到达当前节点 86 | 87 | 另外上述的情况是在当前节点可以同时从上方和左方经过,还要考虑一种情况,当当前节点只能从上方或左方经过时,一旦前面有障碍物,则后面所有的节点的 dp 值都为 0 -------------------------------------------------------------------------------- /637. Average of Levels in Binary Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个非空二叉树, 返回一个由每层节点平均值组成的数组. 5 | 6 | 示例 1: 7 | 8 | ``` 9 | 输入: 10 | 3 11 | / \ 12 | 9 20 13 | / \ 14 | 15 7 15 | 输出: [3, 14.5, 11] 16 | 解释: 17 | 第0层的平均值是 3, 第1层是 14.5, 第2层是 11. 因此返回 [3, 14.5, 11]. 18 | ``` 19 | 20 | 注意: 21 | 22 | 节点值的范围在32位有符号整数范围内。 23 | 24 | 来源:力扣(LeetCode) 25 | 链接:https://leetcode-cn.com/problems/average-of-levels-in-binary-tree 26 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 27 | 28 | # 解法 29 | 30 | ```javascript 31 | var averageOfLevels = function(root) { 32 | let res = []; 33 | if (!root) return res; 34 | let queue = [root]; 35 | while (queue.length) { 36 | let length = queue.length; 37 | let sum = 0; 38 | for (let i = 0; i < length; i++) { 39 | let node = queue.shift(); 40 | sum += node.val; 41 | if (node.left) queue.push(node.left); 42 | if (node.right) queue.push(node.right); 43 | } 44 | res.push(sum / length); 45 | } 46 | return res; 47 | }; 48 | ``` 49 | 50 | # 分析 51 | 52 | 最简单也是性能效率的最优解法,利用队列逐层遍历二叉树,然后计算当前层数所有元素的平均值 53 | 54 | # 解法2 55 | 56 | ```javascript 57 | var averageOfLevels = function(root) { 58 | let res = []; 59 | if (!root) return res; 60 | const traverse = (node, depth) => { 61 | (res[depth] || (res[depth] = [])).push(node.val); 62 | if (node.left) traverse(node.left, depth + 1); 63 | if (node.right) traverse(node.right, depth + 1); 64 | }; 65 | traverse(root, 0); 66 | return res.map(item => item.reduce((pre, cur) => pre + cur) / item.length); 67 | }; 68 | ``` 69 | 70 | # 分析 71 | 72 | 深度遍历解法,前序遍历二叉树,每次递归的时候记录当前的深度,根据深度来将当前节点放入数组的对应的位置 73 | 74 | 遍历完毕会得到一个二维数组,即数组每个元素也是一个数组,保存了当前层数的所有节点 75 | 76 | 将二维数组的每个元素做归并求平均值即可 77 | -------------------------------------------------------------------------------- /653. Two Sum IV - Input is a BST.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉搜索树和一个目标结果,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回 true。 5 | 6 | 案例 1: 7 | 8 | ``` 9 | 输入: 10 | 5 11 | / \ 12 | 3 6 13 | / \ \ 14 | 2 4 7 15 | 16 | Target = 9 17 | 18 | 输出: True 19 | ``` 20 | 21 | 22 | 23 | 案例 2: 24 | 25 | ``` 26 | 输入: 27 | 5 28 | / \ 29 | 3 6 30 | / \ \ 31 | 2 4 7 32 | 33 | Target = 28 34 | 35 | 输出: False 36 | 37 | 38 | ``` 39 | 40 | 来源:力扣(LeetCode) 41 | 链接:https://leetcode-cn.com/problems/two-sum-iv-input-is-a-bst 42 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 43 | 44 | # 解法 45 | 46 | ```javascript 47 | var findTarget = function(root, k) { 48 | let arr = []; 49 | const traverse = (node, arr) => { 50 | if (!node) return; 51 | traverse(node.left, arr); 52 | arr.push(node.val); 53 | traverse(node.right, arr); 54 | }; 55 | traverse(root, arr); 56 | let head = 0; 57 | let tail = arr.length - 1; 58 | while (head < tail) { 59 | if (arr[head] + arr[tail] === k) { 60 | return true; 61 | } else if (arr[head] + arr[tail] < k) { 62 | head++; 63 | } else { 64 | tail--; 65 | } 66 | } 67 | return false 68 | }; 69 | 70 | ``` 71 | 72 | # 分析 73 | 74 | 先用中序遍历将二叉搜索树放入数组中,变成顺序数组 75 | 76 | 然后使用双指针从头尾两头遍历数组 -------------------------------------------------------------------------------- /669. Trim a Binary Search Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。 5 | 6 | 示例 1: 7 | 8 | ``` 9 | 输入: 10 | 1 11 | / \ 12 | 0 2 13 | 14 | L = 1 15 | R = 2 16 | 17 | 输出: 18 | 1 19 | \ 20 | 2 21 | 示例 2: 22 | ``` 23 | 24 | ``` 25 | 输入: 26 | 3 27 | / \ 28 | 0 4 29 | \ 30 | 2 31 | / 32 | 1 33 | 34 | L = 1 35 | R = 3 36 | 37 | 输出: 38 | 3 39 | / 40 | 2 41 | / 42 | 1 43 | ``` 44 | 45 | 来源:力扣(LeetCode) 46 | 链接:https://leetcode-cn.com/problems/trim-a-binary-search-tree 47 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 48 | 49 | # 解法 50 | 51 | ```javascript 52 | var trimBST = function(root, L, R) { 53 | if (!root) return null; 54 | root.left = trimBST(root.left, L, R); 55 | root.right = trimBST(root.right, L, R); 56 | if (root.val < L || root.val > R) { 57 | return root.left || root.right; 58 | } 59 | return root; 60 | }; 61 | ``` 62 | 63 | # 分析 64 | 65 | ​ 深度遍历,从上而下递归,从下而上开始建树,当当前节点不在 [L,R] 范围内时,返回当前节点的左节点,若左节点没有则返回右节点(由于是从下而上的建树,所以当前节点下面的节点一定在 [L,R] 范围内) -------------------------------------------------------------------------------- /67. Add Binary.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定两个二进制字符串,返回他们的和(用二进制表示)。 5 | 6 | 输入为非空字符串且只包含数字 1 和 0。 7 | 8 | 示例 1: 9 | 10 | 输入: a = "11", b = "1" 11 | 输出: "100" 12 | 示例 2: 13 | 14 | 输入: a = "1010", b = "1011" 15 | 输出: "10101" 16 | 17 | 来源:力扣(LeetCode) 18 | 链接:https://leetcode-cn.com/problems/add-binary 19 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 20 | 21 | # 解法 22 | 23 | ```javascript 24 | var addBinary = function(a, b) { 25 | let res = ""; 26 | if (a.length > b.length) { 27 | b = b.padStart(a.length, "0"); 28 | } else { 29 | a = a.padStart(b.length, "0"); 30 | } 31 | let i = b.length - 1; 32 | let carry = 0; // 是否进位 33 | while (i >= 0) { 34 | let temp = ""; // 当前位 35 | let sum = Number(a[i]) + Number(b[i]); 36 | // 当前位需要进位 37 | if (sum === 2) { 38 | temp = carry; 39 | carry = 1; 40 | } else { 41 | // 当指针值相加 + 进位标志(上个指针值计算后需要进位)等于 2 时 42 | // 借助进位标志也可以进位 43 | temp = sum + carry; 44 | if (temp === 2) { 45 | temp = 0; 46 | carry = 1; 47 | } else { 48 | carry = 0; 49 | } 50 | } 51 | res = temp + res; 52 | i--; 53 | } 54 | // 防止遍历结束还需要进位一次 55 | if (carry) { 56 | res = "1" + res; 57 | } 58 | return res; 59 | }; 60 | ``` 61 | 62 | # 分析 63 | 64 | 首先将两个字符串变成相同宽度,长度小的那个往前补 0 65 | 66 | 然后定义一个指针 i 从后往前遍历 67 | 68 | 当指针值相加等于 2 (由于是二进制,只可能是 0,1,2 这三种情况)则需要进位,设置进位标志 (carry)给下个指针使用 69 | 70 | 否则如果和等于 1 ,且进位标志存在,则借助进位标志也可以实现进位 71 | 72 | 最后对遍历结束还需要进位一次的情况做处理,即前面补 1 -------------------------------------------------------------------------------- /671. Second Minimum Node In a Binary Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么这个节点的值不大于它的子节点的值。 5 | 6 | 给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。 7 | 8 | 示例 1: 9 | 10 | ``` 11 | 输入: 12 | 2 13 | / \ 14 | 2 5 15 | / \ 16 | 5 7 17 | 18 | 输出: 5 19 | 说明: 最小的值是 2 ,第二小的值是 5 。 20 | ``` 21 | 22 | 示例 2: 23 | 24 | ``` 25 | 输入: 26 | 2 27 | / \ 28 | 2 2 29 | 30 | 输出: -1 31 | 说明: 最小的值是 2, 但是不存在第二小的值。 32 | 33 | 34 | ``` 35 | 36 | 来源:力扣(LeetCode) 37 | 链接:https://leetcode-cn.com/problems/second-minimum-node-in-a-binary-tree 38 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 39 | 40 | # 解法 41 | 42 | ```javascript 43 | const isLeaf = node => !node.left && !node.right; 44 | 45 | var findSecondMinimumValue = function(root) { 46 | if ( 47 | !root || 48 | isLeaf(root) || 49 | (root.val === root.left.val && root.val === root.right.val) 50 | ) 51 | return -1; 52 | if (root.left.val > root.val && root.right.val > root.val) 53 | return Math.min(root.left.val, root, right.val); 54 | let flag; 55 | const traverse = node => { 56 | if (node.val < flag && node.val !== root.val) flag = node.val; 57 | if (node.left && node.left.val < flag) traverse(node.left); 58 | if (node.right && node.right.val < flag) traverse(node.right); 59 | }; 60 | if (root.right.val > root.val) { 61 | flag = root.right.val; 62 | traverse(root.left); 63 | } else if (root.left.val > root.val) { 64 | flag = root.left.val; 65 | traverse(root.right); 66 | } 67 | return flag; 68 | }; 69 | ``` 70 | 71 | # 分析 72 | 73 | 虽然可以遍历整棵树返回数组排序来暴力解题,这里还是考虑到优化版本 74 | 75 | 首先判断边缘条件 76 | 77 | * 当 root 不存在返回 -1 78 | * 当 root 是叶子节点返回 -1 79 | * 当 root 的左右子节点和 root 的值相同返回 -1 80 | * 当 root 的左右子节点都大于 root 返回左右节点最小的(因为 root 是最小的,所以左右子节点最小的那个就是第二小的) 81 | 82 | 排除边缘条件,根据规则有一个可以跳过遍历的点: 83 | 84 | `当 root 的左右节点有一个大于 root 节点,那那个子节点的子节点都可以跳过遍历` 85 | 86 | 所以定义了一个 flag 变量,保存大于 root 的那个子节点,然后遍历**另一边**的子节点,尝试找到比 flag 小的值(因为虽然此时 flag 为整棵树第二小的值,但是最终第二小的值可能存在在另一边的子树中) 87 | 88 | 同时如果遇到比 flag 大的节点,那这个节点包括下面的子树都可以跳过遍历 89 | 90 | * 如果是 root 的左子节点,那 flag = 左子节点的值,然后遍历 root 右子节点,尝试找到比 flag 小的值 91 | * 如果是 root 的右子节点,那 flag = 右子节点的值,然后遍历 root 左子节点,尝试找到比 flag 小的值 92 | 93 | 当 root 另一边的子树都遍历完毕,此时的 flag 就是最终第二小的值 94 | 95 | -------------------------------------------------------------------------------- /687. Longest Univalue Path.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。 5 | 6 | 注意:两个节点之间的路径长度由它们之间的边数表示。 7 | 8 | 示例 1: 9 | 10 | 输入: 11 | 12 | 5 13 | / \ 14 | 4 5 15 | / \ \ 16 | 1 1 5 17 | 输出: 18 | 19 | 2 20 | 示例 2: 21 | 22 | 输入: 23 | 24 | 1 25 | / \ 26 | 4 5 27 | / \ \ 28 | 4 4 5 29 | 输出: 30 | 31 | 2 32 | 注意: 给定的二叉树不超过10000个结点。 树的高度不超过1000。 33 | 34 | 来源:力扣(LeetCode) 35 | 链接:https://leetcode-cn.com/problems/longest-univalue-path 36 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 37 | 38 | # 解法 39 | 40 | ```javascript 41 | var longestUnivaluePath = function(root) { 42 | let res = 0; 43 | const traverse = node => { 44 | if (!node) return 0; 45 | let left = traverse(node.left); 46 | let right = traverse(node.right); 47 | left = node.left && node.val === node.left.val ? left + 1 : 0; 48 | right = node.right && node.val === node.right.val ? right + 1 : 0; 49 | res = Math.max(res, left + right); 50 | return Math.max(left, right); 51 | }; 52 | traverse(root); 53 | return res; 54 | }; 55 | ``` 56 | 57 | # 分析 58 | 59 | 核心思路:遍历每个节点的最长路径,输出最大值即可 60 | 61 | `当前节点最长路径 = 左节点延伸的最大值 + 右节点延伸的最大值` 62 | 63 | 什么是延伸的最大值?以左节点为例,从当前节点的左节点找,找到和当前节点值相同最大深度 64 | 65 | ![image.png](https://i.loli.net/2019/10/06/haZl35PRAvBjrSO.png) 66 | 67 | 图中根节点 5 的左节点延伸的最大值为 0,因为第一个左节点为 4 不等于 5,所以计算的时候需要进行判断,如果和当前节点的值不同则直接清 0,同时当前节点的右节点延伸的最大值为 2,因为它的值是 5,而它的左节点的值又是 5 68 | 69 | 同时要清楚,最长路径和延伸的最大值是不一样的,例如上图树第二层第一个节点 4 为例,它的最长路径是 5,但它的左节点也就是树第三层第一个节点 4,它的最长路径是 6,所以当前节点的最长路径**不是**左右节点的最长路径之和,而是延伸最大值之和 70 | 71 | 而延伸最大值就是当前节点的左右节点的最大值,我们可以深度遍历二叉树,从下而上开始记录当前节点和它的左右节点 72 | 73 | 另外我们选用从下而上的解法,从上到下复杂度为 O(n)^2,因为每个节点都需要计算一遍下面所有节点的值,而从下到上的复杂度只为 O(n),上面一层的节点只需复用之前节点的值即可,同时从下到上的解法还可以将问题由大化小,先关心叶子节点的处理,然后处理递归的输入和输出即可 74 | 75 | -------------------------------------------------------------------------------- /69. Sqrt(x).md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 实现 int sqrt(int x) 函数。 5 | 6 | 计算并返回 x 的平方根,其中 x 是非负整数。 7 | 8 | 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 9 | 10 | 示例 1: 11 | 12 | 输入: 4 13 | 输出: 2 14 | 示例 2: 15 | 16 | 输入: 8 17 | 输出: 2 18 | 说明: 8 的平方根是 2.82842..., 19 | 由于返回类型是整数,小数部分将被舍去 20 | 21 | 来源:力扣(LeetCode) 22 | 链接:https://leetcode-cn.com/problems/sqrtx 23 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 24 | 25 | # 解法 26 | 27 | ```javascript 28 | var mySqrt = function(x) { 29 | let start = 0; 30 | let end = x; 31 | while (start <= end) { 32 | let mid = ~~((start + end) / 2); 33 | if (mid ** 2 > x) { 34 | end = mid - 1; 35 | } else if (mid ** 2 < x) { 36 | start = mid + 1; 37 | } else { 38 | return mid; 39 | } 40 | } 41 | return start - 1; 42 | }; 43 | ``` 44 | 45 | # 分析 46 | 47 | 由于是**顺序数组**,所以使用二分查找是复杂度最低的解法(之前使用顺序遍历数组非常影响浪费性能) 48 | 49 | -------------------------------------------------------------------------------- /7. Reverse Integer.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。 4 | 5 | 示例 1: 6 | 7 | 输入: 123 8 | 输出: 321 9 | 示例 2: 10 | 11 | 输入: -123 12 | 输出: -321 13 | 示例 3: 14 | 15 | 输入: 120 16 | 输出: 21 17 | 注意: 18 | 19 | 假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−2^31, 2^31 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。 20 | 21 | 来源:力扣(LeetCode) 22 | 链接:https://leetcode-cn.com/problems/reverse-integer 23 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 24 | 25 | # 解法 26 | 27 | ```javascript 28 | const MAX = Math.pow(2, 31) - 1; 29 | 30 | const reverse = function(num) { 31 | let res = 0; 32 | if (num >= 0) { 33 | while (num > 0) { 34 | res = res * 10 + (num % 10); 35 | num = Math.floor(num / 10); 36 | } 37 | if (res > MAX) { 38 | return 0; 39 | } 40 | } else { 41 | num = -num; 42 | while (num > 0) { 43 | res = res * 10 + (num % 10); 44 | num = Math.floor(num / 10); 45 | } 46 | if (res > MAX + 1) { 47 | return 0; 48 | } 49 | res = -res; 50 | } 51 | return res; 52 | }; 53 | 54 | console.log(reverse(-9463847412)); 55 | ``` 56 | 57 | # 分析 58 | 59 | 此题的要点就在于返回的数字必须在指定的范围内,即可能存在传入前数字是合理的,但返回的数字超过指定范围 60 | 61 | 数字转为十进制的范围在 [-2147483648,2147483647] 之间,即可能存在以下情况 62 | 63 | ```javascript 64 | 111111119 -> 911111111 超出范围 65 | ``` 66 | 67 | 所以在转换后需要加上判断 68 | 69 | ## 反转数字 70 | 71 | 另外反转数字,可以使用栈,但是增加了额外的空间,可以使用取余 + 除法运算符 72 | 73 | ```javascript 74 | let num = 12345; 75 | let res = 0; 76 | while (num > 0) { 77 | res = res * 10 + (num % 10); 78 | num = ~~(num / 10); 79 | } 80 | console.log(res); // 54321 81 | ``` 82 | 83 | 原理是每次获得个位并保存,下次循环时将其 * 10,再加上下次循环的个位 84 | 85 | ```javascript 86 | 12345,0 -> 5,1234 -> 54,123 -> 543,12 -> 5432,1 -> 54321 87 | ``` 88 | 89 | ## 负数情况 90 | 91 | 如果是负数的话将负号先提出来,当作正数处理,在最终判断的时候加上负号即可 -------------------------------------------------------------------------------- /70. Climbing Stairs.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 5 | 6 | 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 7 | 8 | 注意:给定 n 是一个正整数。 9 | 10 | 示例 1: 11 | 12 | 输入: 2 13 | 输出: 2 14 | 解释: 有两种方法可以爬到楼顶。 15 | 1. 1 阶 + 1 阶 16 | 2. 2 阶 17 | 示例 2: 18 | 19 | 输入: 3 20 | 输出: 3 21 | 解释: 有三种方法可以爬到楼顶。 22 | 1. 1 阶 + 1 阶 + 1 阶 23 | 2. 1 阶 + 2 阶 24 | 3. 2 阶 + 1 阶 25 | 26 | 来源:力扣(LeetCode) 27 | 链接:https://leetcode-cn.com/problems/climbing-stairs 28 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 29 | 30 | # 解法 31 | 32 | ```javascript 33 | var climbStairs = function(n) { 34 | if (n === 1) return 1; 35 | let pre = 1; 36 | let res = 1; 37 | for (let i = 1; i < n; i++) { 38 | let temp = res; 39 | res = res + pre; 40 | pre = temp; 41 | } 42 | return res; 43 | }; 44 | ``` 45 | 46 | # 分析 47 | 48 | 动态规划解法,类似于斐波那契数列求和 49 | 50 | 假设要爬 5 个阶梯,也就是求 f(5) 的值,爬 5 个阶梯有两种可能 51 | 52 | * 爬 3 个阶梯 + 2 个阶梯 53 | * 爬 4 个阶梯 + 1 个阶梯 54 | 55 | 换成关系式也就是 f(3) + 2 和 f(4) + 1 的总和,也就是 f(X) = f(X - 1) + f(X - 2),即标准的斐波那契数列求和 56 | 57 | -------------------------------------------------------------------------------- /700. Search in a Binary Search Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。 5 | 6 | 例如, 7 | 8 | 给定二叉搜索树: 9 | 10 | 4 11 | / \ 12 | 2 7 13 | / \ 14 | 1 3 15 | 16 | 和值: 2 17 | 你应该返回如下子树: 18 | 19 | 2 20 | / \ 21 | 1 3 22 | 在上述示例中,如果要找的值是 5,但因为没有节点值为 5,我们应该返回 NULL。 23 | 24 | 来源:力扣(LeetCode) 25 | 链接:https://leetcode-cn.com/problems/search-in-a-binary-search-tree 26 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 27 | 28 | # 解法 29 | 30 | ```javascript 31 | var searchBST = function(root, val) { 32 | if (!root) return null; 33 | if (root.val > val) { 34 | return searchBST(root.left, val); 35 | } else if (root.val < val) { 36 | return searchBST(root.right, val); 37 | } else { 38 | return root; 39 | } 40 | }; 41 | ``` 42 | 43 | # 分析 44 | 45 | 同二叉查找树 46 | 47 | # 解法2 48 | 49 | ```javascript 50 | var searchBST = function(root, val) { 51 | if (!root) return null; 52 | let stack = [root]; 53 | while (stack.length) { 54 | let node = stack.pop(); 55 | if (!node) continue; 56 | if (node.val > val) { 57 | stack.push(node.left); 58 | } else if (node.val < val) { 59 | stack.push(node.right); 60 | } else { 61 | return node; 62 | } 63 | } 64 | return null 65 | }; 66 | ``` 67 | 68 | # 分析 69 | 70 | ​ 模拟栈循环版本 71 | 72 | -------------------------------------------------------------------------------- /783. Minimum Distance Between BST Nodes.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉搜索树的根结点 root, 返回树中任意两节点的差的最小值。 5 | 6 | 示例: 7 | 8 | 输入: root = [4,2,6,1,3,null,null] 9 | 输出: 1 10 | 解释: 11 | 注意,root是树结点对象(TreeNode object),而不是数组。 12 | 13 | 给定的树 [4,2,6,1,3,null,null] 可表示为下图: 14 | 15 | 4 16 | / \ 17 | 2 6 18 | / \ 19 | 1 3 20 | 21 | 最小的差值是 1, 它是节点1和节点2的差值, 也是节点3和节点2的差值。 22 | 注意: 23 | 24 | 二叉树的大小范围在 2 到 100。 25 | 二叉树总是有效的,每个节点的值都是整数,且不重复。 26 | 27 | 来源:力扣(LeetCode) 28 | 链接:https://leetcode-cn.com/problems/minimum-distance-between-bst-nodes 29 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 30 | 31 | # 解法 32 | 33 | ```javascript 34 | var minDiffInBST = function(root) { 35 | let res = Infinity; 36 | let pre; 37 | const traverse = node => { 38 | if (!node) return; 39 | traverse(node.left); 40 | if (pre == null) { 41 | pre = node.val; 42 | } else { 43 | let abs = Math.abs(node.val - pre); 44 | res = Math.min(res, abs); 45 | pre = node.val; 46 | } 47 | traverse(node.right); 48 | }; 49 | traverse(root); 50 | return res; 51 | }; 52 | ``` 53 | 54 | # 分析 55 | 56 | 由于是二叉搜索树,它的特点为`中序遍历输出的是顺序数组` 57 | 58 | 可以利用这一特点,中序遍历二叉搜索树,每次遍历的时候就执行判断(不需要全部输出再判断,否则需要完整遍历两遍二叉树),定义一个 pre 变量保存上一个遍历的节点,然后依次比对求绝对值,最后输出最小的绝对值即可 59 | 60 | 有两个边缘条件 61 | 62 | * 当中序遍历数组第一个元素时(二叉搜索树最小的节点),由于 pre 不存在所以需要跳过求绝对值,否则结果始终为 NaN 63 | * res 的初始值须为最大值 Infinity(或者进行判断),如果初始值为 0 则最小值始终都为 0 -------------------------------------------------------------------------------- /83. Remove Duplicates from Sorted List.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 5 | 6 | 示例 1: 7 | 8 | 输入: 1->1->2 9 | 输出: 1->2 10 | 示例 2: 11 | 12 | 输入: 1->1->2->3->3 13 | 输出: 1->2->3 14 | 15 | 来源:力扣(LeetCode) 16 | 链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list 17 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 18 | 19 | # 解法 20 | 21 | ```javascript 22 | var deleteDuplicates = function(head) { 23 | if (!head || !head.next) return head; 24 | let cur = head; 25 | while (cur && cur.next) { 26 | if (cur.val === cur.next.val) { 27 | cur.next = cur.next.next; 28 | } else { 29 | cur = cur.next; 30 | } 31 | } 32 | return head; 33 | }; 34 | ``` 35 | 36 | # 分析 37 | 38 | 定义一个 cur 指针,指向当前的遍历的链表,当 cur 节点的值和下个链表节点的值相同时,将当前链表指向下下个链表 39 | 40 | 例如 1 -> 2 -> 2 -> 2 -> 3 41 | 42 | 当 cur 在链表第二个节点 2 时,由于下个节点的值也为 2 ,所以将 cur 指向第四个节点(并不移动指针),直到找到一个不等于 2 的节点,再移动指针 -------------------------------------------------------------------------------- /844. Backspace String Compare.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定 S 和 T 两个字符串,当它们分别被输入到空白的文本编辑器后,判断二者是否相等,并返回结果。 # 代表退格字符。 5 | 6 | 示例 1: 7 | 8 | 输入:S = "ab#c", T = "ad#c" 9 | 输出:true 10 | 解释:S 和 T 都会变成 “ac”。 11 | 示例 2: 12 | 13 | 输入:S = "ab##", T = "c#d#" 14 | 输出:true 15 | 解释:S 和 T 都会变成 “”。 16 | 示例 3: 17 | 18 | 输入:S = "a##c", T = "#a#c" 19 | 输出:true 20 | 解释:S 和 T 都会变成 “c”。 21 | 示例 4: 22 | 23 | 输入:S = "a#c", T = "b" 24 | 输出:false 25 | 解释:S 会变成 “c”,但 T 仍然是 “b”。 26 | 27 | 28 | 提示: 29 | 30 | 1 <= S.length <= 200 31 | 1 <= T.length <= 200 32 | S 和 T 只含有小写字母以及字符 '#'。 33 | 34 | 来源:力扣(LeetCode) 35 | 链接:https://leetcode-cn.com/problems/backspace-string-compare 36 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 37 | 38 | # 解法 39 | 40 | ```javascript 41 | var backspaceCompare = function(S, T) { 42 | let stackS = []; 43 | let stackT = []; 44 | for (let i = 0; i < S.length; i++) { 45 | if (S[i] === "#") { 46 | stackS.pop(); 47 | } else { 48 | stackS.push(S[i]); 49 | } 50 | } 51 | for (let i = 0; i < T.length; i++) { 52 | if (T[i] === "#") { 53 | stackT.pop(); 54 | } else { 55 | stackT.push(T[i]); 56 | } 57 | } 58 | return stackS.toString() === stackT.toString(); 59 | }; 60 | ``` 61 | 62 | # 分析 63 | 64 | 栈的解法,类似于判断是否正确匹配括号,缺点是空间复杂度较高 65 | 66 | # 解法2 67 | 68 | ```javascript 69 | var backspaceCompare = function(S, T) { 70 | const func = str => { 71 | let p = str.length - 1; 72 | let s = ""; 73 | let spaceCount = 0; 74 | while (p >= 0) { 75 | if (str[p] === "#") { 76 | p--; 77 | spaceCount++; 78 | } else { 79 | if (spaceCount) { 80 | spaceCount--; 81 | p--; 82 | } else { 83 | s += str[p]; 84 | p--; 85 | } 86 | } 87 | } 88 | return s; 89 | }; 90 | return func(T) === func(S); 91 | }; 92 | 93 | ``` 94 | 95 | # 分析 96 | 97 | 指针解法,由于 # 会影响到它**之前**的字符,而 # 后面的字符不会影响,所以需要**从后往前遍历** 98 | 99 | 声明一个 spaceCount 变量保存 # 出现的次数,当匹配到有效字符时,再次向前移动 spaceCount 位 -------------------------------------------------------------------------------- /872. Leaf-Similar Trees.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 请考虑一颗二叉树上所有的叶子,这些叶子的值按从左到右的顺序排列形成一个 叶值序列 。 5 | 6 | ![](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/07/16/tree.png) 7 | 8 | 举个例子,如上图所示,给定一颗叶值序列为 (6, 7, 4, 9, 8) 的树。 9 | 10 | 如果有两颗二叉树的叶值序列是相同,那么我们就认为它们是 叶相似 的。 11 | 12 | 如果给定的两个头结点分别为 root1 和 root2 的树是叶相似的,则返回 true;否则返回 false 。 13 | 14 | 15 | 16 | 提示: 17 | 18 | 给定的两颗树可能会有 1 到 100 个结点。 19 | 20 | 来源:力扣(LeetCode) 21 | 链接:https://leetcode-cn.com/problems/leaf-similar-trees 22 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 23 | 24 | # 解法 25 | 26 | ```javascript 27 | const isLeaf = node => !node.left && !node.right; 28 | 29 | var leafSimilar = function(root1, root2) { 30 | let arr1 = []; 31 | let arr2 = []; 32 | const traverse = (node, arr) => { 33 | if (!node) return; 34 | if (isLeaf(node)) arr.push(node.val); 35 | traverse(node.left, arr); 36 | traverse(node.right, arr); 37 | }; 38 | traverse(root1, arr1); 39 | traverse(root2, arr2); 40 | return String(arr1) === String(arr2); 41 | }; 42 | ``` 43 | 44 | # 分析 45 | 46 | ​ 先序遍历可以从左到右输出,然后判断是否是叶子节点,是就放入到数组,对比两棵树输出的数组即可 47 | -------------------------------------------------------------------------------- /876. Middle of the Linked List.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。 5 | 6 | 如果有两个中间结点,则返回第二个中间结点。 7 | 8 | 9 | 10 | 示例 1: 11 | 12 | 输入:[1,2,3,4,5] 13 | 输出:此列表中的结点 3 (序列化形式:[3,4,5]) 14 | 返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。 15 | 注意,我们返回了一个 ListNode 类型的对象 ans,这样: 16 | ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL. 17 | 示例 2: 18 | 19 | 输入:[1,2,3,4,5,6] 20 | 输出:此列表中的结点 4 (序列化形式:[4,5,6]) 21 | 由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。 22 | 23 | 24 | 提示: 25 | 26 | 给定链表的结点数介于 1 和 100 之间。 27 | 28 | 来源:力扣(LeetCode) 29 | 链接:https://leetcode-cn.com/problems/middle-of-the-linked-list 30 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 31 | 32 | # 解法 33 | 34 | ```javascript 35 | var middleNode = function(head) { 36 | let slow = head; 37 | let fast = head; 38 | while (fast && fast.next) { 39 | slow = slow.next; 40 | fast = fast.next.next; 41 | } 42 | return slow; 43 | }; 44 | ``` 45 | 46 | # 分析 47 | 48 | 定义快慢两个指针,当快指针走完时,慢指针的位置即中点位置 -------------------------------------------------------------------------------- /88. Merge Sorted Array.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。 5 | 6 | 说明: 7 | 8 | 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。 9 | 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 10 | 示例: 11 | 12 | ``` 13 | 输入: 14 | nums1 = [1,2,3,0,0,0], m = 3 15 | nums2 = [2,5,6], n = 3 16 | 17 | 输出: [1,2,2,3,5,6] 18 | ``` 19 | 20 | 来源:力扣(LeetCode) 21 | 链接:https://leetcode-cn.com/problems/merge-sorted-array 22 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 23 | 24 | # 解法 25 | 26 | ```javascript 27 | var merge = function(nums1, m, nums2, n) { 28 | let p = nums1.length - 1; 29 | let p1 = m - 1; 30 | let p2 = n - 1; 31 | while (p2 >= 0) { 32 | // 把 nums2 的剩余元素赋值到 nums1 头部 33 | if (p1 < 0) { 34 | for (let j = 0; j <= p2; j++) { 35 | nums1[j] = nums2[j]; 36 | } 37 | return; 38 | } 39 | if (nums1[p1] < nums2[p2]) { 40 | // nums2 大 41 | nums1[p] = nums2[p2]; 42 | p2--; 43 | p--; 44 | } else { 45 | // nums1 大 46 | nums1[p] = nums1[p1]; 47 | p1--; 48 | p--; 49 | } 50 | } 51 | }; 52 | ``` 53 | 54 | # 分析 55 | 56 | ![image-20191013165253845](/Users/zhouhaolei/Library/Application Support/typora-user-images/image-20191013165253845.png) 57 | 58 | 相对比较好的解法,没有用到额外的空间,定义三个指针 59 | 60 | * p1:指向 nums1 最后一个非 0 元素 61 | * p2: 指向 nums2 最后一个元素 62 | * p: 指向 nums1 最后一个元素 63 | 64 | 比对 p1 和 p2 两个指针,将较大的那个赋值给 p 指针指向的位置,同时 p 指针和较大的那个向左移动一位 65 | 66 | 同时考虑以下情况 67 | 68 | ```javascript 69 | nums1 = [5,6,0] 70 | 71 | nums2 = [2] 72 | ``` 73 | 74 | nums1 的元素始终比 nums2 的元素大,这时 p1 先结束但 p2 还有元素,所以需要循环将 nums2 的元素填入 nums1 头部 75 | 76 | -------------------------------------------------------------------------------- /897. Increasing Order Search Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个树,按中序遍历重新排列树,使树中最左边的结点现在是树的根,并且每个结点没有左子结点,只有一个右子结点。 5 | 6 | 7 | 8 | 示例 : 9 | 10 | 输入:[5,3,6,2,4,null,8,1,null,null,null,7,9] 11 | 12 | 5 13 | / \ 14 | 3 6 15 | / \ \ 16 | 2 4 8 17 | / / \ 18 | 1 7 9 19 | 20 | 21 | 输出:[1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9] 22 | 23 | ``` 24 | 1 25 | \ 26 | 2 27 | \ 28 | 3 29 | \ 30 | 4 31 | \ 32 | 5 33 | \ 34 | 6 35 | \ 36 | 7 37 | \ 38 | 8 39 | \ 40 | 9 41 | ``` 42 | 43 | 44 | 提示: 45 | 46 | 给定树中的结点数介于 1 和 100 之间。 47 | 每个结点都有一个从 0 到 1000 范围内的唯一整数值。 48 | 49 | 来源:力扣(LeetCode) 50 | 链接:https://leetcode-cn.com/problems/increasing-order-search-tree 51 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 52 | 53 | # 解法 54 | 55 | ```javascript 56 | var increasingBST = function(root) { 57 | if (!root) return null; 58 | let arr = []; 59 | const traverse = node => { 60 | if (!node) return; 61 | traverse(node.left); 62 | arr.push(node.val); 63 | traverse(node.right); 64 | }; 65 | traverse(root); 66 | let node = (root = new TreeNode(arr.shift())); 67 | while (arr.length) { 68 | node.right = new TreeNode(arr.shift()); 69 | node = node.right; 70 | } 71 | return root; 72 | }; 73 | ``` 74 | 75 | # 分析 76 | 77 | 中序遍历 + 数组 + 重新建树,需要用到额外空间保存中序遍历的结果 78 | 79 | # 解法2 80 | 81 | ```javascript 82 | var increasingBST = function(root) { 83 | if (!root) return null; 84 | // 新建一个树 85 | let cur = new TreeNode(); 86 | let res = cur; 87 | const traverse = node => { 88 | if (!node) return null; 89 | traverse(node.left, node); 90 | node.left = null; 91 | // 需要使用新建的树去存储节点 92 | // 否则修改原节点的右子节点会导致无限循环(右节点 = 父节点,又会进入当前循环导致爆栈) 93 | cur.right = node; 94 | cur = node; 95 | traverse(node.right, node); 96 | }; 97 | traverse(root); 98 | return res; 99 | }; 100 | ``` 101 | 102 | # 分析 103 | 104 | ​ 中序遍历 + 重新建树,省去了额外保存数组的空间,在中序遍历的同时就开始建树,原理是将中序遍历的节点的左节点清空,右节点指向它的父节点(这里不能直接将有节点指向它的父节点,需要在新建的树中完成操作,否则会导致再次执行到当前循环无限递归而爆栈) 105 | -------------------------------------------------------------------------------- /9. Palindrome Number.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 4 | 5 | 示例 1: 6 | 7 | 输入: 121 8 | 输出: true 9 | 示例 2: 10 | 11 | 输入: -121 12 | 输出: false 13 | 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 14 | 示例 3: 15 | 16 | 输入: 10 17 | 输出: false 18 | 解释: 从右向左读, 为 01 。因此它不是一个回文数。 19 | 进阶: 20 | 21 | 你能不将整数转为字符串来解决这个问题吗? 22 | 23 | 24 | 25 | 来源:力扣(LeetCode) 26 | 链接:https://leetcode-cn.com/problems/palindrome-number 27 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 28 | 29 | # 解法 30 | 31 | ```javascript 32 | /** 33 | * @param {number} x 34 | * @return {boolean} 35 | */ 36 | var isPalindrome = function(x) { 37 | if(x < 0) return false 38 | let _x = x 39 | let y = 0 40 | while (x > 0) { 41 | y = y * 10 + x % 10 42 | x = ~~(x / 10) 43 | } 44 | return y === _x 45 | }; 46 | 47 | console.log(isPalindrome(123)) 48 | ``` 49 | 50 | # 分析 51 | 52 | 首先负数直接不是回文数,依然可以利用取余和除法运算符来获取个位数字,然后获得其反转值,判断反转值是否和原始值相同即可 53 | 54 | `12321 -> 1232,1 -> 123,12 -> 12,123 -> 1,1232 -> 12321 (判断是否和原始值相同即可)` 55 | 56 | -------------------------------------------------------------------------------- /925. Long Pressed Name.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 你的朋友正在使用键盘输入他的名字 name。偶尔,在键入字符 c 时,按键可能会被长按,而字符可能被输入 1 次或多次。 5 | 6 | 你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字(其中一些字符可能被长按),那么就返回 True。 7 | 8 | 9 | 10 | 示例 1: 11 | 12 | 输入:name = "alex", typed = "aaleex" 13 | 输出:true 14 | 解释:'alex' 中的 'a' 和 'e' 被长按。 15 | 示例 2: 16 | 17 | 输入:name = "saeed", typed = "ssaaedd" 18 | 输出:false 19 | 解释:'e' 一定需要被键入两次,但在 typed 的输出中不是这样。 20 | 示例 3: 21 | 22 | 输入:name = "leelee", typed = "lleeelee" 23 | 输出:true 24 | 示例 4: 25 | 26 | 输入:name = "laiden", typed = "laiden" 27 | 输出:true 28 | 解释:长按名字中的字符并不是必要的。 29 | 30 | 31 | 提示: 32 | 33 | name.length <= 1000 34 | typed.length <= 1000 35 | name 和 typed 的字符都是小写字母。 36 | 37 | 来源:力扣(LeetCode) 38 | 链接:https://leetcode-cn.com/problems/long-pressed-name 39 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 40 | 41 | # 解法 42 | 43 | ```javascript 44 | var isLongPressedName = function(name, typed) { 45 | let p = 0; 46 | let q = 0; 47 | let res = true; 48 | // 检查头尾字符 49 | if (name[0] !== typed[0]) return false; 50 | if (name[name.length - 1] !== typed[typed.length - 1]) return false; 51 | while (q < typed.length) { 52 | if (name[p] === typed[q]) { 53 | p++; 54 | q++; 55 | } else if (typed[q] === typed[q - 1]) { 56 | q++; 57 | } else { 58 | return false; 59 | } 60 | } 61 | 62 | return res; 63 | }; 64 | ``` 65 | 66 | # 分析 67 | 68 | ​ 双指针解法,先检查头尾字符,防止在 q 指针遍历完毕而 p 指针还未遍历结束时,p 指针后面的字符和 p 当前指向的字符不同的情况 69 | 70 | 例如 name = 'abbccccccde',type = 'abbbbbcccccccddddddd',此时 q 遍历完毕,p 的指针指向 d,而 p 后面的字符为 e 71 | 72 | 检查头部则放置 type[q - 1] 不存在的情况,所以需要保证第一个元素必须相等才进行 while 循环 73 | 74 | -------------------------------------------------------------------------------- /938. Range Sum of BST.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定二叉搜索树的根结点 root,返回 L 和 R(含)之间的所有结点的值的和。 5 | 6 | 二叉搜索树保证具有唯一的值。 7 | 8 | 9 | 10 | 示例 1: 11 | 12 | ``` 13 | 输入:root = [10,5,15,3,7,null,18], L = 7, R = 15 14 | 输出:32 15 | ``` 16 | 17 | 示例 2: 18 | 19 | ``` 20 | 输入:root = [10,5,15,3,7,13,18,1,null,6], L = 6, R = 10 21 | 输出:23 22 | ``` 23 | 24 | 25 | 提示: 26 | 27 | 树中的结点数量最多为 10000 个。 28 | 最终的答案保证小于 2^31。 29 | 30 | 来源:力扣(LeetCode) 31 | 链接:https://leetcode-cn.com/problems/range-sum-of-bst 32 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 33 | 34 | # 解法 35 | 36 | ```javascript 37 | var rangeSumBST = function(root, L, R) { 38 | let res = 0; 39 | const traverse = node => { 40 | if (!node) return; 41 | // 如果 node 的值小于 L 42 | // 由于二叉搜索树的特点,它的左值也一定小于 L 43 | // 所以没有必要进一步递归 44 | if (node.val > L) traverse(node.left); 45 | if (node.val >= L && node.val <= R) { 46 | res += node.val; 47 | } 48 | // 同上 49 | if (node.val < R) traverse(node.right); 50 | }; 51 | traverse(root); 52 | return res; 53 | }; 54 | ``` 55 | 56 | # 分析 57 | 58 | 深度遍历二叉搜索树,同时考虑以下边界情况 59 | 60 | * 当前节点小于 L,则不需要遍历左子树,因为左子树的节点一定也小于 L 61 | * 当前节点大于 R,则不需要遍历右子树,因为右子树的节点一定也大于 R 62 | 63 | # 解法2 64 | 65 | ```javascript 66 | var rangeSumBST = function(root, L, R) { 67 | let res = 0; 68 | let stack = [root]; 69 | while (stack.length) { 70 | let node = stack.pop(); 71 | if (!node) continue; 72 | // 如果 node 的值小于 L 73 | // 由于二叉搜索树的特点,它的左值也一定小于 L 74 | // 所以只要将右节点推入栈即可 75 | if (node.val < L) { 76 | stack.push(node.right); 77 | } else if (node.val > R) { 78 | stack.push(node.left); 79 | } else { 80 | // 在 L-R 区间则都需要入栈 81 | stack.push(node.left); 82 | stack.push(node.right); 83 | } 84 | if (node.val >= L && node.val <= R) { 85 | res += node.val; 86 | } 87 | } 88 | return res; 89 | }; 90 | ``` 91 | 92 | # 分析 93 | 94 | 模拟调用栈循环版本,利用二叉搜索树左节点小于当前节点,右节点大于当前节点的特点,将符合条件的节点入栈 95 | 96 | 当 node 值小于 L,则左节点一定小于 L,所以只要让右节点入栈即可,反之亦然,最后再循环判断栈中节点是否在 L-R 区间 -------------------------------------------------------------------------------- /94. Binary Tree Inorder Traversal.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉树,返回它的中序 遍历。 5 | 6 | 示例: 7 | 8 | 输入: [1,null,2,3] 9 | 1 10 | \ 11 | 2 12 | / 13 | 3 14 | 15 | 输出: [1,3,2] 16 | 进阶: 递归算法很简单,你可以通过迭代算法完成吗? 17 | 18 | 来源:力扣(LeetCode) 19 | 链接:https://leetcode-cn.com/problems/binary-tree-inorder-traversal 20 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 21 | 22 | # 解法 23 | 24 | ```javascript 25 | var inorderTraversal = function(root) { 26 | let arr = []; 27 | const traverse = node => { 28 | if(!node) return 29 | traverse(node.left); 30 | arr.push(node.val); 31 | traverse(node.right); 32 | }; 33 | traverse(root); 34 | return arr; 35 | }; 36 | ``` 37 | 38 | # 分析 39 | 40 | 递归解法,二叉树中序遍历 41 | 42 | # 解法2 43 | 44 | ```javascript 45 | var inorderTraversal = function(root) { 46 | if (!root) return []; 47 | let arr = []; 48 | let stack = []; 49 | let cur = root; 50 | while (cur || stack.length) { 51 | if (cur) { 52 | while (cur) { 53 | stack.push(cur); 54 | cur = cur.left; 55 | } 56 | } 57 | cur = stack.pop(); 58 | arr.push(cur.val); 59 | cur = cur.right; 60 | } 61 | return arr; 62 | }; 63 | ``` 64 | 65 | # 分析 66 | 67 | 迭代解法,声明一个栈,先讲当前节点的左节点全部入栈,然后将指针指向最后入栈的那个节点的右节点,并再次执行上述逻辑,同时考虑输入/输出以及边界情况即可 -------------------------------------------------------------------------------- /965. Univalued Binary Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。 5 | 6 | 只有给定的树是单值二叉树时,才返回 true;否则返回 false。 7 | 8 | 9 | 10 | 示例 1: 11 | 12 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/29/screen-shot-2018-12-25-at-50104-pm.png) 13 | 14 | 输入:[1,1,1,1,1,null,1] 15 | 输出:true 16 | 示例 2: 17 | 18 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/29/screen-shot-2018-12-25-at-50050-pm.png) 19 | 20 | 输入:[2,2,2,5,2] 21 | 输出:false 22 | 23 | 来源:力扣(LeetCode) 24 | 链接:https://leetcode-cn.com/problems/univalued-binary-tree 25 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 26 | 27 | # 解法 28 | 29 | ```javascript 30 | var isUnivalTree = function(root) { 31 | if (!root) return true; 32 | let left = root.left ? root.left.val : root.val; 33 | let right = root.right ? root.right.val : root.val; 34 | if (root.val === left && root.val === right) { 35 | return isUnivalTree(root.left) && isUnivalTree(root.right); 36 | } else { 37 | return false; 38 | } 39 | }; 40 | ``` 41 | 42 | # 分析 43 | 44 | 深度遍历判断当前节点和左右子节点是否相同即可 45 | 46 | # 解法2 47 | 48 | ```javascript 49 | var isUnivalTree = function(root) { 50 | if (!root) return false; 51 | let queue = [root]; 52 | let val = root.val; 53 | while (queue.length) { 54 | let node = queue.shift(); 55 | if (!node) continue; 56 | if (!node.left || node.left.val === val) { 57 | queue.push(node.left); 58 | } else { 59 | return false; 60 | } 61 | if (!node.right || node.right.val === val) { 62 | queue.push(node.right); 63 | } else { 64 | return false; 65 | } 66 | } 67 | return true; 68 | }; 69 | ``` 70 | 71 | # 分析 72 | 73 | ​ 队列的循环解法,一旦有节点不等于根节点就返回 false,当队列遍历完毕返回 true 74 | 75 | -------------------------------------------------------------------------------- /977. Squares of a Sorted Array.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个按非递减顺序排序的整数数组 A,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。 5 | 6 | 7 | 8 | 示例 1: 9 | 10 | 输入:[-4,-1,0,3,10] 11 | 输出:[0,1,9,16,100] 12 | 示例 2: 13 | 14 | 输入:[-7,-3,2,3,11] 15 | 输出:[4,9,9,49,121] 16 | 17 | 18 | 提示: 19 | 20 | 1 <= A.length <= 10000 21 | -10000 <= A[i] <= 10000 22 | A 已按非递减顺序排序。 23 | 24 | 来源:力扣(LeetCode) 25 | 链接:https://leetcode-cn.com/problems/squares-of-a-sorted-array 26 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 27 | 28 | # 解法 29 | 30 | ```javascript 31 | /** 32 | * @param {number[]} A 33 | * @return {number[]} 34 | */ 35 | var sortedSquares = function(A) { 36 | return A.map(item => Math.abs(item ** 2)).sort((a, b) => a - b); 37 | }; 38 | 39 | ``` 40 | 41 | # 分析 42 | 43 | 某个很无脑的解法嘻嘻~ 44 | 45 | # 解法2 46 | 47 | ```javascript 48 | var sortedSquares = function(A) { 49 | let head = 0; 50 | let tail = A.length - 1; 51 | let res = []; 52 | while (head <= tail) { 53 | let headPow = A[head] ** 2; 54 | let tailPow = A[tail] ** 2; 55 | if (headPow < tailPow) { 56 | res.unshift(tailPow); 57 | tail--; 58 | } else { 59 | res.unshift(headPow); 60 | head++; 61 | } 62 | } 63 | return res; 64 | }; 65 | ``` 66 | 67 | # 分析 68 | 69 | 双指针解法,由于 A 是一个**已经排序**的数组,所以可以定义头尾两个指针,分别像中间靠拢,并将平方值大的元素放入数组的**头部**(因为指针指向的元素的平方会越来越小) 70 | -------------------------------------------------------------------------------- /98. Validate Binary Search Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 5 | 6 | 假设一个二叉搜索树具有如下特征: 7 | 8 | 节点的左子树只包含小于当前节点的数。 9 | 节点的右子树只包含大于当前节点的数。 10 | 所有左子树和右子树自身必须也是二叉搜索树。 11 | 示例 1: 12 | 13 | 输入: 14 | 2 15 | / \ 16 | 1 3 17 | 输出: true 18 | 示例 2: 19 | 20 | 输入: 21 | 5 22 | / \ 23 | 1 4 24 | / \ 25 | 3 6 26 | 输出: false 27 | 解释: 输入为: [5,1,4,null,null,3,6]。 28 | 根节点的值为 5 ,但是其右子节点值为 4 。 29 | 30 | 来源:力扣(LeetCode) 31 | 链接:https://leetcode-cn.com/problems/validate-binary-search-tree 32 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 33 | 34 | # 解法 35 | 36 | ```javascript 37 | var isValidBST = function(root) { 38 | if (!root) return true; 39 | let arr = []; 40 | const traverse = node => { 41 | if (!node) return; 42 | traverse(node.left); 43 | arr.push(node.val); 44 | traverse(node.right); 45 | }; 46 | traverse(root); 47 | for (let i = 0; i < arr.length - 1; i++) { 48 | if (arr[i] >= arr[i + 1]) { 49 | return false; 50 | } 51 | } 52 | return true; 53 | }; 54 | ``` 55 | 56 | # 分析 57 | 58 | 利用二叉搜索树中序遍历为顺序数组的特点,中序遍历目标树,或者一个数组 59 | 60 | 验证数组是否是升序数组即可 61 | 62 | # 解法2 63 | 64 | ```javascript 65 | var isValidBST = function(root) { 66 | const traverse = (root, low, high) => { 67 | if (!root) return true; 68 | if (low < root.val && high > root.val) { 69 | return ( 70 | traverse(root.left, low === -Infinity ? -Infinity : low, root.val) && 71 | traverse(root.right, root.val, high === Infinity ? Infinity : high) 72 | ); 73 | } else { 74 | return false; 75 | } 76 | }; 77 | return traverse(root, -Infinity, Infinity); 78 | }; 79 | ``` 80 | 81 | # 分析 82 | 83 | 递归解法,注意一个额外的要点,二叉搜索树的判断并不止是判断左节点大于当前节点,右节点小于当前节点,而是当前节点小于左子树所有节点,大于右子树所有节点 84 | 85 | 考虑以下情况 86 | 87 | ![image.png](https://i.loli.net/2019/11/26/KBdgUHVk7a9NAJQ.png) 88 | 89 | 上述虽然每一级都满足以上的条件,但是它并不是一个二叉搜索树,因为右边的叶子节点5,比根节点10要小,不满足根节点(10)小于所有的右子树中的节点 90 | 91 | 所以每次递归时需要传入一个区间,判断当前节点是否在这个区间内,并给左右节点分别指定一个区间 92 | 93 | 当 low 的值为负♾时,说明在当前节点树的最左边,相反当 high 的值为正♾,说明在树的最右边,其余情况会根据上一层的区间以及当前节点的值重新生成一个区间 -------------------------------------------------------------------------------- /993. Cousins in Binary Tree.md: -------------------------------------------------------------------------------- 1 | 2 | # 题目 3 | 4 | 在二叉树中,根节点位于深度 0 处,每个深度为 k 的节点的子节点位于深度 k+1 处。 5 | 6 | 如果二叉树的两个节点深度相同,但父节点不同,则它们是一对堂兄弟节点。 7 | 8 | 我们给出了具有唯一值的二叉树的根节点 root,以及树中两个不同节点的值 x 和 y。 9 | 10 | 只有与值 x 和 y 对应的节点是堂兄弟节点时,才返回 true。否则,返回 false。 11 | 12 | 13 | 14 | 示例 1: 15 | 16 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/02/16/q1248-01.png) 17 | 18 | ``` 19 | 输入:root = [1,2,3,4], x = 4, y = 3 20 | 输出:false 21 | ``` 22 | 23 | 示例 2: 24 | 25 | **![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/02/16/q1248-02.png)** 26 | 27 | ``` 28 | 输入:root = [1,2,3,null,4,null,5], x = 5, y = 4 29 | 输出:true 30 | ``` 31 | 32 | 示例 3: 33 | 34 | **![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/02/16/q1248-03.png)** 35 | 36 | ``` 37 | 输入:root = [1,2,3,null,4], x = 2, y = 3 38 | 输出:false 39 | ``` 40 | 41 | 42 | 提示: 43 | 44 | 二叉树的节点数介于 2 到 100 之间。 45 | 每个节点的值都是唯一的、范围为 1 到 100 的整数。 46 | 47 | 来源:力扣(LeetCode) 48 | 链接:https://leetcode-cn.com/problems/cousins-in-binary-tree 49 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 50 | 51 | # 解法 52 | 53 | ```javascript 54 | var isCousins = function(root, x, y) { 55 | const traverse = (node, target) => { 56 | if (!node) return 0; 57 | let queue = [{ node, parent: null }]; 58 | let depth = 0; 59 | while (queue.length) { 60 | let length = queue.length; 61 | for (let i = 0; i < length; i++) { 62 | let { node, parent } = queue.shift(); 63 | if (node.val === target) { 64 | return { depth, parent }; 65 | } 66 | node.left && 67 | queue.push({ 68 | node: node.left, 69 | parent: node 70 | }); 71 | node.right && 72 | queue.push({ 73 | node: node.right, 74 | parent: node 75 | }); 76 | } 77 | depth++; 78 | } 79 | }; 80 | return ( 81 | traverse(root, x).depth === traverse(root, y).depth && 82 | traverse(root, x).parent !== traverse(root, y).parent 83 | ); 84 | }; 85 | ``` 86 | 87 | # 分析 88 | 89 | 堂兄弟节点可以拆解为:`分别求两个节点的深度和父节点,当深度相同且父节点不同时为堂兄弟节点` 90 | 91 | 广度遍历循环版本,利用对象存储深度和父节点,当节点的值等于目标值时,输出深度和父节点 92 | 93 | # 解法2 94 | 95 | ```javascript 96 | const traverse = (node, target, parent = null, depth = 0) => { 97 | let res = { 98 | depth: 0, 99 | parent: null 100 | }; 101 | const _traverse = (node, target, parent = null, depth = 0) => { 102 | if (!node) return 0; 103 | if (node.val === target) { 104 | res.depth = depth; 105 | res.parent = parent; 106 | return; 107 | } 108 | _traverse(node.left, target, node, depth + 1); 109 | _traverse(node.right, target, node, depth + 1); 110 | }; 111 | 112 | _traverse(node, target, parent, depth); 113 | return res; 114 | }; 115 | 116 | var isCousins = function(root, x, y) { 117 | return ( 118 | traverse(root, x).depth === traverse(root, y).depth && 119 | traverse(root, x).parent !== traverse(root, y).parent 120 | ); 121 | }; 122 | ``` 123 | 124 | # 分析 125 | 126 | 先序遍历的递归版本,原理同上,由对象存储改为用形参存储深度和父节点 127 | 128 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 夜宴 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # leetcode 2 | 记录自己刷题和成长历程,明天的你一定会感谢今天努力的自己 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // 某个很无聊的东西 3 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | var _this = this; 42 | Object.defineProperty(exports, "__esModule", { value: true }); 43 | var inquirer_1 = __importDefault(require("inquirer")); 44 | var fs_1 = __importDefault(require("fs")); 45 | var path_1 = require("path"); 46 | var child_process_1 = require("child_process"); 47 | function generateSolutionTemplate(number) { 48 | var res = ""; 49 | for (var i = 1; i <= number; i++) { 50 | res += "\n# \u89E3\u6CD5" + (i === 1 ? "" : i) + "\n \n```javascript\n \n```\n \n# \u5206\u6790\n\n "; 51 | } 52 | return res; 53 | } 54 | (function () { return __awaiter(_this, void 0, void 0, function () { 55 | var res, md, e_1; 56 | return __generator(this, function (_a) { 57 | switch (_a.label) { 58 | case 0: 59 | _a.trys.push([0, 2, , 3]); 60 | return [4 /*yield*/, inquirer_1.default.prompt([ 61 | { 62 | name: "title", 63 | message: "markdown title(required)", 64 | validate: function (message) { return !!message; } 65 | }, 66 | { 67 | name: "solutionsNumber", 68 | message: "number of solution", 69 | default: 1, 70 | validate: function (message) { return Number(message) >= 1; } 71 | } 72 | ])]; 73 | case 1: 74 | res = _a.sent(); 75 | md = "\n# \u9898\u76EE\n \n " + generateSolutionTemplate(res.solutionsNumber) + " \n\n"; 76 | fs_1.default.writeFileSync("./" + res.title + ".md", md); 77 | child_process_1.execSync("open \"" + path_1.resolve(__dirname, res.title) + ".md\""); 78 | return [3 /*break*/, 3]; 79 | case 2: 80 | e_1 = _a.sent(); 81 | console.log(e_1); 82 | return [3 /*break*/, 3]; 83 | case 3: return [2 /*return*/]; 84 | } 85 | }); 86 | }); })(); 87 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | // 某个很无聊的东西 2 | 3 | import inquirer from 'inquirer' 4 | import fs from 'fs' 5 | import {resolve} from "path"; 6 | import {execSync} from "child_process"; 7 | 8 | 9 | function generateSolutionTemplate(number: number): string { 10 | let res = "" 11 | for (let i = 1; i <= number; i++) { 12 | res += ` 13 | # 解法${i === 1 ? "" : i} 14 | 15 | \`\`\`javascript 16 | 17 | \`\`\` 18 | 19 | # 分析 20 | 21 | ` 22 | } 23 | return res 24 | } 25 | 26 | (async () => { 27 | try { 28 | let res = await inquirer.prompt([ 29 | { 30 | name: "title", 31 | message: "markdown title(required)", 32 | validate: message => !!message 33 | }, 34 | { 35 | name: "solutionsNumber", 36 | message: "number of solution", 37 | default: 1, 38 | validate: message => Number(message) >= 1 39 | } 40 | ]); 41 | 42 | 43 | let md = ` 44 | # 题目 45 | 46 | ${generateSolutionTemplate(res.solutionsNumber)} 47 | 48 | ` 49 | fs.writeFileSync(`./${res.title}.md`, md) 50 | execSync(`open "${resolve(__dirname, res.title)}.md"`) 51 | } catch (e) { 52 | console.log(e); 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leetcode", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/inquirer": { 8 | "version": "6.5.0", 9 | "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", 10 | "integrity": "sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==", 11 | "dev": true, 12 | "requires": { 13 | "@types/through": "*", 14 | "rxjs": "^6.4.0" 15 | } 16 | }, 17 | "@types/node": { 18 | "version": "12.7.2", 19 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", 20 | "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==", 21 | "dev": true 22 | }, 23 | "@types/through": { 24 | "version": "0.0.29", 25 | "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.29.tgz", 26 | "integrity": "sha512-9a7C5VHh+1BKblaYiq+7Tfc+EOmjMdZaD1MYtkQjSoxgB69tBjW98ry6SKsi4zEIWztLOMRuL87A3bdT/Fc/4w==", 27 | "dev": true, 28 | "requires": { 29 | "@types/node": "*" 30 | } 31 | }, 32 | "ansi-escapes": { 33 | "version": "4.2.1", 34 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.2.1.tgz", 35 | "integrity": "sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==", 36 | "dev": true, 37 | "requires": { 38 | "type-fest": "^0.5.2" 39 | } 40 | }, 41 | "ansi-regex": { 42 | "version": "4.1.0", 43 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 44 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 45 | "dev": true 46 | }, 47 | "ansi-styles": { 48 | "version": "3.2.1", 49 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 50 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 51 | "dev": true, 52 | "requires": { 53 | "color-convert": "^1.9.0" 54 | } 55 | }, 56 | "chalk": { 57 | "version": "2.4.2", 58 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 59 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 60 | "dev": true, 61 | "requires": { 62 | "ansi-styles": "^3.2.1", 63 | "escape-string-regexp": "^1.0.5", 64 | "supports-color": "^5.3.0" 65 | } 66 | }, 67 | "chardet": { 68 | "version": "0.7.0", 69 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", 70 | "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", 71 | "dev": true 72 | }, 73 | "cli-cursor": { 74 | "version": "3.1.0", 75 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", 76 | "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", 77 | "dev": true, 78 | "requires": { 79 | "restore-cursor": "^3.1.0" 80 | } 81 | }, 82 | "cli-width": { 83 | "version": "2.2.0", 84 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", 85 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", 86 | "dev": true 87 | }, 88 | "color-convert": { 89 | "version": "1.9.3", 90 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 91 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 92 | "dev": true, 93 | "requires": { 94 | "color-name": "1.1.3" 95 | } 96 | }, 97 | "color-name": { 98 | "version": "1.1.3", 99 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 100 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 101 | "dev": true 102 | }, 103 | "emoji-regex": { 104 | "version": "8.0.0", 105 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 106 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 107 | "dev": true 108 | }, 109 | "escape-string-regexp": { 110 | "version": "1.0.5", 111 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 112 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 113 | "dev": true 114 | }, 115 | "external-editor": { 116 | "version": "3.1.0", 117 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", 118 | "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", 119 | "dev": true, 120 | "requires": { 121 | "chardet": "^0.7.0", 122 | "iconv-lite": "^0.4.24", 123 | "tmp": "^0.0.33" 124 | } 125 | }, 126 | "figures": { 127 | "version": "3.0.0", 128 | "resolved": "https://registry.npmjs.org/figures/-/figures-3.0.0.tgz", 129 | "integrity": "sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g==", 130 | "dev": true, 131 | "requires": { 132 | "escape-string-regexp": "^1.0.5" 133 | } 134 | }, 135 | "has-flag": { 136 | "version": "3.0.0", 137 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 138 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 139 | "dev": true 140 | }, 141 | "iconv-lite": { 142 | "version": "0.4.24", 143 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 144 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 145 | "dev": true, 146 | "requires": { 147 | "safer-buffer": ">= 2.1.2 < 3" 148 | } 149 | }, 150 | "inquirer": { 151 | "version": "6.5.1", 152 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.1.tgz", 153 | "integrity": "sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==", 154 | "dev": true, 155 | "requires": { 156 | "ansi-escapes": "^4.2.1", 157 | "chalk": "^2.4.2", 158 | "cli-cursor": "^3.1.0", 159 | "cli-width": "^2.0.0", 160 | "external-editor": "^3.0.3", 161 | "figures": "^3.0.0", 162 | "lodash": "^4.17.15", 163 | "mute-stream": "0.0.8", 164 | "run-async": "^2.2.0", 165 | "rxjs": "^6.4.0", 166 | "string-width": "^4.1.0", 167 | "strip-ansi": "^5.1.0", 168 | "through": "^2.3.6" 169 | } 170 | }, 171 | "is-fullwidth-code-point": { 172 | "version": "3.0.0", 173 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 174 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 175 | "dev": true 176 | }, 177 | "is-promise": { 178 | "version": "2.1.0", 179 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 180 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", 181 | "dev": true 182 | }, 183 | "lodash": { 184 | "version": "4.17.15", 185 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 186 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", 187 | "dev": true 188 | }, 189 | "mimic-fn": { 190 | "version": "2.1.0", 191 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 192 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 193 | "dev": true 194 | }, 195 | "mute-stream": { 196 | "version": "0.0.8", 197 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", 198 | "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", 199 | "dev": true 200 | }, 201 | "onetime": { 202 | "version": "5.1.0", 203 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", 204 | "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", 205 | "dev": true, 206 | "requires": { 207 | "mimic-fn": "^2.1.0" 208 | } 209 | }, 210 | "os-tmpdir": { 211 | "version": "1.0.2", 212 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 213 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 214 | "dev": true 215 | }, 216 | "restore-cursor": { 217 | "version": "3.1.0", 218 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", 219 | "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", 220 | "dev": true, 221 | "requires": { 222 | "onetime": "^5.1.0", 223 | "signal-exit": "^3.0.2" 224 | } 225 | }, 226 | "run-async": { 227 | "version": "2.3.0", 228 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", 229 | "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", 230 | "dev": true, 231 | "requires": { 232 | "is-promise": "^2.1.0" 233 | } 234 | }, 235 | "rxjs": { 236 | "version": "6.5.2", 237 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", 238 | "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", 239 | "dev": true, 240 | "requires": { 241 | "tslib": "^1.9.0" 242 | } 243 | }, 244 | "safer-buffer": { 245 | "version": "2.1.2", 246 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 247 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 248 | "dev": true 249 | }, 250 | "signal-exit": { 251 | "version": "3.0.2", 252 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 253 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", 254 | "dev": true 255 | }, 256 | "string-width": { 257 | "version": "4.1.0", 258 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.1.0.tgz", 259 | "integrity": "sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==", 260 | "dev": true, 261 | "requires": { 262 | "emoji-regex": "^8.0.0", 263 | "is-fullwidth-code-point": "^3.0.0", 264 | "strip-ansi": "^5.2.0" 265 | } 266 | }, 267 | "strip-ansi": { 268 | "version": "5.2.0", 269 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 270 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 271 | "dev": true, 272 | "requires": { 273 | "ansi-regex": "^4.1.0" 274 | } 275 | }, 276 | "supports-color": { 277 | "version": "5.5.0", 278 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 279 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 280 | "dev": true, 281 | "requires": { 282 | "has-flag": "^3.0.0" 283 | } 284 | }, 285 | "through": { 286 | "version": "2.3.8", 287 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 288 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 289 | "dev": true 290 | }, 291 | "tmp": { 292 | "version": "0.0.33", 293 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 294 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 295 | "dev": true, 296 | "requires": { 297 | "os-tmpdir": "~1.0.2" 298 | } 299 | }, 300 | "tslib": { 301 | "version": "1.10.0", 302 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", 303 | "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", 304 | "dev": true 305 | }, 306 | "type-fest": { 307 | "version": "0.5.2", 308 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", 309 | "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==", 310 | "dev": true 311 | } 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leetcode", 3 | "version": "1.0.0", 4 | "description": "不知道怎么让 leetcode 自动关联 github 就先手动提交吧<( ̄3 ̄)>", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/yeyan1996/leetcode.git" 12 | }, 13 | "keywords": [ 14 | "leetcode" 15 | ], 16 | "author": "yeyan1996", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/yeyan1996/leetcode/issues" 20 | }, 21 | "homepage": "https://github.com/yeyan1996/leetcode#readme", 22 | "devDependencies": { 23 | "inquirer": "^6.5.1", 24 | "@types/inquirer": "^6.5.0", 25 | "@types/node": "^12.7.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | // "outDir": "./", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | } 63 | } 64 | --------------------------------------------------------------------------------