├── 1.排序 ├── 冒泡排序.md ├── 十大排序对比.md ├── 堆排序.md ├── 希尔排序.md ├── 归并排序.md ├── 快速排序.md ├── 插入排序.md └── 选择排序.md ├── 2.二分查找 └── 二分查找.md ├── 3.栈、队列 ├── 双栈模拟队列.md ├── 将无序栈转为有序栈.md └── 有效的括号.md ├── 4.链表 ├── 两个链表的第一个公共节点.md ├── 两数相加.md ├── 判断链表是否有环.md ├── 反转链表.md ├── 合并两个有序链表.md ├── 手撕单向链表.md └── 链表中倒数第K个节点.md ├── 5.二叉树 ├── 中序遍历.md ├── 之字形打印二叉树.md ├── 二叉搜索树的后序遍历序列.md ├── 二叉搜索树的第K个节点.md ├── 二叉树.md ├── 二叉树的下一个节点.md ├── 二叉树的最大宽度.md ├── 二叉树的深度.md ├── 二叉树的镜像.md ├── 前序遍历.md ├── 后序遍历.md ├── 对称二叉树.md ├── 层次遍历.md ├── 平衡二叉树.md ├── 按层打印二叉树.md └── 重建二叉树.md ├── 6.双指针 ├── 和为S的两个数(微改版).md ├── 和为S的连续正整数.md ├── 字符串压缩.md ├── 接雨水.md ├── 滑动窗口的最大值.md ├── 盛最多水的容器.md └── 验证回文串.md ├── 7.动态规划 ├── 不同路径.md ├── 斐波那契数列、跳台阶、矩形覆盖.md ├── 最小路径和.md ├── 最长回文子串.md ├── 最长有效括号.md └── 编辑距离.md ├── 8.前端 ├── Promise封装异步上传图片.md ├── new的过程.md ├── spawn函数(Async函数实现原理).md ├── thunk函数(Generator函数实现自动流程管理原理).md ├── 函数柯里化.md ├── 实现Instanceof.md ├── 封装apply.md ├── 封装bind.md ├── 封装call.md ├── 封装map.md ├── 手撕Promise(A+规范).md ├── 手撕Promise(简易版).md ├── 手撕Promise.all.md ├── 数组去重.md ├── 数组扁平化.md ├── 深拷贝.md ├── 红黄绿灯(字节).md ├── 节流.md └── 防抖.md └── README.md /1.排序/冒泡排序.md: -------------------------------------------------------------------------------- 1 | # 冒泡排序 2 | 3 | ## 思路 4 | 5 | > 外层控制循环次数,内层用来比较 6 | 7 | ## 代码 8 | 9 | > ### 平均时间复杂度:O(n^2) 空间复杂度:O(1) 10 | 11 | ```js 12 | function bubbleSort(arr) { 13 | for (let i = arr.length - 1, temp; i > 0; i--) { 14 | for (let j = 0; j < i; j++) { 15 | temp = arr[j]; 16 | if (temp > arr[j + 1]) { 17 | arr[j] = arr[j + 1]; 18 | arr[j + 1] = temp; 19 | } 20 | } 21 | } 22 | return arr; 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /1.排序/十大排序对比.md: -------------------------------------------------------------------------------- 1 | # 十大排序对比 2 | 3 | ## 术语说明 4 | 5 | > 稳定:如果 a 原本在 b 前面,且 a=b,排序之后 a 仍然在 b 的前面
6 | > 不稳定:如果 a 原本在 b 的前面,且 a=b,排序之后 a 可能会出现在 b 的后面
7 | > 内排序:所有排序操作都在内存中完成
8 | > 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行
9 | > 时间复杂度:一个算法执行所耗费的时间
10 | > 空间复杂度:运行完一个程序所需内存的大小
11 | 12 | ![十大排序对比](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAxNi8xMS8yOS80YWJkZTE3NDg4MTdkN2YzNWYyYmY4YjZhMDU4YWE0MA) 13 | 14 | ### 上述名词解释 15 | 16 | > n: 数据规模
17 | > k: “桶”的个数
18 | > In-place: 占用常数内存,不占用额外内存
19 | > Out-place: 占用额外内存
20 | -------------------------------------------------------------------------------- /1.排序/堆排序.md: -------------------------------------------------------------------------------- 1 | # 堆排序 2 | 3 | ## 思路 4 | 5 | > (1) 将初始待排序关键字序列(R1,R2,…,Rn)构建成大顶堆,此堆为初始堆无序区
6 | > (2) 将堆顶元素 R[1]与最后一个元素 R[n]交换,此时得到新堆无序区(R1,R2,…,Rn-1)和新堆有序区(Rn),且满足 R[1,2,…,n-1] 7 | > (3) 由于交换后新堆堆顶 R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,…,Rn-1)调整为新堆,
8 | > 然后再次将 R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2,…,Rn-2)和新的有序区(Rn-1,Rn)。
9 | > 不断重复此过程,直到有序区的元素个数为 n-1,则整个排序过程完成
10 | 11 | ## 代码 12 | 13 | > ### 平均时间复杂度:O(n log n) 空间复杂度:O(1) 14 | 15 | ```js 16 | function heapSort(arr) { 17 | const len = arr.length; 18 | for (let i = len; i > 1; i--) { 19 | buildHeap(arr, i); 20 | let temp = arr[0]; 21 | arr[0] = arr[i - 1]; 22 | arr[i - 1] = temp; 23 | } 24 | return arr; 25 | } 26 | 27 | function buildHeap(arr, end) { 28 | for (let i = end - 1; i > 0; i--) { 29 | if (arr[i] > arr[i - 1]) { 30 | let temp = arr[i]; 31 | arr[i] = arr[i - 1]; 32 | arr[i - 1] = temp; 33 | } 34 | } 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /1.排序/希尔排序.md: -------------------------------------------------------------------------------- 1 | # 希尔排序 2 | 3 | ## 思路 4 | 5 | > 只是给插入排序外加了增量 gap 6 | 7 | ## 代码 8 | 9 | > ### 平均时间复杂度:O(n log n) 空间复杂度:O(1) 10 | 11 | ```js 12 | function shellSort(arr){ 13 | let gap = arr.length; 14 | while(gap > 1){ 15 | gap = parseInt(gap/2); 16 | for(let i = gap;i < arr.length;i++){ 17 | let temp = arr[i]; 18 | let j = i-gap; 19 | while(arr[j] > temp && j >= 0){ 20 | arr[j+gap] = arr[j]; 21 | j- = gap; 22 | } 23 | arr[j+gap] = temp; 24 | } 25 | } 26 | return arr; 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /1.排序/归并排序.md: -------------------------------------------------------------------------------- 1 | # 归并排序 2 | 3 | ## 思路 4 | 5 | > (1) 把长度为 n 的输入序列分为两个长度为 n/2 的子序列
6 | > (2) 对这两个子序列分别采用归并排序
7 | > (3) 将两个排序好的子序列合并成一个最终的排序序列
8 | 9 | ## 代码 10 | 11 | > ### 平均时间复杂度:O(n log n) 空间复杂度:O(n) 12 | 13 | ```js 14 | function mergeSort(arr) { 15 | const len = rr.length; 16 | if (len < 2) { 17 | return arr; 18 | } 19 | let middle = parseInt(len / 2); 20 | let left = arr.slice(0, middle); 21 | let right = arr.slice(middle); 22 | return merge(mergeSort(left), mergeSort(right)); 23 | } 24 | function merge(left, right) { 25 | let result = []; 26 | while (left.length && right.length) { 27 | if (left[0] < right[0]) { 28 | result.push(left.shift()); 29 | } else { 30 | result.push(right.shift()); 31 | } 32 | } 33 | if (left.length === 0) { 34 | result = result.concat(right); 35 | } else if (right.length === 0) { 36 | result = result.concat(left); 37 | } 38 | return result; 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /1.排序/快速排序.md: -------------------------------------------------------------------------------- 1 | # 快速排序 2 | 3 | ## 思路 4 | 5 | > 选择一个元素作为基准,把小于基准的元素放在基准的左边,把大于基准的元素放在基准的右边,对于基准左右两边
6 | > 的值重复递归一二步,直至正序排序
7 | 8 | ## 代码 9 | 10 | > ### 平均时间复杂度:O(n log n) 空间复杂度:O(log n) 11 | 12 | ```js 13 | function quickSort(arr) { 14 | const len = arr.length; 15 | if (len < 2) { 16 | return arr; 17 | } else { 18 | let flag = arr[0]; 19 | let left = []; 20 | let right = []; 21 | for (let i = 1; i < len; i++) { 22 | if (arr[i] < flag) { 23 | left.push(arr[i]); 24 | } else { 25 | right.push(arr[i]); 26 | } 27 | } 28 | return quickSort(left).concat(flag, quickSort(right)); 29 | } 30 | return arr; 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /1.排序/插入排序.md: -------------------------------------------------------------------------------- 1 | # 插入排序 2 | 3 | ## 思路 4 | 5 | > 核心思想:通过构建有序序列,对于未排序数据,在已排序序列中从后往前扫描,找到相应位置插入
6 | > 7 | > (1) 从第一个元素开始,该元素可以认为已经被排序
8 | > (2) 取出下一个元素,在已排序的元素队列中从后往前扫描
9 | > (3) 如果已排序的元素大于新元素,将已排序元素移到下一个位置
10 | > (4) 重复步骤 3,直到找找到已排序的元素小于或等于新元素的位置
11 | > (5) 将新元素插入到该位置后
12 | > (6) 重复步骤 2-5,直至正序排序
13 | 14 | ## 代码 15 | 16 | > ### 平均时间复杂度:O(n^2) 空间复杂度:O(1) 17 | 18 | ```js 19 | function insertionSort(arr) { 20 | for (let i = 1; i < arr.length; i++) { 21 | let temp = arr[i]; 22 | let j = i - 1; 23 | while (arr[j] > temp && j >= 0) { 24 | arr[j + 1] = arr[j--]; 25 | } 26 | arr[j + 1] = temp; 27 | } 28 | return arr; 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /1.排序/选择排序.md: -------------------------------------------------------------------------------- 1 | # 选择排序 2 | 3 | ## 思路 4 | 5 | > 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后再从剩余未排序元素中继续寻找最小(大)
6 | > 元素,放到已排序队列的末尾。以次类推,直到所有元素均排序完毕。
7 | 8 | ## 代码 9 | 10 | > ### 平均时间复杂度:O(n^2) 空间复杂度:O(1) 11 | 12 | ```js 13 | function selectionSort(arr) { 14 | for (let i = 0, len = arr.length - 1, min; i < len; i++) { 15 | min = arr[i]; 16 | for (let j = i + 1; j < len; j++) { 17 | if (arr[j] < min) { 18 | let c = min; 19 | min = arr[j]; 20 | arr[j] = c; 21 | } 22 | } 23 | arr[i] = min; 24 | } 25 | return arr; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /2.二分查找/二分查找.md: -------------------------------------------------------------------------------- 1 | # 二分查找(折半查找) 2 | 3 | ## 来源 4 | 5 | > leetcode: [传送门](https://leetcode-cn.com/problems/binary-search/) 6 | 7 | ## 思路 8 | 9 | > (1)从有序数组的中间元素开始搜素,如果该元素正好是目标元素,则搜索过程结束,否则进行下一步
10 | > (2)如果目标元素大于或小于中间元素,则在数组大于或小于中间元素的那一半区域进行查找,然后重复第一步
11 | > (3)如果某一步数组为空,则表示找不到指定元素
12 | 13 | ## 代码 14 | 15 | > ### 时间复杂度:O(log n) 空间复杂度:O(1) 16 | 17 | ```js 18 | function binarySearch(arr, target) { 19 | let low = 0, 20 | high = arr.length - 1; 21 | while (low <= high) { 22 | let middle = parseInt((low + high) / 2); 23 | if (target === arr[middle]) { 24 | return middle; 25 | } else if (target > arr[middle]) { 26 | low = middle + 1; 27 | } else { 28 | high = middle - 1; 29 | } 30 | } 31 | return -1; 32 | } 33 | ``` 34 | -------------------------------------------------------------------------------- /3.栈、队列/双栈模拟队列.md: -------------------------------------------------------------------------------- 1 | # 双栈模拟队列 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/) 6 | 7 | ## 思路 8 | 9 | > 栈 1 用作入队,栈 2 用作出队,当栈 2 为空时,将栈 1 全部压栈到栈 2,栈 2 在出栈(即出队列) 10 | 11 | ## 代码 12 | 13 | ```js 14 | let stack1 = [], 15 | stack2 = []; 16 | function push(node) { 17 | stack1.push(node); 18 | } 19 | function pop() { 20 | if (stack2.length === 0) { 21 | if (stack1.length === 0) { 22 | return null; 23 | } else { 24 | let len = stack1.length; 25 | for (let i = 0; i < len; i++) { 26 | stack2.push(stack1.pop()); 27 | } 28 | return stack2.pop(); 29 | } 30 | } else { 31 | return stack2.pop(); 32 | } 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /3.栈、队列/将无序栈转为有序栈.md: -------------------------------------------------------------------------------- 1 | # 将无序栈转为有序栈 2 | 3 | ## 来源 4 | 5 | > 华为手撕代码 (要求空间复杂度 O(1)) 6 | 7 | ## 思路 8 | 9 | > 只有递归空间复杂度才是 O(1) 10 | 11 | ## 代码 12 | 13 | ```js 14 | function sort(stack) { 15 | const _sort = () => { 16 | if (stack.length <= 1) return stack; 17 | let num = stack.pop(); 18 | if (num < stack[stack.length - 1]) { 19 | let temp = stack.pop(); 20 | stack.push(num); 21 | num = temp; 22 | _sort(stack); 23 | } else { 24 | _sort(stack); 25 | } 26 | stack.push(num); 27 | }; 28 | let index = stack.length; 29 | while (index > 0) { 30 | _sort(); //每一次排序完栈底元素最小 31 | index--; 32 | } 33 | return stack; 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /3.栈、队列/有效的括号.md: -------------------------------------------------------------------------------- 1 | # 有效的括号 2 | 3 | ## 来源 4 | 5 | > leetcode: [传送门](https://leetcode-cn.com/problems/valid-parentheses/) 6 | 7 | ## 思路 8 | 9 | > 只处理左半边括号 10 | 11 | ## 代码 12 | 13 | ```js 14 | const isValid = (s) => { 15 | let stack = []; 16 | for (let i = 0; i < s.length; i++) { 17 | if (s[i] === "(") { 18 | stack.push(")"); 19 | } else if (s[i] === "[") { 20 | stack.push("]"); 21 | } else if (s[i] === "{") { 22 | stack.push("}"); 23 | } else if (stack.pop() !== s[i]) { 24 | return false; 25 | } 26 | } 27 | return !stack.length; 28 | }; 29 | ``` 30 | -------------------------------------------------------------------------------- /4.链表/两个链表的第一个公共节点.md: -------------------------------------------------------------------------------- 1 | # 两个链表的第一个公共节点 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/) 6 | 7 | ## 思路 8 | 9 | > 用两个指针扫描两个链表,最终两个指针到达 null 或者到达公共节点 10 | 11 | ## 代码 12 | 13 | ```js 14 | function FindFirstCommonNode(pHead1, pHead2) { 15 | let p1 = pHead1; 16 | let p2 = pHead2; 17 | while (p1 !== p2) { 18 | p1 = p1 == null ? pHead2 : p1.next; 19 | p2 = p2 == null ? pHead1 : p2.next; 20 | } 21 | return p1; 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /4.链表/两数相加.md: -------------------------------------------------------------------------------- 1 | # 两数相加 2 | 3 | ## 来源 4 | 5 | > leetcode: [传送门](https://leetcode-cn.com/problems/add-two-numbers/) 6 | 7 | ## 思路 8 | 9 | > 链表转数组 => 数组转数字 => 求和 => 数组转链表 10 | > 11 | > 时间复杂度:O(Max(m,n)) 空间复杂度:O(Max(m,n)) 12 | 13 | ## 代码 14 | 15 | ```js 16 | // 链表节点 17 | function ListNode(val) { 18 | this.val = val; 19 | this.next = null; 20 | } 21 | function addTowNumbers(l1, l2) { 22 | // 链表转数组 23 | let arr1 = [], 24 | arr2 = []; 25 | while (l1) { 26 | arr1.push(l1.val); 27 | l1 = l1.next; 28 | } 29 | while (l2) { 30 | arr2.push(l2.val); 31 | l2 = l2.next; 32 | } 33 | // 数组转数字并求和 34 | const num1 = BigInt(arr1.reverse().join("")); 35 | const num2 = BigInt(arr2.reverse().join("")); 36 | const res = String(num1 + num2).split(""); 37 | // 数组转链表 38 | let result = null; 39 | for (let i = 0; i < res.length; i++) { 40 | let current = new ListNode(res[i]); 41 | current.next = result; 42 | result = current; 43 | } 44 | return result; 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /4.链表/判断链表是否有环.md: -------------------------------------------------------------------------------- 1 | # 判断链表是否有环 2 | 3 | ## 来源 4 | 5 | > leetcode: [传送门](https://leetcode-cn.com/problems/linked-list-cycle/) 6 | 7 | ## 解法一 8 | 9 | > ES6 的 Set 10 | > 11 | > 时间复杂度:O(n) 空间复杂度:O(n) 12 | 13 | ## 代码 14 | 15 | ```js 16 | function hasCycle(head) { 17 | let st = new Set(); 18 | while (head) { 19 | if (st.has(head)) { 20 | return true; 21 | } 22 | st.add(head); 23 | head = head.next; 24 | } 25 | return false; 26 | } 27 | ``` 28 | 29 | ## 解法二:双指针 30 | 31 | ## 思路 32 | 33 | > 快慢指针起初都在头节点,慢指针一步走,快指针两步走,判断快慢指针是否相遇,相遇则链表有环,否则无环 34 | > 35 | > 时间复杂度:O(n) 空间复杂度:O(1) 36 | 37 | ## 代码 38 | 39 | ```js 40 | function hasCycle(head) { 41 | if (head == null || head.next == null) { 42 | return false; 43 | } 44 | let fast = head; 45 | let low = head; 46 | while (fast != null && fast.next != null) { 47 | fast = fast.next.next; 48 | low = low.next; 49 | if (fast === low) { 50 | return true; 51 | } 52 | } 53 | return false; 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /4.链表/反转链表.md: -------------------------------------------------------------------------------- 1 | # 反转链表 2 | 3 | ## 来源 4 | 5 | > leetcode:[传送门](https://leetcode-cn.com/problems/reverse-linked-list/) 6 | 7 | ## 思路 8 | 9 | > 迭代 10 | 11 | ## 代码 12 | 13 | ```js 14 | const reverseList = (head) => { 15 | let temp = null; 16 | while (head) { 17 | temp = { 18 | val: head.val, 19 | next: temp, 20 | }; 21 | head = head.next; 22 | } 23 | return temp; 24 | }; 25 | ``` 26 | -------------------------------------------------------------------------------- /4.链表/合并两个有序链表.md: -------------------------------------------------------------------------------- 1 | # 合并两个有序链表 2 | 3 | ## 来源 4 | 5 | > leetcode: [传送门](https://leetcode-cn.com/problems/merge-two-sorted-lists/) 6 | 7 | ## 代码 8 | 9 | ```js 10 | const mergeTwoLists = function (A, B) { 11 | if (A == null) { 12 | return B; 13 | } 14 | if (B == null) { 15 | return A; 16 | } 17 | if (A.val < B.val) { 18 | A.next = mergeTwoLists(A.next, B); 19 | return A; 20 | } else { 21 | B.next = mergeTwoLists(A, B.next); 22 | return B; 23 | } 24 | }; 25 | ``` 26 | -------------------------------------------------------------------------------- /4.链表/手撕单向链表.md: -------------------------------------------------------------------------------- 1 | # 手撕单向链表 2 | 3 | ## 代码 4 | 5 | ```js 6 | // 定义节点 7 | class Node { 8 | constructor(v, next) { 9 | this.val = v; 10 | this.next = next; 11 | } 12 | } 13 | // 定义链表 14 | class LinkList { 15 | constructor() { 16 | //链表长度 17 | this.size = 0; 18 | //虚拟头部 19 | this.dummyNode = new Node(null, null); 20 | } 21 | //查找节点 22 | find(header, currentIndex, index) { 23 | if (index === currentIndex) { 24 | return header; 25 | } 26 | return this.find(header.next, currentIndex + 1, index); 27 | } 28 | //查找某一节点的后继节点 29 | getNode(index) { 30 | this.checkIndex(index); 31 | if (this.isEmpty()) { 32 | return null; 33 | } 34 | return this.find(this.dummyNode, 0, index).next; 35 | } 36 | //插入节点 37 | insertNode(v, index) { 38 | this.checkIndex(index); 39 | let prev = this.find(this.dummyNode, 0, index); 40 | prev.next = new Node(v, prev.next); 41 | this.size + 1; 42 | return prev.next; 43 | } 44 | //插入头节点 45 | insertFirstNode(v) { 46 | return addNode(v, 0); 47 | } 48 | //插入尾节点 49 | insertLastNode(v) { 50 | return addNode(v, this.size); 51 | } 52 | //在链表中非首尾的任意位置插入节点 53 | insertAnyNode(v, index) { 54 | return addNode(v, index); 55 | } 56 | //删除节点 57 | removeNode(index, isLast) { 58 | this.checkIndex(index); 59 | index = isLast ? index - 1 : index; 60 | let prev = this.find(this.dummyNode, 0, index); 61 | let node = prev.next; 62 | prev.next = node.next; 63 | node.next = null; 64 | this.size--; 65 | return node; 66 | } 67 | //删除头节点 68 | removeFirstNode() { 69 | return this.removeNode(0); 70 | } 71 | //删除尾节点 72 | removeLastNode() { 73 | return this.removeNode(this.size, true); 74 | } 75 | //删除非首尾的任意位置节点 76 | removeAnyNode(index) { 77 | return this.removeNode(index, false); 78 | } 79 | checkIndex(index) { 80 | if (index < 0 || index > this.size) { 81 | throw Error("Index Error"); 82 | } 83 | } 84 | //链表长度 85 | getSize() { 86 | return this.size; 87 | } 88 | //链表判空 89 | isEmpty() { 90 | return this.size === 0; 91 | } 92 | } 93 | ``` 94 | -------------------------------------------------------------------------------- /4.链表/链表中倒数第K个节点.md: -------------------------------------------------------------------------------- 1 | # 链表中倒数第 K 个节点 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) 6 | 7 | ## 解法一:队列 8 | 9 | ## 思路 10 | 11 | > 拿一个队列保存 12 | 13 | ```js 14 | function findKthToTail(head, k) { 15 | let result = []; 16 | while (head) { 17 | result.unshift(head); 18 | head = head.next; 19 | } 20 | return result[k - 1]; 21 | } 22 | ``` 23 | 24 | ## 解法二:双指针 25 | 26 | ## 思路 27 | 28 | > 快指针先走 k-1 步,之后快慢指针同步走,当快指针走到最后一个结点的时候,慢指针也走到了倒数第 k 个结点 29 | 30 | ## 代码 31 | 32 | ```js 33 | function findKthToTail(head, k) { 34 | if (!head || !k || k <= 0) { 35 | return null; 36 | } 37 | let low = head; 38 | let fast = head; 39 | for (let i = 0; i < k - 1; i++) { 40 | if (fast.next) { 41 | fast = fast.next; 42 | } else { 43 | return null; 44 | } 45 | } 46 | while (fast.next) { 47 | fast = fast.next; 48 | low = low.next; 49 | } 50 | return low; 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /5.二叉树/中序遍历.md: -------------------------------------------------------------------------------- 1 | # 中序遍历 2 | 3 | ## 来源 4 | 5 | > leetcode: [传送门](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) 6 | 7 | ## 代码 8 | 9 | ```js 10 | function inOrderTraversal(root) { 11 | let res = []; 12 | let stack = []; 13 | while (root != null || stack.length) { 14 | if (root) { 15 | stack.push(root); 16 | root = root.left; 17 | } else { 18 | let node = stack.pop(); 19 | res.push(node.val); 20 | root = root.right; 21 | } 22 | } 23 | return res; 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /5.二叉树/之字形打印二叉树.md: -------------------------------------------------------------------------------- 1 | # 之字形打印二叉树 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) 6 | 7 | ## 思路 8 | 9 | > 奇数行用栈存储,偶数行用队列存储 10 | 11 | ## 代码 12 | 13 | ```js 14 | function Print(pRoot) { 15 | if (pRoot == null) { 16 | return []; 17 | } 18 | 19 | let queue = [], 20 | temp = [], 21 | res = [], 22 | level = 0, 23 | isEven = true, 24 | isBePrinted = 1; 25 | queue.push(pRoot); 26 | 27 | while (queue.length) { 28 | let node = queue.shift(); 29 | 30 | //判断奇、偶行,奇数行 31 | if (isEven) { 32 | temp.push(node.val); 33 | } else { 34 | temp.unshift(node.val); 35 | } 36 | //行数加一 37 | if (node.left) { 38 | queue.push(node.left); 39 | level++; 40 | } 41 | if (node.right) { 42 | queue.push(node.right); 43 | level++; 44 | } 45 | //当前被操作行数已遍历完 46 | isBePrinted--; 47 | //处理行数、奇偶、temp置空 48 | if (isBePrinted === 0) { 49 | res.push(temp); 50 | temp = []; 51 | isBePrinted = level; 52 | level = 0; 53 | isEven = !isEven; 54 | } 55 | } 56 | return res; 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /5.二叉树/二叉搜索树的后序遍历序列.md: -------------------------------------------------------------------------------- 1 | # 二叉搜索树的后序遍历序列 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) 6 | 7 | ## 思路 8 | 9 | > 1. 找出根节点, 10 | > 2. 在不包括根节点的数组中遍历,找到第一个比根节点大的位置,该位置左边为左子树,右边为右子树 11 | > 3. 遍历右子树,如果发现有小于根节点的值,直接返回 false 12 | > 4. 递归以上步骤,分别判断左右子树是否为二叉搜索树 13 | 14 | ## 代码 15 | 16 | ```js 17 | function VerifySquenceOfBST(arr) { 18 | if (arr == null || arr.length < 1) { 19 | return false; 20 | } 21 | 22 | return judge(arr, 0, arr.length - 1); 23 | } 24 | 25 | function judge(arr, left, right) { 26 | // 递归终止条件:只有一个节点 27 | if (left >= right) { 28 | return true; 29 | } 30 | 31 | // 找出根节点 32 | let node = arr[right]; 33 | // 用来记录序列中第一个比根节点大节点的下标 34 | let index = right; 35 | 36 | for (let i = left; i < right - 1; i++) { 37 | // 找到根节点的右孩子 38 | if (arr[i] > node) { 39 | index = i; 40 | i++; 41 | 42 | // 如果右子树中有比根节点还小的树的话,显然是不成立的 43 | while (i <= right - 1) { 44 | if (arr[i] < node) { 45 | return false; 46 | } 47 | i++; 48 | } 49 | } 50 | } 51 | 52 | // 递归检查左右子树 53 | return judge(arr, left, index - 1) && judge(arr, index, right - 1); 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /5.二叉树/二叉搜索树的第K个节点.md: -------------------------------------------------------------------------------- 1 | # 二叉搜索树第 K 个节点 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) 6 | 7 | ## 思路 8 | 9 | > 利用二叉搜索树的中序遍历是一个递增序列来解决,用数组存储遍历结果,返回第 k 个节点 10 | 11 | ## 代码 12 | 13 | ```js 14 | function KthNode(pRoot, k) { 15 | if (pRoot == null || k < 1) { 16 | return null; 17 | } 18 | 19 | const arr = []; 20 | 21 | //中序遍历 22 | function getTree(root) { 23 | if (root.left) { 24 | getTree(root.left); 25 | } 26 | arr.push(root); 27 | if (root.right) { 28 | getTree(root.right); 29 | } 30 | } 31 | 32 | getTree(pRoot); 33 | 34 | return arr[k - 1]; 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /5.二叉树/二叉树.md: -------------------------------------------------------------------------------- 1 | ## 十一、二叉搜索树的后序遍历序列 2 | 3 | ## 思路 4 | 5 | > 1. 找出根节点, 6 | > 2. 在不包括根节点的数组中遍历,找到第一个比根节点大的位置,该位置左边为左子树,右边为右子树 7 | > 3. 遍历右子树,如果发现有小于根节点的值,直接返回 false 8 | > 4. 递归以上步骤,分别判断左右子树是否为二叉搜索树 9 | 10 | ## 代码 11 | 12 | ```js 13 | function VerifySquenceOfBST(arr) { 14 | if (arr == null || arr.length < 1) { 15 | return false; 16 | } 17 | return judge(arr, 0, arr.length - 1); 18 | } 19 | function judge(arr, left, right) { 20 | // 递归终止条件:只有一个节点 21 | if (left >= right) { 22 | return true; 23 | } 24 | // 找出根节点 25 | let node = arr[right]; 26 | // 用来记录序列中第一个比根节点大节点的下标 27 | let index = right; 28 | for (let i = left; i < right - 1; i++) { 29 | // 找到根节点的右孩子 30 | if (arr[i] > node) { 31 | index = i; 32 | i++; 33 | // 如果右子树中有比根节点还小的树的话,显然是不成立的 34 | while (i <= right - 1) { 35 | if (arr[i] < node) { 36 | return false; 37 | } 38 | i++; 39 | } 40 | } 41 | } 42 | // 递归检查左右子树 43 | return judge(arr, left, index - 1) && judge(arr, index, right - 1); 44 | } 45 | ``` 46 | 47 | ## 十二、二叉搜索树第 K 个节点 48 | 49 | ## 思路:利用二叉搜索树的中序遍历是一个递增序列来解决,用数组存储遍历结果,返回第 k 个节点 50 | 51 | ## 代码 52 | 53 | ```js 54 | function KthNode(pRoot, k) { 55 | if (pRoot == null || k < 1) { 56 | return null; 57 | } 58 | var arr = []; 59 | //中序遍历 60 | function getTree(root) { 61 | if (root.left) { 62 | getTree(root.left); 63 | } 64 | arr.push(root); 65 | if (root.right) { 66 | getTree(root.right); 67 | } 68 | } 69 | getTree(pRoot); 70 | return arr[k - 1]; 71 | } 72 | ``` 73 | 74 | ## 十三、二叉树的最大宽度 75 | 76 | ## 思路 77 | 78 | > 对节点进行编号,根节点编号是 0,左子树编号 = 根节点编号*2+1,右子树编号 = 根节点编号*2+2, 79 | > 最大宽度 = 左子树编号 - 右子树编号 + 1 80 | 81 | ## 代码 82 | 83 | ```js 84 | function maxWidthOfBinaryTree(root) { 85 | if (!root) { 86 | return 0; 87 | } 88 | let res = [], 89 | maxWidth = 1; 90 | recusion(root, 0, 0); 91 | return maxWidth; 92 | function recusion(root, level, num) { 93 | if (res[level]) { 94 | res[level].push(num); 95 | } else { 96 | res[level] = [num]; 97 | } 98 | let tempArr = res[level]; 99 | let tempWidth = tempArr[tempArr.length - 1] - tempArr[0] + 1; 100 | if (tempWidth > maxWidth) { 101 | maxWidth = tempWidth; 102 | } 103 | if (root.left) { 104 | recusion(root.left, level + 1, num * 2 + 1); 105 | } 106 | if (root.right) { 107 | recusion(root.right, level + 1, num * 2 + 2); 108 | } 109 | } 110 | } 111 | ``` 112 | 113 | ## 十四、二叉树的镜像 114 | 115 | ## 代码 116 | 117 | ```js 118 | function Mirror(root) { 119 | //判断根节点 120 | if (root == null) { 121 | return; 122 | } 123 | //终止条件为当前节点为叶子节点 124 | if (root.left == null && root.right == null) { 125 | return root; 126 | } 127 | //交换左右子树 128 | let temp = root.left; 129 | root.left = root.right; 130 | root.right = temp; 131 | //递归左右子树镜像 132 | Mirror(root.left); 133 | Mirror(root.right); 134 | } 135 | ``` 136 | 137 | ## 十五、二叉树的下一个节点 138 | 139 | ## 思路 140 | 141 | > 1. 二叉树为空,返回 null 142 | > 2. 右子树存在,返回右子树的最左节点 143 | > 3. 节点不是根节点。是父节点的左孩子,则返回父节点;否则向上去遍历父节点的父节点,重复之前的判断,返回结果。 144 | 145 | ## 代码 146 | 147 | ```js 148 | function GetNext(pNode) { 149 | if (pNode == null) { 150 | return null; 151 | } 152 | if (pNode.right != null) { 153 | pNode = pNode.right; 154 | while (pNode.left != null) { 155 | pNode = pNode.left; 156 | } 157 | return pNode; 158 | } 159 | while (pNode.next != null) { 160 | let pRoot = pNode.next; 161 | if (pRoot.left === pNode) { 162 | return pRoot; 163 | } 164 | pNode = pNode.next; 165 | } 166 | return null; 167 | } 168 | ``` 169 | -------------------------------------------------------------------------------- /5.二叉树/二叉树的下一个节点.md: -------------------------------------------------------------------------------- 1 | # 二叉树的下一个节点 2 | 3 | ## 来源 4 | 5 | > 牛客网:[传送门](https://www.nowcoder.com/questionTerminal/9023a0c988684a53960365b889ceaf5e) 6 | 7 | ## 思路 8 | 9 | > 1. 二叉树为空,返回 null 10 | > 2. 右子树存在,返回右子树的最左节点 11 | > 3. 节点不是根节点。是父节点的左孩子,则返回父节点;否则向上去遍历父节点的父节点,重复之前的判断,返回结果。 12 | 13 | ## 代码 14 | 15 | ```js 16 | function GetNext(pNode) { 17 | if (pNode == null) { 18 | return null; 19 | } 20 | 21 | if (pNode.right != null) { 22 | pNode = pNode.right; 23 | while (pNode.left != null) { 24 | pNode = pNode.left; 25 | } 26 | return pNode; 27 | } 28 | 29 | while (pNode.next != null) { 30 | let pRoot = pNode.next; 31 | if (pRoot.left === pNode) { 32 | return pRoot; 33 | } 34 | pNode = pNode.next; 35 | } 36 | 37 | return null; 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /5.二叉树/二叉树的最大宽度.md: -------------------------------------------------------------------------------- 1 | # 二叉树的最大宽度 2 | 3 | ## 来源 4 | 5 | > 暂时迷失 6 | 7 | ## 思路 8 | 9 | > 对节点进行编号,根节点编号是 0,左子树编号 = 根节点编号*2+1,右子树编号 = 根节点编号*2+2, 10 | > 最大宽度 = 左子树编号 - 右子树编号 + 1 11 | 12 | ## 代码 13 | 14 | ```js 15 | function maxWidthOfBinaryTree(root) { 16 | if (!root) { 17 | return 0; 18 | } 19 | 20 | let res = [], 21 | maxWidth = 1; 22 | recusion(root, 0, 0); 23 | 24 | return maxWidth; 25 | 26 | function recusion(root, level, num) { 27 | if (res[level]) { 28 | res[level].push(num); 29 | } else { 30 | res[level] = [num]; 31 | } 32 | 33 | let tempArr = res[level]; 34 | let tempWidth = tempArr[tempArr.length - 1] - tempArr[0] + 1; 35 | 36 | if (tempWidth > maxWidth) { 37 | maxWidth = tempWidth; 38 | } 39 | if (root.left) { 40 | recusion(root.left, level + 1, num * 2 + 1); 41 | } 42 | if (root.right) { 43 | recusion(root.right, level + 1, num * 2 + 2); 44 | } 45 | } 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /5.二叉树/二叉树的深度.md: -------------------------------------------------------------------------------- 1 | # 二叉树的深度 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/) 6 | 7 | ## 思路 8 | 9 | > 一旦没有找到节点就返回 0,每弹出一次递归函数就会加 1,树有三层就会得到 3 10 | 11 | ## 代码 12 | 13 | ```js 14 | function maxDepth(root) { 15 | if (!root) { 16 | return 0; 17 | } 18 | return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; 19 | } 20 | ``` 21 | -------------------------------------------------------------------------------- /5.二叉树/二叉树的镜像.md: -------------------------------------------------------------------------------- 1 | # 二叉树的镜像 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/) 6 | 7 | ## 代码 8 | 9 | ```js 10 | function Mirror(root) { 11 | //判断根节点 12 | if (root == null) { 13 | return; 14 | } 15 | 16 | //终止条件为当前节点为叶子节点 17 | if (root.left == null && root.right == null) { 18 | return root; 19 | } 20 | 21 | //交换左右子树 22 | let temp = root.left; 23 | root.left = root.right; 24 | root.right = temp; 25 | 26 | //递归左右子树镜像 27 | Mirror(root.left); 28 | Mirror(root.right); 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /5.二叉树/前序遍历.md: -------------------------------------------------------------------------------- 1 | # 前序遍历 2 | 3 | ## 来源 4 | 5 | > leetocde: [传送门](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/) 6 | 7 | ## 思路 8 | 9 | > 需要一个栈来辅助,把遍历结果 push 进数组中作为返回结果。 10 | > 11 | > 1. 将根节点放进栈中 12 | > 2. 如果栈不为空,出栈一个节点 push 进数组中;如果该节点右子树不为空,把右子树放进栈中; 13 | > 如果该节点左子树不为空,把左子树放进栈中 14 | > 3. 重复步骤二,直到栈为空,返回数组 15 | 16 | ## 代码 17 | 18 | ```js 19 | function preOrderTraversal(root) { 20 | let res = []; 21 | let stack = []; 22 | if (root == null) { 23 | return res; 24 | } 25 | stack.push(root); 26 | while (stack.length) { 27 | let node = stack.pop(); 28 | res.push(node.val); 29 | if (node.right) { 30 | stack.push(node.right); 31 | } 32 | if (node.left) { 33 | stack.push(node.right); 34 | } 35 | } 36 | return res; 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /5.二叉树/后序遍历.md: -------------------------------------------------------------------------------- 1 | # 后序遍历 2 | 3 | ## 来源 4 | 5 | > leetcode: [传送门](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/) 6 | 7 | ## 代码 8 | 9 | ```js 10 | function postOrderTraversal(root) { 11 | let res = []; 12 | let stack = []; 13 | if (root == null) { 14 | return res; 15 | } 16 | stack.push(root); 17 | while (stack.length) { 18 | let node = stack.pop(); 19 | res.unshift(node.val); 20 | if (root.left) { 21 | stack.push(node.left); 22 | } 23 | if (root.right) { 24 | stack.push(node.right); 25 | } 26 | } 27 | return res; 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /5.二叉树/对称二叉树.md: -------------------------------------------------------------------------------- 1 | # 对称二叉树 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/) 6 | 7 | ## 代码 8 | 9 | ```js 10 | function isSymmetrical(pRoot) { 11 | return pRoot == null || judge(pRoot.left, pRoot.right); 12 | } 13 | function judge(node1, node2) { 14 | if (node1 == null && node2 == null) { 15 | return true; 16 | } else if (node1 == null || node2 == null) { 17 | return false; 18 | } 19 | if (node1.val !== node2.val) { 20 | return false; 21 | } else { 22 | return judge(node1.left, node2.right) && judge(node1.right, node2.left); 23 | } 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /5.二叉树/层次遍历.md: -------------------------------------------------------------------------------- 1 | # 层次遍历 2 | 3 | ## 来源 4 | 5 | > leetcode: [传送门](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/) 6 | 7 | ## 代码 8 | 9 | ```js 10 | function printFromTopToBottom(root) { 11 | let res = []; 12 | let queue = []; 13 | if (root == null) { 14 | return res; 15 | } 16 | queue.push(root); 17 | while (queue.length) { 18 | let node = queue.shift(); 19 | res.push(node.val); 20 | if (node.left != null) { 21 | queue.push(node.left); 22 | } 23 | if (node.right != null) { 24 | queue.push(node.right); 25 | } 26 | } 27 | return res; 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /5.二叉树/平衡二叉树.md: -------------------------------------------------------------------------------- 1 | # 平衡二叉树 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof/solution/) 6 | 7 | ## 代码 8 | 9 | ```js 10 | function IsBalanced_Solution(pRoot) { 11 | return depth(pRoot) !== -1; 12 | } 13 | 14 | function depth(pRoot) { 15 | if (pRoot == null) { 16 | return 0; 17 | } 18 | 19 | let left = depth(pRoot.left); 20 | if (left === -1) { 21 | return -1; 22 | } 23 | let right = depth(pRoot.right); 24 | if (right === -1) { 25 | return -1; 26 | } 27 | 28 | return Math.abs(left - right) > 1 ? -1 : Math.max(left, right) + 1; 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /5.二叉树/按层打印二叉树.md: -------------------------------------------------------------------------------- 1 | # 按层打印二叉树 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/) 6 | 7 | ## 思路 8 | 9 | > 用队列存储二叉树每层的值,用数组存储每个队列的值 10 | 11 | ## 代码 12 | 13 | ```js 14 | function Print(pRoot) { 15 | let res = []; 16 | let queue = []; 17 | if (!pRoot) { 18 | return res; 19 | } 20 | queue.push(pRoot); 21 | 22 | while (queue.length) { 23 | let len = queue.length; 24 | let temp = []; 25 | 26 | for (let i = 0; i < len; i++) { 27 | let node = queue.shift(); 28 | temp.push(node.val); 29 | if (node.left) { 30 | queue.push(node.left); 31 | } 32 | if (node.right) { 33 | queue.push(node.right); 34 | } 35 | } 36 | 37 | res.push(temp); 38 | } 39 | 40 | return res; 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /5.二叉树/重建二叉树.md: -------------------------------------------------------------------------------- 1 | # 重建二叉树 (已知前序、中序遍历) 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/solution/) 6 | 7 | ## 代码 8 | 9 | ```js 10 | function reConstructorBinaryTree(pre, vin){ 11 | let result = null; 12 | 13 | if(pre.length > 1){ 14 | let root = pre[0]; 15 | let index = vin.indexOf(root); 16 | let vinLeft = vin.slice(0, index); 17 | let vinRight = vin.slice(index + 1); 18 | pre.shift(); 19 | let preLeft = pre.slice(0, vinLeft.length); 20 | let preRight = pre.slice(vinLeft.length); 21 | 22 | result = { 23 | val:root, 24 | left:reConstructorBinaryTree(preLeft, vinLeft), 25 | right:reConstructorBinaryTree(preRight, vinRight); 26 | } 27 | }else if(pre.length === 1){ 28 | result = { 29 | val:pre[0], 30 | left:null, 31 | right:null 32 | } 33 | } 34 | 35 | return result 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /6.双指针/和为S的两个数(微改版).md: -------------------------------------------------------------------------------- 1 | # 和为 S 的两个数(微改版) 2 | 3 | > 题目描述: 在递增数组中找出两个之和的数,返回乘积最小的两项 4 | 5 | ## 来源 6 | 7 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof/) 8 | 9 | ## 双指针类题目通用思路 10 | 11 | > 1. 确定双指针位置(都在起始位置、一个在起始一个在末尾) 12 | > 2. 确定终止条件(双指针重合、high 走到末尾) 13 | > 3. while 语句中的条件判断(双指针走向 =>同向走、向中间靠拢) 14 | 15 | ## 思路(类似于二分查找) 16 | 17 | > 1. 确定双指针位置(一个在起始、一个在末尾) 18 | > 2. 确定终止条件(双指针重合) 19 | > 3. while 语句中的条件判断(双指针向中间聚拢) 20 | 21 | ## 代码 22 | 23 | ```js 24 | function FindNumbersWithSum(arr, sum) { 25 | let low = 0, 26 | high = arr.length - 1; 27 | let res = []; 28 | 29 | while (low < high) { 30 | let current = arr[low] + arr[high]; 31 | 32 | if (sum === current) { 33 | res.push(arr[low]); 34 | res.push(arr[high]); 35 | break; 36 | } else if (current < sum) { 37 | low++; 38 | } else if (current > sum) { 39 | high--; 40 | } 41 | } 42 | 43 | return res; 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /6.双指针/和为S的连续正整数.md: -------------------------------------------------------------------------------- 1 | # 和为 S 的连续正整数 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) 6 | 7 | ## 双指针类题目通用思路 8 | 9 | > 1. 确定双指针位置(都在起始位置、一个在起始一个在末尾) 10 | > 2. 确定终止条件(双指针重合、high 走到末尾) 11 | > 3. while 语句中的条件判断(双指针走向 =>同向走、向中间靠拢) 12 | 13 | ## 思路 14 | 15 | > 双指针与滑动窗口 => 窗口的左右两边就是两个指针,根据窗口内值之和来确定窗口的位置和宽 16 | > 17 | > 终止条件:双指针重合 18 | 19 | ## 代码 20 | 21 | ```js 22 | function FindContinuousSequence(sum) { 23 | let low = 1, 24 | high = 2; 25 | let temp = [], 26 | res = []; 27 | 28 | while (low < high) { 29 | let s = ((low + high) * (high - low + 1)) / 2; 30 | 31 | if (s === sum) { 32 | let set = new Set(); 33 | 34 | for (let i = low; i <= high; i++) { 35 | set.add(i); 36 | } 37 | 38 | temp = Array.from(set); 39 | res.push(temp); 40 | low++; 41 | } else if (s < sum) { 42 | high++; 43 | } else if (s > sum) { 44 | low++; 45 | } 46 | } 47 | 48 | return res; 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /6.双指针/字符串压缩.md: -------------------------------------------------------------------------------- 1 | # 字符串压缩 2 | 3 | ## 来源 4 | 5 | > leetcode: [传送门](https://leetcode-cn.com/problems/compress-string-lcci/) 6 | 7 | ## 难度类型:Easy 8 | 9 | ## 代码 10 | 11 | ```js 12 | var compressString = function (S) { 13 | let p = 0; 14 | let count = 1; 15 | let res = []; 16 | while (p <= S.length - 1) { 17 | if (S[p] === S[p + 1]) { 18 | count++; 19 | } else { 20 | res.push(S[p]); 21 | res.push(count); 22 | count = 1; 23 | } 24 | p++; 25 | } 26 | if (res.length >= S.length) return S; 27 | return res.join(""); 28 | }; 29 | ``` 30 | -------------------------------------------------------------------------------- /6.双指针/接雨水.md: -------------------------------------------------------------------------------- 1 | # 接雨水 2 | 3 | ## 来源 4 | 5 | > leetcode: [传送门](https://leetcode-cn.com/problems/trapping-rain-water/) 6 | 7 | ## 难度类型:困难 8 | 9 | ## 双指针类题目通用思路 10 | 11 | > 1. 确定双指针位置(都在起始位置、一个在起始一个在末尾) 12 | > 2. 确定终止条件(双指针重合、high 走到末尾) 13 | > 3. while 语句中的条件判断(双指针走向 =>同向走、向中间靠拢) 14 | 15 | ## 思路 16 | 17 | > 1. 确定双指针位置(一个在起始、一个在末尾) 18 | > 2. 确定终止条件(双指针重合) 19 | > 3. while 语句中的条件判断(双指针向中间聚拢) 20 | > 21 | > 时间复杂度:O(n) 22 | 23 | ## 代码 24 | 25 | ```js 26 | const trap = function(arr) { 27 | let left = 0; 28 | let right = arr.length-1; 29 | let res = 0; 30 | let left_max = 0; 31 | let right_max = 0; 32 | 33 | while(left < right){ 34 | 35 | if(arr[left] < arr[right]){ 36 | if(arr[left] >= left_max){ 37 | left_max = arr[left] 38 | } 39 | 40 | res+ = (left_max-arr[left]); 41 | left++; 42 | }else{ 43 | if(arr[right] >= right_max){ 44 | right_max = arr[right] 45 | } 46 | 47 | res+ = (right_max-arr[right]); 48 | right--; 49 | } 50 | 51 | } 52 | 53 | return res 54 | }; 55 | ``` 56 | -------------------------------------------------------------------------------- /6.双指针/滑动窗口的最大值.md: -------------------------------------------------------------------------------- 1 | # 滑动窗口最大值 2 | 3 | ## 来源 4 | 5 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/) 6 | 7 | ## 双指针类题目通用思路 8 | 9 | > 1. 确定双指针位置(都在起始位置、一个在起始一个在末尾) 10 | > 2. 确定终止条件(双指针重合、high 走到末尾) 11 | > 3. while 语句中的条件判断(双指针走向 =>同向走、向中间靠拢) 12 | 13 | ## 思路 14 | 15 | > 双指针间距 size,之后同步走,取出动态数组 temp 的最大值,push 进 res 16 | > 17 | > 终止条件:high 指针走到末尾 18 | 19 | ## 代码 20 | 21 | ```js 22 | function maxInWindows(num, size) { 23 | let res = []; 24 | if (num == null || size < 1) { 25 | return []; 26 | } 27 | 28 | let low = 0; 29 | let high = low + size - 1; 30 | 31 | while (high < num.length) { 32 | let temp = num.slice(low, high + 1); 33 | let maxNum = temp.reduce((prev, next) => { 34 | return Math.max(prev, next); 35 | }); 36 | 37 | res.push(maxNum); 38 | temp = []; 39 | low++; 40 | high++; 41 | } 42 | 43 | return res; 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /6.双指针/盛最多水的容器.md: -------------------------------------------------------------------------------- 1 | # 盛最多水的容器 2 | 3 | ## 来源 4 | 5 | > leetcode: [传送门](https://leetcode-cn.com/problems/container-with-most-water/) 6 | 7 | ## 难度类型:中等 8 | 9 | ## 解法一:双指针 10 | 11 | ## 双指针类题目通用思路 12 | 13 | > 1. 确定双指针位置(都在起始位置、一个在起始一个在末尾) 14 | > 2. 确定终止条件(双指针重合、high 走到末尾) 15 | > 3. while 语句中的条件判断(双指针走向 =>同向走、向中间靠拢) 16 | 17 | ## 思路 18 | 19 | > 1. 确定双指针位置(一个在起始、一个在末尾) 20 | > 2. 确定终止条件(双指针重合) 21 | > 3. while 语句中的条件判断(双指针向中间聚拢) 22 | > 23 | > 时间复杂度:O(n) 24 | 25 | ## 代码 26 | 27 | ```js 28 | const maxArea = function (arr) { 29 | let low = 0; 30 | let high = arr.length - 1; 31 | let res = 0; 32 | 33 | while (low < high) { 34 | let s = (high - low) * Math.min(arr[low], arr[high]); 35 | res = Math.max(res, s); 36 | 37 | if (arr[low] < arr[high]) { 38 | low++; 39 | } else { 40 | high--; 41 | } 42 | } 43 | 44 | return res; 45 | }; 46 | ``` 47 | 48 | ## 解法二:暴力解法 49 | 50 | > 时间复杂度:O(n^2) 51 | 52 | ## 代码 53 | 54 | ```js 55 | const maxArea = function (arr) { 56 | let s; 57 | let temp = []; 58 | 59 | for (let low = 0; low < arr.length; low++) { 60 | for (let high = low + 1; high < arr.length; high++) { 61 | s = (high - low) * Math.min(arr[low], arr[high]); 62 | temp.push(s); 63 | } 64 | } 65 | 66 | let res = 0; 67 | 68 | for (let i = 0; i < temp.length; i++) { 69 | res = Math.max(res, temp[i]); 70 | } 71 | 72 | return res; 73 | }; 74 | ``` 75 | -------------------------------------------------------------------------------- /6.双指针/验证回文串.md: -------------------------------------------------------------------------------- 1 | # 验证回文串 2 | 3 | ## 来源 4 | 5 | > leetcode: [传送门](https://leetcode-cn.com/problems/valid-palindrome/) 6 | 7 | ## 难度类型:Easy 8 | 9 | ## 代码 10 | 11 | ```js 12 | var isPalindrome = function (s) { 13 | s = s.replace(/[^0-9a-zA-Z]/g, "").toLowerCase(); 14 | let low = 0; 15 | let high = s.length - 1; 16 | while (low <= high) { 17 | if (s[low] !== s[high]) { 18 | return false; 19 | } 20 | low++; 21 | high--; 22 | } 23 | return true; 24 | }; 25 | ``` 26 | -------------------------------------------------------------------------------- /7.动态规划/不同路径.md: -------------------------------------------------------------------------------- 1 | # 不同路径 2 | 3 | ## 动态规划 4 | 5 | #### 通用解题思路 6 | 7 | > 1. 定义数组元素含义 8 | > 2. 找出关系数组元素间的关系式,并明确所求结果 9 | > 3. 找出初始值(二维 dp 找边,一维 dp 找点) 10 | 11 | #### 通用写代码思路 12 | 13 | > 1. 边界条件 14 | > 2. 定义一维/二维 dp,填充值为 null 15 | > 3. 初始值 16 | > 4. 状态转移方程 17 | 18 | ## 来源 19 | 20 | > leetcode: [传送门](https://leetcode-cn.com/problems/unique-paths/) 21 | 22 | ## 难度类型:中等 23 | 24 | ## 代码 25 | 26 | ```js 27 | const uniquePaths = function (m, n) { 28 | if (m <= 0 || n <= 0) { 29 | return 0; 30 | } 31 | 32 | //新建二维DP 33 | let dp = []; 34 | for (let i = 0; i < m; i++) { 35 | dp[i] = new Array(); 36 | for (let j = 0; j < n; j++) { 37 | dp[i][j] = null; 38 | } 39 | } 40 | 41 | //初始值 42 | for (let i = 0; i < m; i++) { 43 | dp[i][0] = 1; 44 | } 45 | for (let j = 0; j < n; j++) { 46 | dp[0][j] = 1; 47 | } 48 | 49 | //状态转移方程 50 | for (let i = 1; i < m; i++) { 51 | for (let j = 1; j < n; j++) { 52 | dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; 53 | } 54 | } 55 | 56 | return dp[m - 1][n - 1]; 57 | }; 58 | ``` 59 | -------------------------------------------------------------------------------- /7.动态规划/斐波那契数列、跳台阶、矩形覆盖.md: -------------------------------------------------------------------------------- 1 | # 斐波那契数列/跳台阶/矩形覆盖 2 | 3 | ## 动态规划 4 | 5 | #### 通用解题思路 6 | 7 | > 1. 定义数组元素含义 8 | > 2. 找出关系数组元素间的关系式,并明确所求结果 9 | > 3. 找出初始值(二维 dp 找边,一维 dp 找点) 10 | 11 | #### 通用写代码思路 12 | 13 | > 1. 边界条件 14 | > 2. 定义一维/二维 dp,填充值为 null 15 | > 3. 初始值 16 | > 4. 状态转移方程 17 | 18 | ## 来源 19 | 20 | > 剑指 Offer: [传送门](https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/)\_ 21 | 22 | ## 难度类型:简单 23 | 24 | ## 递归解法 25 | 26 | > 时间复杂度:O(2n) 27 | 28 | ## 代码 29 | 30 | ```js 31 | function fib(n) { 32 | if (n < 0) { 33 | return -1; 34 | } else if (n < 2 && n >= 0) { 35 | return n; 36 | } else { 37 | return fib(n - 1) + fib(n - 2); 38 | } 39 | } 40 | ``` 41 | 42 | ## 动态规划解法 43 | 44 | > 时间复杂度:O(n) 45 | 46 | ## 代码 47 | 48 | ```js 49 | function fib(n) { 50 | //新建一维数组 51 | let arr = new Array(n + 1).fill(null); 52 | 53 | //初始值 54 | arr[0] = 0; 55 | arr[1] = 1; 56 | 57 | //数组元素间关系式 58 | for (let i = 2; i <= n; i++) { 59 | arr[i] = arr[i - 1] + arr[i - 2]; 60 | } 61 | 62 | return arr[n]; 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /7.动态规划/最小路径和.md: -------------------------------------------------------------------------------- 1 | # 最小路径和 2 | 3 | ## 动态规划 4 | 5 | #### 通用解题思路 6 | 7 | > 1. 定义数组元素含义 8 | > 2. 找出关系数组元素间的关系式,并明确所求结果 9 | > 3. 找出初始值(二维 dp 找边,一维 dp 找点) 10 | 11 | #### 通用写代码思路 12 | 13 | > 1. 边界条件 14 | > 2. 定义一维/二维 dp,填充值为 null 15 | > 3. 初始值 16 | > 4. 状态转移方程 17 | 18 | ## 来源 19 | 20 | > leetcode: [传送门](https://leetcode-cn.com/problems/minimum-path-sum/) 21 | 22 | ## 题目描述 23 | 24 | > 输入: 25 | > [ 26 | > [1,3,1], 27 | > [1,5,1], 28 | > [4,2,1] 29 | > ] 30 | > 输出: 7 31 | 32 | ## 难度类型:中等 33 | 34 | ## 代码 35 | 36 | ```js 37 | function minPathSum(arr){ 38 | let m = arr.length; 39 | let n =a rr[0].length; 40 | if(m <= 0 || n <= 0){ 41 | return 0 42 | } 43 | 44 | //新建二维数组 45 | let dp = []; 46 | for(let i=0; i 1. 定义数组元素含义 8 | > 2. 找出关系数组元素间的关系式,并明确所求结果 9 | > 3. 找出初始值(二维 dp 找边,一维 dp 找点) 10 | 11 | #### 通用写代码思路 12 | 13 | > 1. 边界条件 14 | > 2. 定义一维/二维 dp,填充值为 null 15 | > 3. 初始值 16 | > 4. 状态转移方程 17 | 18 | ## 来源 19 | 20 | > leetcode: [传送门](https://leetcode-cn.com/problems/longest-palindromic-substring/) 21 | 22 | ## 难度类型:中等 23 | 24 | ## 代码 25 | 26 | ```js 27 | function longestPalindRome(str) { 28 | let len = str.length; 29 | if (len === 1) { 30 | return str; 31 | } 32 | let start = 0; 33 | let longest = 1; 34 | 35 | //新建二维数组 36 | let dp = []; 37 | for (let i = 0; i < len; i++) { 38 | dp[i] = []; 39 | } 40 | 41 | //处理单个字符、两个相等的字符 42 | for (let i = 0; i < len; i++) { 43 | dp[i][i] = 1; 44 | if (i < len - 1) { 45 | if (str[i] === str[i + 1]) { 46 | dp[i][i + 1] = 1; 47 | start = i; 48 | longest = 2; 49 | } 50 | } 51 | } 52 | 53 | //处理状态转移方程 54 | for (let l = 3; l < len; l++) { 55 | for (let i = 0; i + l - 1 < len; i++) { 56 | let j = i + l - 1; 57 | if ((str[i] === str[j] && dp[i + 1][j - 1] = 1)) { 58 | dp[i][j] = 1; 59 | start = i; 60 | longest = l; 61 | } 62 | } 63 | } 64 | 65 | return str.substr(start, longest); 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /7.动态规划/最长有效括号.md: -------------------------------------------------------------------------------- 1 | # 最长有效括号 2 | 3 | ## 动态规划 4 | 5 | #### 通用解题思路 6 | 7 | > 1. 定义数组元素含义 8 | > 2. 找出关系数组元素间的关系式,并明确所求结果 9 | > 3. 找出初始值(二维 dp 找边,一维 dp 找点) 10 | 11 | #### 通用写代码思路 12 | 13 | > 1. 边界条件 14 | > 2. 定义一维/二维 dp,填充值为 null 15 | > 3. 初始值 16 | > 4. 状态转移方程 17 | 18 | ## 来源 19 | 20 | > leetcode: [传送门](https://leetcode-cn.com/problems/longest-valid-parentheses/) 21 | 22 | ## 难度类型:困难 23 | 24 | ## 代码 25 | 26 | ```js 27 | const longestValidParentheses = function (s) { 28 | let len = s.length; 29 | if (len <= 0) { 30 | return 0; 31 | } 32 | 33 | //新建一维dp,填充值为null 34 | let dp = new Array(len + 1).fill(null); 35 | 36 | //初始值--表示只有一位括号时返回0 37 | dp[0] = 0; 38 | let max = 0; 39 | 40 | //状态转移方程 41 | for (let i = 1; i < len; i++) { 42 | if (s.charAt(i) === ")") { 43 | if (s.charAt(i - 1) === "(") { 44 | dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2; 45 | } else if (i - dp[i - 1] > 0 && s.charAt(i - 1 - dp[i - 1]) === "(") { 46 | dp[i] = 47 | dp[i - 1] + (i - dp[i - 1] >= 2 ? dp[i - 2 - dp[i - 1]] : 0) + 2; 48 | } 49 | } 50 | max = Math.max(dp[i], max); 51 | } 52 | 53 | return max; 54 | }; 55 | ``` 56 | -------------------------------------------------------------------------------- /7.动态规划/编辑距离.md: -------------------------------------------------------------------------------- 1 | # 编辑距离 2 | 3 | ## 动态规划 4 | 5 | #### 通用解题思路 6 | 7 | > 1. 定义数组元素含义 8 | > 2. 找出关系数组元素间的关系式,并明确所求结果 9 | > 3. 找出初始值(二维 dp 找边,一维 dp 找点) 10 | 11 | #### 通用写代码思路 12 | 13 | > 1. 边界条件 14 | > 2. 定义一维/二维 dp,填充值为 null 15 | > 3. 初始值 16 | > 4. 状态转移方程 17 | 18 | ## 来源 19 | 20 | > leetcode: [传送门](https://leetcode-cn.com/problems/edit-distance/) 21 | 22 | ## 难度类型:困难 23 | 24 | ## 代码 25 | 26 | ```js 27 | const minDistance = function (word1, word2) { 28 | let m = word1.length; 29 | let n = word2.length; 30 | if (m <= 0 && n <= 0) { 31 | return 0; 32 | } 33 | 34 | //新建二维数组 35 | let dp = []; 36 | for (let i = 0; i <= m; i++) { 37 | dp[i] = new Array(); 38 | for (let j = 0; j <= n; j++) { 39 | dp[i][j] = null; 40 | } 41 | } 42 | 43 | //初始值 44 | dp[0][0] = 0; 45 | for (let i = 1; i <= m; i++) { 46 | dp[i][0] = dp[i - 1][0] + 1; 47 | } 48 | for (let j = 1; j <= n; j++) { 49 | dp[0][j] = dp[0][j - 1] + 1; 50 | } 51 | 52 | //状态转移方程 53 | for (let i = 1; i <= m; i++) { 54 | for (let j = 1; j <= n; j++) { 55 | if (word1.charAt(i - 1) === word2.charAt(j - 1)) { 56 | dp[i][j] = dp[i - 1][j - 1]; 57 | } else { 58 | dp[i][j] = 59 | Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1; 60 | } 61 | } 62 | } 63 | 64 | return dp[m][n]; 65 | }; 66 | ``` 67 | -------------------------------------------------------------------------------- /8.前端/Promise封装异步上传图片.md: -------------------------------------------------------------------------------- 1 | # `Promise`封装异步上传图片 2 | 3 | ```js 4 | function loadImageAsync(url) { 5 | return new Promise(function (resolve, reject) { 6 | let image = new Image(); 7 | image.onload = function () { 8 | resolve(image); 9 | }; 10 | 11 | image.onerror = function () { 12 | reject(new Error("Could not load image at" + url)); 13 | }; 14 | 15 | image.src = url; 16 | }); 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /8.前端/new的过程.md: -------------------------------------------------------------------------------- 1 | # `new`的过程 2 | 3 | ## 原理 4 | 5 | > 1. 创建一个空对象
6 | > 2. 链接到原型
7 | > 3. 绑定 this
8 | > 4. 返回一个新对象
9 | 10 | ```js 11 | function create() { 12 | let obj = Object.create(); 13 | let Con = [].shift.call(arguments); 14 | obj._proto_ = Con.prototype; 15 | let result = Con.apply(obj, arguments); 16 | return typeof result === "object" ? result : obj; 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /8.前端/spawn函数(Async函数实现原理).md: -------------------------------------------------------------------------------- 1 | # `spawn` 函数(`Async` 函数实现原理) 2 | 3 | > ## 说明:`spwan` 函数--自动执行器,需先了解 `Generator` 函数,`Async/await` 是 `Generator` 函数的语法糖 4 | 5 | ```js 6 | function spawn(genF) { 7 | return new Promise((resolve, reject) => { 8 | var gen = genF(); 9 | 10 | function step(nextF) { 11 | try { 12 | var next = nextF(); 13 | } catch (e) { 14 | return reject(e); 15 | } 16 | 17 | if (next.done) { 18 | return resolve(next.value); 19 | } 20 | 21 | Promise.resolve(next.value).then( 22 | (v) => { 23 | step(() => gen.next(v)); 24 | }, 25 | (e) => { 26 | step(() => gen.throw(e)); 27 | } 28 | ); 29 | } 30 | 31 | step(() => { 32 | gen.next(undefined); 33 | }); 34 | }); 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /8.前端/thunk函数(Generator函数实现自动流程管理原理).md: -------------------------------------------------------------------------------- 1 | # `thunk` 函数(`Generator` 函数实现自动流程管理原理) 2 | 3 | ## 说明 4 | 5 | > Thunk 函数是用来解决 JS 中传名调用的一种实现方式
6 | > Thunk 函数是一个单参数函数,只接受回调函数作为参数
7 | > Thunk 函数用于 Generator 函数的自动流程管理
8 | 9 | (ES5) 10 | 11 | ```js 12 | var Thunk = function (fn) { 13 | return function () { 14 | var args = Array.prototype.slice.call(arguments); 15 | 16 | return function (callback) { 17 | args.push(callback); 18 | return fn.apply(this, args); 19 | }; 20 | }; 21 | }; 22 | ``` 23 | 24 | (ES6) 25 | 26 | ```js 27 | const Thunk = function(fn){ 28 | return fucntion(...args){ 29 | return function(callback){ 30 | return fn.call(this, ...args, callback); 31 | } 32 | } 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /8.前端/函数柯里化.md: -------------------------------------------------------------------------------- 1 | # 函数柯里化 2 | 3 | ## 描述 4 | 5 | > 实现 add()方法,使计算结果能够满足如下预期:
6 | > add(1)(2)(3) = 6;
7 | > add(1, 2, 3)(4) = 10;
8 | > add(1)(2)(3)(4)(5) = 15;
9 | 10 | ```js 11 | function add() { 12 | // 第一次执行时,定义一个数组专门用来存储所有的参数 13 | let _args = Array.prototype.slice.call(arguments); 14 | 15 | // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值 16 | let _adder = function () { 17 | _args.push(...arguments); 18 | return _adder; 19 | }; 20 | 21 | // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回 22 | _adder.toString = function () { 23 | return _args.reduce(function (a, b) { 24 | return a + b; 25 | }); 26 | }; 27 | 28 | return _adder; 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /8.前端/实现Instanceof.md: -------------------------------------------------------------------------------- 1 | # 实现`Instanceof` 2 | 3 | ## 核心:原型链的向上查找 4 | 5 | ```js 6 | function myInstanceof(left, right) { 7 | if (typeof left !== "object" || left == null) return false; 8 | 9 | let proto = Object.getPrototypeOf(left); 10 | while (true) { 11 | if (proto == null) return false; 12 | if (proto === right.prototype) return true; 13 | proto = Object.getPrototypeOf(proto); 14 | } 15 | } 16 | ``` 17 | -------------------------------------------------------------------------------- /8.前端/封装apply.md: -------------------------------------------------------------------------------- 1 | # 封装 apply 2 | 3 | ```js 4 | Function.prototype.myApply = function (context) { 5 | let context = context || window; 6 | context.fn = this; 7 | 8 | let result; 9 | if (arguments[1]) { 10 | result = context.fn(...arguments[1]); 11 | } else { 12 | result = context.fn(); 13 | } 14 | delete context.fn; 15 | 16 | return result; 17 | }; 18 | ``` 19 | -------------------------------------------------------------------------------- /8.前端/封装bind.md: -------------------------------------------------------------------------------- 1 | # 封装 bind 2 | 3 | ```js 4 | Function.prototype.myBind = function (context) { 5 | if (typeof this !== "function") { 6 | throw new TypeError("Error"); 7 | } 8 | 9 | let _this = this; 10 | let args = [...arguments].slice(1); 11 | return function F() { 12 | if (this instanceof F) { 13 | return new _this(...args, ...arguments); 14 | } 15 | 16 | return _this.apply(context, args.concat(...arguments)); 17 | }; 18 | }; 19 | ``` 20 | -------------------------------------------------------------------------------- /8.前端/封装call.md: -------------------------------------------------------------------------------- 1 | # 封装 call 2 | 3 | ```js 4 | Function.prototype.myCall = function (context) { 5 | let context = context || window; 6 | context.fn = this; 7 | 8 | let args = [...arguments].slice(1); 9 | let result = context.fn(...args); 10 | delete context.fn; 11 | 12 | return result; 13 | }; 14 | ``` 15 | -------------------------------------------------------------------------------- /8.前端/封装map.md: -------------------------------------------------------------------------------- 1 | # 封装 Map 2 | 3 | > fn: 回调 context:回调作用域指定的 this 4 | 5 | ```js 6 | Array.prototype.myMap = function (fn, context) { 7 | // 1. 获取调用者this,并转为数组 8 | let arr = [].slice.call(this); 9 | 10 | // 2. 遍历调用者 11 | let arrMap = []; 12 | for (let i = 0; i < arr.length; i++) { 13 | if (!arr.hasOwnProperty(i)) { 14 | continue; 15 | } 16 | arrMap.push(fn.call(context, arr[i], i, this)); 17 | } 18 | 19 | return arrMap; 20 | }; 21 | ``` 22 | -------------------------------------------------------------------------------- /8.前端/手撕Promise(A+规范).md: -------------------------------------------------------------------------------- 1 | # 手撕`Promise` 2 | 3 | > ### Promise A+规范 4 | 5 | ```js 6 | class Promise { 7 | constructor(executor) { 8 | this.state = "pending"; 9 | this.value = undefined; 10 | this.reason = undefined; 11 | this.onResolvedCallbacks = []; 12 | this.onRejectedCallbacks = []; 13 | 14 | let resolve = (value) => { 15 | if (this.state === "pending") { 16 | this.state = "resolved"; 17 | this.value = value; 18 | this.onResolvedCallbacks.forEach((fn) => fn()); 19 | } 20 | }; 21 | 22 | let reject = (reason) => { 23 | if (this.state === "pending") { 24 | this.state = "rejected"; 25 | this.reason = reason; 26 | this.onRejectedCallbacks.forEach((fn) => fn()); 27 | } 28 | }; 29 | 30 | try { 31 | executor(resolve, reject); 32 | } catch (err) { 33 | reject(err); 34 | } 35 | } 36 | 37 | then(onResolved, onRejected) { 38 | // onResolved如果不是函数,就忽略onResolved,直接返回value 39 | onResolved = 40 | typeof onResolved === "function" ? onResolved : (value) => value; 41 | // onRejected如果不是函数,就忽略onRejected,直接扔出错误 42 | onRejected = 43 | typeof onRejected === "function" 44 | ? onRejected 45 | : (err) => { 46 | throw err; 47 | }; 48 | 49 | let promise2 = new Promise((resolve, reject) => { 50 | if (this.state === "resolved") { 51 | // 异步 52 | setTimeout(() => { 53 | try { 54 | let x = onResolved(this.value); 55 | resolvePromise(promise2, x, resolve, reject); 56 | } catch (e) { 57 | reject(e); 58 | } 59 | }, 0); 60 | } 61 | 62 | if (this.state === "rejected") { 63 | // 异步 64 | setTimeout(() => { 65 | // 如果报错 66 | try { 67 | let x = onRejected(this.reason); 68 | resolvePromise(promise2, x, resolve, reject); 69 | } catch (e) { 70 | reject(e); 71 | } 72 | }, 0); 73 | } 74 | 75 | if (this.state === "pending") { 76 | this.onResolvedCallbacks.push(() => { 77 | // 异步 78 | setTimeout(() => { 79 | try { 80 | let x = onResolved(this.value); 81 | resolvePromise(promise2, x, resolve, reject); 82 | } catch (e) { 83 | reject(e); 84 | } 85 | }, 0); 86 | }); 87 | this.onRejectedCallbacks.push(() => { 88 | // 异步 89 | setTimeout(() => { 90 | try { 91 | let x = onRejected(this.reason); 92 | resolvePromise(promise2, x, resolve, reject); 93 | } catch (e) { 94 | reject(e); 95 | } 96 | }, 0); 97 | }); 98 | } 99 | }); 100 | 101 | // 返回promise,完成链式 102 | return promise2; 103 | } 104 | } 105 | ``` 106 | -------------------------------------------------------------------------------- /8.前端/手撕Promise(简易版).md: -------------------------------------------------------------------------------- 1 | # 手撕`Promise` 2 | 3 | > ### `Promise`简易版 4 | 5 | ```js 6 | class Promise { 7 | constructor(executor) { 8 | this.state = "pending"; 9 | this.value = undefined; 10 | this.reason = undefined; 11 | 12 | let resolve = (value) => { 13 | if (this.state === "pending") { 14 | this.state = "resolved"; 15 | this.value = value; 16 | } 17 | }; 18 | 19 | let reject = (reason) => { 20 | if (this.state === "pending") { 21 | this.state = "rejected"; 22 | this.reason = reason; 23 | } 24 | }; 25 | 26 | try { 27 | executor(resolve, reject); 28 | } catch (err) { 29 | reject(err); 30 | } 31 | } 32 | 33 | then(onResolved, onRejected) { 34 | if (this.state === "resolved") { 35 | onResolved(this.value); 36 | } 37 | 38 | if (this.state === "rejected") { 39 | onRejected(this.reason); 40 | } 41 | } 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /8.前端/手撕Promise.all.md: -------------------------------------------------------------------------------- 1 | # 手撕`Promise.all` 2 | 3 | ```js 4 | function promiseAll(promises) { 5 | return new Promise(function (resolve, reject) { 6 | if (!Array.isArray(promises)) { 7 | return reject(new Error("Promises must be an array")); 8 | } 9 | 10 | let resolvedCount = 0; 11 | let promiseNum = promises.length; 12 | let resloveValue = []; 13 | 14 | for (let i = 0; i < promiseNum; i++) { 15 | Promise.resolve(promises[i]).then( 16 | (value) => { 17 | resloveValue[i] = value; 18 | resolvedCount++; 19 | if (resolvedCount === promiseNum) { 20 | return resloveValue; 21 | } 22 | }, 23 | (err) => { 24 | return reject(err); 25 | } 26 | ); 27 | } 28 | }); 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /8.前端/数组去重.md: -------------------------------------------------------------------------------- 1 | # 数组去重 2 | 3 | ## 第一种:数组的`indxOf`方法 4 | 5 | > #### 时间复杂度:O(n^2) 6 | 7 | ```js 8 | function unique(arr) { 9 | const temp = []; 10 | 11 | for (let i = 0; i < arr.length; i++) { 12 | if (temp.indexOf(arr[i]) === -1) { 13 | temp.push(arr[i]); 14 | } 15 | } 16 | 17 | return temp; 18 | } 19 | ``` 20 | 21 | ## 第二种:排序后相邻去重法 22 | 23 | > #### 时间复杂度:O(n log n) 24 | 25 | ```js 26 | function unique(arr) { 27 | arr.sort(); 28 | const temp = [arr[0]]; 29 | 30 | for (let i = 1; i < arr.length; i++) { 31 | if (arr[i] !== temp[temp.length - 1]) { 32 | temp.push(arr[i]); 33 | } 34 | } 35 | 36 | return temp; 37 | } 38 | ``` 39 | 40 | ## 第三种:`ES6`的`Set`方法 41 | 42 | > #### 时间复杂度:O(n) 43 | 44 | ```js 45 | function unique(arr) { 46 | return [...new Set(arr)]; 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /8.前端/数组扁平化.md: -------------------------------------------------------------------------------- 1 | # 数组扁平化 2 | 3 | ## 第一种:循环+递归 4 | 5 | ```js 6 | function flattenDeep(arr) { 7 | let newArr = []; 8 | 9 | for (let i = 0; i < arr.length; i++) { 10 | if (Array.isArray(arr[i])) { 11 | newArr.push.apply(newArr, flattenDeep(arr[i])); 12 | } else { 13 | newArr.push(arr[i]); 14 | } 15 | } 16 | 17 | return newArr; 18 | } 19 | ``` 20 | 21 | ## 第二种:`apply` + `some` 22 | 23 | ```js 24 | function flattenDeep(arr) { 25 | while (arr.some((item) => Array.isArray(item))) { 26 | arr = [].concat.apply([], arr); 27 | } 28 | 29 | return arr; 30 | } 31 | ``` 32 | 33 | ## 第三种:扩展运算符(...) 34 | 35 | ```js 36 | function flattenDeep(arr) { 37 | while (arr.some((item) => Array.isArray(item))) { 38 | arr = [].concat(...arr); 39 | } 40 | 41 | return arr; 42 | } 43 | ``` 44 | 45 | ## 第四种:数组的`reduce` 46 | 47 | ```js 48 | function flattenDeep(arr) { 49 | return arr.reduce((prev, next) => { 50 | return prev.concat(Array.isArray(next) ? flattenDeep(next) : next); 51 | }, []); 52 | } 53 | ``` 54 | 55 | ## 第五种:`ES10`的`flat` 56 | 57 | ```js 58 | function flattenDeep(arr) { 59 | return arr.flat(Infinity); 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /8.前端/深拷贝.md: -------------------------------------------------------------------------------- 1 | # 深拷贝 2 | 3 | ## 第一种:递归 4 | 5 | ```js 6 | function deepClone(obj) { 7 | let data; 8 | 9 | // 处理数组 10 | if (Object.prototype.toString.call(obj) === "[Object Array]") { 11 | data = []; 12 | for (let index = 0; index < obj.length; index++) { 13 | data.push(deepClone(obj[index])); 14 | } 15 | } else if (Object.prototype.toString.call(obj) === "[Object Object]") { 16 | // 处理对象 17 | data = {}; 18 | for (let key in obj) { 19 | data[key] = deepClone(obj[key]); 20 | } 21 | } else { 22 | // 基本数据类型 23 | return obj; 24 | } 25 | 26 | return data; 27 | } 28 | ``` 29 | 30 | ## 第二种:序列化与反序列化 31 | 32 | ```js 33 | function deepClone(obj) { 34 | return JSON.parse(JSON.strigify(obj)); 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /8.前端/红黄绿灯(字节).md: -------------------------------------------------------------------------------- 1 | # 红黄绿灯(字节跳动面试题) 2 | 3 | > ### 题目:红灯 3 秒亮一次,绿灯 2 秒亮一次,黄灯 1 秒亮一次;如何让三个灯不断交替重复亮灯? 4 | 5 | > ### 思路:`Promise` + 递归 6 | 7 | ```js 8 | function red() { 9 | console.log("red"); 10 | } 11 | function green() { 12 | console.log("green"); 13 | } 14 | function yellow() { 15 | console.log("yellow"); 16 | } 17 | 18 | const light = (timmer, cb) => { 19 | return new Promise((resolve, reject) => { 20 | setTimeout(() => { 21 | cb(); 22 | resolve(); 23 | }, timmer); 24 | }); 25 | }; 26 | 27 | const step = () => { 28 | Promise.resolve() 29 | .then(() => { 30 | light(3000, red); 31 | }) 32 | .then(() => { 33 | light(2000, green); 34 | }) 35 | .then(() => { 36 | light(1000, yellow); 37 | }) 38 | .then(() => { 39 | step(); 40 | }); 41 | }; 42 | 43 | step(); 44 | ``` 45 | -------------------------------------------------------------------------------- /8.前端/节流.md: -------------------------------------------------------------------------------- 1 | # 节流 2 | 3 | ```js 4 | /** 5 | * @desc 函数节流 6 | * @param func 函数 7 | * @param wait 延迟执行毫秒数 8 | * @param type 1 表示时间戳版,2 表示定时器版 9 | * @returns {Void} 10 | */ 11 | const throttle = (func, wait, type) => { 12 | let timeout; 13 | let previous = 0; 14 | 15 | return function () { 16 | let context = this; 17 | let args = arguments; 18 | 19 | if (type === 1) { 20 | let now = Date.now(); 21 | 22 | if (now - previous > wait) { 23 | func.apply(context, args); 24 | previous = now; 25 | } 26 | } else if (type === 2) { 27 | if (!timeout) { 28 | timeout = setTimeout(() => { 29 | timeout = null; 30 | func.apply(context, args); 31 | }, wait); 32 | } 33 | } 34 | }; 35 | }; 36 | ``` 37 | -------------------------------------------------------------------------------- /8.前端/防抖.md: -------------------------------------------------------------------------------- 1 | # 防抖 2 | 3 | ```js 4 | /** 5 | * @desc 函数防抖 6 | * @param func 函数 7 | * @param wait 延迟执行毫秒数 8 | * @param immediate true 表立即执行,false 表非立即执行 9 | * @returns {Void} 10 | */ 11 | const debounce = (func, wait, immediate = true) => { 12 | let timeout; 13 | 14 | return function () { 15 | let context = this; 16 | let args = arguments; 17 | if (timeout) clearTimeout(timeout); 18 | 19 | if (immediate) { 20 | let callNow = !timeout; 21 | 22 | timeout = setTimeout(() => { 23 | timeout = null; 24 | }, wait); 25 | 26 | if (callNow) func.apply(context, args); 27 | } else { 28 | timeout = setTimeout(() => { 29 | func.apply(context, args); 30 | }, wait); 31 | } 32 | }; 33 | }; 34 | ``` 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # leetcode 2 | 3 | > ## 校招/社招前端算法试题分类 4 | 5 | ### 排序 6 | 7 | - [冒泡排序](https://github.com/xingpengchao/leetcode/blob/master/1.排序/冒泡排序.md) 8 | - [选择排序](https://github.com/xingpengchao/leetcode/blob/master/1.排序/选择排序.md) 9 | - [插入排序](https://github.com/xingpengchao/leetcode/blob/master/1.排序/插入排序.md) 10 | - [希尔排序](https://github.com/xingpengchao/leetcode/blob/master/1.排序/希尔排序.md) 11 | - [归并排序](https://github.com/xingpengchao/leetcode/blob/master/1.排序/归并排序.md) 12 | - [快速排序](https://github.com/xingpengchao/leetcode/blob/master/1.排序/快速排序.md) 13 | - [堆排序](https://github.com/xingpengchao/leetcode/blob/master/1.排序/堆排序.md) 14 | - [十大排序对比](https://github.com/xingpengchao/leetcode/blob/master/1.排序/十大排序对比.md) 15 | 16 | ### 二分查找 17 | 18 | - [二分查找](https://github.com/xingpengchao/leetcode/blob/master/2.二分查找/二分查找.md) 19 | 20 | ### 栈、队列 21 | 22 | - [双栈模拟队列](https://github.com/xingpengchao/leetcode/blob/master/3.栈、队列/双栈模拟队列.md) 23 | - [有效的括号](https://github.com/xingpengchao/leetcode/blob/master/3.栈、队列/有效的括号.md) 24 | - [将无序栈转为有序栈](https://github.com/xingpengchao/leetcode/blob/master/3.栈、队列/将无序栈转为有序栈.md) 25 | 26 | ### 链表 27 | 28 | - [反转链表](https://github.com/xingpengchao/leetcode/blob/master/4.链表/反转链表.md) 29 | - [手撕单向链表](https://github.com/xingpengchao/leetcode/blob/master/4.链表/手撕单向链表.md) 30 | - [合并两个有序链表](https://github.com/xingpengchao/leetcode/blob/master/4.链表/合并两个有序链表.md) 31 | - [链表中倒数第 K 个节点](https://github.com/xingpengchao/leetcode/blob/master/4.链表/链表中倒数第K个节点.md) 32 | - [两个链表的第一个公共节点](https://github.com/xingpengchao/leetcode/blob/master/4.链表/两个链表的第一个公共节点.md) 33 | - [判断链表是否有环](https://github.com/xingpengchao/leetcode/blob/master/4.链表/判断链表是否有环.md) 34 | - [两数相加](https://github.com/xingpengchao/leetcode/blob/master/4.链表/两数相加.md) 35 | 36 | ### 二叉树 37 | 38 | - [前序遍历](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/前序遍历.md) 39 | - [中序遍历](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/中序遍历.md) 40 | - [后序遍历](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/后序遍历.md) 41 | - [层次遍历](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/层次遍历.md) 42 | - [二叉树的深度](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/二叉树的深度.md) 43 | - [对称二叉树](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/对称二叉树.md) 44 | - [平衡二叉树](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/平衡二叉树.md) 45 | - [重建二叉树](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/重建二叉树.md) 46 | - [按层打印二叉树](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/按层打印二叉树.md) 47 | - [之字形打印二叉树](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/之字形打印二叉树.md) 48 | - [二叉搜索树的后序遍历序列](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/二叉搜索树的后序遍历序列.md) 49 | - [二叉搜索树的第 K 个节点](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/二叉搜索树的第K个节点.md) 50 | - [二叉树的镜像](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/二叉树的镜像.md) 51 | - [二叉树的最大宽度](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/二叉树的最大宽度.md) 52 | - [二叉树的下一个节点](https://github.com/xingpengchao/leetcode/blob/master/5.二叉树/二叉树的下一个节点.md) 53 | 54 | ### 双指针 55 | 56 | - [字符串压缩](https://github.com/xingpengchao/leetcode/blob/master/6.双指针/字符串压缩.md) 57 | - [验证回文串](https://github.com/xingpengchao/leetcode/blob/master/6.双指针/验证回文串.md) 58 | - [滑动窗口的最大值](https://github.com/xingpengchao/leetcode/blob/master/6.双指针/滑动窗口的最大值.md) 59 | - [和为 S 的连续正整数](https://github.com/xingpengchao/leetcode/blob/master/6.双指针/和为S的连续正整数.md) 60 | - [和为 S 的两个数(微改版)]() 61 | - [盛最多水的容器](https://github.com/xingpengchao/leetcode/blob/master/6.双指针/盛最多水的容器.md) 62 | - [接雨水](https://github.com/xingpengchao/leetcode/blob/master/6.双指针/接雨水.md) 63 | 64 | ### 动态规划 65 | 66 | - [斐波那契数列/跳台阶/矩形覆盖](https://github.com/xingpengchao/leetcode/blob/master/7.动态规划/斐波那契数列、跳台阶、矩形覆盖.md) 67 | - [最小路径和](https://github.com/xingpengchao/leetcode/blob/master/7.动态规划/最小路径和.md) 68 | - [不同路径](https://github.com/xingpengchao/leetcode/blob/master/7.动态规划/不同路径.md) 69 | - [最长回文子串](https://github.com/xingpengchao/leetcode/blob/master/7.动态规划/最长回文子串.md) 70 | - [编辑距离](https://github.com/xingpengchao/leetcode/blob/master/7.动态规划/编辑距离.md) 71 | - [最长有效括号](https://github.com/xingpengchao/leetcode/blob/master/7.动态规划/最长有效括号.md) 72 | 73 | 95 | --------------------------------------------------------------------------------