├── .gitignore ├── README.md ├── 算法思维 ├── 递归思维.md ├── 二叉搜索树.md ├── 回溯法.md └── 滑动窗口思想.md ├── 基础算法篇 ├── 排序算法.md ├── 二分搜索.md └── 动态规划.md ├── 数据结构篇 ├── 二进制.md ├── 栈和队列.md ├── 链表.md └── 二叉树.md └── 入门 ├── 经典算法TS实现.md └── 力扣经典算法(Java).md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 算法模板 2 | 3 | 算法模板,最科学的刷题方式,最快速的刷题路径,一个月从入门到 offer,你值得拥有 🐶~ 4 | 5 | 算法模板顾名思义就是刷题的套路模板,掌握了刷题模板之后,刷题也变得好玩起来了~ 6 | 7 | ## 在线阅读 8 | 9 | 本算法模板主要参考自[greyireland](https://github.com/greyireland)的[algorithm-pattern](https://github.com/greyireland/algorithm-pattern)项目,将其Go语言代码翻译成JavaScript语言代码。 10 | 11 | ## 核心内容 12 | 13 | ### 数据结构篇 🐰 14 | 15 | - [二叉树](https://github.com/ligecarryme/algorithm-pattern-JavaScript/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AF%87/%E4%BA%8C%E5%8F%89%E6%A0%91.md) 16 | - [链表](https://github.com/ligecarryme/algorithm-pattern-JavaScript/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AF%87/%E9%93%BE%E8%A1%A8.md) 17 | - [栈和队列](https://github.com/ligecarryme/algorithm-pattern-JavaScript/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AF%87/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97.md) 18 | - [二进制](https://github.com/ligecarryme/algorithm-pattern-JavaScript/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AF%87/%E4%BA%8C%E8%BF%9B%E5%88%B6.md) 19 | 20 | ### 基础算法篇 🐮 21 | 22 | - [二分搜索](https://github.com/ligecarryme/algorithm-pattern-JavaScript/blob/master/%E5%9F%BA%E7%A1%80%E7%AE%97%E6%B3%95%E7%AF%87/%E4%BA%8C%E5%88%86%E6%90%9C%E7%B4%A2.md) 23 | - [排序算法](https://github.com/ligecarryme/algorithm-pattern-JavaScript/blob/master/%E5%9F%BA%E7%A1%80%E7%AE%97%E6%B3%95%E7%AF%87/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md) 24 | - [动态规划](https://github.com/ligecarryme/algorithm-pattern-JavaScript/blob/master/%E5%9F%BA%E7%A1%80%E7%AE%97%E6%B3%95%E7%AF%87/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92.md) 25 | 26 | ### 算法思维篇 🦁 27 | 28 | - [递归思维](https://github.com/ligecarryme/algorithm-pattern-JavaScript/blob/master/%E7%AE%97%E6%B3%95%E6%80%9D%E7%BB%B4/%E9%80%92%E5%BD%92%E6%80%9D%E7%BB%B4.md) 29 | - [滑动窗口思想](https://github.com/ligecarryme/algorithm-pattern-JavaScript/blob/master/%E7%AE%97%E6%B3%95%E6%80%9D%E7%BB%B4/%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%80%9D%E6%83%B3.md) 30 | - [二叉搜索树](https://github.com/ligecarryme/algorithm-pattern-JavaScript/blob/master/%E7%AE%97%E6%B3%95%E6%80%9D%E7%BB%B4/%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) 31 | - [回溯法](https://github.com/ligecarryme/algorithm-pattern-JavaScript/blob/master/%E7%AE%97%E6%B3%95%E6%80%9D%E7%BB%B4/%E5%9B%9E%E6%BA%AF%E6%B3%95.md) 32 | 33 | -------------------------------------------------------------------------------- /算法思维/递归思维.md: -------------------------------------------------------------------------------- 1 | ## 递归 2 | 3 | ### 介绍 4 | 5 | 将大问题转化为小问题,通过递归依次解决各个小问题。 6 | 7 | ### 示例 8 | 9 | ##### 334.反转字符串 [reverse-string](https://leetcode-cn.com/problems/reverse-string/) 10 | 11 | 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 `char[]` 的形式给出。不要给另外的数组分配额外的空间,你必须[原地](https://baike.baidu.com/item/原地算法)修改输入数组、使用 O(1) 的额外空间解决这一问题。 12 | 13 | > 输入:["h","e","l","l","o"] 14 | > 输出:["o","l","l","e","h"] 15 | 16 | 思路:递归、交换都可以 17 | 18 | ```js 19 | var reverseString = function(s) { 20 | reverse(s,0,s.length-1); 21 | return s; 22 | }; 23 | 24 | var reverse = function(s, left, right){ //递归写法 25 | if(left >= right){ 26 | return; 27 | } 28 | [s[left], s[right]] = [s[right],s[left]]; 29 | reverse(s,left+1,right-1); 30 | } 31 | ``` 32 | 33 | ```js 34 | var reverseString = function(s) { 35 | let len = s.length; 36 | for (let i = 0; i < len >> 1; i++) { 37 | [s[i], s[len - 1 - i]] = [s[len - 1 - i], s[i]]; 38 | } 39 | }; 40 | ``` 41 | 42 | ##### 24.两两交换链表中的节点 [swap-nodes-in-pairs](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) 43 | 44 | 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 45 | 46 | > 输入:head = [1,2,3,4] 47 | > 输出:[2,1,4,3] 48 | 49 | ```js 50 | function swapPairs(head: ListNode | null): ListNode | null { 51 | return helper(head) 52 | }; 53 | function helper(head: ListNode | null): ListNode | null { 54 | if(head === null || head.next === null){ 55 | return head 56 | } 57 | let headNext: ListNode = head.next.next 58 | let p: ListNode = head.next 59 | p.next = head 60 | head.next = helper(headNext) 61 | return p 62 | } 63 | ``` 64 | 65 | ##### [95. 不同的二叉搜索树 II](https://leetcode-cn.com/problems/unique-binary-search-trees-ii/) 66 | 67 | 给定一个整数 *n*,生成所有由 1 ... *n* 为节点所组成的 **二叉搜索树** 。 68 | 69 | ```tsx 70 | function generateTrees(n: number): Array { 71 | if (n === 0) { 72 | return [null] 73 | } 74 | return generate(1, n) 75 | }; 76 | function generate(start: number, end: number): Array { 77 | if (start > end) { 78 | return [null] 79 | } 80 | const ans: TreeNode[] = [] 81 | for (let i = start; i <= end; i++) { 82 | const left: TreeNode[] = generate(start, i - 1) 83 | const right: TreeNode[] = generate(i + 1, end) 84 | for (let j = 0; j < left.length; j++) { 85 | for (let k = 0; k < right.length; k++) { 86 | const root: TreeNode = new TreeNode(i) 87 | root.left = left[j] 88 | root.right = right[k] 89 | ans.push(root) 90 | } 91 | } 92 | } 93 | return ans 94 | } 95 | ``` 96 | 97 | ##### [509. 斐波那契数](https://leetcode-cn.com/problems/fibonacci-number/) 98 | 99 | ```tsx 100 | function fib(n: number): number { 101 | if (n === 0 || n === 1) { 102 | return n 103 | } 104 | return fib(n - 1) + fib(n - 2) 105 | }; 106 | ``` 107 | 108 | ## 练习 109 | 110 | - [reverse-string](https://leetcode-cn.com/problems/reverse-string/) 111 | - [swap-nodes-in-pairs](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) 112 | - [unique-binary-search-trees-ii](https://leetcode-cn.com/problems/unique-binary-search-trees-ii/) 113 | - [fibonacci-number](https://leetcode-cn.com/problems/fibonacci-number/) 114 | 115 | -------------------------------------------------------------------------------- /算法思维/二叉搜索树.md: -------------------------------------------------------------------------------- 1 | ## 二叉搜索树 2 | 3 | ### 定义 4 | 5 | - 每个节点中的值必须大于(或等于)存储在其左侧子树中的任何值。 6 | - 每个节点中的值必须小于(或等于)存储在其右子树中的任何值。 7 | 8 | ### 应用 9 | 10 | ##### [98. 验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree/) 11 | 12 | ```tsx 13 | function isValidBST(root: TreeNode | null): boolean { 14 | return helper(root, -Infinity, Infinity) 15 | }; 16 | 17 | function helper(node: TreeNode | null, lower: number, upper: number): boolean { 18 | if (node === null) { 19 | return true 20 | } 21 | if (node.val <= lower || node.val >= upper) { 22 | return false 23 | } 24 | return helper(node.left, lower, node.val) && helper(node.right, node.val, upper) 25 | } 26 | ``` 27 | 28 | ##### [701. 二叉搜索树中的插入操作](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) 29 | 30 | 给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 31 | 32 | ```tsx 33 | function insertIntoBST(root: TreeNode | null, val: number): TreeNode | null { 34 | if (root === null) { 35 | return new TreeNode(val) 36 | } 37 | if (root.val > val) { 38 | root.left = insertIntoBST(root.left, val) 39 | } else { 40 | root.right = insertIntoBST(root.right, val) 41 | } 42 | return root 43 | }; 44 | ``` 45 | 46 | ##### [450. 删除二叉搜索树中的节点](https://leetcode-cn.com/problems/delete-node-in-a-bst/) 47 | 48 | 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 49 | 50 | > 思路:[秒懂就完事了](https://leetcode-cn.com/problems/delete-node-in-a-bst/solution/miao-dong-jiu-wan-shi-liao-by-terry2020-tc0o/) 51 | 52 | ```tsx 53 | 54 | function deleteNode(root: TreeNode | null, key: number): TreeNode | null { 55 | if (root === null) { 56 | return root 57 | } 58 | if (root.val < key) { 59 | root.right = deleteNode(root.right, key) 60 | } else if (root.val > key) { 61 | root.left = deleteNode(root.left, key) 62 | } else { 63 | if (root.left === null) { 64 | return root.right 65 | } else if (root.right === null) { 66 | return root.left 67 | } else { 68 | let cur: TreeNode = root.right 69 | while (cur.left !== null) { 70 | cur = cur.left 71 | } 72 | cur.left = root.left 73 | return root.right 74 | } 75 | } 76 | return root 77 | }; 78 | ``` 79 | 80 | ##### [110. 平衡二叉树](https://leetcode-cn.com/problems/balanced-binary-tree/) 81 | 82 | 给定一个二叉树,判断它是否是高度平衡的二叉树。 83 | 84 | ```js 85 | const isBalanced = function (root) { 86 | if (maxDepth(root) === -1) { 87 | return false 88 | } 89 | return true 90 | }; 91 | 92 | const maxDepth = function (root) { 93 | if (root === null) { 94 | return 0 95 | } 96 | const left = maxDepth(root.left) 97 | const right = maxDepth(root.right) 98 | if (left === -1 || right === -1 || Math.abs(right - left) > 1) { 99 | return -1 100 | } 101 | if (left > right) { 102 | return left + 1 103 | } else { 104 | return right + 1 105 | } 106 | } 107 | ``` 108 | 109 | ## 练习 110 | 111 | - [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) 112 | - [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) 113 | - [delete-node-in-a-bst](https://leetcode-cn.com/problems/delete-node-in-a-bst/) 114 | - [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) 115 | 116 | -------------------------------------------------------------------------------- /基础算法篇/排序算法.md: -------------------------------------------------------------------------------- 1 | ## 排序算法 2 | 3 | #### 快速排序 4 | 5 | ```js 6 | var quickSort = function (arrays, start, end) { 7 | if (start < end) { 8 | // pivot 是分割点,pivot左边的比nums[pivot]小,右边的比nums[pivot]大 9 | let pivot = partition(arrays, start, end); 10 | quickSort(arrays, start, pivot - 1); 11 | quickSort(arrays, pivot + 1, end); 12 | } 13 | return arrays; 14 | } 15 | 16 | var partition = function (nums, start, end) { 17 | let target = nums[end]; 18 | let i = start; 19 | // 将小于target移到数组前面 20 | for (let j = start; j < end; j++) { 21 | if (nums[j] < target) { 22 | [nums[i], nums[j]] = [nums[j], nums[i]] 23 | i++; 24 | } 25 | } 26 | // 把中间的值换为用于比较的基准值 27 | [nums[i], nums[end]] = [nums[end], nums[i]] 28 | return i; 29 | } 30 | 31 | let arrays = [72, 6, 57, 88, 60, 42, 83, 73, 48, 85] 32 | console.log(quickSort(arrays, 0, arrays.length - 1)) 33 | ``` 34 | 35 | 下面这种分治的算法较好理解,挖坑填数。 36 | 37 | ```js 38 | var partition = function (nums, left, right) { 39 | let pivot = nums[left]; 40 | while (left < right) { 41 | while (left < right && nums[right] > pivot) { //从右向左找到比基准小的 42 | right--; 43 | } 44 | if (left < right) { 45 | nums[left] = nums[right]; //覆盖基准的值 46 | left++; //左指针前进一位 47 | } 48 | while (left < right && nums[left] < pivot) { 49 | left++; 50 | } 51 | if (left < right) { 52 | nums[right] = nums[left]; 53 | right--; 54 | } 55 | } 56 | nums[left] = pivot; //left === right 57 | return left; 58 | } 59 | ``` 60 | 61 | #### 归并排序 62 | 63 | ```js 64 | var mergeSort = function (nums, start, end) { 65 | if (start < end) { 66 | let mid = (start + end) >> 1; 67 | mergeSort(nums, start, mid); 68 | mergeSort(nums, mid + 1, end); 69 | nums = merge(nums, start, mid, end); 70 | } 71 | return nums; 72 | } 73 | 74 | var merge = function (nums, left, mid, right) { 75 | let arr = []; 76 | let i = left, j = mid + 1; 77 | while (i <= mid && j <= right) { 78 | if (nums[i] < nums[j]) { 79 | arr.push(nums[i]); 80 | i++; 81 | } else { 82 | arr.push(nums[j]); 83 | j++; 84 | } 85 | } 86 | while (i <= mid) { 87 | arr.push(nums[i++]); 88 | } 89 | while (j <= right) { 90 | arr.push(nums[j++]); 91 | } 92 | return arr; 93 | } 94 | 95 | let nums = [2, 3, 5, 9, 4, 6, 8]; 96 | console.log(mergeSort(nums,0,nums.length-1)) 97 | ``` 98 | 99 | #### 堆排序 100 | 101 | 用数组表示的完美二叉树 complete binary tree 102 | 103 | [动画展示](https://www.bilibili.com/video/av18980178/) 104 | 105 | ```js 106 | var move = function (nums, low, high) { 107 | let i = low, j = 2 * i + 1; 108 | let temp = nums[i]; 109 | while (j <= high) { 110 | if (j < high && nums[j] < nums[j + 1]) { 111 | j++; 112 | } 113 | if (temp < nums[j]) { 114 | nums[i] = nums[j]; 115 | i = j; 116 | j = 2 * i + 1; 117 | } else { 118 | break; 119 | } 120 | } 121 | nums[i] = temp; 122 | } 123 | 124 | var heapSort = function (nums) { 125 | let i, temp; 126 | let n = nums.length - 1; 127 | for (i = n >> 1; i >= 0; i--) { 128 | move(nums, i, n); 129 | } 130 | for (i = n; i >= 1; i--) { 131 | temp = nums[0]; 132 | nums[0] = nums[i]; 133 | nums[i] = temp; 134 | move(nums, 0, i - 1); 135 | } 136 | return nums; 137 | } 138 | 139 | let arr = [2,4,1,5,8,6,7,9] 140 | console.log(heapSort(arr)) 141 | ``` 142 | 143 | ## 参考 144 | 145 | [十大经典排序](https://www.cnblogs.com/onepixel/p/7674659.html) 146 | 147 | [二叉堆](https://labuladong.gitbook.io/algo/shu-ju-jie-gou-xi-lie/er-cha-dui-xiang-jie-shi-xian-you-xian-ji-dui-lie) 148 | 149 | ## 练习 150 | 151 | - 手写快排、归并、堆排序 -------------------------------------------------------------------------------- /数据结构篇/二进制.md: -------------------------------------------------------------------------------- 1 | ## 二进制 2 | 3 | JS二进制基础知识: [二进制](https://wangdoc.com/javascript/operators/bit.html) 4 | 5 | ##### [136.只出现一次的数字](https://leetcode-cn.com/problems/single-number/) 6 | 7 | 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 8 | 9 | > 输入: [2,2,1] 10 | > 输出: 1 11 | > 12 | > 4 ^ 1 ^ 2 ^ 1 ^ 2 = 4 13 | 14 | 思路:a === a ^ b ^ b 15 | 16 | ```js 17 | var singleNumber = function(nums) { 18 | let res = 0; 19 | for(let num of nums){ 20 | res ^= num; 21 | } 22 | return res; 23 | }; 24 | ``` 25 | 26 | ##### [137.只出现一次的数字 II](https://leetcode-cn.com/problems/single-number-ii/) 27 | 28 | 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。 29 | 30 | > 输入: [2,2,3,2] 31 | > 输出: 3 32 | 33 | ```js 34 | var singleNumber = function(nums) { 35 | let seenOnce = 0, seenTwice = 0; 36 | for(let num of nums){ 37 | seenOnce = ~seenTwice & (seenOnce ^ num); 38 | seenTwice = ~seenOnce & (seenTwice ^ num); 39 | } 40 | return seenOnce; 41 | }; 42 | ``` 43 | 44 | ##### [260. 只出现一次的数字 III](https://leetcode-cn.com/problems/single-number-iii/) 45 | 46 | ```js 47 | /* 48 | * 执行用时:560 ms, 在所有 JavaScript 提交中击败了8.67%的用户 😂,indexof这么耗时 49 | * 内存消耗:38.4 MB, 在所有 JavaScript 提交中击败了91.67%的用户 50 | */ 51 | var singleNumber = function (nums) { 52 | const res = [] 53 | nums.forEach(num => { 54 | if (nums.indexOf(num) === nums.lastIndexOf(num)) { 55 | res.push(num) 56 | } 57 | }) 58 | return res 59 | }; 60 | ``` 61 | 62 | ```js 63 | var singleNumber = function (nums) { 64 | let ret = 0; 65 | for (let n of nums) { 66 | ret ^= n; 67 | } 68 | let div = 1; 69 | while ((div & ret) === 0) { 70 | div <<= 1; 71 | } 72 | let a = 0, b = 0; 73 | for (let n of nums) { 74 | if ((div & n) !== 0) { 75 | a ^= n; 76 | } else { 77 | b ^= n; 78 | } 79 | } 80 | return [a, b]; 81 | } 82 | ``` 83 | 84 | ##### [191. 位1的个数](https://leetcode-cn.com/problems/number-of-1-bits/) 85 | 86 | ```js 87 | var hammingWeight = function(n) { 88 | let res = 0 89 | while(n){ 90 | n = n & (n-1) 91 | res++ 92 | } 93 | return res 94 | }; 95 | ``` 96 | 97 | ##### [338. 比特位计数](https://leetcode-cn.com/problems/counting-bits/) 98 | 99 | 给定一个非负整数 **num**。对于 **0 ≤ i ≤ num** 范围中的每个数字 **i** ,计算其二进制数中的 1 的数目并将它们作为数组返回。 100 | 101 | ```js 102 | var countBits = function (num) { 103 | const res = [] 104 | for (let i = 0; i <= num; i++) { 105 | let n = i 106 | let times = 0 107 | while (n) { 108 | n = n & (n - 1) 109 | times++ 110 | } 111 | res.push(times) 112 | } 113 | return res 114 | }; 115 | ``` 116 | 117 | ##### [190. 颠倒二进制位](https://leetcode-cn.com/problems/reverse-bits/) 118 | 119 | 颠倒给定的 32 位无符号整数的二进制位。 120 | 121 | ```js 122 | var reverseBits = function (n) { 123 | let rev = 0; 124 | for (let i = 0; i < 32 && n > 0; ++i) { 125 | rev |= (n & 1) << (31 - i); 126 | n >>>= 1; 127 | } 128 | return rev >>> 0; 129 | }; 130 | ``` 131 | 132 | ##### [201. 数字范围按位与](https://leetcode-cn.com/problems/bitwise-and-of-numbers-range/) 133 | 134 | 给你两个整数 `left` 和 `right` ,表示区间 `[left, right]` ,返回此区间内所有数字 **按位与** 的结果(包含 `left` 、`right` 端点)。 135 | 136 | ```js 137 | var rangeBitwiseAnd = function (left, right) { 138 | let count = 0 139 | while (left !== right) { 140 | left >>= 1 141 | right >>= 1 142 | count++ 143 | } 144 | return left << count 145 | }; 146 | ``` 147 | 148 | ## 总结 149 | 150 | 玄 151 | 152 | ## 练习 153 | 154 | - [single-number](https://leetcode-cn.com/problems/single-number/) 155 | - [single-number-ii](https://leetcode-cn.com/problems/single-number-ii/) 156 | - [single-number-iii](https://leetcode-cn.com/problems/single-number-iii/) 157 | - [number-of-1-bits](https://leetcode-cn.com/problems/number-of-1-bits/) 158 | - [counting-bits](https://leetcode-cn.com/problems/counting-bits/) 159 | - [reverse-bits](https://leetcode-cn.com/problems/reverse-bits/) -------------------------------------------------------------------------------- /入门/经典算法TS实现.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | ### 排序算法 4 | 5 | | 算法 | 稳定 | 原地排序 | 时间复杂度 | 空间复杂度 | 备注 | 6 | | :--------------: | :--: | :------: | :--------------------------: | :--------: | :----------------------: | 7 | | 冒泡排序 | yes | yes | N^2^ | 1 | | 8 | | 选择排序 | no | yes | N2 | 1 | | 9 | | 插入排序 | yes | yes | N \~ N2 | 1 | 时间复杂度和初始顺序有关 | 10 | | 希尔排序 | no | yes | N 的若干倍乘于递增序列的长度 | 1 | | 11 | | 快速排序 | no | yes | NlogN | logN | | 12 | | 三向切分快速排序 | no | yes | N \~ NlogN | logN | 适用于有大量重复主键 | 13 | | 归并排序 | yes | no | NlogN | N | | 14 | | 堆排序 | no | yes | NlogN | 1 | | 15 | 16 | #### 选择排序 17 | 18 | ```tsx 19 | const selectSort = function (nums: number[]) { 20 | const len = nums.length 21 | for (let i = 0; i < len; i++) { 22 | let min = i 23 | for (let j = i + 1; j < len; j++) { 24 | if (nums[j] < nums[min]) { 25 | min = j 26 | } 27 | } 28 | ;[nums[i], nums[min]] = [nums[min], nums[i]] 29 | } 30 | return nums 31 | } 32 | ``` 33 | 34 | #### 冒泡排序 35 | 36 | ```tsx 37 | const bubbleSort = function (arrs: number[]) { 38 | const len = arrs.length 39 | let isSortFlag = false // 如果排好序只需遍历一遍 40 | for (let i = len - 1; i > 0 && !isSortFlag; i--) { 41 | isSortFlag = true 42 | for (let j = 0; j < i; j++) { 43 | if (arrs[j] > arrs[j + 1]) { 44 | isSortFlag = false 45 | ;[arrs[j], arrs[j + 1]] = [arrs[j + 1], arrs[j]] 46 | } 47 | } 48 | } 49 | return arrs 50 | } 51 | ``` 52 | 53 | #### 插入排序 54 | 55 | ```tsx 56 | const insertSort = function (nums: number[]) { 57 | const len = nums.length 58 | for (let i = 1; i < len; i++) { 59 | for (let j = i; j > 0; j--) { 60 | if (nums[j] < nums[j - 1]) { 61 | ;[nums[j], nums[j - 1]] = [nums[j - 1], nums[j]] 62 | } 63 | } 64 | } 65 | return nums 66 | } 67 | ``` 68 | 69 | #### 希尔排序 70 | 71 | ```tsx 72 | const shellSort = (nums: number[]) => { 73 | const len = nums.length 74 | let h = len >> 1 75 | while (h >= 1) { 76 | for (let i = h; i < len; i++) { 77 | for (let j = i; j >= h && nums[j] < nums[j - h]; j -= h) { 78 | [nums[j], nums[j - h]] = [nums[j - h], nums[j]] 79 | } 80 | } 81 | h >>= 1 82 | } 83 | } 84 | ``` 85 | 86 | #### 归并排序 87 | 88 | ```tsx 89 | const mergeSort = (nums: number[], left: number, right: number) => { 90 | if (left === right) return [nums[left]] 91 | const mid = left + ((right - left) >> 1) 92 | const leftArray = mergeSort(nums, left, mid) 93 | const rightArray = mergeSort(nums, mid + 1, right) 94 | const newArray: number[] = new Array(leftArray.length + rightArray.length) 95 | let i = 0, j = 0, k = 0 96 | while (i < leftArray.length && j < rightArray.length) { 97 | newArray[k++] = leftArray[i] < rightArray[j] ? leftArray[i++] : rightArray[j++] 98 | } 99 | while (i < leftArray.length) { 100 | newArray[k++] = leftArray[i++] 101 | } 102 | while (j < rightArray.length) { 103 | newArray[k++] = rightArray[j++] 104 | } 105 | return newArray 106 | } 107 | ``` 108 | 109 | #### 快速排序 110 | 111 | ```tsx 112 | const quickSort = (nums: number[], start: number, end: number) => { 113 | let left = start 114 | let right = end 115 | const pivot = nums[start] 116 | while (left < right) { 117 | while (left < right && nums[right] > pivot) { 118 | right-- 119 | } 120 | while (left < right && nums[left] < pivot) { 121 | left++ 122 | } 123 | if (nums[left] === nums[right] && left < right) { 124 | left++ 125 | } else { 126 | [nums[left], nums[right]] = [nums[right], nums[left]] 127 | } 128 | } 129 | if (start < left - 1) { 130 | quickSort(nums, start, left - 1) 131 | } 132 | if (right + 1 < end) { 133 | quickSort(nums, right + 1, end) 134 | } 135 | return nums 136 | } 137 | ``` 138 | 139 | #### 堆排序 140 | 141 | ```java 142 | 143 | ``` 144 | 145 | -------------------------------------------------------------------------------- /算法思维/回溯法.md: -------------------------------------------------------------------------------- 1 | ## 回溯法 2 | 3 | ### 背景 4 | 5 | 回溯法(backtrack)常用于遍历列表所有子集,是 DFS 深度搜索一种,一般用于全排列,穷尽所有可能,遍历的过程实际上是一个决策树的遍历过程。时间复杂度一般 O(N!),它不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。 6 | 7 | ### 模板 8 | 9 | ``` 10 | result = [] 11 | func backtrack(选择列表,路径): 12 | if 满足结束条件: 13 | result.add(路径) 14 | return 15 | for 选择 in 选择列表: 16 | 做选择 17 | backtrack(选择列表,路径) 18 | 撤销选择 19 | ``` 20 | 21 | 核心就是从选择列表里做一个选择,然后一直递归往下搜索答案,如果遇到路径不通,就返回来撤销这次选择。 22 | 23 | ### 示例 24 | 25 | ##### [78. 子集](https://leetcode-cn.com/problems/subsets/) 26 | 27 | 给你一个整数数组 `nums` ,数组中的元素 **互不相同** 。返回该数组所有可能的子集(幂集)。解集 **不能** 包含重复的子集。你可以按 **任意顺序** 返回解集。 28 | 29 | ```tsx 30 | function subsets(nums: number[]): number[][] { 31 | const res: number[][] = [] 32 | helper(0, nums, res, []) 33 | return res 34 | }; 35 | function helper(i: number, nums: number[], res: number[][], tmp: number[]): void { 36 | res.push([].concat(tmp)) 37 | for (let j = i; j < nums.length; j++) { 38 | tmp.push(nums[j]) 39 | helper(j + 1, nums, res, tmp) 40 | tmp.pop() 41 | } 42 | } 43 | ``` 44 | 45 | ##### [90. 子集 II](https://leetcode-cn.com/problems/subsets-ii/) 46 | 47 | 给你一个整数数组 `nums` ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。 48 | 49 | ```tsx 50 | function subsetsWithDup(nums: number[]): number[][] { 51 | const res: number[][] = [] 52 | nums = nums.sort() 53 | helper(0, nums, res, []) 54 | return res 55 | }; 56 | function helper(i: number, nums: number[], res: number[][], tmp: number[]): void { 57 | res.push([].concat(tmp)) 58 | for (let j = i; j < nums.length; j++) { 59 | if (j > i && nums[j] === nums[j - 1]) { 60 | continue 61 | } 62 | tmp.push(nums[j]) 63 | helper(j + 1, nums, res, tmp) 64 | tmp.pop() 65 | } 66 | } 67 | ``` 68 | 69 | ##### [46. 全排列](https://leetcode-cn.com/problems/permutations/) 70 | 71 | 给定一个不含重复数字的数组 `nums` ,返回其 **所有可能的全排列** 。你可以 **按任意顺序** 返回答案。 72 | 73 | ```tsx 74 | function permute(nums: number[]): number[][] { 75 | const res: number[][] = [] 76 | const templist: number[] = [] 77 | const visited: boolean[] = new Array(nums.length).fill(false) 78 | backTrack(nums, res, templist, visited) 79 | return res 80 | }; 81 | 82 | function backTrack(nums, res, templist, visited) { 83 | if (templist.length === nums.length) { 84 | res.push([].concat(templist)) 85 | return 86 | } 87 | for (let i = 0; i < nums.length; i++) { 88 | if (visited[i]) { 89 | continue 90 | } 91 | templist.push(nums[i]) 92 | visited[i] = true 93 | backTrack(nums, res, templist, visited) 94 | visited[i] = false 95 | templist.pop(nums[i]) 96 | } 97 | } 98 | ``` 99 | 100 | ##### [47. 全排列 II](https://leetcode-cn.com/problems/permutations-ii/) 101 | 102 | 给定一个可包含重复数字的序列 `nums` ,**按任意顺序** 返回所有不重复的全排列。 103 | 104 | ```tsx 105 | function permuteUnique(nums: number[]): number[][] { 106 | nums = nums.sort((a, b) => a - b) 107 | const res: number[][] = [] 108 | const templist: number[] = [] 109 | const visited: boolean[] = new Array(nums.length).fill(false) 110 | backTrack(nums, res, templist, visited) 111 | return res 112 | }; 113 | 114 | function backTrack(nums, res, templist, visited) { 115 | if (templist.length === nums.length) { 116 | res.push([].concat(templist)) 117 | return 118 | } 119 | for (let i = 0; i < nums.length; i++) { 120 | if (visited[i]) { 121 | continue 122 | } 123 | if (i > 0 && nums[i] === nums[i - 1] && !visited[i - 1]) { 124 | continue 125 | } 126 | templist.push(nums[i]) 127 | visited[i] = true 128 | backTrack(nums, res, templist, visited) 129 | visited[i] = false 130 | templist.pop(nums[i]) 131 | } 132 | } 133 | ``` 134 | 135 | ## 练习 136 | 137 | - [subsets](https://leetcode-cn.com/problems/subsets/) 138 | - [subsets-ii](https://leetcode-cn.com/problems/subsets-ii/) 139 | - [permutations](https://leetcode-cn.com/problems/permutations/) 140 | - [permutations-ii](https://leetcode-cn.com/problems/permutations-ii/) 141 | 142 | 挑战题目 143 | 144 | - [combination-sum](https://leetcode-cn.com/problems/combination-sum/) 145 | - [letter-combinations-of-a-phone-number](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/) 146 | - [palindrome-partitioning](https://leetcode-cn.com/problems/palindrome-partitioning/) 147 | - [restore-ip-addresses](https://leetcode-cn.com/problems/restore-ip-addresses/) 148 | - [permutations](https://leetcode-cn.com/problems/permutations/) 149 | 150 | -------------------------------------------------------------------------------- /算法思维/滑动窗口思想.md: -------------------------------------------------------------------------------- 1 | ## 滑动窗口 2 | 3 | ### 模板 4 | 5 | ```c++ 6 | /* 滑动窗口算法框架 */ 7 | void slidingWindow(string s, string t) { 8 | unordered_map need, window; 9 | for (char c : t) need[c]++; 10 | 11 | int left = 0, right = 0; 12 | int valid = 0; 13 | while (right < s.size()) { 14 | // c 是将移入窗口的字符 15 | char c = s[right]; 16 | // 右移窗口 17 | right++; 18 | // 进行窗口内数据的一系列更新 19 | ... 20 | 21 | /*** debug 输出的位置 ***/ 22 | printf("window: [%d, %d)\n", left, right); 23 | /********************/ 24 | 25 | // 判断左侧窗口是否要收缩 26 | while (window needs shrink) { 27 | // d 是将移出窗口的字符 28 | char d = s[left]; 29 | // 左移窗口 30 | left++; 31 | // 进行窗口内数据的一系列更新 32 | ... 33 | } 34 | } 35 | } 36 | ``` 37 | 38 | 需要变化的地方 39 | 40 | - 1、右指针右移之后窗口数据更新 41 | - 2、判断窗口是否要收缩 42 | - 3、左指针右移之后窗口数据更新 43 | - 4、根据题意计算结果 44 | 45 | ### 示例 46 | 47 | ##### [76. 最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/) 48 | 49 | 给你一个字符串 `s` 、一个字符串 `t` 。返回 `s` 中涵盖 `t` 所有字符的最小子串。如果 `s` 中不存在涵盖 `t` 所有字符的子串,则返回空字符串 `""` 。 50 | 51 | ```tsx 52 | function minWindow(s: string, t: string): string { 53 | if (s === '' || t === '') { 54 | return '' 55 | } 56 | const need: number[] = new Array(128).fill(0) 57 | for (let i = 0; i < t.length; i++) { 58 | need[t.charCodeAt(i)]++ 59 | } 60 | let left: number = 0, right: number = 0, size = Number.MAX_SAFE_INTEGER, count: number = t.length, start: number = 0 61 | while (right < s.length) { 62 | let c: string = s[right] 63 | if (need[c.charCodeAt(0)] > 0) { 64 | count-- 65 | } 66 | need[c.charCodeAt(0)]-- 67 | if (count === 0) { 68 | while (left < right && need[s.charCodeAt(left)] < 0) { 69 | need[s.charCodeAt(left)]++ 70 | left++ 71 | } 72 | if (right - left + 1 < size) { 73 | size = right - left + 1 74 | start = left 75 | } 76 | need[s.charCodeAt(left)]++ 77 | left++ 78 | count++ 79 | } 80 | right++ 81 | } 82 | return size === Number.MAX_SAFE_INTEGER ? '' : s.substr(start, size) 83 | }; 84 | ``` 85 | 86 | ##### [567. 字符串的排列](https://leetcode-cn.com/problems/permutation-in-string/) 87 | 88 | 给定两个字符串 `s1` 和 `s2`,写一个函数来判断 `s2` 是否包含 `s1` 的排列。换句话说,第一个字符串的排列之一是第二个字符串的 **子串** 。 89 | 90 | ```tsx 91 | function checkInclusion(s1: string, s2: string): boolean { 92 | if (s2.length < s1.length) { 93 | return false 94 | } 95 | const s1arr: number[] = new Array(26).fill(0) 96 | const s2arr: number[] = new Array(26).fill(0) 97 | for (let i = 0; i < s1.length; i++) { 98 | s1arr[s1.charCodeAt(i) - 97]++ 99 | s2arr[s2.charCodeAt(i) - 97]++ 100 | } 101 | if (s1arr.toString() === s2arr.toString()) { 102 | return true 103 | } 104 | for (let j = s1.length; j < s2.length; j++) { 105 | s2arr[s2.charCodeAt(j) - 97]++ 106 | s2arr[s2.charCodeAt(j - s1.length) - 97]-- 107 | if (s1arr.toString() === s2arr.toString()) { 108 | return true 109 | } 110 | } 111 | return false 112 | }; 113 | ``` 114 | 115 | ```tsx 116 | function checkInclusion(s1: string, s2: string): boolean { 117 | const win = new Map() 118 | const need = new Map() 119 | for (let i = 0; i < s1.length; i++) { 120 | need.set(s1[i], need.has(s1[i]) ? need.get(s1[i]) + 1 : 1) 121 | } 122 | let left: number = 0, right: number = 0, match: number = 0 123 | while (right < s2.length) { 124 | const c: string = s2[right] 125 | right++ 126 | if (need.has(c)) { 127 | win.set(c, win.has(c) ? win.get(c) + 1 : 1) 128 | if (win.get(c) === need.get(c)) { 129 | match++ 130 | } 131 | } 132 | while (right - left >= s1.length) { 133 | if (match === need.size) { 134 | return true 135 | } 136 | const d: string = s2[left] 137 | left++ 138 | if (need.has(d)) { 139 | if (win.get(d) === need.get(d)) { 140 | match-- 141 | } 142 | win.set(d, win.get(d) - 1) 143 | } 144 | } 145 | } 146 | return false 147 | }; 148 | ``` 149 | 150 | ##### [438. 找到字符串中所有字母异位词](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/) 151 | 152 | 给定一个字符串 **s** 和一个非空字符串 **p**,找到 **s** 中所有是 **p** 的字母异位词的子串,返回这些子串的起始索引。 153 | 154 | ```tsx 155 | function findAnagrams(s: string, p: string): number[] { 156 | const win = new Map() 157 | const need = new Map() 158 | const ans: number[] = [] 159 | for (let i = 0; i < p.length; i++) { 160 | need.set(p[i], need.has(p[i]) ? need.get(p[i]) + 1 : 1) 161 | } 162 | let left: number = 0, right: number = 0, match: number = 0 163 | while (right < s.length) { 164 | const c: string = s[right] 165 | right++ 166 | if (need.has(c)) { 167 | win.set(c, win.has(c) ? win.get(c) + 1 : 1) 168 | if (win.get(c) === need.get(c)) { 169 | match++ 170 | } 171 | } 172 | while (right - left >= p.length) { 173 | if (match === need.size) { 174 | ans.push(left) 175 | } 176 | const d: string = s[left] 177 | left++ 178 | if (need.has(d)) { 179 | if (win.get(d) === need.get(d)) { 180 | match-- 181 | } 182 | win.set(d, win.get(d) - 1) 183 | } 184 | } 185 | } 186 | return ans 187 | }; 188 | ``` 189 | 190 | ##### [3. 无重复字符的最长子串](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) 191 | 192 | 给定一个字符串,请你找出其中不含有重复字符的 **最长子串** 的长度。 193 | 194 | ```tsx 195 | function lengthOfLongestSubstring(s: string): number { 196 | const win = new Set() 197 | let left: number = 0, right: number = 0, ans: number = 0 198 | while (right < s.length) { 199 | let c: string = s[right] 200 | if (win.has(c)) { 201 | c = s[left] 202 | win.delete(c) 203 | left++ 204 | } else { 205 | win.add(c) 206 | right++ 207 | } 208 | ans = Math.max(ans, win.size) 209 | } 210 | return ans 211 | }; 212 | ``` 213 | 214 | ### 总结 215 | 216 | - 和双指针题目类似,更像双指针的升级版,滑动窗口核心点是维护一个窗口集,根据窗口集来进行处理 217 | - 核心步骤 218 | - right 右移 219 | - 收缩 220 | - left 右移 221 | - 求结果 222 | 223 | ### 练习 224 | 225 | - [minimum-window-substring](https://leetcode-cn.com/problems/minimum-window-substring/) 226 | - [permutation-in-string](https://leetcode-cn.com/problems/permutation-in-string/) 227 | - [find-all-anagrams-in-a-string](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/) 228 | - [longest-substring-without-repeating-characters](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) -------------------------------------------------------------------------------- /数据结构篇/栈和队列.md: -------------------------------------------------------------------------------- 1 | ## 栈和队列 2 | 3 | ### 简介 4 | 5 | **栈**的特点是后入先出,根据这个特点可以临时保存一些数据,之后用到依次再弹出来,常用于 DFS 深度搜索 6 | 7 | **队列**一般常用于 BFS 广度搜索,类似一层一层的搜索。 8 | 9 | ### Stack 栈 10 | 11 | ##### [155.最小栈](https://leetcode-cn.com/problems/min-stack/) 12 | 13 | > 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。 14 | 15 | 思路:用两个栈实现,一个最小栈始终保证最小值在顶部 16 | 17 | ```js 18 | /** 19 | * initialize your data structure here. 20 | */ 21 | var MinStack = function() { 22 | this.stack = []; // JS用数组表示栈 23 | this.minStack = [Infinity]; 24 | }; 25 | 26 | /** 27 | * @param {number} x 28 | * @return {void} 29 | */ 30 | MinStack.prototype.push = function(x) { 31 | this.stack.push(x); 32 | this.minStack.push(Math.min(this.minStack[this.minStack.length-1],x)); 33 | }; 34 | 35 | /** 36 | * @return {void} 37 | */ 38 | MinStack.prototype.pop = function() { 39 | this.stack.pop(); 40 | this.minStack.pop(); 41 | }; 42 | 43 | /** 44 | * @return {number} 45 | */ 46 | MinStack.prototype.top = function() { 47 | return this.stack[this.stack.length-1]; 48 | }; 49 | 50 | /** 51 | * @return {number} 52 | */ 53 | MinStack.prototype.getMin = function() { 54 | return this.minStack[this.minStack.length-1]; 55 | }; 56 | ``` 57 | 58 | ##### [150.逆波兰表达式求值 ](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) 59 | 60 | ```js 61 | var evalRPN = function (tokens) { 62 | const stack = [] 63 | for (const item of tokens) { 64 | if (!isNaN(+item)) { 65 | stack.push(item) 66 | } else { 67 | let res = 0 68 | const top = parseInt(stack.pop()) //在栈中的位置靠上(被除数) 69 | const down = parseInt(stack.pop()) //靠下(除数) 70 | switch (item) { 71 | case '+': 72 | res = top + down 73 | break 74 | case '-': 75 | res = down - top 76 | break 77 | case '*': 78 | res = top * down 79 | break 80 | case '/': 81 | res = parseInt(down / top) 82 | break 83 | } 84 | stack.push(res) 85 | } 86 | } 87 | return stack[0] 88 | }; 89 | ``` 90 | 91 | ##### [394. 字符串解码](https://leetcode-cn.com/problems/decode-string/) 92 | 93 | 给定一个经过编码的字符串,返回它解码后的字符串。 94 | 95 | > 输入:s = "3[a]2[bc]" 96 | > 输出:"aaabcbc" 97 | 98 | ```js 99 | var decodeString = function (s) { 100 | const stack = [] 101 | for (let i = 0; i < s.length; i++) { 102 | const char = s[i] 103 | if (char !== ']') { 104 | stack.push(char) 105 | } else { 106 | let str = '' 107 | while (stack.length > 0) { 108 | const temp = stack.pop() 109 | if (temp !== '[') { 110 | str = temp + str 111 | } else { 112 | let num = '' 113 | while (stack.length > 0) { 114 | const tempNum = stack.pop() // 处理类似100的数字,此时栈中['1','0','0'] 115 | if (!isNaN(tempNum)) { 116 | num = tempNum + num 117 | } else { 118 | stack.push(tempNum) 119 | break 120 | } 121 | } 122 | str = str.repeat(+num) 123 | break 124 | } 125 | } 126 | stack.push(str) 127 | } 128 | } 129 | return stack.join('') 130 | } 131 | ``` 132 | 133 | ##### [94. 二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) 134 | 135 | ```js 136 | var inorderTraversal = function (root) { //递归 137 | if (root === null) { 138 | return [] 139 | } 140 | const res = []; 141 | (function inOrder(root) { 142 | if (!root) { 143 | return 144 | } 145 | inOrder(root.left) 146 | res.push(root.val) 147 | inOrder(root.right) 148 | })(root); 149 | return res 150 | }; 151 | ``` 152 | 153 | ```js 154 | var inorderTraversal = function (root) { 155 | const stack = [] 156 | const res = [] 157 | let current = root 158 | while (stack.length > 0 || current) { 159 | while (current) { 160 | stack.push(current) 161 | current = current.left 162 | } 163 | current = stack.pop() 164 | res.push(current.val) 165 | current = current.right 166 | } 167 | return res 168 | }; 169 | ``` 170 | 171 | ##### [133.克隆图](https://leetcode-cn.com/problems/clone-graph/) 172 | 173 | 给你无向 **[连通](https://baike.baidu.com/item/连通图/6460995?fr=aladdin)** 图中一个节点的引用,请你返回该图的 [**深拷贝**](https://baike.baidu.com/item/深拷贝/22785317?fr=aladdin)(克隆)。 174 | 175 | ```js 176 | var cloneGraph = function (node) { 177 | let visited = new Map(); 178 | return (function clone(node, visited) { 179 | if (!node) { 180 | return null 181 | } 182 | if (visited.has(node)) { 183 | return visited.get(node) 184 | } 185 | const newNode = new Node(node.val) 186 | visited.set(node, newNode) 187 | for (let i = 0; i < node.neighbors.length; i++) { 188 | newNode.neighbors[i] = clone(node.neighbors[i], visited) 189 | } 190 | return newNode 191 | })(node, visited) 192 | }; 193 | ``` 194 | 195 | ##### [200. 岛屿数量](https://leetcode-cn.com/problems/number-of-islands/) 196 | 197 | 给你一个由 `'1'`(陆地)和 `'0'`(水)组成的的二维网格,请你计算网格中岛屿的数量。 198 | 199 | ```js 200 | var numIslands = function (grid) { 201 | const nr = grid.length 202 | const nc = grid[0].length 203 | let resNum = 0 204 | for (let i = 0; i < nr; i++) { 205 | for (let j = 0; j < nc; j++) { 206 | if (grid[i][j] === '1') { 207 | resNum++ 208 | dfs(grid, i, j) 209 | } 210 | } 211 | } 212 | return resNum 213 | }; 214 | 215 | const dfs = (grid, row, col) => { 216 | const gr = grid.length 217 | const gc = grid[0].length 218 | if (row < 0 || col < 0 || row >= gr || col >= gc || grid[row][col] === '0') { 219 | return 0 220 | } 221 | grid[row][col] = '0' 222 | dfs(grid, row + 1, col) 223 | dfs(grid, row - 1, col) 224 | dfs(grid, row, col + 1) 225 | dfs(grid, row, col - 1) 226 | } 227 | ``` 228 | 229 | [84. 柱状图中最大的矩形](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) 230 | 231 | ```js 232 | 233 | ``` 234 | 235 | ### Queue 队列 236 | 237 | ##### [232. 用栈实现队列](https://leetcode-cn.com/problems/implement-queue-using-stacks/) 238 | 239 | ```js 240 | /** 241 | * Initialize your data structure here. 242 | */ 243 | var MyQueue = function () { 244 | this.stack1 = [] 245 | this.stack2 = [] 246 | }; 247 | 248 | /** 249 | * Push element x to the back of queue. 250 | * @param {number} x 251 | * @return {void} 252 | */ 253 | MyQueue.prototype.push = function (x) { 254 | this.stack1.push(x) 255 | }; 256 | 257 | /** 258 | * Removes the element from in front of queue and returns that element. 259 | * @return {number} 260 | */ 261 | MyQueue.prototype.pop = function () { 262 | if (this.stack2.length > 0) { 263 | return this.stack2.pop() 264 | } else { 265 | while (this.stack1.length > 0) { 266 | this.stack2.push(this.stack1.pop()) 267 | } 268 | return this.stack2.pop() 269 | } 270 | }; 271 | 272 | /** 273 | * Get the front element. 274 | * @return {number} 275 | */ 276 | MyQueue.prototype.peek = function () { 277 | if (this.stack2.length > 0) { 278 | return this.stack2[this.stack2.length - 1] 279 | } else { 280 | while (this.stack1.length > 0) { 281 | this.stack2.push(this.stack1.pop()) 282 | } 283 | return this.stack2[this.stack2.length - 1] 284 | } 285 | }; 286 | 287 | /** 288 | * Returns whether the queue is empty. 289 | * @return {boolean} 290 | */ 291 | MyQueue.prototype.empty = function () { 292 | if (this.stack1.length === 0 && this.stack2.length === 0) { 293 | return true 294 | } 295 | return false 296 | }; 297 | ``` 298 | 299 | ##### [542. 01 矩阵](https://leetcode-cn.com/problems/01-matrix/) 300 | 301 | ```js 302 | var updateMatrix = function (matrix) { 303 | const dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]] 304 | const m = matrix.length, n = matrix[0].length 305 | const dist = new Array(m).fill(new Array(n).fill(0)) 306 | const seen = new Array(m).fill(new Array(n).fill(false)) 307 | const queue = [] 308 | for (let i = 0; i < m; i++) { 309 | for (let j = 0; j < n; j++) { 310 | if (matrix[i][j] === 0) { 311 | queue.push([i, j]) 312 | seen[i][j] = true 313 | } 314 | } 315 | } 316 | while (queue.length > 0) { 317 | const [i, j] = queue.shift() 318 | for (let d = 0; d < 4; d++) { 319 | const ni = i + dirs[d][0] 320 | const nj = j + dirs[d][1] 321 | if (ni >= 0 && ni < m && nj >= 0 && nj < n && !seen[ni][nj]) { 322 | dist[ni][nj] = dist[i][j] + 1 323 | queue.push([ni, nj]) 324 | seen[ni][nj] = true 325 | } 326 | } 327 | } 328 | return dist 329 | }; 330 | ``` 331 | 332 | ## 总结 333 | 334 | - 熟悉栈的使用场景 335 | - 后入先出,保存临时值 336 | - 利用栈 DFS 深度搜索 337 | - 熟悉队列的使用场景 338 | - 利用队列 BFS 广度搜索 339 | 340 | ## 练习 341 | 342 | - [min-stack](https://leetcode-cn.com/problems/min-stack/) 343 | - [evaluate-reverse-polish-notation](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) 344 | - [decode-string](https://leetcode-cn.com/problems/decode-string/) 345 | - [binary-tree-inorder-traversal](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) 346 | - [clone-graph](https://leetcode-cn.com/problems/clone-graph/) 347 | - [number-of-islands](https://leetcode-cn.com/problems/number-of-islands/) 348 | - [largest-rectangle-in-histogram](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) 349 | - [implement-queue-using-stacks](https://leetcode-cn.com/problems/implement-queue-using-stacks/) 350 | - [01-matrix](https://leetcode-cn.com/problems/01-matrix/) -------------------------------------------------------------------------------- /基础算法篇/二分搜索.md: -------------------------------------------------------------------------------- 1 | ## 二分搜索 2 | 3 | ### 二分搜索模板 4 | 5 | 给一个**有序数组**和目标值,找第一次/最后一次/任何一次出现的索引,如果没有出现返回 -1 6 | 7 | 模板四点要素 8 | 9 | - 1、初始化:start=0、end=len-1 10 | - 2、循环条件:start <= end 11 | - 3、比较中点和目标值:A[mid] ==、 <、> target 12 | - 4、判断最后两个元素是否符合:A[start]、A[end] ? target 13 | 14 | 时间复杂度 O(logn),使用场景一般是有序数组的查找。 15 | 16 | ##### [704.二分查找(经典示例)](https://leetcode-cn.com/problems/binary-search/) 17 | 18 | ```js 19 | // 二分搜索最常用模板 20 | var search = function(nums, target) { 21 | let left = 0, mid = 0, right = nums.length-1; 22 | while(left + 1 < right){ 23 | mid = left + ((right-left) >> 1); // 考虑到 start + end 数据溢出的情况 24 | if(nums[mid] === target){ 25 | return mid; 26 | } 27 | if(nums[mid] > target){ 28 | right = mid - 1; 29 | } else { 30 | left = mid + 1; 31 | } 32 | } 33 | // 最后剩下两个元素,手动判断 34 | if (nums[left] === target) { 35 | return left; 36 | } 37 | if (nums[right] === target) { 38 | return right; 39 | } 40 | return -1; 41 | }; 42 | ``` 43 | 44 | 大部分二分查找类的题目都可以用这个模板,然后做一点特殊逻辑即可。 45 | 46 | **模板 #1** `(left <= right)` 47 | 48 | 二分查找的最基础和最基本的形式。 49 | 查找条件可以在不与元素的两侧进行比较的情况下确定(或使用它周围的特定元素)。 50 | 不需要后处理,因为每一步中,你都在检查是否找到了元素。如果到达末尾,则知道未找到该元素。 51 | 52 | **模板 #2** `(left < right)` 53 | 54 | 一种实现二分查找的高级方法。 55 | 查找条件需要访问元素的直接右邻居。 56 | 使用元素的右邻居来确定是否满足条件,并决定是向左还是向右。 57 | 保证查找空间在每一步中至少有 2 个元素。 58 | 需要进行后处理。 当你剩下 1 个元素时,循环 / 递归结束。 需要评估剩余元素是否符合条件。 59 | 60 | **模板 #3** `(left + 1 < right)` 61 | 62 | 实现二分查找的另一种方法。 63 | 搜索条件需要访问元素的直接左右邻居。 64 | 使用元素的邻居来确定它是向右还是向左。 65 | 保证查找空间在每个步骤中至少有 3 个元素。 66 | 需要进行后处理。 当剩下 2 个元素时,循环 / 递归结束。 需要评估其余元素是否符合条件。 67 | 68 | ![](https://camo.githubusercontent.com/b3228d92578552d9098924866218c3183e3ae63d66d46a9d156e478329344206/68747470733a2f2f696d672e667569626f6f6d2e636f6d2f696d672f62696e6172795f7365617263685f74656d706c6174652e706e67) 69 | 70 | 如果是最简单的二分搜索,不需要找第一个、最后一个位置、或者是没有重复元素,可以使用**模板#1**,代码更简洁: 71 | 72 | ```js 73 | var search = function(nums, target) { 74 | let start = 0, mid = 0, end = nums.length-1; 75 | while(start <= end){ 76 | mid = start + ((end-start)>>1); 77 | if(nums[mid] === target){ 78 | return mid; 79 | } 80 | if(nums[mid] > target){ 81 | end = mid - 1; 82 | } else { 83 | start = mid + 1; 84 | } 85 | } 86 | return -1; 87 | }; 88 | ``` 89 | 90 | JS 用 indexOf 也可以: 91 | 92 | ```js 93 | var search = function(nums, target) { 94 | return nums.indexOf(target); 95 | }; 96 | ``` 97 | 98 | ##### [34.在排序数组中查找元素的第一个和最后一个位置](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/) 99 | 100 | 给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]。 101 | 102 | **进阶:** 103 | 104 | - 你可以设计并实现时间复杂度为 `O(log n)` 的算法解决此问题吗? 105 | 106 | > 输入:nums = [5,7,7,8,8,10], target = 8 107 | > 输出:[3,4] 108 | 109 | ```js 110 | var searchRange = function(nums, target) { 111 | if(targetnums[nums.length-1]){ 112 | return [-1,-1]; 113 | } 114 | let res = [-1,-1]; 115 | let left = 0, mid = 0, right = nums.length-1; 116 | while(left <= right){ 117 | mid = left + ((right-left) >> 1); 118 | if(nums[mid] >= target){ 119 | if(mid === 0 || (nums[mid] === target && nums[mid-1]> 1) 164 | if (nums[mid] < target) { 165 | left = mid 166 | } else { 167 | right = mid 168 | } 169 | } 170 | if (target <= nums[left]) { 171 | return left 172 | } else if (target <= nums[right]) { 173 | return right 174 | } else { 175 | return right + 1 176 | } 177 | }; 178 | ``` 179 | 180 | ##### [74. 搜索二维矩阵](https://leetcode-cn.com/problems/search-a-2d-matrix/) 181 | 182 | 编写一个高效的算法来判断 `m x n` 矩阵中,是否存在一个目标值。 183 | 184 | ```js 185 | var searchMatrix = function (matrix, target) { 186 | let row = matrix.length 187 | let col = matrix[0].length 188 | let start = 0 189 | let end = row * col - 1 190 | let mid = 0 191 | while (start + 1 < end) { 192 | mid = start + ((end - start) >> 1) 193 | val = matrix[parseInt(mid / col)][mid % col] 194 | if (val > target) { 195 | end = mid 196 | } else if (val < target) { 197 | start = mid 198 | } else { 199 | return true 200 | } 201 | } 202 | if (matrix[parseInt(start / col)][start % col] === target || matrix[parseInt(end / col)][end % col] === target) { 203 | return true 204 | } 205 | return false 206 | }; 207 | ``` 208 | 209 | ##### [278. 第一个错误的版本](https://leetcode-cn.com/problems/first-bad-version/) 210 | 211 | 假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。 你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 212 | 213 | ```js 214 | var solution = function (isBadVersion) { 215 | /** 216 | * @param {integer} n Total versions 217 | * @return {integer} The first bad version 218 | */ 219 | return function (n) { 220 | let start = 1 221 | let end = n 222 | let mid = 1 223 | while (start + 1 < end) { 224 | mid = start + ((end - start) >> 1) 225 | if(isBadVersion(mid)){ 226 | end = mid 227 | } else { 228 | start = mid 229 | } 230 | } 231 | if(isBadVersion(start)){ 232 | return start 233 | } 234 | return end 235 | }; 236 | }; 237 | ``` 238 | 239 | ##### [153. 寻找旋转排序数组中的最小值](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) 240 | 241 | 假设按照升序排序的数组在预先未知的某个点上进行了旋转( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。 请找出其中最小的元素。 242 | 243 | ```js 244 | var findMin = function (nums) { 245 | let start = 0 246 | let end = nums.length - 1 247 | let mid = 0 248 | while (start + 1 < end) { 249 | let mid = start + ((end - start) >> 1) 250 | if (nums[mid] > nums[end]) { // 以最后的为target 251 | start = mid 252 | } else { 253 | end = mid 254 | } 255 | } 256 | if (nums[start] > nums[end]) { 257 | return nums[end] 258 | } 259 | return nums[start] 260 | }; 261 | ``` 262 | 263 | ##### [154. 寻找旋转排序数组中的最小值 II](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) 264 | 265 | 给你一个可能存在 **重复** 元素值的数组 `nums` ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 **最小元素** 。 266 | 267 | ```js 268 | var findMin = function (nums) { 269 | let start = 0 270 | let end = nums.length - 1 271 | let mid = 0 272 | while (start + 1 < end) { 273 | while (start < end && nums[start] === nums[start + 1]) { 274 | start++ 275 | } 276 | while (start < end && nums[end] === nums[end - 1]) { 277 | end-- 278 | } 279 | let mid = start + ((end - start) >> 1) 280 | if (nums[mid] >= nums[end]) { 281 | start = mid 282 | } else { 283 | end = mid 284 | } 285 | } 286 | if(nums[start] > nums[end]){ 287 | return nums[end] 288 | } 289 | return nums[start] 290 | }; 291 | ``` 292 | 293 | ##### [33. 搜索旋转排序数组](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) 294 | 295 | 给你 **旋转后** 的数组 `nums` 和一个整数 `target` ,如果 `nums` 中存在这个目标值 `target` ,则返回它的下标,否则返回 `-1` 。 296 | 297 | ```js 298 | var search = function (nums, target) { 299 | let start = 0 300 | let end = nums.length - 1 301 | let mid = 0 302 | while (start + 1 < end) { 303 | let mid = start + ((end - start) >> 1) 304 | if (nums[mid] === target) { 305 | return mid 306 | } 307 | if (nums[start] < nums[mid]) { 308 | if (nums[start] <= target && target <= nums[mid]) { 309 | end = mid 310 | } else { 311 | start = mid 312 | } 313 | } 314 | if (nums[end] > nums[mid]) { 315 | if (nums[end] >= target && nums[mid] <= target) { 316 | start = mid 317 | } else { 318 | end = mid 319 | } 320 | } 321 | } 322 | if (nums[start] === target) { 323 | return start 324 | } else if (nums[end] === target) { 325 | return end 326 | } 327 | return -1 328 | }; 329 | ``` 330 | 331 | ##### [81. 搜索旋转排序数组 II](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) 332 | 333 | 同上,数字可重复。 334 | 335 | ```js 336 | var search = function (nums, target) { 337 | let start = 0 338 | let end = nums.length - 1 339 | let mid = 0 340 | while (start + 1 < end) { 341 | let mid = start + ((end - start) >> 1) 342 | if (nums[mid] === target) { 343 | return true 344 | } 345 | while (start < end && nums[start] === nums[start + 1]) { 346 | start++ 347 | } 348 | while (start < end && nums[end] === nums[end - 1]) { 349 | end-- 350 | } 351 | if (nums[start] < nums[mid]) { 352 | if (nums[start] <= target && target <= nums[mid]) { 353 | end = mid 354 | } else { 355 | start = mid 356 | } 357 | } 358 | if (nums[end] > nums[mid]) { 359 | if (nums[end] >= target && nums[mid] <= target) { 360 | start = mid 361 | } else { 362 | end = mid 363 | } 364 | } 365 | } 366 | if (nums[start] === target || nums[end] === target) { 367 | return true 368 | } 369 | return false 370 | }; 371 | ``` 372 | 373 | ## 总结 374 | 375 | 二分搜索核心四点要素(必背&理解) 376 | 377 | - 1、初始化:start=0、end=len-1 378 | - 2、循环退出条件:start + 1 < end 379 | - 3、比较中点和目标值:A[mid] ==、 <、> target 380 | - 4、判断最后两个元素是否符合:A[start]、A[end] ? target 381 | 382 | ## 练习题 383 | 384 | - [search-for-range](https://www.lintcode.com/problem/search-for-a-range/description) 385 | - [search-insert-position](https://leetcode-cn.com/problems/search-insert-position/) 386 | - [search-a-2d-matrix](https://leetcode-cn.com/problems/search-a-2d-matrix/) 387 | - [first-bad-version](https://leetcode-cn.com/problems/first-bad-version/) 388 | - [find-minimum-in-rotated-sorted-array](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) 389 | - [find-minimum-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) 390 | - [search-in-rotated-sorted-array](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) 391 | - [search-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) -------------------------------------------------------------------------------- /数据结构篇/链表.md: -------------------------------------------------------------------------------- 1 | ## 链表 2 | 3 | ### 核心点 4 | 5 | - null/nil 异常处理 6 | - dummy node 哑巴节点 7 | - 快慢指针 8 | - 插入一个节点到排序链表 9 | - 从一个链表中移除一个节点 10 | - 翻转链表 11 | - 合并两个链表 12 | - 找到链表的中间节点 13 | 14 | **链表的数据结构** 15 | 16 | ```js 17 | /** 18 | * Definition for singly-linked list. 19 | */ 20 | function ListNode(val) { 21 | this.val = val; 22 | this.next = null; 23 | } 24 | ``` 25 | 26 | ### 练习 27 | 28 | ##### 83.删除排序链表中的重复元素 [remove-duplicates-from-sorted-list](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) 29 | 30 | > 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 31 | 32 | ```js 33 | var deleteDuplicates = function(head) { 34 | let current = head; 35 | while(current !== null && current.next !== null){ 36 | if(current.val === current.next.val){ 37 | current.next = current.next.next; 38 | }else{ 39 | current = current.next; 40 | } 41 | } 42 | return head; 43 | }; 44 | ``` 45 | 46 | ```js 47 | var deleteDuplicates = function(head) { // 递归写法 48 | if(head === null || head.next === null){ 49 | return head; 50 | } 51 | head.next = deleteDuplicates(head.next); 52 | return head.val === head.next.val ? head.next : head; 53 | }; 54 | ``` 55 | 56 | ##### 82.删除排序链表中的重复元素Ⅱ[remove-duplicates-from-sorted-list-ii](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/) 57 | 58 | > 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中没有重复出现的数字。 59 | 60 | ```js 61 | var deleteDuplicates = function(head) { 62 | if (head === null || head.next === null) { 63 | return head; 64 | } 65 | let dummy = new ListNode(-1); 66 | dummy.next = head; 67 | let front = dummy; 68 | let back = head.next; 69 | while(back !== null){ 70 | if(front.next.val !== back.val){ 71 | front = front.next; 72 | back = back.next; 73 | } else { 74 | while(back !== null && front.next.val === back.val){ 75 | back = back.next; 76 | } 77 | front.next = back; 78 | back = back === null ? null : back.next; 79 | } 80 | } 81 | return dummy.next; 82 | }; 83 | ``` 84 | 85 | ##### 206.反转一个单链表(头插法)。[reverse-linked-list](https://leetcode-cn.com/problems/reverse-linked-list/) 86 | 87 | > 输入: 1->2->3->4->5->NULL 88 | > 输出: 5->4->3->2->1->NULL 89 | 90 | ```js 91 | var reverseList = function(head) { 92 | if(head === null || head.next === null) return head; 93 | let dummy = new ListNode(-1); 94 | while(head !== null){ 95 | let p = head.next; 96 | head.next = dummy.next; 97 | dummy.next = head; 98 | head = p; 99 | } 100 | return dummy.next; 101 | }; 102 | ``` 103 | 104 | ##### 92.反转链表Ⅱ [reverse-linked-list-ii](https://leetcode-cn.com/problems/reverse-linked-list-ii/) 105 | 106 | 反转从位置 *m* 到 *n* 的链表。请使用一趟扫描完成反转。**说明:**1 ≤ *m* ≤ *n* ≤ 链表长度。 107 | 108 | > 输入: 1->2->3->4->5->NULL, m = 2, n = 4 109 | > 输出: 1->4->3->2->5->NULL 110 | 111 | ```js 112 | var reverseBetween = function(head, m, n) { 113 | let dummy = new ListNode(-1); 114 | dummy.next = head; //哑巴节点 115 | head = dummy; 116 | for(let i=m;i>1;i--){ 117 | head = head.next; // 让head指向反转子列表的前一个节点 118 | } 119 | let pre = head.next; //指向子列表的第一个节点 120 | for(let j=m;j 输入:1->2->4, 1->3->4 135 | > 输出:1->1->2->3->4->4 136 | 137 | ```js 138 | // 迭代法 139 | var mergeTwoLists = function(l1, l2) { 140 | let dummy = new ListNode(-1); 141 | let pre = dummy; 142 | while(l1 !== null && l2 !== null){ 143 | if(l1.val <= l2.val){ 144 | pre.next = l1; 145 | l1 = l1.next; 146 | }else{ 147 | pre.next = l2; 148 | l2 = l2.next; 149 | } 150 | pre = pre.next; 151 | } 152 | pre.next = l1 === null ? l2 : l1; 153 | return dummy.next; 154 | }; 155 | ``` 156 | 157 | ```js 158 | //递归 159 | var mergeTwoLists = function(l1, l2) { 160 | if(l1 === null){ 161 | return l2; 162 | }else if(l2 === null){ 163 | return l1; 164 | }else if(l1.val <= l2.val){ 165 | l1.next = mergeTwoLists(l1.next,l2); 166 | return l1; 167 | }else{ 168 | l2.next = mergeTwoLists(l1,l2.next); 169 | return l2; 170 | } 171 | }; 172 | ``` 173 | 174 | ##### 86.[分隔链表](https://leetcode-cn.com/problems/partition-list/) 175 | 176 | 给定一个链表和一个特定值 *x*,对链表进行分隔,使得所有小于 *x* 的节点都在大于或等于 *x* 的节点之前。你应当保留两个分区中每个节点的初始相对位置。 177 | 178 | > 输入: head = 1->4->3->2->5->2, x = 3 179 | > 输出: 1->2->2->4->3->5 180 | 181 | ```js 182 | var partition = function(head, x) { 183 | let biggerX = new ListNode(0); 184 | let post = biggerX; 185 | let smallerX = new ListNode(0); 186 | let pre = smallerX; 187 | let temp; 188 | while(head !== null){ 189 | temp = head.next; 190 | if(head.val < x){ 191 | pre.next = head; 192 | pre = pre.next; 193 | }else{ 194 | post.next = head; 195 | post = post.next; 196 | } 197 | head = temp; 198 | } 199 | post.next = null; //最后一个的下一个指向为空,否则会出现环 200 | pre.next = biggerX.next; 201 | return smallerX.next; 202 | }; 203 | ``` 204 | 205 | ##### [148. 排序链表](https://leetcode-cn.com/problems/sort-list/) 206 | 207 | 给你链表的头结点 `head` ,请将其按 **升序** 排列并返回 **排序后的链表** 。`O(nlogn)`时间复杂度,常数级空间。 208 | 209 | ```js 210 | var sortList = function (head) { 211 | return mergeSort(head) 212 | }; 213 | 214 | const findMidNode = head => { 215 | let slow = head 216 | let fast = head.next 217 | while (fast !== null && fast.next !== null) { 218 | slow = slow.next 219 | fast = fast.next.next 220 | } 221 | return slow 222 | } 223 | 224 | const mergeTwoList = (l1, l2) => { 225 | const dummy = new ListNode(0) 226 | let p = dummy 227 | while (l1 !== null && l2 !== null) { 228 | if (l1.val < l2.val) { 229 | p.next = l1 230 | l1 = l1.next 231 | } else { 232 | p.next = l2 233 | l2 = l2.next 234 | } 235 | p = p.next 236 | } 237 | if (l1 !== null) { // l1不为空,p指向剩下的链表 238 | p.next = l1 239 | } 240 | if (l2 !== null) { 241 | p.next = l2 242 | } 243 | return dummy.next 244 | } 245 | 246 | const mergeSort = head => { 247 | if (head === null || head.next === null) { 248 | return head 249 | } 250 | let mid = findMidNode(head) 251 | let tail = mid.next 252 | mid.next = null 253 | let left = mergeSort(head) 254 | let right = mergeSort(tail) 255 | return mergeTwoList(left, right) 256 | } 257 | ``` 258 | 259 | ##### [143. 重排链表](https://leetcode-cn.com/problems/reorder-list/) 260 | 261 | 给定一个单链表 L:L0→L1→…→Ln-1→Ln , 262 | 将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→… 263 | 264 | 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 265 | 266 | ```js 267 | var reorderList = function (head) { 268 | const nodeArr = [] 269 | let node = head 270 | while (node !== null) { 271 | nodeArr.push(node) 272 | node = node.next 273 | } 274 | let i = 0, j = nodeArr.length - 1 275 | while (i < j) { 276 | nodeArr[i].next = nodeArr[j] 277 | i++ 278 | if (i === j) break 279 | nodeArr[j].next = nodeArr[i] 280 | j-- 281 | } 282 | nodeArr[i].next = null 283 | } 284 | ``` 285 | 286 | ##### [141. 环形链表](https://leetcode-cn.com/problems/linked-list-cycle/) 287 | 288 | 给定一个链表,判断链表中是否有环。 289 | 290 | ```js 291 | var hasCycle = function (head) { 292 | if (head === null || head.next === null) { 293 | return false 294 | } 295 | let slow = head 296 | let fast = head.next 297 | while (fast !== null && fast.next !== null && slow !== null) { 298 | if (slow === fast) { 299 | return true 300 | } 301 | slow = slow.next 302 | fast = fast.next.next 303 | } 304 | return false 305 | } 306 | ``` 307 | 308 | ##### [142. 环形链表 II](https://leetcode-cn.com/problems/linked-list-cycle-ii/) 309 | 310 | 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 `null`。 311 | 312 | ```js 313 | var detectCycle = function (head) { 314 | let visited = new Set() //使用哈希表,空间复杂度O(n) 315 | while (head !== null) { 316 | if(visited.has(head)){ 317 | return head 318 | } 319 | visited.add(head) 320 | head = head.next 321 | } 322 | return null 323 | }; 324 | ``` 325 | 326 | ```js 327 | var detectCycle = function (head) { 328 | if (head === null || head.next === null) { 329 | return null 330 | } 331 | let slow = head 332 | let fast = head.next 333 | while(fast !== null && fast.next !== null){ 334 | if(fast === slow) { //数学推导,看官网题解 335 | slow = slow.next 336 | fast = head 337 | while(fast !== slow){ 338 | slow = slow.next 339 | fast = fast.next 340 | } 341 | return slow 342 | } 343 | slow = slow.next 344 | fast = fast.next.next 345 | } 346 | return null 347 | }; 348 | ``` 349 | 350 | ##### [234. 回文链表](https://leetcode-cn.com/problems/palindrome-linked-list/) 351 | 352 | 请判断一个链表是否为回文链表。 353 | 354 | ```js 355 | var isPalindrome = function (head) { 356 | if (head === null || head.next === null) { 357 | return true 358 | } 359 | let slow = head 360 | let fast = head.next 361 | while (fast !== null && fast.next !== null) { 362 | slow = slow.next 363 | fast = fast.next.next 364 | } 365 | let tail = reverseList(slow.next) 366 | slow.next = null 367 | while (head !== null && tail !== null) { 368 | if (head.val !== tail.val) { 369 | return false 370 | } 371 | head = head.next 372 | tail = tail.next 373 | } 374 | return true 375 | }; 376 | 377 | const reverseList = head => { 378 | if (head === null) { 379 | return head 380 | } 381 | const dummy = new ListNode(0) 382 | let prev = head 383 | while (prev !== null) { //头插法 384 | let temp = prev 385 | prev = prev.next 386 | temp.next = dummy.next 387 | dummy.next = temp 388 | } 389 | return dummy.next 390 | } 391 | ``` 392 | 393 | ##### [138. 复制带随机指针的链表](https://leetcode-cn.com/problems/copy-list-with-random-pointer/) 394 | 395 | 给你一个长度为 `n` 的链表,每个节点包含一个额外增加的随机指针 `random` ,该指针可以指向链表中的任何节点或空节点。 396 | 397 | ```js 398 | var copyRandomList = function (head) { 399 | if (head === null) { 400 | return head 401 | } 402 | let cur = head 403 | while (cur !== null) { 404 | const clone = new Node(cur.val, cur.next, cur.random) 405 | const temp = cur.next 406 | cur.next = clone 407 | cur = temp 408 | } 409 | cur = head 410 | while (cur !== null) { 411 | if (cur.random !== null) { 412 | cur.next.random = cur.random.next 413 | } 414 | cur = cur.next.next 415 | } 416 | cur = head 417 | let cloneHead = cur.next 418 | while (cur !== null && cur.next !== null) { 419 | const temp = cur.next 420 | cur.next = cur.next.next 421 | cur = temp 422 | } 423 | return cloneHead 424 | }; 425 | ``` 426 | 427 | ## 练习 428 | 429 | - [remove-duplicates-from-sorted-list](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) 430 | - [remove-duplicates-from-sorted-list-ii](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/) 431 | - [reverse-linked-list](https://leetcode-cn.com/problems/reverse-linked-list/) 432 | - [reverse-linked-list-ii](https://leetcode-cn.com/problems/reverse-linked-list-ii/) 433 | - [merge-two-sorted-lists](https://leetcode-cn.com/problems/merge-two-sorted-lists/) 434 | - [partition-list](https://leetcode-cn.com/problems/partition-list/) 435 | - [sort-list](https://leetcode-cn.com/problems/sort-list/) 436 | - [reorder-list](https://leetcode-cn.com/problems/reorder-list/) 437 | - [linked-list-cycle](https://leetcode-cn.com/problems/linked-list-cycle/) 438 | - [linked-list-cycle-ii](https://leetcode-cn.com/problems/linked-list-cycle-ii/) 439 | - [palindrome-linked-list](https://leetcode-cn.com/problems/palindrome-linked-list/) 440 | - [copy-list-with-random-pointer](https://leetcode-cn.com/problems/copy-list-with-random-pointer/) -------------------------------------------------------------------------------- /数据结构篇/二叉树.md: -------------------------------------------------------------------------------- 1 | ## 二叉树 2 | 3 | ### 二叉树遍历 4 | 5 | **前序遍历**:**先访问根节点**,再前序遍历左子树,再前序遍历右子树。**中序遍历**:先中序遍历左子树,**再访问根节点**,再中序遍历右子树。**后序遍历**:先后序遍历左子树,再后序遍历右子树,**再访问根节点**。 6 | 7 | 注意点: 8 | 9 | - 以根访问顺序决定是什么遍历 10 | - 左子树都是优先右子树 11 | 12 | #### 树结构 13 | 14 | ```tsx 15 | function TreeNode(val){ 16 | this.val = val; 17 | this.left = null; 18 | this.right = null; 19 | } 20 | 21 | class TreeNode { // es6 + ts 22 | val: any 23 | left: TreeNode | null 24 | right: TreeNode | null 25 | constructor(val: any){ 26 | this.val = val 27 | this.left = null 28 | this.right = null 29 | } 30 | } 31 | ``` 32 | 33 | #### 根据数组构建二叉树 34 | 35 | ```js 36 | const buildTreeByArray = function (array, index) { 37 | let tn = null; 38 | if (index < array.length) { 39 | const value = array[index]; 40 | if (value !== null) { 41 | tn = new TreeNode(value); 42 | tn.left = buildTreeByArray(array, 2 * index + 1); 43 | tn.right = buildTreeByArray(array, 2 * index + 2); 44 | } 45 | return tn; 46 | } 47 | return tn; 48 | } 49 | 50 | const binaryTree = function (array) { 51 | return buildTreeByArray(array, 0); 52 | } 53 | 54 | const arr = [1,2,3,null,4,5,null,null,null,6,7]; 55 | let root = binaryTree(arr); 56 | ``` 57 | 58 | #### 前序递归 59 | 60 | ```js 61 | function preOrder(root) { 62 | if(root === null){ 63 | return null; 64 | } 65 | console.log(root.val); 66 | preOrder(root.left); 67 | preOrder(root.right); 68 | } 69 | ``` 70 | 71 | #### 前序非递归 72 | 73 | ```js 74 | const preOrderTraversal = function (root) { 75 | if(root === null){ 76 | return []; 77 | } 78 | const res = []; 79 | const stack = []; 80 | stack.push(root); 81 | while(stack.length !== 0){ 82 | const node = stack.pop(); 83 | res.push(node.val); 84 | if (node.right !== null) { 85 | stack.push(node.right); 86 | } 87 | if (node.left !== null) { 88 | stack.push(node.left); 89 | } 90 | } 91 | return res; 92 | } 93 | ``` 94 | 95 | #### 中序递归 96 | 97 | ```js 98 | function inOrder(root){ 99 | if(root === null){ 100 | return null; 101 | } 102 | inOrder(root.left); 103 | console.log(root.val); 104 | inOrder(root.right); 105 | } 106 | ``` 107 | 108 | #### 中序非递归 109 | 110 | ```js 111 | const inOrderTraversal = function(root){ 112 | if (root === null) { 113 | return []; 114 | } 115 | const res = []; 116 | const stack = []; 117 | let node = root; 118 | while(stack.length!==0 || node!==null){ 119 | while(node !== null){ 120 | stack.push(node); 121 | node = node.left; 122 | } 123 | node = stack.pop(); 124 | res.push(node.val); 125 | node = node.right; 126 | } 127 | return res; 128 | } 129 | ``` 130 | 131 | #### 后序递归 132 | 133 | ```js 134 | function postOrder(root){ 135 | if(root === null){ 136 | return null; 137 | } 138 | postOrder(root.left); 139 | postOrder(root.right); 140 | console.log(root.val); 141 | } 142 | ``` 143 | 144 | #### 后序非递归 145 | 146 | ```js 147 | const postOrderTraversal = function(root){ //翻转非递归 后序遍历 148 | if (root === null) { 149 | return []; 150 | } 151 | const res = []; 152 | const stack = []; 153 | stack.push(root); 154 | while(stack.length !== 0){ 155 | let node = stack.pop(); 156 | res.push(node.val); 157 | if (node.left !== null) { 158 | stack.push(node.left); 159 | } 160 | if (node.right !== null) { 161 | stack.push(node.right); 162 | } 163 | } 164 | return res.reverse(); 165 | } 166 | ``` 167 | 168 | #### 深度搜索DFS 169 | 170 | ```js 171 | const dfsUpToDown = function(root){ //递归,从上到下 172 | const res = []; 173 | dfs(root, res); 174 | return res; 175 | } 176 | 177 | const dfs = function(node, res){ 178 | if (node === null) { 179 | return null; 180 | } 181 | res.push(node.val); 182 | dfs(node.left, res); 183 | dfs(node.right, res); 184 | } 185 | 186 | const dfsDownToUp = function(root){ //从下到上 187 | return divideAndConquer(root); 188 | } 189 | 190 | const divideAndConquer = function(node){ //分治法 191 | const res = []; 192 | if (node === null) { 193 | return null; 194 | } 195 | let left = divideAndConquer(node.left); 196 | let right = divideAndConquer(node.right); 197 | res.push(node.val); 198 | if (left !== null) { 199 | res = res.concat(left.flat()); 200 | } 201 | if (right !== null) { 202 | res = res.concat(right.flat()); 203 | } 204 | return res; 205 | } 206 | ``` 207 | 208 | #### 广度搜索BFS 209 | 210 | ```js 211 | const bfs = function(root){ 212 | let res = []; 213 | const queue = []; 214 | queue.push(root); 215 | while(queue.length !== 0){ 216 | const node = queue.shift(); 217 | res.push(node.val); 218 | if (node.left !== null) { 219 | queue.push(node.left); 220 | } 221 | if (node.right !== null) { 222 | queue.push(node.right); 223 | } 224 | } 225 | return res; 226 | } 227 | ``` 228 | 229 | [104.二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) 230 | 231 | 给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。**说明:** 叶子节点是指没有子节点的节点。 232 | 233 | > 给定二叉树 [3,9,20,null,null,15,7], 234 | > 235 | > 3 236 | > / \ 237 | > 9 20 238 | > / \ 239 | > 15 7 240 | > 241 | > 返回它的最大深度 3 。 242 | 243 | ```js 244 | const maxDepth = function(root) { //递归 245 | if(root === null){ 246 | return 0; 247 | } 248 | return Math.max(maxDepth(root.left), maxDepth(root.right))+1; 249 | }; 250 | ``` 251 | 252 | [110.平衡二叉树](https://leetcode-cn.com/problems/balanced-binary-tree/) 253 | 254 | 给定一个二叉树,判断它是否是高度平衡的二叉树。 255 | 256 | ```js 257 | const isBalanced = function(root) { 258 | if(maxDepth(root) === -1) { 259 | return false 260 | } 261 | return true 262 | }; 263 | 264 | const maxDepth = function(root) { 265 | if(root === null) { 266 | return 0 267 | } 268 | const left = maxDepth(root.left) 269 | const right = maxDepth(root.right) 270 | if(left === -1 || right === -1 || Math.abs(right - left) > 1) { //判断左右子树的高度 271 | return -1 272 | } 273 | if(left > right) { 274 | return left + 1 275 | }else{ 276 | return right + 1 277 | } 278 | 279 | } 280 | ``` 281 | 282 | [124.二叉树中的最大路径和](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) 283 | 284 | **路径** 被定义为一条从树中任意节点出发,沿父节点—子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 **至多出现一次** 。该路径 **至少包含一个** 节点,且不一定经过根节点。 285 | **路径和** 是路径中各节点值的总和。 286 | 给你一个二叉树的根节点 `root` ,返回其 **最大路径和** 。 287 | 288 | ```js 289 | var maxPathSum = function (root) { 290 | let maxSum = Number.MIN_SAFE_INTEGER 291 | function maxGain(node) { 292 | if (!node) { 293 | return 0 294 | } 295 | const left = maxGain(node.left) 296 | const right = maxGain(node.right) 297 | maxSum = Math.max(maxSum, node.val, node.val + left + right, node.val + left, node.val + right) 298 | return Math.max(node.val, node.val + left, node.val + right) 299 | } 300 | maxGain(root) 301 | return maxSum 302 | }; 303 | ``` 304 | 305 | [236. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) 306 | 307 | 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 308 | 309 | ```js 310 | var lowestCommonAncestor = function (root, p, q) { 311 | let ans = root 312 | const dfs = (node) => { 313 | if (node === null) { 314 | return false 315 | } 316 | const lson = dfs(node.left) 317 | const rson = dfs(node.right) 318 | if (((node.val === p.val || node.val === q.val) && (lson || rson)) || (lson && rson)) { 319 | ans = node 320 | } 321 | return (node.val === p.val || node.val === q.val) || lson || rson 322 | } 323 | dfs(root) 324 | return ans 325 | }; 326 | ``` 327 | 328 | [102. 二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 329 | 330 | 给你一个二叉树,请你返回其按 **层序遍历** 得到的节点值。 331 | 332 | ```js 333 | var levelOrder = function (root) { 334 | if (root === null) { 335 | return [] 336 | } 337 | const res = [] 338 | const queue = [] 339 | queue.push(root) 340 | while (queue.length > 0) { 341 | const currentQueueSize = queue.length 342 | const list = [] // 存放每一层的节点值 343 | for (let i = 1; i <= currentQueueSize; i++) { 344 | const node = queue.shift() 345 | list.push(node.val) 346 | if (node.left) { 347 | queue.push(node.left) 348 | } 349 | if (node.right) { 350 | queue.push(node.right) 351 | } 352 | } 353 | res.push(list) 354 | } 355 | return res 356 | }; 357 | ``` 358 | 359 | [107. 二叉树的层序遍历 II](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) 360 | 361 | 给定一个二叉树,返回其节点值自底向上的层次遍历。(即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) 362 | 363 | ```js 364 | if (root === null) { 365 | return [] 366 | } 367 | const res = [] 368 | const queue = [] 369 | queue.push(root) 370 | while (queue.length > 0) { 371 | const currentQueueSize = queue.length 372 | const list = [] 373 | for (let i = 1; i <= currentQueueSize; i++) { 374 | const node = queue.shift() 375 | list.push(node.val) 376 | if (node.left) { 377 | queue.push(node.left) 378 | } 379 | if (node.right) { 380 | queue.push(node.right) 381 | } 382 | } 383 | res.unshift(list) //unshift 从头部插入 384 | } 385 | return res 386 | ``` 387 | 388 | [103. 二叉树的锯齿形层序遍历](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) 389 | 390 | 给定一个二叉树,返回其节点值的锯齿形层序遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。 391 | 392 | ```js 393 | var zigzagLevelOrder = function(root) { 394 | if (root === null) { 395 | return [] 396 | } 397 | const res = [] 398 | const queue = [] 399 | queue.push(root) 400 | while (queue.length > 0) { 401 | const currentQueueSize = queue.length 402 | const list = [] 403 | for (let i = 1; i <= currentQueueSize; i++) { 404 | const node = queue.shift() 405 | list.push(node.val) 406 | if (node.left) { 407 | queue.push(node.left) 408 | } 409 | if (node.right) { 410 | queue.push(node.right) 411 | } 412 | } 413 | res.push(list) 414 | } 415 | return res.map((item, index) => { 416 | if(index % 2 === 1) { 417 | item = item.reverse() 418 | } 419 | return item 420 | }) 421 | }; 422 | ``` 423 | 424 | [98. 验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree/) 425 | 426 | 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 427 | 428 | 假设一个二叉搜索树具有如下特征: 429 | 节点的左子树只包含小于当前节点的数。 430 | 节点的右子树只包含大于当前节点的数。 431 | 所有左子树和右子树自身必须也是二叉搜索树。 432 | 433 | ```js 434 | var isValidBST = function (root) { //中序遍历,值会从小到大排列 435 | const list = [] 436 | inOrder(root, list) 437 | for(let i=0;i= list[i+1]){ 439 | return false 440 | } 441 | } 442 | return true 443 | }; 444 | 445 | const inOrder = (node, list) => { 446 | if(node === null) { 447 | return null 448 | } 449 | const left = inOrder(node.left,list) 450 | list.push(node.val) 451 | const right = inOrder(node.right,list) 452 | } 453 | ``` 454 | 455 | ```js 456 | var isValidBST = function (root) { //递归写法 457 | return helper(root, -Infinity, Infinity) 458 | }; 459 | 460 | const helper = (node, smaller, bigger) => { 461 | if (node === null) { 462 | return true 463 | } 464 | if (node.val <= smaller || node.val >= bigger) { 465 | return false 466 | } 467 | return helper(node.left, smaller, node.val) && helper(node.right, node.val, bigger) 468 | } 469 | ``` 470 | 471 | [701. 二叉搜索树中的插入操作](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) 472 | 473 | 给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。 474 | 475 | ```js 476 | var insertIntoBST = function (root, val) { 477 | if (root === null) { 478 | return new TreeNode(val) 479 | } 480 | if (root.val > val) { 481 | root.left = insertIntoBST(root.left, val) 482 | } else { 483 | root.right = insertIntoBST(root.right, val) 484 | } 485 | return root 486 | }; 487 | ``` 488 | 489 | ## 总结 490 | 491 | - 掌握二叉树递归与非递归遍历 492 | - 理解 DFS 前序遍历与分治法 493 | - 理解 BFS 层次遍历 494 | 495 | ## 练习 496 | 497 | - [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) 498 | - [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) 499 | - [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) 500 | - [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) 501 | - [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 502 | - [binary-tree-level-order-traversal-ii](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) 503 | - [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) 504 | - [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) 505 | - [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) -------------------------------------------------------------------------------- /基础算法篇/动态规划.md: -------------------------------------------------------------------------------- 1 | # 动态规划 2 | 3 | ## 背景 4 | 5 | 先从一道题目开始~ 6 | 7 | 如题 [triangle](https://leetcode-cn.com/problems/triangle/) 8 | 9 | > 给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 10 | 11 | 例如,给定三角形: 12 | 13 | ```js 14 | [ 15 | [2], 16 | [3,4], 17 | [6,5,7], 18 | [4,1,8,3] 19 | ] 20 | ``` 21 | 22 | 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 23 | 24 | 使用 DFS(遍历 或者 分治法) 25 | 26 | 超时 27 | 28 | ```js 29 | var row = 0; 30 | 31 | var minimumTotal = function(tringle){ 32 | row = tringle.length; 33 | return helper(0,0,tringle); 34 | } 35 | 36 | var helper = function(level, c, tringle){ 37 | if (level === row-1) { 38 | return tringle[level][c]; 39 | } 40 | let left = helper(level+1, c, tringle); 41 | let right = helper(level+1, c+1, tringle); 42 | return Math.min(left,right) + tringle[level][c]; 43 | } 44 | 45 | let tringle = [ 46 | [2], 47 | [3,4], 48 | [6,5,7], 49 | [4,1,8,3] 50 | ]; 51 | 52 | console.log(minimumTotal(tringle)); //11 53 | ``` 54 | 55 | 动态规划就是把大问题变成小问题,并解决了小问题重复计算的方法称为动态规划 56 | 57 | 动态规划和 DFS 区别 58 | 59 | - 二叉树子问题是没有交集,所以大部分二叉树都用递归或者分治法,即DFS,就可以解决。 60 | - 像 triangle 这种是有重复走的情况,**子问题是有交集**,所以可以用动态规划来解决。 61 | 62 | **动态规划三个特性**: 63 | 64 | - 最优子结构 65 | 66 | 最优子结构指的是,问题的最优解包含子问题的最优解。反过来说就是,我们可以通过子问题的最优解,推导出问题的最优解。如果我们把最优子结构,对应到我们前面定义的动态规划问题模型上,那我们也可以理解为,后面阶段的状态可以通过前面阶段的状态推导出来。 67 | 68 | - 无后效性 69 | 70 | 无后效性有两层含义,第一层含义是,在推导后面阶段的状态的时候,我们只关心前面阶段的状态值,不关心这个状态是怎么一步一步推导出来的。第二层含义是,某阶段状态一旦确定,就不受之后阶段的决策影响。无后效性是一个非常“宽松”的要求。只要满足前面提到的动态规划问题模型,其实基本上都会满足无后效性。 71 | 72 | - 重复子问题 73 | 74 | 如果用一句话概括一下,那就是,不同的决策序列,到达某个相同的阶段时,可能会产生重复的状态。所以才会用一个数组记录中间结果,避免重复计算。 75 | 76 | 动态规划,自底向上 77 | 78 | 这里变成一位数组,因为层号实际上可以不用记录,每次记录上一层的值,到当前层就把以前的覆盖到,动态规划运用场景其中一条就是最优子结构,往下走不用回头一定是最优的。 79 | 80 | `f(i,j)=min(f(i+1,j),f(i+1,j+1))+triangle[i][j]` 81 | 82 | ```tsx 83 | var minimumTotal = function(tringle){ 84 | let row = tringle.length; 85 | let dp = new Array(row+1).fill(0); 86 | for (let level = row-1;level>=0;level--){ 87 | for (let i = 0; i <= level; i++) { //第i行有i个数 88 | dp[i] = Math.min(dp[i],dp[i+1]) + tringle[level][i]; 89 | } 90 | } 91 | return dp[0]; 92 | } 93 | 94 | function minimumTotal(triangle: number[][]): number { // 时间、空间复杂度O(n2) 95 | const len = triangle.length 96 | const ans: number[][] = new Array(len).fill(0).map(() => new Array(len).fill(0)) 97 | ans[0][0] = triangle[0][0] 98 | for (let i = 1; i < len; i++) { 99 | ans[i][0] = ans[i - 1][0] + triangle[i][0] 100 | for (let j = 1; j < triangle[i].length; j++) { 101 | ans[i][j] = Math.min(ans[i - 1][j], ans[i - 1][j - 1]) + triangle[i][j] 102 | } 103 | ans[i][i] = ans[i - 1][i - 1] + triangle[i][i] 104 | } 105 | return Math.min(...ans[len - 1]) 106 | }; 107 | ``` 108 | 109 | ## 递归和动规关系 110 | 111 | 递归是一种程序的实现方式:函数的自我调用 112 | 113 | ``` 114 | Function(x) { 115 | ... 116 | Funciton(x-1); 117 | ... 118 | } 119 | ``` 120 | 121 | 动态规划:是一种解决问题的思想,大规模问题的结果,是由小规模问题的结果运算得来的。动态规划可用递归来实现(Memorization Search)。 122 | 123 | ## 使用场景 124 | 125 | 满足三个条件:最优子结构,无后效性,重复子问题 126 | 127 | 简单来说就是: 128 | 129 | - 满足以下条件之一 130 | - 求最大/最小值(Maximum/Minimum ) 131 | - 求是否可行(Yes/No ) 132 | - 求可行个数(Count(*) ) 133 | - 满足不能排序或者交换(Can not sort / swap ) 134 | 135 | 如题:[longest-consecutive-sequence](https://leetcode-cn.com/problems/longest-consecutive-sequence/) 位置可以交换,所以不用动态规划 136 | 137 | ## 四点要素 138 | 139 | 1. 状态 State 140 | - 灵感,创造力,存储小规模问题的结果 141 | 2. 方程 Function 142 | - 状态之间的联系,怎么通过小的状态,来算大的状态 143 | 3. 初始化 Intialization 144 | - 最极限的小状态是什么, 起点 145 | 4. 答案 Answer 146 | - 最大的那个状态是什么,终点 147 | 148 | ## 常见四种类型 149 | 150 | 1. Matrix DP (10%) 151 | 2. Sequence (40%) 152 | 3. Two Sequences DP (40%) 153 | 4. Backpack (10%) 154 | 155 | > 注意点 156 | > 157 | > - 贪心算法大多题目靠背答案,所以如果能用动态规划就尽量用动规,不用贪心算法 158 | 159 | ## 1、矩阵类型(10%) 160 | 161 | ##### [64. 最小路径和](https://leetcode-cn.com/problems/minimum-path-sum/) 162 | 163 | 给定一个包含非负整数的 `*m* x *n*` 网格 `grid` ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。**说明:**每次只能向下或者向右移动一步。 164 | 165 | ```js 166 | var minPathSum = function (grid) { 167 | let m = grid.length 168 | let n = grid[0].length 169 | for (let i = 1; i < m; i++) { 170 | grid[i][0] += grid[i - 1][0] 171 | } 172 | for (let i = 1; i < n; i++) { 173 | grid[0][i] += grid[0][i - 1] 174 | } 175 | for (let i = 1; i < m; i++) { 176 | for (let j = 1; j < n; j++) { 177 | grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j] 178 | } 179 | } 180 | return grid[m - 1][n - 1] 181 | }; 182 | ``` 183 | 184 | ##### [62. 不同路径](https://leetcode-cn.com/problems/unique-paths/) 185 | 186 | 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径? 187 | 188 | ```js 189 | var uniquePaths = function (m, n) { 190 | let dp = new Array(m).fill(new Array(n).fill(1)) //第一行和第一列都是1 191 | for (let i = 1; i < m; i++) { 192 | for (let j = 1; j < n; j++) { 193 | dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 194 | } 195 | } 196 | return dp[m - 1][n - 1] 197 | }; 198 | ``` 199 | 200 | ##### [63. 不同路径 II](https://leetcode-cn.com/problems/unique-paths-ii/) 201 | 202 | 同上,不过增加了障碍物。 203 | 204 | > 思路:第一行(列)遇到障碍物之前置为1,之后置为0(包括障碍物) 205 | 206 | ```js 207 | var uniquePathsWithObstacles = function (obstacleGrid) { 208 | let m = obstacleGrid.length 209 | let n = obstacleGrid[0].length 210 | if (obstacleGrid[0][0] === 1) { 211 | return 0 212 | } 213 | for (let i = 0; i < m; i++) { 214 | if (obstacleGrid[i][0] === 1) { 215 | for (let j = i; j < m; j++) { 216 | obstacleGrid[j][0] = 0 217 | } 218 | break 219 | } 220 | obstacleGrid[i][0] = 1 221 | } 222 | for (let i = 1; i < n; i++) { 223 | if (obstacleGrid[0][i] === 1) { 224 | for (let j = i; j < n; j++) { 225 | obstacleGrid[0][j] = 0 226 | } 227 | break 228 | } 229 | obstacleGrid[0][i] = 1 230 | } 231 | for (let i = 1; i < m; i++) { 232 | for (let j = 1; j < n; j++) { 233 | if (obstacleGrid[i][j] === 1) { 234 | obstacleGrid[i][j] = 0 235 | } else { 236 | obstacleGrid[i][j] = obstacleGrid[i - 1][j] + obstacleGrid[i][j - 1] 237 | } 238 | } 239 | } 240 | return obstacleGrid[m - 1][n - 1] 241 | }; 242 | ``` 243 | 244 | ## 2、序列类型(40%) 245 | 246 | ### Sequence 247 | 248 | ##### [70. 爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/) 249 | 250 | ```js 251 | var climbStairs = function (n) { 252 | const res = [1, 2] 253 | for (let i = 2; i < n; i++) { 254 | res[i] = res[i - 1] + res[i - 2] 255 | } 256 | return res[n - 1] 257 | }; 258 | ``` 259 | 260 | ##### [55. 跳跃游戏](https://leetcode-cn.com/problems/jump-game/) 261 | 262 | 给定一个非负整数数组 `nums` ,你最初位于数组的 **第一个下标** 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。 263 | 264 | ```js 265 | var canJump = function (nums) { 266 | let k = 0 267 | for (let i = 0; i < nums.length; i++) { 268 | if (i > k) { return false } 269 | k = Math.max(k, i + nums[i]) 270 | } 271 | return true 272 | }; 273 | ``` 274 | 275 | ##### [45. 跳跃游戏 II](https://leetcode-cn.com/problems/jump-game-ii/) 276 | 277 | 使用最少的跳跃次数到达数组的最后一个位置。 278 | 279 | ```js 280 | function jump(nums: number[]): number { //从后往前推 281 | let pos: number = nums.length - 1 282 | let step: number = 0 283 | while (pos > 0) { 284 | for (let i = 0; i < pos; i++) { 285 | if (i + nums[i] >= pos) { //第一次到达终点的位置,然后往前找能到达该位置的点。 286 | pos = i 287 | step++ 288 | break 289 | } 290 | } 291 | } 292 | return step 293 | }; 294 | ``` 295 | 296 | ```tsx 297 | function jump(nums: number[]): number { //从前往后推 298 | let maxPos: number = 0 299 | let step: number = 0 300 | let end: number = 0 301 | for (let i = 0; i < nums.length - 1; i++) { 302 | maxPos = Math.max(i + nums[i], maxPos) 303 | if(i === end) { 304 | end = maxPos 305 | step++ 306 | } 307 | } 308 | return step 309 | }; 310 | ``` 311 | 312 | ##### [132. 分割回文串 II](https://leetcode-cn.com/problems/palindrome-partitioning-ii/) 313 | 314 | 给你一个字符串 `s`,请你将 `s` 分割成一些子串,使每个子串都是回文。返回符合要求的 最少分割次数 。 315 | 316 | ##### [300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/) 317 | 318 | 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 319 | 320 | 子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 321 | 322 | ```tsx 323 | function lengthOfLIS(nums: number[]): number { 324 | let dp: number[] = new Array(nums.length).fill(1) 325 | for (let i = 1; i < nums.length; i++) { 326 | for (let j = 0; j < i; j++) { 327 | if (nums[j] < nums[i]) { 328 | dp[i] = Math.max(dp[i], dp[j] + 1) 329 | } 330 | } 331 | } 332 | return Math.max(...dp) 333 | }; 334 | ``` 335 | 336 | ##### [139. 单词拆分](https://leetcode-cn.com/problems/word-break/) 337 | 338 | 给定一个**非空**字符串 *s* 和一个包含**非空**单词的列表 *wordDict*,判定 *s* 是否可以被空格拆分为一个或多个在字典中出现的单词。 339 | 340 | ```tsx 341 | function wordBreak(s: string, wordDict: string[]): boolean { 342 | const len: number = s.length 343 | const dp: boolean[] = new Array(len + 1).fill(false) 344 | const wordDictSet: Set = new Set(wordDict) 345 | dp[0] = true 346 | for (let i = 1; i <= len; i++) { 347 | for (let j = 0; j < i; j++) { 348 | if (dp[j] && wordDictSet.has(s.slice(j, i))) { 349 | dp[i] = true 350 | break 351 | } 352 | } 353 | } 354 | return dp[len] 355 | }; 356 | ``` 357 | 358 | ### Two Sequences DP 359 | 360 | ##### [1143. 最长公共子序列](https://leetcode-cn.com/problems/longest-common-subsequence/) 361 | 362 | 给定两个字符串 `text1` 和 `text2`,返回这两个字符串的最长 **公共子序列** 的长度。如果不存在 **公共子序列** ,返回 `0` 。 363 | 364 | ```tsx 365 | function longestCommonSubsequence(text1: string, text2: string): number { 366 | const col: number = text1.length 367 | const row: number = text2.length 368 | const dp: number[][] = new Array(row + 1).fill(0).map(() => new Array(col + 1).fill(0)) 369 | for (let i = 1; i <= row; i++) { 370 | for (let j = 1; j <= col; j++) { 371 | if (text2[i - 1] === text1[j - 1]) { 372 | dp[i][j] = dp[i - 1][j - 1] + 1 373 | } else { 374 | dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) 375 | } 376 | } 377 | } 378 | return dp[row][col] 379 | }; 380 | ``` 381 | 382 | ##### [72. 编辑距离](https://leetcode-cn.com/problems/edit-distance/) 383 | 384 | 给你两个单词 `word1` 和 `word2`,请你计算出将 `word1` 转换成 `word2` 所使用的最少操作数 。 385 | 386 | ```tsx 387 | function minDistance(word1: string, word2: string): number { 388 | let l1: number = word1.length 389 | let l2: number = word2.length 390 | let dp: number[][] = new Array(l1 + 1).fill(0).map(() => new Array(l2 + 1).fill(0)) 391 | for (let i = 0; i <= l1; i++) { 392 | dp[i][0] = i 393 | } 394 | for (let j = 0; j <= l2; j++) { 395 | dp[0][j] = j 396 | } 397 | for (let i = 1; i <= l1; i++) { 398 | for (let j = 1; j <= l2; j++) { 399 | if (word1[i - 1] === word2[j - 1]) { 400 | dp[i][j] = dp[i - 1][j - 1] 401 | } else { 402 | dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 403 | } 404 | } 405 | } 406 | return dp[l1][l2] 407 | }; 408 | ``` 409 | 410 | ## 零钱和背包(10%) 411 | 412 | ##### [322. 零钱兑换](https://leetcode-cn.com/problems/coin-change/) 413 | 414 | 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 415 | 416 | ```tsx 417 | function coinChange(coins: number[], amount: number): number { 418 | const dp: number[] = new Array(amount + 1).fill(amount + 1) 419 | dp[0] = 0 420 | for (let i = 1; i <= amount; i++) { 421 | for (let j = 0; j < coins.length; j++) { 422 | if (i - coins[j] >= 0) { 423 | dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1) 424 | } 425 | } 426 | } 427 | if (dp[amount] > amount) { 428 | return -1 429 | } 430 | return dp[amount] 431 | }; 432 | ``` 433 | 434 | ##### [92.背包问题](https://www.lintcode.com/problem/backpack/description) 435 | 436 | 在`n`个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为`m`,每个物品的大小为Ai 437 | 438 | ```tsx 439 | function backpack(m: number, A: number[]): number { 440 | const dp: boolean[][] = new Array(A.length+1).fill(false).map(()=>new Array(m+1).fill(false)) 441 | for(let i=1;i<=A.length;i++){ 442 | for(let j=0;j<=m;j++){ 443 | dp[i][j] = dp[i-1][j] 444 | if(j-A[i-1] >= 0 && dp[i-1][j-A[i-1]]){ 445 | dp[i][j] = true 446 | } 447 | } 448 | } 449 | for(let i=m;i>=0;i--){ 450 | if(dp[A.length][i]){ 451 | return i 452 | } 453 | } 454 | return 0 455 | } 456 | ``` 457 | 458 | ## 练习 459 | 460 | Matrix DP (10%) 461 | 462 | - [triangle](https://leetcode-cn.com/problems/triangle/) 463 | - [minimum-path-sum](https://leetcode-cn.com/problems/minimum-path-sum/) 464 | - [unique-paths](https://leetcode-cn.com/problems/unique-paths/) 465 | - [unique-paths-ii](https://leetcode-cn.com/problems/unique-paths-ii/) 466 | 467 | Sequence (40%) 468 | 469 | - [climbing-stairs](https://leetcode-cn.com/problems/climbing-stairs/) 470 | - [jump-game](https://leetcode-cn.com/problems/jump-game/) 471 | - [jump-game-ii](https://leetcode-cn.com/problems/jump-game-ii/) 472 | - [palindrome-partitioning-ii](https://leetcode-cn.com/problems/palindrome-partitioning-ii/) 473 | - [longest-increasing-subsequence](https://leetcode-cn.com/problems/longest-increasing-subsequence/) 474 | - [word-break](https://leetcode-cn.com/problems/word-break/) 475 | 476 | Two Sequences DP (40%) 477 | 478 | - [longest-common-subsequence](https://leetcode-cn.com/problems/longest-common-subsequence/) 479 | - [edit-distance](https://leetcode-cn.com/problems/edit-distance/) 480 | 481 | Backpack & Coin Change (10%) 482 | 483 | - [coin-change](https://leetcode-cn.com/problems/coin-change/) 484 | - [backpack](https://www.lintcode.com/problem/backpack/description) 485 | - [backpack-ii](https://www.lintcode.com/problem/backpack-ii/description) -------------------------------------------------------------------------------- /入门/力扣经典算法(Java).md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | ### 常用方法 4 | 5 | **char数组转换为String类型** 6 | 7 | ```java 8 | char[] chars = ['a','b','c']; 9 | String s = String.valueOf(chars); //方法一 10 | String s = new String(chars); //方法二 new一个String 11 | ``` 12 | 13 | **String类型转char数组** 14 | 15 | ``` java 16 | char[] chars = s.toCharArray(); 17 | ``` 18 | 19 | ```java 20 | Pattern.matchs() // 字符串匹配 21 | ``` 22 | 23 | ### 动态规划 24 | 25 | **第一步骤**:定义**数组元素的含义**,我们会用一个数组,来保存历史数组,假设用一维数组 dp[] 吧。这个时候有一个非常非常重要的点,就是规定你这个数组元素的含义,例如你的 dp[i] 是代表什么意思。 26 | 27 | **第二步骤**:找出**数组元素之间的关系式**,我觉得动态规划,还是有一点类似于我们高中学习时的**归纳法**的,当我们要计算 dp[n] 时,是可以利用 dp[n-1],dp[n-2].....dp[1],来推出 dp[n] 的,也就是可以利用**历史数据**来推出新的元素值,所以我们要找出数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],这个就是他们的关系式了。而这一步,也是最难的一步,后面我会讲几种类型的题来说。 28 | 29 | **第三步骤**:找出**初始值**。学过**数学归纳法**的都知道,虽然我们知道了数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],我们可以通过 dp[n-1] 和 dp[n-2] 来计算 dp[n],但是,我们得知道初始值啊,例如一直推下去的话,会由 dp[3] = dp[2] + dp[1]。而 dp[2] 和 dp[1] 是不能再分解的了,所以我们必须要能够直接获得 dp[2] 和 dp[1] 的值,而这,就是**所谓的初始值**。 30 | 31 | 由了**初始值**,并且有了**数组元素之间的关系式**,那么我们就可以得到 dp[n] 的值了,而 dp[n] 的含义是由你来定义的,你想**求什么,就定义它是什么**,这样,这道题也就解出来了。 32 | 33 | 90%的字符串匹配问题可用动态规划解决。 34 | 35 | ### 二叉树遍历 36 | 37 | #### 节点查找: 38 | 39 | ```java 40 | public TreeNode preOrder(TreeNode A,TreeNode B){ 41 | if (A==null) return null; 42 | if (A.val== B.val) return A; 43 | TreeNode tmpA,tmpB; 44 | if ((tmpA=preOrder(A.left,B))!=null){ 45 | return tmpA; 46 | }else if((tmpB=preOrder(A.right,B))!=null){ 47 | return tmpB; 48 | }else 49 | return null; 50 | } 51 | ``` 52 | 53 | #### 先序遍历 54 | 55 | ```java 56 | public void traversePreOrder(Node node) { //递归 57 | if (node != null) { 58 | visit(node.value); 59 | traversePreOrder(node.left); 60 | traversePreOrder(node.right); 61 | } 62 | } 63 | ``` 64 | 65 | ```java 66 | public void traversePreOrderWithoutRecursion() { //非递归 67 | Stack stack = new Stack(); 68 | Node current = root; 69 | stack.push(root); 70 | while(!stack.isEmpty()) { 71 | current = stack.pop(); //先根后左子树、右子树 72 | visit(current.value); 73 | 74 | if(current.right != null) { 75 | stack.push(current.right); 76 | } 77 | if(current.left != null) { 78 | stack.push(current.left); 79 | } 80 | } 81 | } 82 | 83 | ``` 84 | 85 | #### 中序遍历 86 | 87 | ```java 88 | public void traverseInOrder(Node node) {//递归 89 | if (node != null) { 90 | traverseInOrder(node.left); 91 | visit(node.value); 92 | traverseInOrder(node.right); 93 | } 94 | ``` 95 | 96 | ```java 97 | public void traverseInOrderWithoutRecursion() {//非递归 98 | Stack stack = new Stack(); 99 | Node current = root; 100 | stack.push(root); 101 | while(!stack.isEmpty()) { 102 | while(current.left != null) {//先左子树、然后根、右子树 103 | current = current.left; 104 | stack.push(current); 105 | } 106 | current = stack.pop(); 107 | visit(current.value); 108 | if(current.right != null) { 109 | current = current.right; 110 | stack.push(current); 111 | } 112 | } 113 | } 114 | ``` 115 | 116 | #### 后序遍历 117 | 118 | ```java 119 | public void traversePostOrder(Node node) { //递归 120 | if (node != null) { 121 | traversePostOrder(node.left); 122 | traversePostOrder(node.right); 123 | visit(node.value); 124 | } 125 | } 126 | ``` 127 | 128 | ```java 129 | public void traversePostOrderWithoutRecursion() { //后序的递归后续理解 130 | Stack stack = new Stack(); 131 | Node prev = root; 132 | Node current = root; 133 | stack.push(root); 134 | 135 | while (!stack.isEmpty()) { 136 | current = stack.peek(); 137 | boolean hasChild = (current.left != null || current.right != null); 138 | boolean isPrevLastChild = (prev == current.right || 139 | (prev == current.left && current.right == null)); 140 | 141 | if (!hasChild || isPrevLastChild) { 142 | current = stack.pop(); 143 | visit(current.value); 144 | prev = current; 145 | } else { 146 | if (current.right != null) { 147 | stack.push(current.right); 148 | } 149 | if (current.left != null) { 150 | stack.push(current.left); 151 | } 152 | } 153 | } 154 | ``` 155 | 156 | #### 层次遍历 157 | 158 | ```java 159 | public void levelIterator(BiTree root) 160 | { 161 | if(root == null) 162 | { 163 | return ; 164 | } 165 | LinkedList queue = new LinkedList(); 166 | BiTree current = null; 167 | queue.offer(root); //将根节点入队 168 | while(!queue.isEmpty()) 169 | { 170 | current = queue.poll(); //出队队头元素并访问 171 | System.out.print(current.val +"-->"); 172 | if(current.left != null) //如果当前节点的左节点不为空入队 173 | { 174 | queue.offer(current.left); 175 | } 176 | if(current.right != null) //如果当前节点的右节点不为空,把右节点入队 177 | { 178 | queue.offer(current.right); 179 | } 180 | } 181 | 182 | ``` 183 | 184 | #### DFS 深度搜索-从上到下 185 | 186 | ```java 187 | public class Solution { 188 | public List dfsUpToDown(TreeNode root) { 189 | List res = new LinkedList<>(); 190 | dfs(root, res); 191 | return res; 192 | } 193 | 194 | public void dfs(TreeNode node, List res) { 195 | if (node == null) { 196 | return; 197 | } 198 | res.add(node.val); 199 | dfs(node.left, res); 200 | dfs(node.right, res); 201 | } 202 | } 203 | ``` 204 | 205 | #### DFS 深度搜索-从下向上(分治法) 206 | 207 | ```java 208 | public class Solution { 209 | public List prerderTraversal(TreeNode root) { 210 | return divideAndConquer(root); 211 | } 212 | 213 | public List divideAndConquer(TreeNode node) { 214 | List result = new LinkedList<>(); 215 | if (node == null) { 216 | return null; 217 | } 218 | // 分治 219 | List left = divideAndConquer(node.left); 220 | List right = divideAndConquer(node.right); 221 | // 合并结果 222 | result.add(node.val); 223 | if (left != null) { 224 | result.addAll(left); 225 | } 226 | if (right != null) { 227 | result.addAll(right); 228 | } 229 | return result; 230 | } 231 | } 232 | ``` 233 | 234 | 注意点: 235 | 236 | > DFS 深度搜索(从上到下) 和分治法区别:前者一般将最终结果通过指针参数传入,后者一般递归返回结果最后合并 237 | 238 | #### BFS 层次遍历 239 | 240 | ```java 241 | public class Solution { 242 | public List levelOrder(TreeNode root) { 243 | List res = new LinkedList<>(); 244 | Queue queue = new LinkedList<>(); 245 | queue.add(root); 246 | while(!queue.isEmpty()) { 247 | // size记录当前层有多少元素(遍历当前层,再添加下一层) 248 | int size = queue.size(); 249 | for (int i = 0; i < size; ++i) { 250 | TreeNode node = queue.poll(); 251 | res.add(node.val); 252 | if (node.left != null) { 253 | queue.add(node.left); 254 | } 255 | if (node.right != null) { 256 | queue.add(node.right); 257 | } 258 | } 259 | } 260 | return res; 261 | } 262 | } 263 | ``` 264 | 265 | ### 图遍历 266 | 267 | #### 深度优先遍历 268 | 269 | ```java 270 | public void dfs(int start) { 271 | boolean[] isVisited = new boolean[adjVertices.size()]; 272 | dfsRecursive(start, isVisited); 273 | } 274 | 275 | private void dfsRecursive(int current, boolean[] isVisited) {//递归 276 | isVisited[current] = true; 277 | visit(current); 278 | for (int dest : adjVertices.get(current)) { 279 | if (!isVisited[dest]) 280 | dfsRecursive(dest, isVisited); 281 | } 282 | } 283 | ``` 284 | 285 | ```java 286 | public void dfsWithoutRecursion(int start) {//非递归 287 | Stack stack = new Stack(); 288 | boolean[] isVisited = new boolean[adjVertices.size()]; 289 | stack.push(start); 290 | while (!stack.isEmpty()) { 291 | int current = stack.pop(); 292 | isVisited[current] = true; 293 | visit(current); 294 | for (int dest : adjVertices.get(current)) { 295 | if (!isVisited[dest]) 296 | stack.push(dest); 297 | } 298 | } 299 | } 300 | ``` 301 | 302 | #### 广度优先遍历 303 | 304 | ```java 305 | static void BFS(Graph g){ 306 | Queue queue = new LinkedList(g.VertexNum); 307 | int i,j; 308 | for(i = 0; i < g.VertexNum; i++){ //初始化顶点的访问情况 309 | g.isTrav[i] = 0; 310 | } 311 | for(i = 0; i < g.VertexNum; i++){ //遍历每一个顶点,实现从每一个顶点出发的广度优先遍历 312 | g.isTrav[i] = 1; 313 | System.out.println("->"+g.Vertex[i]); 314 | queue.offer(i); 315 | while(!queue.isEmpty()){ //队列空了才会跳出循环,意味着从某个顶点出发的广度优先遍历已经结束了,下次循环意味着要从另外一个顶点出发 316 | vertex = queue.poll(); 317 | for(j = 0; j < g.VertexNum; j++){ 318 | if(g.EdgeWeight[i][j] != g.MaxValue && g.isTrav[i] == 0){ 319 | System.out.println(g.Vertex[i]); 320 | g.isTrav[i] = 1; 321 | queue.offer(i); 322 | } 323 | } 324 | } 325 | ``` 326 | 327 | ### 贪心算法 328 | 329 | ### 递归 330 | 331 | ### 回溯 332 | 333 | 回溯法(back tracking)(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。(基于深度优先搜索) 334 | 335 | > 八皇后问题,在8X8格的国际象棋上摆放八个皇后(棋子),使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上。 336 | > 337 | > Step 1: 先尝试放置第一枚皇后,被涂黑的地方是不能放皇后 338 | 339 | > img 340 | 341 | > Step 2 : 第二行的皇后只能放在第三格或第四格,比方我们放第三格,则: 342 | > 343 | > img 344 | 345 | > Step 3 : 可以看到再难以放下第三个皇后,此时我们就要用到**回溯**算法了。我们把第二个皇后更改位置,此时我们能放下第三枚皇后了。 346 | > 347 | > img 348 | 349 | > Step 4 : 虽然是能放置第三个皇后,但是第四个皇后又无路可走了。返回上层调用(3号皇后),而3号也别无可去,继续回溯上层调用(2号),2号已然无路可去,继续回溯上层(1号),于是1号皇后改变位置如下,继续回溯。 350 | > 351 | > img 352 | 353 | 354 | 355 | ### 快速幂 356 | 357 | 二分角度 358 | 359 | ```java 360 | (n&1)==1 //判断n是否为奇数 1101 1110 361 | n>>=1 //n右移一位 1000>>1 --> 100 362 | ``` 363 | 364 | ### 排序算法 365 | 366 | | 算法 | 稳定 | 原地排序 | 时间复杂度 | 空间复杂度 | 备注 | 367 | | :--------------: | :--: | :------: | :--------------------------: | :--------: | :----------------------: | 368 | | 冒泡排序 | yes | yes | N^2^ | 1 | | 369 | | 选择排序 | no | yes | N2 | 1 | | 370 | | 插入排序 | yes | yes | N \~ N2 | 1 | 时间复杂度和初始顺序有关 | 371 | | 希尔排序 | no | yes | N 的若干倍乘于递增序列的长度 | 1 | | 372 | | 快速排序 | no | yes | NlogN | logN | | 373 | | 三向切分快速排序 | no | yes | N \~ NlogN | logN | 适用于有大量重复主键 | 374 | | 归并排序 | yes | no | NlogN | N | | 375 | | 堆排序 | no | yes | NlogN | 1 | | 376 | 377 | #### 选择排序 378 | 379 | ```java 380 | public void swap(int[] T,int i,int j){ 381 | int temp = T[i]; 382 | T[i] = T[j]; 383 | T[j] = temp; 384 | } 385 | 386 | public int[] selectSort(int[] nums){//选择排序 387 | for (int i=0;i0&&!isSort;i--){ 406 | isSort=true; 407 | for (int j=0;jnums[j+1]){//将大的元素向上冒泡 409 | isSort=false; 410 | swap(nums,j,j+1); 411 | } 412 | } 413 | } 414 | return nums; 415 | } 416 | ``` 417 | 418 | #### 插入排序 419 | 420 | ```java 421 | public int[] insertSort(int[] nums){ 422 | for (int i=0;i0&&nums[j]= 1) { 441 | for (int i = h; i < N; i++) { //循环内是插入排序 442 | for (int j = i; j >= h && nums[j]pivot && right>left){ //从右指向左,遇到比基准小的换到前面去 482 | right--; 483 | } 484 | while (arr[left]left){//从左指向右,遇到比基准大的换到后面去 485 | left++; 486 | } 487 | if (arr[left]==arr[right] && leftstart) quickSort(arr,start,left-1); 492 | if (right+1= 0; i--) { 502 | adjustHeap(array, i, array.length); //调整堆 503 | } 504 | // 上述逻辑,建堆结束 505 | // 下面,开始排序逻辑 506 | for (int j = array.length - 1; j > 0; j--) { 507 | // 元素交换,作用是去掉大顶堆 508 | // 把大顶堆的根元素,放到数组的最后;换句话说,就是每一次的堆调整之后,都会有一个元素到达自己的最终位置 509 | swap(array, 0, j); 510 | // 元素交换之后,毫无疑问,最后一个元素无需再考虑排序问题了。 511 | // 接下来我们需要排序的,就是已经去掉了部分元素的堆了,这也是为什么此方法放在循环里的原因 512 | // 而这里,实质上是自上而下,自左向右进行调整的 513 | adjustHeap(array, 0, j); 514 | } 515 | return array; 516 | } 517 | /** 518 | * 整个堆排序最关键的地方 519 | * @param array 待组堆 520 | * @param i 起始结点 521 | * @param length 堆的长度 522 | */ 523 | public static void adjustHeap(int[] array, int i, int length) { 524 | // 先把当前元素取出来,因为当前元素可能要一直移动 525 | int temp = array[i]; 526 | for (int k = 2 * i + 1; k < length; k = 2 * k + 1) { //2*i+1为左子树i的左子树(因为i是从0开始的),2*k+1为k的左子树 527 | // 让k先指向子节点中最大的节点 528 | if (k + 1 < length && array[k] < array[k + 1]) { //如果有右子树,并且右子树大于左子树 529 | k++; 530 | } 531 | //如果发现结点(左右子结点)大于根结点,则进行值的交换 532 | if (array[k] > temp) { 533 | swap(array, i, k); 534 | // 如果子节点更换了,那么,以子节点为根的子树会受到影响,所以,循环对子节点所在的树继续进行判断 535 | i = k; 536 | } else { //不用交换,直接终止循环 537 | break; 538 | } 539 | } 540 | } 541 | ``` 542 | 543 | #### 桶排序 544 | 545 | ```java 546 | 547 | ``` 548 | 549 | ### 链表 550 | 551 | #### 头插法 552 | 553 | #### 尾插法 --------------------------------------------------------------------------------