├── 3Sum.md ├── AddTwoNumbers.md ├── BinaryTreeInorderTraversal.md ├── ClimbingStairs.md ├── DesignCircularDeque.md ├── EvaluateReversePolishNotation.md ├── FirstMissingPositive.md ├── InvertBinaryTree.md ├── LinkedListCycle.md ├── LinkedListCycle2.md ├── LongestVaildParentheses.md ├── MajorityElement1.md ├── MajorityElement2.md ├── MaximumDepthofBinaryTree.md ├── MergeTwoSortedLists.md ├── MergekSortedLists.md ├── MinimumDistanceBetweenBSTNodes.md ├── NumberofIslands.md ├── PathSum.md ├── Permutations.md ├── README.md ├── ReverseLinkedList.md ├── ReverseString.md ├── ReverseWordsInaString.md ├── SlidingWindowMaximum.md ├── StringToInteger.md ├── TwoSum.md ├── VaildSudoku.md ├── VaildataBinarySearchTree.md ├── ValidParentheses.md ├── images ├── LinkedListCycle.png ├── LinkedListCycle2.png ├── title.png └── title2.png └── sqrtx.md /3Sum.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/3 3 | Title:3Sum 4 | Difficulty: medium 5 | Author:小鹿 6 | --- 7 | 8 | 9 | 10 | #### 题目三:ADD Two Numbers 11 | 12 | Given an array `nums` of *n* integers, are there elements *a*, *b*, *c* in `nums` such that *a* + *b* + *c* = 0? Find all unique triplets in the array which gives the sum of zero. 13 | 14 | **Note:** 15 | 16 | The solution set must not contain duplicate triplets. 17 | 18 | **Example:** 19 | 20 | ``` 21 | Given array nums = [-1, 0, 1, 2, -1, -4], 22 | 23 | A solution set is: 24 | [ 25 | [-1, 0, 1], 26 | [-1, -1, 2] 27 | ] 28 | ``` 29 | 30 | 31 | 32 | **Solve:** 33 | 34 | ###### ▉ 算法思路: 35 | 36 | > 1、直接用三个 for 循环遍历所有数据,找出符合条件的数据,时间复杂度为 O(n^3)。能不能更快效率? 37 | > 38 | > 2、先对数组内数据进行一次排序。O(nlogn) 39 | > 40 | > 3、最外层一个 for 循环,先把其中一个值固定住(存放到变量),然后分别用两个指针指向数据的非固定值的头部和尾部,通过 while 循环来遍历。 41 | > 42 | > 4、如果三个数据相加等于 0 了,就存储该三个值且更新 head 和 end 指针。 43 | > 44 | > 5、如果不等于小于或大于 0 ,就更新 head 和 end 指针移动重新查找符合条件的值。 45 | > 46 | > 6、返回结果集 result。 47 | 48 | 49 | 50 | ###### ▉ 边界条件: 51 | 52 | > 1、判断数组内元素是否都为整数或负数,直接返回。 53 | > 54 | > 2、判断固定值、head 以及 end 指针的值前后元素是否相同,去掉重复计算。 55 | > 56 | > 3、判断 head 和 end 指针的大小关系。 57 | > 58 | > 4、注意去掉重复数据 59 | 60 | 61 | 62 | ###### ▉ 代码实现: 63 | 64 | ```javascript 65 | /** 66 | * @param {number[]} nums 67 | * @return {number[][]} 68 | */ 69 | 70 | var threeSum = function(nums) { 71 | //用来存取最后结果集 72 | let result = new Array(); 73 | //头指针 74 | let head; 75 | //尾指针 76 | let end; 77 | //固定值 78 | let fixedVal; 79 | 80 | //排序 81 | nums.sort((a, b) => { 82 | return a-b; 83 | }); 84 | 85 | //判断数组内元素是否都为整数或负数,直接返回 86 | if(nums[0] > 0 || nums[nums.length - 1] < 0) return result; 87 | 88 | // 开始遍历 89 | for (let i = 0; i < nums.length; i++) { 90 | //固定值 91 | fixedVal = nums[i]; 92 | // 如果前后元素相同,跳过此次循环(固定值) 93 | if(fixedVal === nums[i-1]) continue; 94 | //一开始的固定值为nums[0],所以头指针为 i+1 下一个元素 95 | head = i+1; 96 | //尾指针 97 | end = nums.length - 1; 98 | //如果头指针小于尾指针元素 99 | while(head < end){ 100 | //判断固定值+头指针+尾指针是否等于0 101 | if(nums[head] + nums[end] + fixedVal === 0){ 102 | //声明数组,存放这三个值 103 | let group = new Array(); 104 | group.push(nums[head]); 105 | group.push(nums[end]); 106 | group.push(fixedVal); 107 | result.push(group); 108 | //存放完毕之后,不要忘记头指针和尾指针的移动(否则会产生死循环) 109 | head += 1; 110 | end -= 1; 111 | //如果头指针满足小于尾指针且移动后的指针和移动前的指针元素相等,再往前移动 112 | while(head < end && nums[head] === nums[head - 1]){ 113 | head += 1; 114 | } 115 | //如果头指针满足小于尾指针且移动后的指针和移动前的指针元素相等,再往后移动 116 | while(head < end && nums[end] === nums[end + 1]){ 117 | end -= 1; 118 | } 119 | //小于 0 需要移动头指针,因为尝试着让数据比原有数据大一点 120 | }else if(nums[head] + nums[end] + fixedVal < 0){ 121 | head++; 122 | }else{ 123 | //否则,尾指针向前移动,让数据小于元数据 124 | end--; 125 | } 126 | } 127 | } 128 | return result; 129 | } 130 | //测试 131 | let nums = [-1, 0, 1, 2, -1, -4]; 132 | let result = threeSum(nums); 133 | for(let item in result){ 134 | console.log(result[item]) 135 | } 136 | ``` 137 | 138 | 139 | 140 | ###### ▉ sort 排序: 141 | 142 | > **定义**:sort() 方法用于对数组的元素进行排序。 在原来数组上进行排序,不生成副本。 143 | > 144 | > **使用:** 145 | > 146 | > 1)无参:按照字母的顺序对元素排序,即便是数字,先转换 String 再排序(按照字符编码),往往得不到我们要的结果。 147 | > 148 | > 2)有参:参数为比较函数,比较函数有两个参数 a,b (默认的 a 是小于 b 的) 149 | > 150 | > - 若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。(从小到大) 151 | > - 若 a 等于 b,则返回 0。(按照无参排序) 152 | > - 若 a 大于 b,则返回一个大于 0 的值。(从大到小) 153 | > 154 | > **内部实现:** 155 | > 156 | > 在 Chrome 浏览器中 sort 的你内部实现是快速排序,但是 FireFox 内部使用的归并排序,两者的区别就是快速排序不如归并排序稳定,但是大多数情况下还是可以使用快排的,只有个别要求必须稳定。所谓的稳定性就是原始数据相同的元素在排序之后位置是否改变? 157 | > 158 | > **性能问题:** 159 | > 160 | > 1、sort 会产生性能问题,因为无论是快排还是归并,都涉及到递归,如果递归深度过大,导致堆栈溢出,v8 引擎的解决办法就是设置一个递归深度阈值,小于阀值采用插入排序,大于阀值改用快排。 161 | > 162 | > 2、快排在在最差的情况下算法也会退化,因为根据 pivot 选择的不同,最坏情况时间复杂度退化到 O(n^2). 163 | > 164 | > 3、怎么进行改进?有兴趣可以看下方参考链接! 165 | > 166 | > 参考链接:https://efe.baidu.com/blog/talk-about-sort-in-front-end/ 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /AddTwoNumbers.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/2 3 | Title:ADD Two Numbers 4 | Difficulty: medium 5 | Author:小鹿 6 | --- 7 | 8 | #### 题目二:ADD Two Numbers 9 | 10 | You are given two **non-empty** linked lists representing two non-negative integers. The digits are stored in **reverse order** and each of their nodes contain a single digit. Add the two numbers and return it as a linked list. 11 | 12 | You may assume the two numbers do not contain any leading zero, except the number 0 itself. 13 | 14 | **Example:** 15 | 16 | ``` 17 | Input: (2 -> 4 -> 3) + (5 -> 6 -> 4) 18 | Output: 7 -> 0 -> 8 19 | Explanation: 342 + 465 = 807. 20 | ``` 21 | 22 | 23 | 24 | **Solve:** 25 | 26 | ###### ▉ 算法思路: 27 | 28 | > 1)观察 Example 规律,关联到链表,用一个带头的链表存储。 29 | > 30 | > 2)多位数加多位数,反转链表转化整数,如果整数相加,可能会溢出,此方法行不通。 31 | > 32 | > 3)直接进行位数运算,两链表每取出一个就做运算,将结果放入到新链表中。 33 | 34 | 35 | 36 | ###### ▉ 临界条件: 37 | 38 | > 1)一个链表比另一个链表长; 39 | > 40 | > 2)其中一个链表为 null。 41 | > 42 | > 3)求和运算会出现额外的进位(一般进位与最高位进位两种情况)。 43 | 44 | 45 | 46 | ###### ▉ 步骤: 47 | 48 | > 1)遍历链表之前,要定义一个哨兵结点、临时结点、存储计算结果的结点、进位标志; 49 | > 50 | > 2)开始遍历数据,判断当前结点是否为 null,为 null 就用 0 代替,否则取出数值; 51 | > 52 | > 3)求和(加 carray 进位),判断是否进位?记录进位值; 53 | > 54 | > 4)求模取余,计算两位数的各位数存储到链表中,指针向后移动; 55 | > 56 | > 5)判断结点是否为 null,继续遍历(如果链表 l2 比 l1 短,没有下一结点只能返回本身下次处理当做 null 处理) 57 | > 58 | > 6) 退出 while 循环勿忘最高位满位情况,carray 还存放着 1,所以判断最高位是否需要进位,存放到链表最后 59 | 60 | 61 | 62 | ###### ▉ 代码实现: 63 | 64 | ```javascript 65 | /** 66 | * 性能分析: 67 | * 1)遍历整个链表,时间复杂度为 O(n)。 68 | * 2)需要额外的 n 大小的空间存储 计算结果结点,空间复杂度为 O(n)。 69 | */ 70 | var addTwoNumbers = function(l1, l2) { 71 | //定义哨兵结点 72 | let head = new ListNode("head"); 73 | let current = head;//临时指针 74 | //存储计算后的链表 75 | let sumNode = head; 76 | //定义进位变量 77 | let carray = 0; 78 | //开始遍历两个链表取数据,判断链表是否为 null 79 | while(l1 !== null || l2 !== null){ 80 | //判断取数据的链表是否为nulL,为 null 就用 0 替换 81 | let num1 = 0; 82 | let num2 = 0; 83 | if(l1 == null){ 84 | num1 = 0; 85 | }else{ 86 | num1 = l1.val; 87 | } 88 | if(l2 == null){ 89 | num2 = 0; 90 | }else{ 91 | num2 = l2.val; 92 | } 93 | // let num1 = l1 == null ? 0 : l1.val; 94 | // let num2 = l2 == null ? 0 : l2.val; 95 | //计算取出的两个数值的和用于判断是否满进位,如果满 10,carray 需要记录进位,默认为 0 96 | let sum = num1 + num2 + carray; 97 | //判断是否需要存储进位值 1 98 | if(sum > 9){ 99 | carray = 1; 100 | }else{ 101 | carray = 0; 102 | } 103 | //carray = sum > 9 ? 1 : 0; 104 | //将两数之和相加[取模(取余运算)]添加到 sumNode 新链表中,一次排列 105 | current.next = new ListNode(sum % 10) 106 | //将指针指向下一链表结点 107 | current = current.next; 108 | //继续遍历链表中的数据,判断下一结点是否为 null 109 | if(l1 !== null){ 110 | l1 = l1.next; 111 | }else{ 112 | //如果链表 l1 比 l2 短,没有下一结点只能返回本身下次处理当做 null 处理 113 | l1 = l1; 114 | } 115 | if(l2 !== null){ 116 | l2 = l2.next; 117 | }else{ 118 | //如果链表 l2 比 l1 短,没有下一结点只能返回本身下次处理当做 null 处理 119 | l2 = l2; 120 | } 121 | // l1 为不为 null 才满足条件 122 | // l1 = l1 ? l1.next : l1; 123 | // l2 = l2 ? l2.next : l2; 124 | } 125 | //最高位满位情况,carray 还存放着 1,所以判断最高位是否需要进位 126 | if(carray === 1){ 127 | //有哨兵的,所以需要 next 才能存放下一结点 128 | current.next = new ListNode(1); 129 | } 130 | //返回哨兵结点之后的链表 131 | return head.next; 132 | } 133 | ``` 134 | 135 | 136 | 137 | ###### ▉ 代码缩减: 138 | 139 | ```javascript 140 | var addTwoNumbers = function(l1, l2) { 141 | //定义哨兵结点 142 | let head = new ListNode("head"); 143 | let current = head;//临时指针 144 | //存储计算后的链表 145 | let sumNode = head; 146 | //定义进位变量 147 | let carray = 0; 148 | //开始遍历两个链表取数据,判断链表是否为 null 149 | while(l1 !== null || l2 !== null){ 150 | //判断取数据的链表是否为nulL,为 null 就用 0 替换 151 | let num1 = l1 == null ? 0 : l1.val; 152 | let num2 = l2 == null ? 0 : l2.val; 153 | //计算取出的两个数值的和用于判断是否满进位,如果满 10,carray 需要记录进位,默认为 0 154 | let sum = num1 + num2 + carray; 155 | //判断是否需要存储进位值 1 156 | if(sum > 9){ 157 | carray = 1; 158 | }else{ 159 | carray = 0; 160 | } 161 | //carray = sum > 9 ? 1 : 0; 162 | //将两数之和相加[取模(取余运算)]添加到 sumNode 新链表中,一次排列 163 | current.next = new ListNode(sum % 10) 164 | //将指针指向下一链表结点 165 | current = current.next; 166 | //继续遍历链表中的数据,判断下一结点是否为 null 167 | l1 为不为 null 才满足条件 168 | l1 = l1 ? l1.next : l1; 169 | l2 = l2 ? l2.next : l2; 170 | } 171 | //最高位满位情况,carray 还存放着 1,所以判断最高位是否需要进位 172 | if(carray === 1){ 173 | //有哨兵的,所以需要 next 才能存放下一结点 174 | current.next = new ListNode(1); 175 | } 176 | //返回哨兵结点之后的链表 177 | return head.next; 178 | } 179 | ``` 180 | 181 | 182 | 183 | ###### ▉ 总结:需要注意几点。 184 | 185 | > 1、` l1 = l1 ? l1.next : l1` 代表的是 l1 不等于 null 会去 l1.next 的值。 186 | > 187 | > 2、用到哨兵思想,所以注意当前的指针指向。 188 | > 189 | > 3、两位数取模运算。 190 | 191 | 192 | 193 | ###### ▉ 扩展: 194 | 195 | > 三位数怎么取得各个位置上的数字?(水仙花数) 196 | > 197 | > **答:** 198 | 199 | ```javascript 200 | //移动小数点向前一位,得到小数点后一位 201 | 个位:a = 123 % 10 = 3 202 | //移动小数点向前两位,得到小数点后两位,除以10取整 203 | 十位:b = parseInt((123 % 100) / 10) 204 | //移动小数点向前三位,得到小数点后三位,除以100取整 205 | 百位::c = parseInt((123 % 1000) / 100) 206 | //依次类推..... 207 | ``` 208 | 209 | -------------------------------------------------------------------------------- /BinaryTreeInorderTraversal.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/25 3 | Title:Binary Tree Inorder Traversal 4 | Difficulty: Medium 5 | Author:小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Binary Tree Inorder Traversal(二叉树中序遍历) 11 | 12 | Given a binary tree, return the *inorder* traversal of its nodes' values. 13 | 14 | > 给定一个二叉树,返回它的*中序* 遍历。 15 | 16 | **Example:** 17 | 18 | ``` 19 | Input: [1,null,2,3] 20 | 1 21 | \ 22 | 2 23 | / 24 | 3 25 | 26 | Output: [1,3,2] 27 | ``` 28 | 29 | **Follow up:** Recursive solution is trivial, could you do it iteratively? 30 | 31 | > **进阶:** 递归算法很简单,你可以通过迭代算法完成吗? 32 | 33 | 34 | 35 | ## solve: 36 | 37 | ###### ▉ 问题分析 38 | 39 | > 1)二叉树的前、中、后遍历,首先明白前、中、后遍历的顺序是什么,对于二叉树的中序遍历来说,顺序是左子树节点 —> 根节点 —> 右子树节点。 40 | > 41 | > 2)通常递归的方法解决二叉树的遍历最方便不过,但是我还是喜欢增加点难度,用一般的迭代循环来实现。 42 | 43 | 44 | 45 | ###### ▉ 算法思路 46 | 47 | > 递归法: 48 | > 49 | > 1)判断当前树是否为空。 50 | > 51 | > 2)递归树的左子树结点。 52 | > 53 | > 3)输出当前结点的值。 54 | > 55 | > 4)递归树的右子树节点。 56 | > 57 | > 迭代循环法: 58 | > 59 | > 1)声明一个栈,将树的左子节点入栈。 60 | > 61 | > 2)每出栈一个结点,输出当前结点的值,且将该结点的右子树进行遍历打印,保证每个出栈的结点输出值之后,再输出上一个左子节点之前,将当前结点的右子节点遍历毕。 62 | > 63 | > 3)整棵树遍历完毕的终止条件就是当前栈是否存在结点(树的左子节点)。 64 | 65 | 66 | 67 | ###### ▉ 递归实现 68 | 69 | ```javascript 70 | var inorderTraversal = function(root) { 71 | let arr = [] 72 | const inorder = root =>{ 73 | // 判断当前的结点是否为空 74 | if(root == null) return null; 75 | // 递归左子树 76 | inorder(root.left) 77 | // 输出结点值 78 | arr.push(root.val) 79 | // 递归右子树 80 | inorder(root.right) 81 | } 82 | inorder(root) 83 | return arr 84 | }; 85 | ``` 86 | 87 | 88 | 89 | ###### ▉ 迭代实现 90 | 91 | ```javascript 92 | // 迭代实现二叉树的中序遍历 93 | var inorderTraversal = function(root) { 94 | let stack = []; 95 | let result = []; 96 | 97 | while(true){ 98 | // 判断树是否为空 99 | if(root == null) return result; 100 | 101 | // 先将树的左子节点推入栈中 102 | while(root !== null){ 103 | stack.push(root); 104 | root = root.left; 105 | } 106 | 107 | // 遍历的终止条件 108 | if(stack.length !== 0){ 109 | // 输出栈中的结点 110 | let temp = stack.pop(); 111 | result.push(temp.val); 112 | // 如果当前存在右子节点,要先打印右子树节点 113 | root = temp.right; 114 | }else{ 115 | break; 116 | } 117 | } 118 | return result; 119 | } 120 | ``` 121 | 122 | 123 | 124 | ###### ▉ 举一反三 125 | 126 | > 1)试着分别写出前序遍历、后序遍历的递归实现和迭代实现代码。 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /ClimbingStairs.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/12 3 | Title:Clibing Srairs 4 | Difficulty: Easy 5 | Author:小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Climbing Stairs 11 | 12 | You are climbing a stair case. It takes *n* steps to reach to the top. 13 | 14 | Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? 15 | 16 | **Note:** Given *n* will be a positive integer. 17 | 18 | 假设你正在爬楼梯。需要 *n* 阶你才能到达楼顶。 19 | 20 | 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 21 | 22 | **注意:**给定 *n* 是一个正整数。 23 | 24 | **Example 1:** 25 | 26 | ``` 27 | Input: 2 28 | Output: 2 29 | Explanation: There are two ways to climb to the top. 30 | 1. 1 step + 1 step 31 | 2. 2 steps 32 | ``` 33 | 34 | **Example 2:** 35 | 36 | ``` 37 | Input: 3 38 | Output: 3 39 | Explanation: There are three ways to climb to the top. 40 | 1. 1 step + 1 step + 1 step 41 | 2. 1 step + 2 steps 42 | 3. 2 steps + 1 step 43 | ``` 44 | 45 | 46 | 47 | ## slove 48 | 49 | #### ▉ 算法思路 50 | 51 | > 二种解决思路,第一利用递归;第二利用动态规划。 52 | > 53 | > 1)递归的实现方式: 54 | > 55 | > 首先,我们要知道递归使用应该满足的三个条件,之前在前边的题型中讲到过,后边我会整理成系列文章供大家方便学习。然后按照我们之前讲过的方式去写出递归公式,然后转化为递归的代码。我们会发现递归的时间复杂度为 O(2^n),我们是否还记得递归的缺点有一条就是警惕递归重复元素计算。就是因为有了重复元素的计算,导致了时间复杂度成指数的增长。 56 | > 57 | > 为了能够降低时间复杂度,我们可以用散列表来记录重复元素记录过的值,但是需要申请额外的空间进行存储,导致空间复杂度为O(n),时间复杂度降为O(n),也正是利用了空间换时间的思想。 58 | > 59 | > 2)动态规划的实现方式: 60 | > 61 | > 我们可以仔细发现上方的递归的方式还是可以优化的,我们换种方式去思考,从底向上去思考,其实我们计算一个值之存储之前的两个值就可以了(比如:计算 f(6) ,需要知道 f(5) 和 f(4) 的值就可以了),我们可以不用存储之前的值,此时可以将空间复杂度降为 O(1)。 62 | 63 | 64 | 65 | #### ▉ 代码实现(递归) 66 | 67 | > 优化后的递归实现。 68 | 69 | ```javascript 70 | //递归实现 71 | //时间复杂度为 O(n),空间复杂度为 O(n) 72 | var climbStairs = function(n) { 73 | let map = new Map(); 74 | if(n === 1) return 1; 75 | if(n === 2) return 2; 76 | if(map.has(n)){ 77 | return map.get(n); 78 | }else{ 79 | let value = climbStairs(n - 1) +climbStairs(n - 2); 80 | map.set(n,value); 81 | return value; 82 | } 83 | }; 84 | ``` 85 | 86 | 87 | 88 | #### ▉ 代码实现(动态规划) 89 | 90 | ```javascript 91 | //动态规划 92 | //时间复杂度为O(n) 空间复杂度为O(1) 93 | var climbStairs = function(n) { 94 | if(n < 1) return 0; 95 | if(n === 1) return 1; 96 | if(n === 2) return 2; 97 | 98 | let a = 1; 99 | let b = 2; 100 | let temp = 0; 101 | 102 | for (let i = 3; i < n + 1; i++) { 103 | temp = a + b; 104 | a = b; 105 | b = temp; 106 | } 107 | return temp; 108 | } 109 | ``` 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /DesignCircularDeque.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/15 3 | Title: Design Circular Deque 4 | Difficulty: Medium 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Design Circular Deque 11 | 12 | Design your implementation of the circular double-ended queue (deque). 13 | 14 | Your implementation should support following operations: 15 | 16 | - `MyCircularDeque(k)`: Constructor, set the size of the deque to be k. 17 | - `insertFront()`: Adds an item at the front of Deque. Return true if the operation is successful. 18 | - `insertLast()`: Adds an item at the rear of Deque. Return true if the operation is successful. 19 | - `deleteFront()`: Deletes an item from the front of Deque. Return true if the operation is successful. 20 | - `deleteLast()`: Deletes an item from the rear of Deque. Return true if the operation is successful. 21 | - `getFront()`: Gets the front item from the Deque. If the deque is empty, return -1. 22 | - `getRear()`: Gets the last item from Deque. If the deque is empty, return -1. 23 | - `isEmpty()`: Checks whether Deque is empty or not. 24 | - `isFull()`: Checks whether Deque is full or not. 25 | 26 | > 设计实现双端队列。 27 | > 你的实现需要支持以下操作: 28 | > 29 | > - MyCircularDeque(k):构造函数,双端队列的大小为k。 30 | > - insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。 31 | > - insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。 32 | > - deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。 33 | > - deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。 34 | > - getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。 35 | > - getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。 36 | > - isEmpty():检查双端队列是否为空。 37 | > - isFull():检查双端队列是否满了。 38 | 39 | **Example:** 40 | 41 | ``` 42 | MyCircularDeque circularDeque = new MycircularDeque(3); // set the size to be 3 43 | circularDeque.insertLast(1); // return true 44 | circularDeque.insertLast(2); // return true 45 | circularDeque.insertFront(3); // return true 46 | circularDeque.insertFront(4); // return false, the queue is full 47 | circularDeque.getRear(); // return 2 48 | circularDeque.isFull(); // return true 49 | circularDeque.deleteLast(); // return true 50 | circularDeque.insertFront(4); // return true 51 | circularDeque.getFront(); // return 4 52 | ``` 53 | 54 | 55 | 56 | **Note:** 57 | 58 | - All values will be in the range of [0, 1000]. 59 | - The number of operations will be in the range of [1, 1000]. 60 | - Please do not use the built-in Deque library. 61 | 62 | 63 | 64 | ## Solve: 65 | 66 | ###### ▉ 算法思路 67 | 68 | > 借助 Javascript 中数组中的 API 可快速实现一个双向队列。如: 69 | > 70 | > - `arr.pop()` : 删除数组尾部最后一个数据。 71 | > - `arr.push() `:在数组尾部插入一个数据。 72 | > - `arr.shift()`:数组头部删除第一个数据。 73 | > - `arr.unshift()`:数组头部插入一个数据。 74 | > 75 | > 以上数组提供的 API 使得更方便的对数组进行操作和模拟其他数据结构的操作,栈、队列等。 76 | 77 | 78 | 79 | ###### ▉ 代码实现 80 | 81 | ```javascript 82 | //双端列表构造 83 | var MyCircularDeque = function(k) { 84 | this.deque = []; 85 | this.size = k; 86 | }; 87 | 88 | /** 89 | * Adds an item at the front of Deque. Return true if the operation is successful. 90 | * @param {number} value 91 | * @return {boolean} 92 | * 功能:队列头部入队 93 | */ 94 | MyCircularDeque.prototype.insertFront = function(value) { 95 | if(this.deque.length === this.size){ 96 | return false; 97 | }else{ 98 | this.deque.unshift(value); 99 | return true; 100 | } 101 | }; 102 | 103 | /** 104 | * Adds an item at the rear of Deque. Return true if the operation is successful. 105 | * @param {number} value 106 | * @return {boolean} 107 | * 功能:队列尾部入队 108 | */ 109 | MyCircularDeque.prototype.insertLast = function(value) { 110 | if(this.deque.length === this.size){ 111 | return false; 112 | }else{ 113 | this.deque.push(value); 114 | return true; 115 | } 116 | }; 117 | 118 | /** 119 | * Deletes an item from the front of Deque. Return true if the operation is successful. 120 | * @return {boolean} 121 | * 功能:队列头部出队 122 | */ 123 | MyCircularDeque.prototype.deleteFront = function() { 124 | if(this.deque.length === 0){ 125 | return false; 126 | }else{ 127 | this.deque.shift(); 128 | return true; 129 | } 130 | }; 131 | 132 | /** 133 | * Deletes an item from the rear of Deque. Return true if the operation is successful. 134 | * @return {boolean} 135 | * 功能:队列尾部出队 136 | */ 137 | MyCircularDeque.prototype.deleteLast = function() { 138 | if(this.deque.length === 0){ 139 | return false; 140 | }else{ 141 | this.deque.pop(); 142 | return true; 143 | } 144 | }; 145 | 146 | /** 147 | * Get the front item from the deque. 148 | * @return {number} 149 | * 功能:获取队列头部第一个数据 150 | */ 151 | MyCircularDeque.prototype.getFront = function() { 152 | if(this.deque.length === 0){ 153 | return -1; 154 | }else{ 155 | return this.deque[0]; 156 | } 157 | }; 158 | 159 | /** 160 | * Get the last item from the deque. 161 | * @return {number} 162 | * 功能:获取队列尾部第一个数据 163 | */ 164 | MyCircularDeque.prototype.getRear = function() { 165 | if(this.deque.length === 0){ 166 | return -1; 167 | }else{ 168 | return this.deque[this.deque.length - 1]; 169 | } 170 | }; 171 | 172 | /** 173 | * Checks whether the circular deque is empty or not. 174 | * @return {boolean} 175 | * 功能:判断双端队列是否为空 176 | */ 177 | MyCircularDeque.prototype.isEmpty = function() { 178 | if(this.deque.length === 0){ 179 | return true; 180 | }else{ 181 | return false; 182 | } 183 | }; 184 | 185 | /** 186 | * Checks whether the circular deque is full or not. 187 | * @return {boolean} 188 | * 功能:判断双端队列是否为满 189 | */ 190 | MyCircularDeque.prototype.isFull = function() { 191 | if(this.deque.length === this.size){ 192 | return true; 193 | }else{ 194 | return false; 195 | } 196 | }; 197 | 198 | //测试 199 | var obj = new MyCircularDeque(3) 200 | var param_1 = obj.insertFront(1) 201 | var param_2 = obj.insertLast(2) 202 | console.log('-----------------------------插入数据------------------------') 203 | console.log(`${param_1}${param_2}`) 204 | var param_3 = obj.deleteFront() 205 | var param_4 = obj.deleteLast() 206 | console.log('-----------------------------删除数据------------------------') 207 | console.log(`${param_3}${param_4}`) 208 | var param_5 = obj.getFront() 209 | var param_6 = obj.getRear() 210 | console.log('-----------------------------获取数据------------------------') 211 | console.log(`${param_5}${param_6}`) 212 | var param_7 = obj.isEmpty() 213 | var param_8 = obj.isFull() 214 | console.log('-----------------------------判断空/满------------------------') 215 | console.log(`${param_7}${param_8}`) 216 | ``` 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | -------------------------------------------------------------------------------- /EvaluateReversePolishNotation.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/14 3 | Title: Evaluate Reverse Polish Notation 4 | Difficulty: Medium 5 | Author:小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Evaluate Reverse Polish Notation 11 | 12 | Evaluate the value of an arithmetic expression in [Reverse Polish Notation](http://en.wikipedia.org/wiki/Reverse_Polish_notation). 13 | 14 | Valid operators are `+`, `-`, `*`, `/`. Each operand may be an integer or another expression. 15 | 16 | **Note:** 17 | 18 | - Division between two integers should truncate toward zero. 19 | - The given RPN expression is always valid. That means the expression would always evaluate to a result and there won't be any divide by zero operation. 20 | 21 | > 根据[逆波兰表示法](https://baike.baidu.com/item/%E9%80%86%E6%B3%A2%E5%85%B0%E5%BC%8F/128437),求表达式的值。 22 | > 23 | > 有效的运算符包括 `+`, `-`, `*`, `/` 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。 24 | > 25 | > **说明:** 26 | > 27 | > - 整数除法只保留整数部分。 28 | > - 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。 29 | 30 | **Example 1:** 31 | 32 | ``` 33 | Input: ["2", "1", "+", "3", "*"] 34 | Output: 9 35 | Explanation: ((2 + 1) * 3) = 9 36 | ``` 37 | 38 | **Example 2:** 39 | 40 | ``` 41 | Input: ["4", "13", "5", "/", "+"] 42 | Output: 6 43 | Explanation: (4 + (13 / 5)) = 6 44 | ``` 45 | 46 | **Example 3:** 47 | 48 | ``` 49 | Input: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"] 50 | Output: 22 51 | Explanation: 52 | ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 53 | = ((10 * (6 / (12 * -11))) + 17) + 5 54 | = ((10 * (6 / -132)) + 17) + 5 55 | = ((10 * 0) + 17) + 5 56 | = (0 + 17) + 5 57 | = 17 + 5 58 | = 22 59 | ``` 60 | 61 | 62 | 63 | ## Solve: 64 | 65 | ###### ▉ 算法思路 66 | 67 | > 仔细观察上述的逆波兰表达式,可以发现一个规律就是每遇到一个操作符,就将操作符前的两个操作数进行运算,将结果保存到原位置。 68 | > 69 | > 1)我们可以将这个过程用栈来进行操作。 70 | > 71 | > 2)所有的操作数都执行近栈操作,当遇到操作符时,在栈中取出两个操作数进行计算,然后再将其压入栈内,继续遍历数组元素,直到遍历完整个数组为止。 72 | > 73 | > 3)到最后,栈内只剩下一个数,那就是最后的结果。 74 | 75 | 76 | 77 | ###### ▉ 注意事项 78 | 79 | > 虽然过程很好理解,代码写起来很简单,但是想把算法写的全面还是需要考虑到很多方面的。 80 | > 81 | > 1)数组中的是字符串类型,要进行数据类型转换 `parseInt()` 。 82 | > 83 | > 2)两个操作数进行运算时,第二个出栈的操作数在前,第一个出栈的操作数在后(注意除法)。 84 | > 85 | > 3)对于浮点型数据,只取小数点之前的整数。 86 | > 87 | > 4)关于负的浮点型(尤其是 0 点几 ),要取 0 绝对值 0 ,或直接转化为整数。 88 | 89 | 90 | 91 | ###### ▉ 代码实现 92 | 93 | ```javascript 94 | var evalRPN = function(tokens) { 95 | // 声明栈 96 | let stack = []; 97 | for(let item of tokens){ 98 | switch(item){ 99 | case '+': 100 | let a1 = stack.pop(); 101 | let b1 = stack.pop(); 102 | stack.push(b1 + a1); 103 | break; 104 | case '-': 105 | let a2 = stack.pop(); 106 | let b2 = stack.pop(); 107 | stack.push(b2 - a2); 108 | break; 109 | case '*': 110 | let a3 = stack.pop(); 111 | let b3 = stack.pop(); 112 | stack.push(b3 * a3); 113 | break; 114 | case '/': 115 | let a4 = stack.pop(); 116 | let b4 = stack.pop(); 117 | stack.push(parseInt(b4 / a4)); 118 | break; 119 | default: 120 | stack.push(parseInt(item)); 121 | } 122 | } 123 | return parseInt(stack.pop()); 124 | }; 125 | ``` 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /FirstMissingPositive.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/6 3 | Title:First Missing Positive 4 | Difficulty: Difficulty 5 | Author:小鹿 6 | --- 7 | 8 | 9 | 10 | #### 题目:First Missing Positive 11 | 12 | Given an unsorted integer array, find the smallest missing positive integer. 13 | 14 | **Note:** 15 | 16 | Your algorithm should run in *O*(*n*) time and uses constant extra space. 17 | 18 | **Example 1:** 19 | 20 | ``` 21 | Input: [1,2,0] 22 | Output: 3 23 | ``` 24 | 25 | **Example 2:** 26 | 27 | ``` 28 | Input: [3,4,-1,1] 29 | Output: 2 30 | ``` 31 | 32 | **Example 3:** 33 | 34 | ``` 35 | Input: [7,8,9,11,12] 36 | Output: 1 37 | ``` 38 | 39 | 40 | 41 | #### Solve: 42 | 43 | ###### ▉ 算法思路: 44 | 45 | > 桶排序思想。 46 | > 47 | > 遍历第一遍数据,将数据存放到与下标相同的位置,遍历第二遍数据,判断每个下标对应的数据是否为下标值。如果不是,该数值就是当前确实的最小正整数;当数组遍历完还有没找到,那么数组的长度 + 1 就是缺失的最小正整数值。 48 | 49 | 50 | 51 | ###### ▉ 算法步骤: 52 | 53 | > 1、把数组的每一个下标当做是一个桶,每个桶只能存放一个数据(每个下标对应一个数据),我们规定桶中的数据和下标的对应关系是 0 下标对应数据1,1下标对应数据2,2下标对应数据3......,题目要求我们在O(n)的时间复杂度内找出一组数据中缺失的最小正整数。 54 | > 55 | > 2、遍历第一遍数组中的数据,按照步骤 1 的规定,先判断两个当前下标对应的数据是否为规定的数据,如果不是,我们将该数据存放到规定的下标对应的数组中。然后交换的数据继续进行判断,直到该数据放到规定下标的数组中为止(小于等于 0 和 数据大于数组长度的除外)。 56 | > 57 | > 3、再遍历数组,从下标 0 开始判断该下标是否存放规定的数据,如果不是则该下标就是这组数据中缺失的最小正整数。 58 | > 59 | > 4、如果数组中的数据都匹配对应的下标,那么最小正整数就是数组长度加一。 60 | 61 | 62 | 63 | ###### ▉ 代码实现: 64 | 65 | ```javascript 66 | /** 67 | * @param {number[]} nums 68 | * @return {number} 69 | * 边界条件: 70 | * 1) 判断传入的数组是否为空。 71 | * 2) 判断数组中的数据只有 1 个。 72 | * 3) 交换数据时,判断相同数据的处理。 73 | */ 74 | var firstMissingPositive = function(nums) { 75 | //遍历数组 76 | for(let i = 0;i < nums.length;i++){ 77 | //如果当前的数据不对应正确的下标 + 1 && 数据大于 0 && 长度小于等于数组 78 | while(nums[i] != i+1 && nums[i] > 0 && nums[i] <= nums.length){ 79 | //判断该数据对应正确位置的数据是否相同 80 | if(nums[nums[i]-1] == nums[i]) { 81 | //如果相同,将该下标对应的元素置为 0 82 | nums[i] = 0; 83 | break; 84 | } 85 | //如果不重复,就交换数据 86 | let temp = nums[nums[i] - 1]; 87 | nums[nums[i] - 1] = nums[i]; 88 | nums[i] = temp; 89 | } 90 | } 91 | //遍历所有数据,找出缺失的最小正整数 92 | let index = 0; 93 | for(; index < nums.length; index++){ 94 | if(nums[index] !== index+1){ 95 | break; 96 | } 97 | } 98 | return index+1; 99 | }; 100 | 101 | //测试 102 | let arr =[]; 103 | console.log(firstMissingPositive(arr)) 104 | ``` 105 | 106 | 107 | 108 | ###### ▉ 总结: 109 | 110 | > 1、桶排序的思想。 111 | > 112 | > 2、桶排序还可以实现在一组数据中查找重复的数据。 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /InvertBinaryTree.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/21 3 | Title: Invert Binary Tree 4 | Difficulty: Easy 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Invert Binary Tree(翻转二叉树) 11 | 12 | Invert a binary tree. 13 | 14 | > 反转二叉树 15 | 16 | **Example:** 17 | 18 | Input: 19 | 20 | ``` 21 | 4 22 | / \ 23 | 2 7 24 | / \ / \ 25 | 1 3 6 9 26 | ``` 27 | 28 | Output: 29 | 30 | ``` 31 | 4 32 | / \ 33 | 7 2 34 | / \ / \ 35 | 9 6 3 1 36 | ``` 37 | 38 | 39 | 40 | ## Solve: 41 | 42 | ###### ▉ 问题分析 43 | 44 | > 由上图可以分析反转二叉树,只是对左右子树的数据进行交换,再仔细观察,并不是每个节点的左右子树进行交换,而是左子树的叶子节点和右子树的叶子节点进行交换,另外两个子节点进行交换,那我们不得不用到递归了。 45 | 46 | 47 | 48 | ###### ▉ 算法思路 49 | 50 | > 1)判断树是否为空(同时也是终止条件)。 51 | > 52 | > 2)左右子树结点交换。 53 | > 54 | > 3)分别对左右子树进行递归。 55 | 56 | 57 | 58 | ###### ▉ 代码实现 59 | 60 | ```javascript 61 | var invertTree = function(root) { 62 | //判断当前树是否为 null 63 | if(root == null) return root; 64 | 65 | let right = root.right; 66 | let left = root.left; 67 | root.right = left; 68 | root.left = right; 69 | 70 | invertTree(left); 71 | invertTree(right); 72 | return root; 73 | }; 74 | ``` 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /LinkedListCycle.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/7 3 | Title: Linked List Cycle 4 | Difficulty: Easy 5 | Author:小鹿 6 | --- 7 | 8 | 9 | 10 | #### 题目:Linked List Cycle I 11 | 12 | Given a linked list, determine if it has a cycle in it. 13 | 14 | To represent a cycle in the given linked list, we use an integer `pos` which represents the position (0-indexed) in the linked list where tail connects to. If `pos` is `-1`, then there is no cycle in the linked list. 15 | 16 | **Example 1:** 17 | 18 | ``` 19 | Input: head = [3,2,0,-4], pos = 1 20 | Output: true 21 | Explanation: There is a cycle in the linked list, where tail connects to the second node. 22 | ``` 23 | 24 | ![img](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist.png) 25 | 26 | **Example 2:** 27 | 28 | ``` 29 | Input: head = [1,2], pos = 0 30 | Output: true 31 | Explanation: There is a cycle in the linked list, where tail connects to the first node. 32 | ``` 33 | 34 | ![img](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test2.png) 35 | 36 | **Example 3:** 37 | 38 | ``` 39 | Input: head = [1], pos = -1 40 | Output: false 41 | Explanation: There is no cycle in the linked list. 42 | ``` 43 | 44 | ![img](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test3.png) 45 | 46 | **Follow up:** 47 | 48 | Can you solve it using *O(1)* (i.e. constant) memory? 49 | 50 | 51 | 52 | #### Slove: 53 | 54 | ###### ▉ 算法思路: 55 | 56 | > 两种解题思路: 57 | > 58 | > 1)哈希表法:遍历链表,没遍历一个节点就要在哈希表中判断是否存在该结点,如果存在,则为环;否则,将该结点插入到哈希表中继续遍历。 59 | > 60 | > 2)用两个快慢指针,快指针走两步,慢指针走一步,如果快指针与慢指针重合了,则检测的当前链表为环;如果当前指针或下一指针为 null ,则链表不为环。 61 | 62 | 63 | 64 | ###### ▉ 方法一:哈希表 65 | 66 | ```javascript 67 | /** 68 | * Definition for singly-linked list. 69 | * function ListNode(val) { 70 | * this.val = val; 71 | * this.next = null; 72 | * } 73 | */ 74 | /** 75 | * @param {ListNode} head 76 | * @return {boolean} 77 | */ 78 | 79 | var hasCycle = function(head) { 80 | let fast = head; 81 | let map = new Map(); 82 | while(fast !== null){ 83 | if(map.has(fast)){ 84 | return true; 85 | }else{ 86 | map.set(fast); 87 | fast = fast.next; 88 | } 89 | } 90 | return false; 91 | }; 92 | 93 | ``` 94 | 95 | 96 | 97 | ###### ▉ 方法二:快慢指针 98 | 99 | ```javascript 100 | var hasCycle = function(head) { 101 | if(head == null || head.next == null){ 102 | return false; 103 | } 104 | let fast = head.next; 105 | let slow = head; 106 | while(slow != fast){ 107 | if(fast == null || fast.next == null){ 108 | return false; 109 | } 110 | slow = slow.next; 111 | fast = fast.next.next; 112 | } 113 | return true; 114 | }; 115 | ``` 116 | 117 | 118 | 119 | ###### ▉ 方法二:快慢指针 120 | 121 | > 这部分代码是我自己写的,和上边的快慢指针思路相同,运行结果相同,但是当运行在 leetcode 时,就会提示超出时间限制,仔细对比代码,我们可以发现,在逻辑顺序上还是存在差别的,之所以超出时间限制,是因为代码的运行耗时长。 122 | 123 | ```javascript 124 | //超出时间限制 125 | var hasCycle = function(head) { 126 | if(head == null || head.next == null){ 127 | return false; 128 | } 129 | let fast = head.next; 130 | let slow = head; 131 | while(fast !== null && fast.next !== null){ 132 | if(slow === fast) return true; 133 | slow = head.next; 134 | fast = fast.next.next; 135 | } 136 | return false; 137 | }; 138 | ``` 139 | 140 | -------------------------------------------------------------------------------- /LinkedListCycle2.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/8 3 | Title: Linked List Cycle II 4 | Difficulty: medium 5 | Author:小鹿 6 | --- 7 | 8 | 9 | 10 | #### 题目:Linked List Cycle II 11 | 12 | Given a linked list, return the node where the cycle begins. If there is no cycle, return `null`. 13 | 14 | To represent a cycle in the given linked list, we use an integer `pos` which represents the position (0-indexed) in the linked list where tail connects to. If `pos` is `-1`, then there is no cycle in the linked list. 15 | 16 | **Note:** Do not modify the linked list. 17 | 18 | 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 `null`。 19 | 20 | 为了表示给定链表中的环,我们使用整数 `pos` 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 `pos` 是 `-1`,则在该链表中没有环。 21 | 22 | **说明:**不允许修改给定的链表。 23 | 24 | **Example 1:** 25 | 26 | ``` 27 | Input: head = [3,2,0,-4], pos = 1 28 | Output: tail connects to node index 1 29 | Explanation: There is a cycle in the linked list, where tail connects to the second node. 30 | ``` 31 | 32 | ![img](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist.png) 33 | 34 | **Example 2:** 35 | 36 | ``` 37 | Input: head = [1,2], pos = 0 38 | Output: tail connects to node index 0 39 | Explanation: There is a cycle in the linked list, where tail connects to the first node. 40 | ``` 41 | 42 | ![img](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test2.png) 43 | 44 | **Example 3:** 45 | 46 | ``` 47 | Input: head = [1], pos = -1 48 | Output: no cycle 49 | Explanation: There is no cycle in the linked list. 50 | ``` 51 | 52 | ![img](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test3.png) 53 | 54 | **Follow up**: 55 | Can you solve it without using extra space? 56 | 57 | 58 | 59 | #### Solve: 60 | 61 | ###### ▉ 算法思路: 62 | 63 | > 题目要求返回单链表中存在循环链表的位置。 64 | > 65 | > 1、首先,先判断该单链表是否存在循环链表?用两个快慢指针(fast、slow)分别指向链表的头部,fast 每次移动两步,slow 每次移动一步,fast 移动的步数是 slow 的两倍。 66 | > 67 | > 2、当 slow 与 fast 发生重合的时候,则存在链表。(slow 遍历完单链表一遍,fast 遍历了单链表两遍,2 倍的关系,如果 pos = 0 时,正好在头结点重合)。 68 | > 69 | > 3、如果 pos > 0 的时候。也就是说单链表中存在环,slow 到与 fast 指针重合的地方走过的路径为 n,fast 走过的路径是 slow 的两倍也就是 2n 。如果此时让 fast 每次走一步,走 n 步之后还会回到重合点。 70 | > 71 | > 4、那么怎么知道环接入的位置呢?这里稍微用的到点数学知识,看下图: 72 | > 73 | > 当 fast 指针和 slow 指针重合时,我们在声明一个 q 指针来计算到环结点的距离,因为 fast 改为每次移动一步,且 q 也要移动一步,fast 与 q 重合的地方就是环的接入点。(因为 slow 走过的路径和 fast 走过的路径相同,其中不重合的地方就是接入点) 74 | 75 | ![](/images/LinkedListCycle.png) 76 | 77 | ![](/images/LinkedListCycle2.png) 78 | 79 | ###### ▉ 代码实现: 80 | 81 | ```javascript 82 | /** 83 | * Definition for singly-linked list. 84 | * function ListNode(val) { 85 | * this.val = val; 86 | * this.next = null; 87 | * } 88 | */ 89 | 90 | /** 91 | * @param {ListNode} head 92 | * @return {ListNode} 93 | */ 94 | var detectCycle = function(head) { 95 | //判断头结点是否为 null 96 | if(head == null || head.next == null){ 97 | return null; 98 | } 99 | let fast = head; 100 | let slow = head; 101 | while(fast !== null && fast.next !== null){ 102 | fast = fast.next.next; 103 | slow = slow.next; 104 | //查找接入点 105 | if(fast == slow){ 106 | slow = head; 107 | while(slow !== fast){ 108 | fast = fast.next; 109 | slow = slow.next; 110 | } 111 | return slow; 112 | } 113 | } 114 | return null; 115 | }; 116 | ``` 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /LongestVaildParentheses.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/13 3 | Title: Longest Vaild Parentheses 4 | Difficulty: Difficulty 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Longest Vaild Parentheses 11 | 12 | Given a string containing just the characters `'('` and `')'`, find the length of the longest valid (well-formed) parentheses substring. 13 | 14 | **Example 1:** 15 | 16 | ```javascript 17 | Input: "(()" 18 | Output: 2 19 | Explanation: The longest valid parentheses substring is "()" 20 | ``` 21 | 22 | **Example 2:** 23 | 24 | ```javascript 25 | Input: ")()())" 26 | Output: 4 27 | Explanation: The longest valid parentheses substring is "()()" 28 | ``` 29 | 30 | 31 | 32 | ## Solve: 33 | 34 | ###### ▉ 算法思路 35 | 36 | > 通过栈来记录括号的有效长度。 37 | 38 | 39 | 40 | ###### ▉ 代码实现 41 | 42 | ```javascript 43 | var longestValidParentheses = function(s) { 44 | // 存储最大有效长度 45 | let longest = 0; 46 | // 栈用来存储接下来遇到的匹配括号的长度 47 | let stk = [0]; 48 | for (let i = 0; i < s.length; ++i) { 49 | // 如果为左括号,就入栈数字 0 50 | if (s[i] === '(') { 51 | stk.push(0); 52 | } else { 53 | // 如果为右括号,栈长度小于等于1,入栈 0 (占位) 54 | if (stk.length > 1) { 55 | // 取出当前刚入栈的长度数字 56 | let v = stk.pop(); 57 | // 取出之前连续记录的有效长度 58 | let lastCount = stk[stk.length - 1]; 59 | //将三者进行叠加(例:(())情况) 60 | stk[stk.length - 1] = lastCount + v + 2; 61 | // longest 一直保存最长有效长度 62 | longest = Math.max(longest, stk[stk.length - 1]); 63 | } else { 64 | //入栈 0 (占位) 65 | stk = [0]; 66 | } 67 | } 68 | } 69 | return longest; 70 | }; 71 | ``` 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /MajorityElement1.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/4 3 | Title: Majority Element 1 4 | Difficulty: easy 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | #### 题目:Majority Element 1 11 | 12 | Given an array of size *n*, find the majority element. The majority element is the element that appears **more than** `⌊ n/2 ⌋`times. 13 | 14 | You may assume that the array is non-empty and the majority element always exist in the array. 15 | 16 | **Example 1:** 17 | 18 | ``` 19 | Input: [3,2,3] 20 | Output: 3 21 | ``` 22 | 23 | **Example 2:** 24 | 25 | ``` 26 | Input: [2,2,1,1,1,2,2] 27 | Output: 2 28 | ``` 29 | 30 | 31 | 32 | #### solve: 33 | 34 | ###### ▉ 算法思路:摩尔投票算法 35 | 36 | > 题目的要求是让我们求数组中超过一半数据以上相同的元素且总是存在的。有这样一个思路要和大家分享: 37 | > 38 | > 假如有这样一种数据,数组中的所存在的这个超过 n/2 以上的数据(众数)肯定比与此众数不相同的其他元素个数要多(n/2 以上)。我们可以这样统计,用一个变量来计数,另一个变量记录计数的该元素,遍历整个数组,如果遇到相同的 count 加 +1,不同的就 -1 ,最后所存储的就是众数,因为其他数据的个数比众数个数要少嘛,这就是所谓的**摩尔投票算法**。 39 | 40 | 41 | 42 | ###### ▉ 代码实现: 43 | 44 | ```javascript 45 | /** 46 | * @param {number[]} nums 47 | * @return {number} 48 | */ 49 | var majorityElement = function(nums) { 50 | //用来计数相同的数据 51 | let count = 0; 52 | //存储当前的元素 53 | let majority = 0; 54 | //遍历整个数组 55 | for(let i = 0;i < nums.length; ++i){ 56 | //如果 count 为 0 57 | if(count === 0){ 58 | //将当前数据为众数 59 | majority = nums[i]; 60 | count++; 61 | }else if(majority === nums[i]){ 62 | //如果遍历的当前数据与存储的当前数据相同,计数+1 63 | count++; 64 | }else{ 65 | //若不相同,计数 - 1 66 | count--; 67 | } 68 | } 69 | //假设相同的众数呢? 70 | if(count === 0){ 71 | return -1; 72 | }else{ 73 | return majority; 74 | } 75 | }; 76 | ``` 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /MajorityElement2.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/5 3 | Title: Majority Element 2 4 | Difficulty: medium 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | #### 问题:Majority Element 2 11 | 12 | Given an integer array of size *n*, find all elements that appear more than `⌊ n/3 ⌋` times. 13 | 14 | **Note:** The algorithm should run in linear time and in O(1) space. 15 | 16 | **Example 1:** 17 | 18 | ``` 19 | Input: [3,2,3] 20 | Output: [3] 21 | ``` 22 | 23 | **Example 2:** 24 | 25 | ``` 26 | Input: [1,1,1,3,3,2,2,2] 27 | Output: [1,2] 28 | ``` 29 | 30 | 31 | 32 | #### solve: 33 | 34 | ###### ▉ 算法思路: 35 | 36 | > 1、上一篇是一个求超过 n/2 个数的数,采取的是用一个计数变量和计数元素变量来完成的。本篇题目是超过 n/3 的个数, 我们分别用两个变量来完成,**条件限制是时间复杂度是O(n),空间复杂度为O(1)**。 37 | > 38 | > 2、继续使用摩投票算法,假设要投票,选取票数超过 n/3 以上选为候选人,一个 n 被分为三等份,也就是说最多有两位当选人。 39 | > 40 | > 3、假设第一个人投甲,第二个投乙,甲、乙都站到台上各个计数为 1 ,随之第三个投甲,第四个投乙,计数分别加 1 ,第五个投丙,因为只有两个台子,所以那就让甲乙票数计数各减 1 ,当甲、乙的票数分别减到 0 之后,第 m 个人投丙,就让丙代替甲或已,继续遍历数据,直到遍历完成为止。 41 | > 42 | > 4、上述只选择出了那些有可能满足条件的数据,下面对cm、cn存储的数据做验证。 43 | 44 | 45 | 46 | ###### ▉ 其他解法: 47 | 48 | > 如果没有题目中时间复杂度和空间复杂度的限制,有多种解决方法: 49 | > 50 | > **1、散列表解决:**先遍历一遍数据,统计每个数字出现的次数,然后再遍历一遍散列表,找出满足条件的数字。时间复杂度为O(n),空间复杂度为 O(n)。 51 | > 52 | > **2、排序解决:**先进行一次排序(快排),然后遍历数据,找出满足条件的数据。时间复杂度为O(nlogn),空间复杂度为O(1)。 53 | 54 | 55 | 56 | ###### ▉ 代码实现: 57 | 58 | ```javascript 59 | /** 60 | * @param {number[]} nums 61 | * @return {number[]} 62 | */ 63 | var majorityElement = function(nums) { 64 | let [m,n,cm,cn,countm,countn] = [0,0,0,0,0,0]; 65 | let result = []; 66 | 67 | for(let i = 0;i < nums.length; ++i){ 68 | //m === nums[i]和n === nums[i]的判断一定放到 cm === 0 和 cn === 0 之前,负责产生 bug。 69 | if(m === nums[i]){ 70 | cm ++; 71 | }else if(n === nums[i]){ 72 | cn ++; 73 | }else if(cm === 0){ 74 | m = nums[i]; 75 | cm ++; 76 | }else if(cn === 0){ 77 | n = nums[i]; 78 | cn ++; 79 | }else{ 80 | cn--; 81 | cm--; 82 | } 83 | } 84 | //验证 cm、cn 存储的数据 85 | for(let index in nums){ 86 | if(nums[index] === m){ 87 | ++countm; 88 | } 89 | if(nums[index] === n){ 90 | ++countn; 91 | } 92 | } 93 | 94 | if(countm > Math.floor(nums.length/3)){ 95 | result.push(m); 96 | } 97 | 98 | if(countn > Math.floor(nums.length/3) && n != m){ 99 | result.push(n); 100 | } 101 | return result; 102 | }; 103 | 104 | //测试 105 | let arr =[1,2,2,3,2,1,1,3]; 106 | console.log(majorityElement(arr)); 107 | ``` 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /MaximumDepthofBinaryTree.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/22 3 | Title: Maximum Depth of Binary Tree 4 | Difficulty: Medium 5 | Author:小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Maximum Depth of Binary Tree(二叉树的最大深度) 11 | 12 | Given a binary tree, find its maximum depth. 13 | 14 | The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node. 15 | 16 | > 给定一个二叉树,找出其最大深度。 17 | > 18 | > 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 19 | > 20 | > **说明:** 叶子节点是指没有子节点的节点。 21 | 22 | **Note:** A leaf is a node with no children. 23 | 24 | **Example:** 25 | 26 | Given binary tree `[3,9,20,null,null,15,7]`, 27 | 28 | ``` 29 | 3 30 | / \ 31 | 9 20 32 | / \ 33 | 15 7 34 | ``` 35 | 36 | return its depth = 3. 37 | 38 | 39 | 40 | ## Solve: 41 | 42 | ###### ▉ 问题分析 43 | 44 | > 求二叉树的最大深度,我们要知道树的深度怎么计算的? 45 | > 46 | > 1)树的深度,深度,顾名思义,从上到下,第一层为 1,每向下一层,深度 + 1。 47 | > 48 | > 2)观察上图,我们计算时,只需记录两个子树最深的结点为主。 49 | > 50 | > 3)求二叉树的深度,必然要用到递归来解决。 51 | 52 | 53 | 54 | ###### ▉ 算法思路 55 | 56 | > 1)判断树是否为 null。 57 | > 58 | > 2)分别递归左右子树。 59 | > 60 | > 3)只计算叠加计数(递归最深)最大的数字。 61 | 62 | 63 | 64 | ###### ▉ 代码实现 65 | 66 | ```javascript 67 | var maxDepth = function(root) { 68 | // 如果根节点为 null 69 | if(root === null) return 0; 70 | // 递归左子树 71 | let depthLeft = maxDepth(root.left); 72 | // 递归右子树 73 | let depthRight = maxDepth(root.right); 74 | // 将子问题合并求总问题 75 | return Math.max(depthLeft,depthRight) + 1; 76 | }; 77 | ``` 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /MergeTwoSortedLists.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/9 3 | Title: Merge Two Sorted Lists 4 | Difficulty: Easy 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | #### 题目:Merge Two Sorted Lists 11 | 12 | Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists. 13 | 14 | **Example:** 15 | 16 | ``` 17 | Input: 1->2->4, 1->3->4 18 | Output: 1->1->2->3->4->4 19 | ``` 20 | 21 | 22 | 23 | #### Solve: 24 | 25 | ###### ▉ 算法思路 26 | 27 | > 1、正常思路,循环遍历迭代比较大小,每取出一个数据,将小数据加入到额外的数组中去,直到比较完毕,将其中一个剩余的数组追加到额外的数组尾部。 28 | > 29 | > 2、递归思路,满足递归的三个条件: 30 | > 31 | > - 将问题能不能化为子问题去解决? 32 | > - 子问题的解决方式是否和总问题相似? 33 | > - 是否有终止条件? 34 | 35 | 36 | 37 | ###### ▉ 递归实现 38 | 39 | ```javascript 40 | var mergeTwoLists = function(l1, l2) { 41 | let result = null; 42 | //终止条件 43 | if(l1 == null) return l2; 44 | if(l2 == null) return l1; 45 | 46 | //判断数值大小递归 47 | if(l1.val < l2.val){ 48 | result = l1; 49 | result.next = mergeTwoLists(l1.next,l2); 50 | }else{ 51 | result = l2; 52 | result.next = mergeTwoLists(l2.next,l1); 53 | } 54 | 55 | //返回结果 56 | return result; 57 | }; 58 | ``` 59 | 60 | 61 | 62 | ###### ▉ 怎么理解递归? 63 | 64 | > 其实递归最难的就是我们应该怎么去理解它,当我们完全理解了递归之后,就会发现递归非常方便,代码简洁。 65 | > 66 | > 我们经常理解递归会陷入到递归的细节上去,往往只递,归的时候就完全模糊了,我也试着找了网上的关于递归解释的,这么说吧,关于递归理解和使用,只有总结出自己的一套理解方法,才能真正的掌握递归,下面总结一下我自己理解的递归。 67 | 68 | 1、明确递归可以解决什么问题,也就是上边所讲到的解决的问题应该满足递归的三个条件。详细分开讲解: 69 | 70 | - **将问题划分为子问题:**如果我们判断该问题可以用递归解决了,比如合并两个链表中比较结点大小,然后加入到新链表,然后在比较下一个结点大小,这个过程就是一个将问题化为子问题的过程,比较当前结点大小先要比较后一个节点与当前结点的大小,可以理解成“递”的过程。(就好比一扇扇包含关系的大门,问一共有几所大门,你拿着要是去打开,发现里边还有一扇,然后再打开,发现还有一扇,直到最后一扇。通常我们使用迭代循环来解决这个问题,就好比打开一扇大门就 加一;而递归要做的就是当大门全部打开的时候,从里往外走关闭大门的时候统计大门的数量) 71 | 72 | - **寻找终止条件:**递归必须有个终止条件,也就是子问题的解决方案,如果没有终止条件,问题就不会得到解答。如上方合并链表的时候,经过递归不断的比较下一结点,知道其中一个链表比较完毕为空了,结果返回第二个链表,也就是达到了终止的条件,开始“归”的过程。 73 | 74 | - **递推公式:**你会问了,怎么写出递推公式呢?既然终止条件有了,那我们开始找出递归公式,下面是我自己总结出来的经验。 75 | 76 | ① 一看参数和 return。什么意思呢?比如上方合并链表的代码,分别明确函数的参数和返回值是什么?参数是两个合并的链表结点头结点。返回值是合并后的链表。 77 | 78 | ② 二凑参数和return。就是说我们要去按照参数和返回值去用递归伪造它,比较完成第一个结点,当然传入第二个节点,返回第一个结点到新链表尾部,那么递归就会返回新链表的下一结点。要屏蔽掉递归的细节,只看参数和返回值。 79 | 80 | 81 | 82 | ###### ▉ 递归缺点 83 | 84 | > 有时候问题可以使用递归,但是由于递归的缺点会放弃使用。 85 | > 86 | > 1、递归警惕堆栈溢出。 87 | > 88 | > 2、警惕递归重复元素计算。 89 | > 90 | > 3、递归的高空间复杂度。 91 | 92 | 93 | 94 | ###### ▉ 怎么正确写出递归代码? 95 | 96 | > 1、将问题化为子问题。 97 | > 98 | > 2、解决子问题。 99 | > 100 | > 3、寻找终止条件。 101 | > 102 | > 4、写出递归公式。 103 | > 104 | > 5、将递推公式转化为代码。 -------------------------------------------------------------------------------- /MergekSortedLists.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/10 3 | Title: Merge K Sorted Lists 4 | Difficulty: Difficulty 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Merge K Sorted Lists 11 | 12 | Merge *k* sorted linked lists and return it as one sorted list. Analyze and describe its complexity. 13 | 14 | > 合并 *k* 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。 15 | 16 | **Example:** 17 | 18 | ```javascript 19 | Input: 20 | [ 21 | 1->4->5, 22 | 1->3->4, 23 | 2->6 24 | ] 25 | Output: 1->1->2->3->4->4->5->6 26 | ``` 27 | 28 | 29 | 30 | ## Solve: 31 | 32 | #### ▉ 算法思路 33 | 34 | > 如果我们完成了简单的基于两个单链表的合并之后,对于这个题来说,考察点是分治算法,我认为还有一个考察点就是递归调用,分治的同时经常用递归来解决。 35 | > 36 | > 1、本道题可以借助归并排序的思想,稍加改造就可以解决。 37 | > 38 | > 2、将数组中的链表分治,就是不断的将数组中的链表中间划分,分别合并,然后整体合并成一个大链表。 39 | 40 | 41 | 42 | #### ▉ 代码实现 43 | 44 | ```javascript 45 | /** 46 | * @param {number[]} nums 47 | * @return {number[]} 48 | * 功能:合并 k 个链表 49 | * 边界条件: 50 | * 1)判断数组是否为空 51 | * 2)判断数组长度为 1 时 52 | * 3)判断数组长度为 2 时 53 | * 4)判断数组长度大于 2 时 54 | */ 55 | var mergeKLists = function(lists) { 56 | // 当 lists 中有一个链表时 57 | if(lists.length == 0){ 58 | return null; 59 | }else if(lists.length == 1){ 60 | // 判断数组长度为 1 时 61 | return lists[0]; 62 | }else if(lists.length == 2){ 63 | // 判断数组长度为 2 时 64 | return mergeTwoLists(lists[0],lists[1]); 65 | }else{ 66 | // 判断数组长度大于 2 时 67 | // 取数组的中部坐标 68 | let middle = Math.floor(lists.length/2); 69 | // 取左右两边数组 70 | let leftList = lists.slice(0,middle); 71 | let rightList = lists.slice(middle); 72 | // 递归、分割、合并 73 | return mergeTwoLists(mergeKLists(leftList),mergeKLists(rightList)); 74 | } 75 | }; 76 | //两个链表合并 77 | var mergeTwoLists = function(l1, l2) { 78 | let result = null; 79 | 80 | //终止条件 81 | if(l1 == null) return l2; 82 | if(l2 == null) return l1; 83 | 84 | //判断数值大小递归 85 | if(l1.val < l2.val){ 86 | result = l1; 87 | result.next = mergeTwoLists(l1.next,l2); 88 | }else{ 89 | result = l2; 90 | result.next = mergeTwoLists(l2.next,l1); 91 | } 92 | 93 | //返回结果 94 | return result; 95 | }; 96 | ``` 97 | 98 | 99 | 100 | #### ▉ 扩展:分治算法 101 | 102 | > 分治算法经常和递归一块使用,所谓分治算法,顾名思义,分而治之,最基本的分之算法在归并排序、快速排序都有用到。也就是将原问题划分成 n 个规模较小,并且结构与原问题相似的子问题,递归地解决这些子问题,然后再合并其结果,就得到原问题的解。 103 | 104 | 105 | 106 | ##### 1、分治算法递归每层操作 107 | 108 | - 分解:将原问题分解成一系列的子问题。 109 | - 解决:递归地求解各个子问题,若子问题足够小,则直接求解; 110 | - 合并:将子问题的结果合并成原问题。 111 | 112 | 113 | 114 | ##### 2、分治算法满足的条件 115 | 116 | - **可分解:**原问题与分解成的小问题具有相同的模式; 117 | - **无关联:**原问题分解成的子问题可以独立求解,子问题之间没有相关性,这一点是分治算法跟动态规划的明显区别。 118 | - **终止条件:**具有分解终止条件; 119 | - **合并不能太复杂:**可以将子问题合并成原问题,而这个合并操作的复杂度不能太高,否则就起不到减小算法总体复杂度的效果了。 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /MinimumDistanceBetweenBSTNodes.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/27 3 | Title: Minimum Distance Between BST Nodes 4 | Difficulty: Easy 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Minimum Distance Between BST Nodes(二叉搜索树结点最小距离) 11 | 12 | Given a Binary Search Tree (BST) with the root node `root`, return the minimum difference between the values of any two different nodes in the tree. 13 | 14 | > 给定一个二叉搜索树的根结点 `root`, 返回树中任意两节点的差的最小值。 15 | 16 | **Example :** 17 | 18 | ``` 19 | Input: root = [4,2,6,1,3,null,null] 20 | Output: 1 21 | Explanation: 22 | Note that root is a TreeNode object, not an array. 23 | 24 | The given tree [4,2,6,1,3,null,null] is represented by the following diagram: 25 | 26 | 4 27 | / \ 28 | 2 6 29 | / \ 30 | 1 3 31 | 32 | while the minimum difference in this tree is 1, it occurs between node 1 and node 2, also between node 3 and node 2. 33 | ``` 34 | 35 | **Note:** 36 | 37 | 1. The size of the BST will be between 2 and `100`. 38 | 2. The BST is always valid, each node's value is an integer, and each node's value is different. 39 | 40 | 41 | 42 | ## Solve: 43 | 44 | ###### ▉ 问题分析 45 | 46 | > 返回树中任意两个节点的差值,根据问题,可以将问题理解为,每个任意节点的差值的绝对值,那怎么进行相减呢?还需要用到树中的中序遍历。 47 | 48 | 49 | 50 | ###### ▉ 算法思路 51 | 52 | > 1)对树进行中序遍历,中序遍历的的值是逐渐递增的,我们将其遍历整棵树存入数组。 53 | > 54 | > 2)然后对数组中的前后两个数求差值,求的最小的差值为树中任意两点的差的最小值。 55 | 56 | 57 | 58 | ###### ▉ 代码实现 59 | 60 | ```javascript 61 | var minDiffInBST = function(root) { 62 | if(root == null) return null; 63 | let arr = []; 64 | let min = Number.MAX_VALUE 65 | // 中序遍历 66 | distance = (root)=>{ 67 | if(root == null) return null; 68 | distance(root.left) 69 | arr.push(root.val); 70 | distance(root.right) 71 | } 72 | 73 | distance(root); 74 | // 求差值 75 | for(let i = 1;i < arr.length;i++){ 76 | min = Math.min(min,arr[i] - arr[i - 1]) 77 | } 78 | return min; 79 | }; 80 | ``` 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | ♂ -------------------------------------------------------------------------------- /NumberofIslands.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/28 3 | Title: Number of Islands 4 | Difficulty: Medium 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Number of Islands(岛屿的数量) 11 | 12 | Given a 2d grid map of `'1'`s (land) and `'0'`s (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water. 13 | 14 | > 给定一个由 `'1'`(陆地)和 `'0'`(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 15 | 16 | **Example 1:** 17 | 18 | ``` 19 | Input: 20 | 11110 21 | 11010 22 | 11000 23 | 00000 24 | 25 | Output: 1 26 | ``` 27 | 28 | **Example 2:** 29 | 30 | ``` 31 | Input: 32 | 11000 33 | 11000 34 | 00100 35 | 00011 36 | 37 | Output: 3 38 | ``` 39 | 40 | 41 | 42 | ## Solve: 43 | 44 | ###### ▉ 问题分析 45 | 46 | 47 | 48 | ###### ▉ 算法思路 49 | 50 | 51 | 52 | ###### ▉ 代码实现 53 | 54 | ```javascript 55 | var numIslands = function(grid) { 56 | // 判断图是否为空 57 | if(grid.length === 0) return 0; 58 | // 数量 59 | let count = 0; 60 | dfsSearch = (grid,i,j) =>{ 61 | if(i < 0 || j < 0 || i >= grid.length || j >= grid[0].length) return; 62 | 63 | if(grid[i][j] === '1'){ 64 | grid[i][j] = '0'; 65 | dfsSearch(grid,i,j + 1); 66 | dfsSearch(grid,i,j - 1); 67 | dfsSearch(grid,i + 1,j); 68 | dfsSearch(grid,i - 1,j); 69 | } 70 | } 71 | for(let i = 0;i < grid.length;i++){ 72 | for(let j = 0;j < grid[0].length;j++){ 73 | if(grid[i][j] === '1'){ 74 | dfsSearch(grid,i,j); 75 | count++; 76 | } 77 | } 78 | } 79 | return count; 80 | }; 81 | ``` 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /PathSum.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/26 3 | Title: Path Sum 4 | Difficulty: Easy 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Path Sum(路径总和) 11 | 12 | Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum. 13 | 14 | **Note:** A leaf is a node with no children. 15 | 16 | > 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。 17 | > 18 | > **说明:** 叶子节点是指没有子节点的节点。 19 | > 20 | > **示例:** 21 | > 给定如下二叉树,以及目标和 `sum = 22`, 22 | 23 | **Example:** 24 | 25 | Given the below binary tree and `sum = 22`, 26 | 27 | ``` 28 | 5 29 | / \ 30 | 4 8 31 | / / \ 32 | 11 13 4 33 | / \ \ 34 | 7 2 1 35 | ``` 36 | 37 | return true, as there exist a root-to-leaf path `5->4->11->2` which sum is 22. 38 | 39 | 40 | 41 | ## Solve: 42 | 43 | ###### ▉ 问题分析 44 | 45 | > 1)搜索从根节点到叶子节点能够达到目标值,毋庸置疑,一看到检索二叉搜索树,就应该想起递归。 46 | > 47 | > 2)第一个想法就是一个个进行相加,将每条路径相加完成之后,判断是否达到当前目标值? 48 | > 49 | > 3)但是 (2)的解决方案的想法要用到递归上,需要转变一下思路,毕竟我们要用递归的编程技巧来解决,所以思维需要稍微转变一下(将问题化成子问题,子问题与总问题有相同的解决思路)。 50 | 51 | 52 | 53 | ###### ▉ 算法思路 54 | 55 | > 1)既然要用到递归来解决,这样来想,每遍历一个结点,我们就用 sum 减去当前结点,然后问题就会变成从当前结点到叶子节点能够满足 sum 减去当前结点的值,那么就存在满足条件的路径。 56 | > 57 | > 2)在(1)中,将问题化成子问题,然后子问题和问题有相同的解决思路,那么就可以使用递归来解决。 58 | > 59 | > 3)用 flag 做标识,一旦满足路径之和等于目标值,就让改变 flag 的状态。 60 | 61 | 62 | 63 | ###### ▉ 代码实现 64 | 65 | ```javascript 66 | var hasPathSum = function(root, sum) { 67 | let flag = false; 68 | // 判断当前二叉树是否等于 null 69 | if(root == null) return false; 70 | 71 | dfs = (root,sum)=>{ 72 | // 如果当前结点为 null ,返回 false 73 | if(root == null) return false; 74 | // 判断左右子树是否为 null 75 | if(root.left == null && root.right == null){ 76 | // 如果不为 null,就判断当前的值和最后一个结点值是否相同 77 | if(sum == root.val){ 78 | flag = true; 79 | return; 80 | } 81 | } 82 | // 递归搜索所有路径 83 | dfs(root.left,sum - root.val) 84 | dfs(root.right,sum - root.val) 85 | } 86 | dfs(root,sum) 87 | return flag; 88 | }; 89 | ``` 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /Permutations.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/30 3 | Title: Permutations 4 | Difficulty: Medium 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Permutations(全排列) 11 | 12 | Given a collection of **distinct** integers, return all possible permutations. 13 | 14 | > 给定一个**没有重复**数字的序列,返回其所有可能的全排列。 15 | 16 | **Example:** 17 | 18 | ``` 19 | Input: [1,2,3] 20 | Output: 21 | [ 22 | [1,2,3], 23 | [1,3,2], 24 | [2,1,3], 25 | [2,3,1], 26 | [3,1,2], 27 | [3,2,1] 28 | ] 29 | ``` 30 | 31 | 32 | 33 | 34 | 35 | ## Solve: 36 | 37 | ###### ▉ 算法思路 38 | 39 | 40 | 41 | ###### ▉ 代码实现 42 | 43 | ```javascript 44 | var permute = function(nums) { 45 | const nl = nums.length; 46 | if(nl === 0) {return [];} 47 | if(nl === 1) {return [nums];} 48 | const result = []; 49 | for(let i = 0; i < nl; i ++) { 50 | const subArr = []; 51 | for(let j = 0; j < nl; j ++) { 52 | if(i !== j) { 53 | subArr.push(nums[j]); 54 | } 55 | } 56 | const subRes = permute(subArr); 57 | const sl = subRes.length; 58 | for(let j = 0; j < sl; j ++){ 59 | result.push([nums[i]].concat(subRes[j])); 60 | } 61 | } 62 | return result; 63 | }; 64 | 65 | // 测试 66 | let arr = [1,2,3]; 67 | console.log(permute(arr)) 68 | ``` 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://github.com/luxiangqiang/JS-LeetCode/blob/master/images/title2.png) 2 | 3 | # 前言 4 | 5 | > 2019/4/1 起,每天都会做一道 LeetCode 算法题!30天锻炼自己多种解题思路,用心记录每道算法题的收获! 6 | 7 | #### 1、练习“站桩”每天要坚持,一天都不能少! 8 | 9 | #### 2、练“武”不练“功”,到老一场空 10 | 11 | #### 3、内练心智,外练筋骨。 12 | 13 | #### 4、再好的功夫,练不好基本功,也会走样! 14 | 15 | #### 5、练”武“太急是大忌,”基本功“要稳! 16 | 17 | | 题号 | 日期 | 难度 | 英文地址 | 中文地址 | JavaScript | Java | Python | 18 | | :----: | :-------: | :--: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :--: | :----: | 19 | | 1 题 | 2019/4/1 | 简单 | [Two Sum](https://leetcode.com/problems/two-sum/) | [两数之和](https://leetcode-cn.com/problems/two-sum/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/TwoSum.md) | | | 20 | | 2 题 | 2019/4/2 | 中等 | [Add Sum Numbers](https://leetcode.com/problems/add-two-numbers/) | [两数相加](https://leetcode-cn.com/problems/add-two-numbers/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/AddTwoNumbers.md) | | | 21 | | 15 题 | 2019/4/3 | 中等 | [3Sum](https://leetcode.com/problems/3sum/) | [三数之和](https://leetcode-cn.com/problems/3sum/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/3Sum.md) | | | 22 | | 169 题 | 2019/4/4 | 简单 | [Majority Element I](https://leetcode.com/problems/majority-element/) | [求众数 I](https://leetcode-cn.com/problems/majority-element/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/MajorityElement1.md) | | | 23 | | 229 题 | 2019/4/5 | 中等 | [Majority Element II](https://leetcode.com/problems/majority-element-ii/) | [求众数 II](https://leetcode-cn.com/problems/majority-element-ii/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/MajorityElement2.md) | | | 24 | | 41 题 | 2019/4/6 | 困难 | [First Missing Positive](https://leetcode.com/problems/first-missing-positive/) | [缺失的第一个正数](https://leetcode-cn.com/problems/first-missing-positive/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/FirstMissingPositive.md) | | | 25 | | 141 题 | 2019/4/7 | 简单 | [Linked List Cycle](https://leetcode.com/problems/linked-list-cycle/) | [环形链表 I](https://leetcode-cn.com/problems/linked-list-cycle/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/LinkedListCycle.md) | | | 26 | | 142 题 | 2019/4/8 | 中等 | [Linked List Cycle II](https://leetcode-cn.com/problems/linked-list-cycle-ii/) | [环形链表 II](https://leetcode-cn.com/problems/linked-list-cycle-ii/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/LinkedListCycle2.md) | | | 27 | | 21 题 | 2019/4/9 | 简单 | [Merge Two Sorted Lists](https://leetcode-cn.com/problems/merge-two-sorted-lists/) | [合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/MergeTwoSortedLists.md) | | | 28 | | 23 题 | 2019/4/10 | 困难 | [Merge K Sorted Lists](https://leetcode-cn.com/problems/merge-k-sorted-lists/) | [ 合并K个排序链表](https://leetcode-cn.com/problems/merge-k-sorted-lists/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/MergekSortedLists.md) | | | 29 | | 20 题 | 2019/4/11 | 简单 | [Valid Parentheses](https://leetcode-cn.com/problems/valid-parentheses/) | [有效的括号](https://leetcode-cn.com/problems/valid-parentheses/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/ValidParentheses.md) | | | 30 | | 70 题 | 2019/4/12 | 简单 | [Climbing Stairs](https://leetcode-cn.com/problems/climbing-stairs/) | [爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/ClimbingStairs.md) | | | 31 | | 32 题 | 2019/4/13 | 困难 | [Longest Valid Parentheses](https://leetcode-cn.com/problems/longest-valid-parentheses/) | [最长有效括号](https://leetcode-cn.com/problems/longest-valid-parentheses/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/LongestVaildParentheses.md) | | | 32 | | 150 题 | 2019/4/14 | 中等 | [ Evaluate Reverse Polish Notation](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) | [逆波兰表达式求值](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/EvaluateReversePolishNotation.md) | | | 33 | | 641 题 | 2019/4/15 | 中等 | [Design Circular Deque](https://leetcode-cn.com/problems/design-circular-deque/) | [设计循环双端队列](https://leetcode-cn.com/problems/design-circular-deque/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/DesignCircularDeque.md) | | | 34 | | 239 题 | 2019/4/16 | 困难 | [Sliding Window Maximum](https://leetcode-cn.com/problems/sliding-window-maximum/) | [滑动窗口最大值](https://leetcode-cn.com/problems/sliding-window-maximum/) | [题目解析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/SlidingWindowMaximum.md) | | | 35 | | 69 题 | 2019/4/17 | 简单 | [Sqrt(x)](https://leetcode-cn.com/problems/sqrtx/) | [x 的平方根](https://leetcode-cn.com/problems/sqrtx/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/sqrtx.md) | | | 36 | | 344 题 | 2019/4/18 | 简单 | [Reverse String](https://leetcode-cn.com/problems/reverse-string/) | [反转字符串](https://leetcode-cn.com/problems/reverse-string/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/ReverseString.md) | | | 37 | | 8 题 | 2019/4/19 | 中等 | [String to Integer (atoi)](https://leetcode-cn.com/problems/string-to-integer-atoi/) | [字符串转换整数 (atoi)](https://leetcode-cn.com/problems/string-to-integer-atoi/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/StringToInteger.md) | | | 38 | | 151 题 | 2019/4/20 | 中等 | [Reverse Words in a String](https://leetcode-cn.com/problems/reverse-words-in-a-string/) | [翻转字符串里的单词](https://leetcode-cn.com/problems/reverse-words-in-a-string/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/ReverseWordsInaString.md) | | | 39 | | 226 题 | 2019/4/21 | 简单 | [Invert Binary Tree](https://leetcode-cn.com/problems/invert-binary-tree/) | [翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/InvertBinaryTree.md) | | | 40 | | 104题 | 2019/4/22 | 简单 | [Maximum Depth of Binary Tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) | [二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/MaximumDepthofBinaryTree.md) | | | 41 | | 206题 | 2019/4/23 | 简单 | [Reverse Linked List](https://leetcode-cn.com/problems/reverse-linked-list/) | [反转链表](https://leetcode-cn.com/problems/reverse-linked-list/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/ReverseLinkedList.md) | | | 42 | | 98 题 | 2019/4/24 | 中等 | [Validate Binary Search Tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) | [验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/VaildataBinarySearchTree.md) | | | 43 | | 94 题 | 2019/4/25 | 中等 | [Binary Tree Inorder Traversal](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) | [二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/BinaryTreeInorderTraversal.md) | | | 44 | | 112 题 | 2019/4/26 | 简单 | [Path Sum](https://leetcode-cn.com/problems/path-sum/) | [路径总和](https://leetcode-cn.com/problems/path-sum/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/PathSum.md) | | | 45 | | 783 题 | 2019/4/27 | 简单 | [Minimum Distance Between BST Nodes](https://leetcode-cn.com/problems/minimum-distance-between-bst-nodes/) | [二叉搜索树结点最小距离](https://leetcode-cn.com/problems/minimum-distance-between-bst-nodes/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/MinimumDistanceBetweenBSTNodes.md) | | | 46 | | 200 题 | 2019/4/28 | 中等 | [Number of Islands](https://leetcode-cn.com/problems/number-of-islands/) | [岛屿的个数](https://leetcode-cn.com/problems/number-of-islands/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/NumberofIslands.md) | | | 47 | | 36 题 | 2019/4/29 | 中等 | [Valid Sudoku](https://leetcode-cn.com/problems/valid-sudoku/) | [有效的数独](https://leetcode-cn.com/problems/valid-sudoku/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/VaildSudoku.md) | | | 48 | | 46 题 | 2019/4/30 | 中等 | [Permutations](https://leetcode-cn.com/problems/permutations/) | [全排列](https://leetcode-cn.com/problems/permutations/) | [题目分析](https://github.com/luxiangqiang/JS-LeetCode/blob/master/Permutations.md) | | | 49 | 50 | 51 | 52 | ## 后记 53 | 54 | 通过一个月的 LeedCode 算法题的记录,逐渐的发现有几个特别重要且难理解的几个算法和数据结构,所以接下来的一个月主攻这几部分,比如:递归、回溯算法、贪心算法以及动态规划等。 55 | 56 | 在网上看了很多博客,发现很多都是懵懵懂懂,即使写文章的人很明白,但是那种让读者看起来非常懵懵的文章占大多数的。我自己在查找这方面文章时,也走了很多坑,所以呢,根据以前做过的题,将其综合起来进行分析,通过深层的理解,将最初的、基本的算法思想能够简单易懂的写出来分享给每个人,对我这个渣渣来说,也是挺有难度的,那就尝试的去努力做吧。 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /ReverseLinkedList.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/23 3 | Title: Reverse Linked List 4 | Difficulty: Easy 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Reverse Linked List(反转链表) 11 | 12 | Reverse a singly linked list. 13 | 14 | **Example:** 15 | 16 | ``` 17 | Input: 1->2->3->4->5->NULL 18 | Output: 5->4->3->2->1->NULL 19 | ``` 20 | 21 | **Follow up:** 22 | 23 | A linked list can be reversed either iteratively or recursively. Could you implement both? 24 | 25 | 26 | 27 | ## Solve: 28 | 29 | ###### ▉ 问题分析 30 | 31 | > 1)反转链表的我们第一能够想到的方法就是最常用的方法,声明三个指针,把头结点变为尾结点,然后下一结点拼接到尾结点的头部,一次类推。说白了就是就是直接将链表指针反转就可以实现反转链表。 32 | 33 | 34 | 35 | ###### ▉ 算法思路 36 | 37 | > 两种方法: 38 | > 39 | > - 一般反转 40 | > - 递归法 41 | > 42 | > 一般解决: 43 | > 44 | > 1)定义三个指针,分别为 Pnext、pre、current,current 存储当前结点, pre 指向反转好的结点的头结点,Pnext 存储下一结点信息。 45 | > 46 | > 2)判断当前结点是否可以反转(是否为空链表或链表大于 1 个结点)? 47 | > 48 | > 步骤: 49 | > 50 | > 1)Pnext 指针存储下一结点 。 51 | > 52 | > 2)当前结点的 next 结点是否为 null (为 null 的话当前结点就是最后的一个结点),如果为 null,将当前节点赋值为 head 头指针(断裂处)。 53 | > 54 | > 3)将 pre 指针指向的结点赋值当前节点 current 的下一结点 next。 55 | > 56 | > 4)然后让 pre 指针指向当前节点 current。 57 | > 58 | > 5)current 继续遍历, 当前节点指向 current 指向 Pnext。 59 | > 60 | > 递归法(重点分析): 61 | > 62 | > 1)先确定终止条件:当下一结点为 null 时,返回当前节点; 63 | > 64 | > 2)判断当前的链表是否为 null; 65 | > 66 | > 3)递归找到尾结点,将其存储为头结点。 67 | > 68 | > 4)此时递归的层次是第二层递归,所以要设置为头结点的下一结点就是当前第二层结点,并且将第二节点的下一结点设置为 bull。 69 | 70 | 71 | 72 | ###### ▉ 测试用例 73 | 74 | > 1)链表是空链表。 75 | > 76 | > 2)当前链表的长度小于等于 1。 77 | > 78 | > 3)输入长度大于 1 的链表。 79 | 80 | 81 | 82 | ###### ▉ 递归法 83 | 84 | ```javascript 85 | const reverseList = (head)=>{ 86 | if(head == null || head.next == null){ 87 | return head; 88 | }else{ 89 | let newhead = reverseList(head.next); 90 | head.next.next = head; 91 | head.next = null; 92 | return newhead; 93 | } 94 | } 95 | ``` 96 | 97 | 98 | 99 | ###### ▉ 性能分析 100 | 101 | > - 时间复杂度:O(n)。只需遍历整个链表就可以完成反转,时间复杂度为 O(n)。 102 | > - 空间复杂度:O(1)。只需要常量级的空间,空间复杂度为 O(1)。 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /ReverseString.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/18 3 | Title: Reverse String 4 | Difficulty: Easy 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Reverse String(反转字符串) 11 | 12 | Write a function that reverses a string. The input string is given as an array of characters `char[]`. 13 | 14 | Do not allocate extra space for another array, you must do this by **modifying the input array in-place** with O(1) extra memory. 15 | 16 | You may assume all the characters consist of [printable ascii characters](https://en.wikipedia.org/wiki/ASCII#Printable_characters). 17 | 18 | > 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 `char[]` 的形式给出。 19 | > 20 | > 不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。 21 | > 22 | > 你可以假设数组中的所有字符都是 [ASCII](https://baike.baidu.com/item/ASCII) 码表中的可打印字符。 23 | 24 | **Example 1:** 25 | 26 | ``` 27 | Input: ["h","e","l","l","o"] 28 | Output: ["o","l","l","e","h"] 29 | ``` 30 | 31 | **Example 2:** 32 | 33 | ``` 34 | Input: ["H","a","n","n","a","h"] 35 | Output: ["h","a","n","n","a","H"] 36 | ``` 37 | 38 | 39 | 40 | ## Slove: 41 | 42 | ###### ▉ 问题分析 43 | 44 | > 1)反转字符串,第一想到的就是将字符窗倒序存储到数组中,可以先输出,然后再 for 循环加入到数组中。题目中要求需要原地修改输入数组,那么我们再想一个办法。 45 | > 46 | > 2)数组中的操作,需要原地修改数组,第一想到的就是用到指针,为什么能想到用指针呢?当你做这种数组的也好还是链表的也好,做多了之后,下意识的给出解决方法然后去看看可不可行。 47 | 48 | 49 | 50 | ###### ▉ 算法思路 51 | 52 | > 通过以上分析,得出两种方法: 53 | > 54 | > - 字符串反转法 55 | > - 双指针法 56 | > 57 | > 字符串反转法: 58 | > 59 | > 1)字符串反转法很简单了,按照我们正常的思路走就可以了,首先输出数组中的字符拼接成字符串。 60 | > 61 | > 2)然后从字符串尾部开始遍历,正向输入数组。 62 | > 63 | > 双指针法: 64 | > 65 | > 1)定义两个指针,分别指向字符串头部和尾部。 66 | > 67 | > 2)两个指针指向的值进行交换。 68 | > 69 | > 3)注意终止条件。 70 | > 71 | > - 如果为偶数,当头指针 - 1 等于尾指针时,反转完毕。 72 | > - 如果为奇数,当两个指针相等时,反转完毕。 73 | 74 | 75 | 76 | ###### ▉ 测试用例 77 | 78 | > 1)空字符串。 79 | > 80 | > 2)偶数个数的字符串。 81 | > 82 | > 3)奇数个数的字符串。 83 | > 84 | > 4)长度为 1 的字符串。 85 | 86 | 87 | 88 | ###### ▉ 双指针法 89 | 90 | ```javascript 91 | var reverseString = function(s) { 92 | //判断输入的字符串是否为空 93 | if(s.length ==0) return s; 94 | //定义两个指针 95 | let low = 0; 96 | let high = s.length - 1; 97 | // 循环反转字符 98 | while(true){ 99 | // 分为奇数/偶数两种可能 100 | if(low === high || high + 1 === low) break; 101 | let temp = s[low]; 102 | s[low] = s[high]; 103 | s[high] = temp; 104 | low++; 105 | high--; 106 | } 107 | // 返回反转好的字符串 108 | return s; 109 | }; 110 | ``` 111 | 112 | 113 | 114 | ###### ▉ 字符串反转法 115 | 116 | ```javascript 117 | var reverseString = function(s) { 118 | //判断输入的字符串是否为空 119 | if(s.length ==0) return s; 120 | let str = ""; 121 | // 输出字符串 122 | for(let i in s){ 123 | str = str+`${s[i]}`; 124 | } 125 | // 反转字符串输入 126 | for(let i = s.length - 1,j = 0;i >= 0;i--,j++){ 127 | s[j] = str.charAt(i); 128 | } 129 | //返回反转好的字符串 130 | return s; 131 | }; 132 | ``` 133 | 134 | 135 | 136 | ###### ▉ 性能分析 137 | 138 | > 字符串反转法 139 | > 140 | > - 时间复杂度:O(n)。遍历整个数组,时间复杂度为 O(n)。 141 | > - 空间复杂度:O(n)。输出数组需要额外的存储空间,如果用数组存储,需要开辟大小为 n 大小的内存空间。如果需要数组输出拼接字符串,空间复杂度为 O(1) 了。 142 | > 143 | > 双指针法: 144 | > 145 | > - 时间复杂度:O(n)。需要遍历 n/2 的数据,时间复杂度为 O(n)。 146 | > - 空间复杂度:O(n)。只需常量级别的内存空间,空间复杂度为O(1)。 147 | 148 | 149 | 150 | ###### ▉ 考查内容 151 | 152 | > 1)对字符串的基本操作。 153 | > 154 | > 2)数组的基本操作。 155 | 156 | -------------------------------------------------------------------------------- /ReverseWordsInaString.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/20 3 | Title: Reverse Words In a String 4 | Difficulty: Midumn 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Reverse Words In a String(翻转字符串里的单词) 11 | 12 | Given an input string, reverse the string word by word. 13 | 14 | > 给定一个字符串,逐个翻转字符串中的每个单词。 15 | 16 | **Example 1:** 17 | 18 | ``` 19 | Input: "the sky is blue" 20 | Output: "blue is sky the" 21 | ``` 22 | 23 | **Example 2:** 24 | 25 | ``` 26 | Input: " hello world! " 27 | Output: "world! hello" 28 | Explanation: Your reversed string should not contain leading or trailing spaces. 29 | ``` 30 | 31 | **Example 3:** 32 | 33 | ``` 34 | Input: "a good example" 35 | Output: "example good a" 36 | Explanation: You need to reduce multiple spaces between two words to a single space in the reversed string. 37 | ``` 38 | 39 | 40 | 41 | **Note:** 42 | 43 | - A word is defined as a sequence of non-space characters. 44 | - Input string may contain leading or trailing spaces. However, your reversed string should not contain leading or trailing spaces. 45 | - You need to reduce multiple spaces between two words to a single space in the reversed string. 46 | 47 | > **说明:** 48 | > 49 | > - 无空格字符构成一个单词。 50 | > - 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 51 | > - 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 52 | 53 | 54 | 55 | ## Solve: 56 | 57 | ###### ▉ 问题分析 58 | 59 | > 60 | 61 | 62 | 63 | ###### ▉ 算法思路 64 | 65 | > 66 | 67 | 68 | 69 | ###### ▉ 测试用例 70 | 71 | > 72 | 73 | 74 | 75 | ###### ▉ 代码实现 76 | 77 | ```javascript 78 | var reverseWords = function(s) { 79 | // 判断当前的单词是否为空字符串 80 | if(s.length === 0) return ""; 81 | 82 | let [index,len] = [0,s.length]; 83 | let word = ""; 84 | let result = ""; 85 | 86 | while(index < len){ 87 | // 跳过空格 88 | while(index < len && s.charAt(index) == ' '){ 89 | index ++; 90 | } 91 | 92 | // 反转单词 93 | while(index < len && s.charAt(index) !== ' '){ 94 | word = `${word}${s.charAt(index)}`; 95 | index ++; 96 | } 97 | // 拼接 98 | result = word + ' ' + result; 99 | word = ""; 100 | } 101 | return result.trim(); 102 | }; 103 | ``` 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /SlidingWindowMaximum.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/16 3 | Title: Sliding Window Maximum 4 | Difficulty: Difficulty 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Sliding Window Maximum 11 | 12 | Given an array *nums*, there is a sliding window of size *k* which is moving from the very left of the array to the very right. You can only see the *k* numbers in the window. Each time the sliding window moves right by one position. Return the max sliding window. 13 | 14 | > 给定一个数组 *nums*,有一个大小为 *k* 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 *k* 内的数字。滑动窗口每次只向右移动一位。 15 | > 16 | > 返回滑动窗口最大值。 17 | 18 | **Example:** 19 | 20 | ``` 21 | Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3 22 | Output: [3,3,5,5,6,7] 23 | Explanation: 24 | 25 | Window position Max 26 | --------------- ----- 27 | [1 3 -1] -3 5 3 6 7 3 28 | 1 [3 -1 -3] 5 3 6 7 3 29 | 1 3 [-1 -3 5] 3 6 7 5 30 | 1 3 -1 [-3 5 3] 6 7 5 31 | 1 3 -1 -3 [5 3 6] 7 6 32 | 1 3 -1 -3 5 [3 6 7] 7 33 | ``` 34 | 35 | **Note:** 36 | You may assume *k* is always valid, 1 ≤ k ≤ input array's size for non-empty array. 37 | 38 | **Follow up:** 39 | Could you solve it in linear time? 40 | 41 | 42 | 43 | ## Solve: 44 | 45 | ###### ▉ 问题分析 46 | 47 | > 暴力破解法 48 | > 49 | > 1)看到这个题目最容易想到的就是暴力破解法,借助一个 for 循环,两个变量,整体移动窗口,然后每移动一次就在大小为 k 的窗口求出最大值。 50 | > 51 | > 2)但是这样的解决效率非常低,如果数据非常大时,共有 n1 个数据,窗口大小为 n2(n1 远远大于 n2),时间复杂度为 n2(n1 - n2) 。也就是 n1 * n2,最坏时间复杂度为 n^2。 52 | > 53 | > 优先级队列 54 | > 55 | > 1)每次移动窗口求最大值,以及在动态数据中求最大值,我们想到的就是优先级队列,而优先级队列的实现是堆这种数据结构,这道题用堆解决效率更高。如果对堆不熟悉,赶紧给自己补补功课吧!底部有我写的文章链接。 56 | > 57 | > 2)通过堆的优化,向堆中插入数据时间复杂度为 logn ,所以时间复杂度为 nlogn。 58 | 59 | 60 | 61 | ###### ▉ 算法思路 62 | 63 | > 暴力破解法: 64 | > 65 | > 1)用两个指针,分别指向窗口的起始位置和终止位置,然后遍历窗口中的数据,求出最大值;向前移动两个指针,然后操作,直到遍历数据完成位置。 66 | > 67 | > 优先级队列: 68 | > 69 | > 1)需要维护大小为 k 的大顶堆,堆顶就是当前窗口最大的数据,当移动窗口时,如果插入的数据大于堆顶的数据,将其加入到结果集中。同时要删除数据,如果删除的数据为最大数据且插入的数据小于删除的数据时,向大小为 k 的以 logn 的时间复杂度插入,返回堆顶元素。 70 | 71 | 72 | 73 | ###### ▉ 暴力破解法 74 | 75 | ```javascript 76 | 77 | var maxSlidingWindow = function(nums, k) { 78 | if(k > nums.length || k === 0) return []; 79 | let res = [], maxIndex = -1; 80 | for(let l = 0, r = k-1;r < nums.length;l++, r++){ 81 | if(maxIndex < l){ 82 | // 遍历求出最大值 83 | let index = l; 84 | for(let i = l;i <= r;i++) { 85 | if(nums[i] > nums[index]) index = i; 86 | } 87 | maxIndex = index; 88 | } 89 | if(nums[r] > nums[maxIndex]){ 90 | maxIndex = r; 91 | } 92 | res.push(nums[maxIndex]); 93 | } 94 | return res; 95 | }; 96 | ``` 97 | 98 | 99 | 100 | ###### ▉ 优先级队列 101 | 102 | ```javascript 103 | let count = 0; 104 | let heap = []; 105 | let n = 0; 106 | var maxSlidingWindow = function(nums, k) { 107 | let pos = k; 108 | n = k; 109 | let result = []; 110 | let len = nums.length; 111 | 112 | // 判断数组和最大窗口树是否为空 113 | if(nums.length === 0 || k === 0) return result; 114 | 115 | // 建大顶堆 116 | let j = 0 117 | for(;j < k; j++){ 118 | insert(nums[j]); 119 | } 120 | result.push(heap[1]); 121 | 122 | // 移动窗口 123 | while(len - pos > 0){ 124 | if(nums[k] > heap[1]){ 125 | result.push(nums[k]); 126 | insert(nums[k]); 127 | nums.shift(); 128 | pos++; 129 | }else{ 130 | if(nums.shift() === heap[1]){ 131 | removeMax(); 132 | } 133 | insert(nums[k-1]); 134 | result.push(heap[1]); 135 | pos++; 136 | } 137 | } 138 | return result; 139 | }; 140 | 141 | 142 | 143 | // 插入数据 144 | const insert = (data) =>{ 145 | //判断堆满 146 | // if(count >= n) return; // >= 147 | 148 | // 插入到数组尾部 149 | count++ 150 | heap[count] = data; 151 | 152 | //自下而上堆化 153 | let i = count; 154 | while(i / 2 > 0 && heap[i] > heap[parseInt(i/2)]){ 155 | swap(heap,i,parseInt(i/2)); 156 | i = parseInt(i/2); 157 | } 158 | } 159 | 160 | // 两个数组内元素交换 161 | swap = (arr,x,y) =>{ 162 | let temp = arr[x]; 163 | arr[x] = arr[y]; 164 | arr[y] = temp; 165 | } 166 | 167 | // 堆的删除 168 | const removeMax = () =>{ 169 | // 判断堆空 170 | if(count <= 0) return ; 171 | 172 | // 最大数据移到最后删除 173 | heap[1] = heap[count]; 174 | 175 | // 长度减一 176 | count--; 177 | // 删除数据 178 | heap.pop(); 179 | 180 | // 从上到下堆化 181 | heapify(heap,count,1); 182 | } 183 | 184 | // 从上到下堆化 185 | const heapify = (heap,count,i) =>{ 186 | while(true){ 187 | // 存储堆子节点的最大值下标 188 | let maxPos = i; 189 | 190 | // 左子节点比父节点大 191 | if(i*2 < n && heap[i*2] > heap[i]) maxPos = i*2; 192 | // 右子节点比父节点大 193 | if(i*2+1 <= n && heap[i*2+1] > heap[maxPos]) maxPos = i*2+1; 194 | 195 | // 如果没有发生替换,则说明该堆只有一个结点(父节点)或子节点都小于父节点 196 | if(maxPos === i) break; 197 | 198 | // 交换 199 | swap(heap,maxPos,i); 200 | // 继续堆化 201 | i = maxPos; 202 | } 203 | } 204 | ``` 205 | 206 | 207 | 208 | ###### ▉ 性能分析 209 | 210 | > 暴力破解法 211 | > 212 | > - 时间复杂度:O(n^2). 213 | > - 空间复杂度:O(1). 214 | > 215 | > 优先级队列 216 | > 217 | > - 时间复杂度:nlogn. 218 | > - 空间复杂度:O(1). 219 | 220 | 221 | 222 | ###### ▉ 扩展 223 | 224 | > 堆: 225 | > 226 | > 1)堆插入、删除操作 227 | > 228 | > 2)如何实现一个堆? 229 | > 230 | > 3)堆排序 231 | > 232 | > 4)堆的应用 233 | 234 | 详细查看写的另一篇关于堆的文章:[数据结构与算法之美【堆】](http://luxiangqiang.xn--6qq986b3xl/2019/01/07/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E4%B9%8B%E7%BE%8E%E3%80%90%E5%A0%86%E3%80%91/) 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | -------------------------------------------------------------------------------- /StringToInteger.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/19 3 | Title: String To Integer 4 | Difficulty: Medium 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:String To Integer(字符串转换整数 (atoi)) 11 | 12 | Implement `atoi` which converts a string to an integer. 13 | 14 | The function first discards as many whitespace characters as necessary until the first non-whitespace character is found. Then, starting from this character, takes an optional initial plus or minus sign followed by as many numerical digits as possible, and interprets them as a numerical value. 15 | 16 | The string can contain additional characters after those that form the integral number, which are ignored and have no effect on the behavior of this function. 17 | 18 | If the first sequence of non-whitespace characters in str is not a valid integral number, or if no such sequence exists because either str is empty or it contains only whitespace characters, no conversion is performed. 19 | 20 | If no valid conversion could be performed, a zero value is returned. 21 | 22 | > 请你来实现一个 `atoi` 函数,使其能将字符串转换成整数。 23 | > 24 | > 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。 25 | > 26 | > 当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。 27 | > 28 | > 该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。 29 | > 30 | > 注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。 31 | > 32 | > 在任何情况下,若函数不能进行有效的转换时,请返回 0。 33 | > 34 | > **说明:** 35 | > 36 | > 假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,qing返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。 37 | 38 | **Note:** 39 | 40 | - Only the space character `' '` is considered as whitespace character. 41 | - Assume we are dealing with an environment which could only store integers within the 32-bit signed integer range: [−231, 231 − 1]. If the numerical value is out of the range of representable values, INT_MAX (231 − 1) or INT_MIN (−231) is returned. 42 | 43 | **Example 1:** 44 | 45 | ``` 46 | Input: "42" 47 | Output: 42 48 | ``` 49 | 50 | **Example 2:** 51 | 52 | ``` 53 | Input: " -42" 54 | Output: -42 55 | Explanation: The first non-whitespace character is '-', which is the minus sign. 56 | Then take as many numerical digits as possible, which gets 42. 57 | ``` 58 | 59 | **Example 3:** 60 | 61 | ``` 62 | Input: "4193 with words" 63 | Output: 4193 64 | Explanation: Conversion stops at digit '3' as the next character is not a numerical digit. 65 | ``` 66 | 67 | **Example 4:** 68 | 69 | ``` 70 | Input: "words and 987" 71 | Output: 0 72 | Explanation: The first non-whitespace character is 'w', which is not a numerical 73 | digit or a +/- sign. Therefore no valid conversion could be performed. 74 | ``` 75 | 76 | **Example 5:** 77 | 78 | ``` 79 | Input: "-91283472332" 80 | Output: -2147483648 81 | Explanation: The number "-91283472332" is out of the range of a 32-bit signed integer. 82 | Thefore INT_MIN (−231) is returned. 83 | ``` 84 | 85 | 86 | 87 | ## Solve: 88 | 89 | ###### ▉ 问题分析 90 | 91 | > 将字符串转化为数字,根据题目要求可以分出一下几种情况: 92 | > 93 | > - 如果字符串都是数字,直接进行转换。 94 | > - 如果字符串中只有数字和空格,要跳过空格,只输出数字。 95 | > - 数字前正负号要保留。 96 | > - 如果字符串中有数字和字母。 97 | > - 字母在数字前,直接返回数字 0 ; 98 | > - 字母在数字后,忽略数字后的字母; 99 | > - 如果输出的数字大于限制值,输出规定的限制值。 100 | > - 如果输出的数字小于限制值,输出规定的限制值。 101 | > 102 | > 上述将所有条件弄明白之后,然后进行规划解决上述问题: 103 | 104 | 105 | 106 | ###### ▉ 算法思路 107 | 108 | > 整体将字符串转化为每个字符来进行判断: 109 | > 110 | > 1)空格:如果当前的字符为空格,直接跳过,判断下一字符。 111 | > 112 | > 2)符号位:如果判断当前的字符为 “ + ” 或 “ - “,用 sign 存储 1 或 -1,最后乘最后得出的数字。 113 | > 114 | > 3)只取数字:判断当前是否满足条件(0<= c <=9),不满足条件直接跳出循环。 115 | > 116 | > 4)虽然取到数字,判断数字是否超出最大值/最小值,我们用判断位数,最大值为 7 位,每遍历一个数组,我们就进行乘以 10 加 个位数。 117 | 118 | 119 | 120 | ###### ▉ 测试用例 121 | 122 | > 1)只输入数字字符串 123 | > 124 | > 2)带空格、负数的字符串 125 | > 126 | > 3)带字母的字符串 127 | > 128 | > 4)空字符串 129 | > 130 | > 5)超出限制的字符串 131 | 132 | 133 | 134 | ###### ▉ 代码实现 135 | 136 | ```javascript 137 | var myAtoi = function(str) { 138 | // 最大值与最小值 139 | let MAX_VALUE = Math.pow(2,31)-1; 140 | let MIN_VALUE = -Math.pow(2,31); 141 | // 判断字符窗是否为空 142 | if(str == null || str.length == 0) return 0; 143 | // 初始化 144 | // sign:记录正负号 145 | // base: 记录数字的位数 146 | let [index,base,sign,len] = [0,0,1,str.length]; 147 | 148 | // 跳过空格 149 | while(index < len && str.charAt(index) == ' '){ 150 | index++; 151 | } 152 | 153 | // 获取符号位 154 | if(index < len && (str.charAt(index) == "+") || str.charAt(index) == '-'){ // 记录正负号 155 | sign = 1 - 2 * ((str.charAt(index++) == '-') ? 1 : 0); 156 | } 157 | 158 | // 只取数字,碰到非数字退出循环 159 | while(index < len && parseInt(str.charAt(index)) >= 0 && parseInt(str.charAt(index)) <= 9){ 160 | // 溢出判断,MAX_VALUE 的个位为 7 161 | if(base > parseInt(MAX_VALUE/10) || (base == parseInt(MAX_VALUE/10) && parseInt(str.charAt(index)) > 7)){ 162 | if(sign == 1){ 163 | return MAX_VALUE; 164 | }else{ 165 | return MIN_VALUE; 166 | } 167 | } 168 | // 记录位数 169 | base = base * 10 + parseInt(str.charAt(index++)); 170 | } 171 | // 返回符号位 * 当前字符串中的数字 172 | return sign * base; 173 | }; 174 | ``` 175 | 176 | 177 | 178 | ###### ▉ 考查内容 179 | 180 | > 1)对字符串的基本操作。 181 | > 182 | > 2)代码的全面性、鲁棒性。 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /TwoSum.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/1 3 | Title:Two Sum 4 | Difficulty: simple 5 | Author:小鹿 6 | --- 7 | 8 | 9 | 10 | #### 题目一:Two Sum 11 | 12 | Given an array of integers, return **indices** of the two numbers such that they add up to a specific target. 13 | 14 | You may assume that each input would have **exactly** one solution, and you may not use the *same* element twice. 15 | 16 | **Example:** 17 | 18 | ```javascript 19 | Given nums = [2, 7, 11, 15], target = 9, 20 | 21 | Because nums[0] + nums[1] = 2 + 7 = 9, 22 | return [0, 1] 23 | ``` 24 | 25 | 26 | 27 | **Solve:** 28 | 29 | ###### ▉ 法一:暴力破解法 30 | 31 | > 算法思路:用目标值减去数组中的某一个数值,查找差值是否在数组中存在。 32 | 33 | ```javascript 34 | /** 35 | * 步骤: 36 | * 1)外循环:target 值要一一减去数组中的元素,记录差值 37 | * 2)内循环:拿着差值去数组中比较判断是否存在 38 | * 3)如果存在:返回两个元素的下标 39 | * 4)如果不存在:继续遍历 40 | * 41 | * 性能分析: 42 | * 1)时间复杂度分析:两层 for 循环,所以时间复杂度为 O(n^2) 43 | * 2)空间复杂度分析:不需要额外的空间,所以空间复杂度为 O(1) 44 | */ 45 | var twoSum = function(nums, target) { 46 | for(let j = 0;j < nums.length; j++){ 47 | subtract = target - nums[j]; 48 | for(let i = 0;i < nums.length; i++){ 49 | if(nums[i] == subtract && i !== j){ 50 | return [j,i] 51 | }else{ 52 | continue; 53 | } 54 | } 55 | } 56 | return false; 57 | }; 58 | //测试 59 | const nums = [2,7,11,15]; 60 | console.log(twoSum(nums,26)); 61 | ``` 62 | 63 | 64 | 65 | ###### ▉ 法二:两遍哈希表 66 | 67 | > 算法思路:先遍历数组将下标对应的元素存到散列表,然后同目标值减去的值去散列表中查看是否存在。 68 | 69 | ```javascript 70 | /** 71 | * 步骤: 72 | * 1)遍历数组数据,将根据下标和元素值存放到散列表中。 73 | * 2)目标值减去数组元素差值并在散列表中查找。 74 | * 3)如果存在,返回两元素的下标。 75 | * 4)不存在继续遍历 76 | * 77 | * 性能分析: 78 | * 1)时间复杂度分析:随机访问的时间复杂度为O(1),但是需要遍历所有数据,所以时间复杂度为 O(n)。 79 | * 2)空间复杂度分析:需要额外的 n 大小的空间存储散列表,空间复杂度为 O(n)。 80 | */ 81 | var twoSum = function(nums, target) { 82 | var map = new Map(); 83 | for(let i = 0;i < nums.length; i++){ 84 | map.set(nums[i],i) 85 | } 86 | for (let j = 0; j < nums.length; j++) { 87 | substra = target - nums[j]; 88 | if(map.has(substra) && map.get(substra) !== j){ 89 | return [j,map.get(substra)] 90 | } 91 | } 92 | } 93 | 94 | // 测试 95 | const nums = [2,7,11,15]; 96 | console.log(twoSum(nums,9)); 97 | ``` 98 | 99 | 100 | 101 | ###### ▉ 法三:一遍哈希表 102 | 103 | > 算法思路:遍历目标值减去数组元素的差值同时判断该值在散列表中是否存在差值,如果存在,则返回;否则将数据加入到散列表中。 104 | 105 | ```javascript 106 | /** 107 | * 步骤: 108 | * 1)遍历目标值减去数组元素的差值同时判断该值在散列表中是否存在差值 109 | * 2)存在该差值,返回该元素下标 110 | * 3)不存在,将该差值存储到散列表中继续遍历。 111 | * 112 | * 性能分析: 113 | * 1)时间复杂度分析:随机访问的时间复杂度为O(1),但是需要遍历所有数据,所以时间复杂度为 O(n)。 114 | * 2)空间复杂度分析:需要额外的 n 大小的空间存储散列表,空间复杂度为 O(n)。 115 | */ 116 | var twoSum = function(nums, target) { 117 | var map = new Map(); 118 | for(let i = 0;i < nums.length; i++){ 119 | substra = target - nums[i]; 120 | if(map.has(substra)){ 121 | return [i,map.get(substra)] 122 | } 123 | map.set(nums[i],i) 124 | } 125 | return false; 126 | } 127 | 128 | // 测试 129 | const nums = [2,7,11,15]; 130 | console.log(twoSum(nums,26)); 131 | ``` 132 | 133 | 134 | 135 | ###### ▉ 总结: 136 | 137 | > 1、涉及到查找、判断是否存在,相关的数据结构有**散列表**、平衡二叉树、二分查找、跳表、二叉查找树。 138 | > 139 | > 2、使用数据结构的时候注意**适用条件**。 140 | > 141 | > 3、注意对时间复杂度、空间复杂度的优化策略。 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /VaildSudoku.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/29 3 | Title: Valid Sudoku 4 | Difficulty: Medium 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Valid Sudoku (有效的数独) 11 | 12 | Determine if a 9x9 Sudoku board is valid. Only the filled cells need to be validated **according to the following rules**: 13 | 14 | 1. Each row must contain the digits `1-9` without repetition. 15 | 2. Each column must contain the digits `1-9` without repetition. 16 | 3. Each of the 9 `3x3` sub-boxes of the grid must contain the digits `1-9` without repetition. 17 | 18 | ![img](https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Sudoku-by-L2G-20050714.svg/250px-Sudoku-by-L2G-20050714.svg.png) 19 | 20 | A partially filled sudoku which is valid. 21 | 22 | The Sudoku board could be partially filled, where empty cells are filled with the character `'.'`. 23 | 24 | **Example 1:** 25 | 26 | ``` 27 | Input: 28 | [ 29 | ["5","3",".",".","7",".",".",".","."], 30 | ["6",".",".","1","9","5",".",".","."], 31 | [".","9","8",".",".",".",".","6","."], 32 | ["8",".",".",".","6",".",".",".","3"], 33 | ["4",".",".","8",".","3",".",".","1"], 34 | ["7",".",".",".","2",".",".",".","6"], 35 | [".","6",".",".",".",".","2","8","."], 36 | [".",".",".","4","1","9",".",".","5"], 37 | [".",".",".",".","8",".",".","7","9"] 38 | ] 39 | Output: true 40 | ``` 41 | 42 | **Example 2:** 43 | 44 | ``` 45 | Input: 46 | [ 47 | ["8","3",".",".","7",".",".",".","."], 48 | ["6",".",".","1","9","5",".",".","."], 49 | [".","9","8",".",".",".",".","6","."], 50 | ["8",".",".",".","6",".",".",".","3"], 51 | ["4",".",".","8",".","3",".",".","1"], 52 | ["7",".",".",".","2",".",".",".","6"], 53 | [".","6",".",".",".",".","2","8","."], 54 | [".",".",".","4","1","9",".",".","5"], 55 | [".",".",".",".","8",".",".","7","9"] 56 | ] 57 | Output: false 58 | Explanation: Same as Example 1, except with the 5 in the top left corner being 59 | modified to 8. Since there are two 8's in the top left 3x3 sub-box, it is invalid. 60 | ``` 61 | 62 | **Note:** 63 | 64 | - A Sudoku board (partially filled) could be valid but is not necessarily solvable. 65 | - Only the filled cells need to be validated according to the mentioned rules. 66 | - The given board contain only digits `1-9` and the character `'.'`. 67 | - The given board size is always `9x9`. 68 | 69 | 70 | 71 | ## Solve: 72 | 73 | ###### ▉ 问题分析 74 | 75 | 76 | 77 | ###### ▉ 算法思路 78 | 79 | 80 | 81 | ###### ▉ 代码实现 82 | 83 | ```javascript 84 | var isValidSudoku = function(board) { 85 | let mapRow = new Set(); 86 | let mapColumn = new Set(); 87 | let mapBorder = new Set(); 88 | for(let i = 0;i < 9;i++){ 89 | for(let j = 0;j < 9;j++){ 90 | // 判断当前每行 91 | if(board[i][j] !== '.'){ 92 | if(mapRow.has(board[i][j])){ 93 | return false; 94 | }else{ 95 | mapRow.add(board[i][j]) 96 | } 97 | } 98 | // 判断当前每列 99 | if(board[j][i] !== '.'){ 100 | if(mapColumn.has(board[j][i])){ 101 | return false; 102 | }else{ 103 | mapColumn.add(board[j][i]) 104 | } 105 | } 106 | } 107 | mapRow.clear(); 108 | mapColumn.clear(); 109 | } 110 | // 判断当前 3x3 格子 111 | let arr = [0,3,6] 112 | for(let i = 0;i < 3;i ++ ){ 113 | for(let j = 0;j < 3;j ++ ){ 114 | for(let x1 = arr[i];x1 < arr[i] + 3;x1++){ 115 | for(let y1 = arr[j];y1 < arr[j] + 3;y1++){ 116 | if(board[x1][y1] !== '.'){ 117 | if(mapBorder.has(board[x1][y1])){ 118 | return false; 119 | }else{ 120 | mapBorder.add(board[x1][y1]) 121 | } 122 | } 123 | } 124 | } 125 | mapBorder.clear(); 126 | } 127 | } 128 | return true; 129 | }; 130 | ``` 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /VaildataBinarySearchTree.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/24 3 | Title: Vaildata Binary Search Tree 4 | Difficulty: Medium 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ### 题目:Vaildata Binary Search Tree(验证二叉搜索树) 11 | 12 | Given a binary tree, determine if it is a valid binary search tree (BST). 13 | 14 | Assume a BST is defined as follows: 15 | 16 | - The left subtree of a node contains only nodes with keys **less than** the node's key. 17 | - The right subtree of a node contains only nodes with keys **greater than** the node's key. 18 | - Both the left and right subtrees must also be binary search trees. 19 | 20 | > 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 21 | > 22 | > 假设一个二叉搜索树具有如下特征: 23 | > 24 | > - 节点的左子树只包含**小于**当前节点的数。 25 | > - 节点的右子树只包含**大于**当前节点的数。 26 | > - 所有左子树和右子树自身必须也是二叉搜索树。 27 | 28 | **Example 1:** 29 | 30 | ``` 31 | Input: 32 | 2 33 | / \ 34 | 1 3 35 | Output: true 36 | ``` 37 | 38 | **Example 2:** 39 | 40 | ``` 41 | 5 42 | / \ 43 | 1 4 44 | / \ 45 | 3 6 46 | Output: false 47 | Explanation: The input is: [5,1,4,null,null,3,6]. The root node's value 48 | is 5 but its right child's value is 4. 49 | ``` 50 | 51 | 52 | 53 | ## Solve: 54 | 55 | ###### ▉ 问题分析 56 | 57 | > 看到此题的入手点就是上方提出的三点二叉搜索树的三点要求: 58 | > 59 | > - 节点的左子树只包含**小于**当前节点的数。 60 | > - 节点的右子树只包含**大于**当前节点的数。 61 | > - 所有左子树和右子树自身必须也是二叉搜索树。 62 | > 63 | > 1)以上三点要求最容易解决的就是一个中序遍历,判断遍历出的每个元素后一个元素是否大于前一个元素,如果不符合条件,那么就不是一个二分搜索树。 64 | 65 | 66 | 67 | ###### ▉ 算法思路 68 | 69 | > 1)定义全局的 boolean 变量,用来返回是否为 二叉搜索树。 70 | > 71 | > 2)定义一个边界值赋予 max 变量。每遍历一次,如果符合前后大小的要求,就将当前节点的值赋值给 max 变量,用于下一次遍历的结点的大小比较。如果不符合要求,我们将其布尔变量置为 false。 72 | > 73 | > 3)整个过程是用递归来解决的,在理解上还是有点不符合常规思路的。也是整个问题分析中最重要的一点。 74 | 75 | 76 | 77 | ###### ▉ 代码实现 78 | 79 | ```javascript 80 | var isValidBST = function(root) { 81 | // boolean 变量 82 | let isValidBSTFlag = true; 83 | // 最大值变量 84 | let max = -Number.MAX_VALUE; 85 | const orderSearch = root => { 86 | // 终止条件(判断当前结点是否为 null) 87 | if (root) { 88 | // 中序遍历 89 | orderSearch(root.left); 90 | // 判断遍历前后的值是否逐渐升序 91 | if (root.val > max) { 92 | // 存储当前结点值,进行下一次比较 93 | max = root.val; 94 | } else { 95 | // 当前节点值小于前一结点值,返回 false 96 | isValidBSTFlag = false; 97 | } 98 | orderSearch(root.right); 99 | } 100 | } 101 | orderSearch(root); 102 | return isValidBSTFlag; 103 | }; 104 | ``` 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /ValidParentheses.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/11 3 | Title: Valid Parentheses 4 | Difficulty: Easy 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:Valid Parentheses 11 | 12 | Given a string containing just the characters `'('`, `')'`, `'{'`, `'}'`, `'['` and `']'`, determine if the input string is valid. 13 | 14 | An input string is valid if: 15 | 16 | 1. Open brackets must be closed by the same type of brackets. 17 | 2. Open brackets must be closed in the correct order. 18 | 19 | Note that an empty string is also considered valid. 20 | 21 | 给定一个只包括 `'('`,`')'`,`'{'`,`'}'`,`'['`,`']'` 的字符串,判断字符串是否有效。 22 | 23 | 有效字符串需满足: 24 | 25 | 1. 左括号必须用相同类型的右括号闭合。 26 | 2. 左括号必须以正确的顺序闭合。 27 | 28 | 注意空字符串可被认为是有效字符串。 29 | 30 | **Example 1:** 31 | 32 | ``` 33 | Input: "()" 34 | Output: true 35 | ``` 36 | 37 | **Example 2:** 38 | 39 | ``` 40 | Input: "()[]{}" 41 | Output: true 42 | ``` 43 | 44 | **Example 3:** 45 | 46 | ``` 47 | Input: "(]" 48 | Output: false 49 | ``` 50 | 51 | **Example 4:** 52 | 53 | ``` 54 | Input: "([)]" 55 | Output: false 56 | ``` 57 | 58 | **Example 5:** 59 | 60 | ``` 61 | Input: "{[]}" 62 | Output: true 63 | ``` 64 | 65 | 66 | 67 | ## Solve: 68 | 69 | #### ▉ 算法思路 70 | 71 | > 1、首先,我们通过上边的例子可以分析出什么样子括号匹配是复合物条件的,两种情况。 72 | > 73 | > - 第一种(非嵌套情况):`{} []` ; 74 | > - 第二种(嵌套情况):`{ [ ( ) ] }` 。 75 | > 76 | > 除去这两种情况都不是符合条件的。 77 | > 78 | > 2、然后,我们将这些括号自右向左看做栈结构,右侧是栈顶,左侧是栈尾。 79 | > 80 | > 3、如果编译器中的括号左括号,我们就入栈(左括号不用检查匹配);如果是右括号,就取出栈顶元素检查是否匹配。(提前将成对的括号通过键值对的方式存到散列表中) 81 | > 82 | > 4、如果匹配,就出栈。否则,就返回 false; 83 | 84 | 85 | 86 | 87 | 88 | #### ▉ 代码实现 89 | 90 | > 下方代码在标准的 Leetcode 测试中并不是最省内存和效率高的,因为我们用到了 Map,在内 91 | 92 | ```javascript 93 | var isValid = function(s) { 94 | let stack = []; 95 | //将括号匹配存入散列表中 96 | let map = new Map(); 97 | map.set(")","("); 98 | map.set("]","["); 99 | map.set("}","{"); 100 | // 取出字符串中的括号 101 | for(let i = 0; i < s.length; i++){ 102 | let c = s[i]; 103 | //如果是右括号,如果栈中不为空就出栈栈顶数据 104 | if(map.has(c)){ 105 | //判断栈此时是否为0 106 | if(stack.length !== 0){ 107 | //如果栈顶元素不相同,就返回 false 108 | if(stack.pop() !== map.get(c)){ 109 | return false; 110 | } 111 | //如果此时栈内无元素,返回false 112 | }else{ 113 | return false; 114 | } 115 | }else{ 116 | //如果是左括号,就进栈 117 | stack.push(c); 118 | } 119 | } 120 | //如果栈为空,括号全部匹配成功 121 | return stack.length === 0; 122 | }; 123 | let str = "({(})"; 124 | console.log(isValid(str)); 125 | ``` 126 | 127 | 128 | 129 | #### ▉ 代码改进 130 | 131 | > 1)该改进用对象替代了 Map,节省了内存空间。 132 | > 133 | > 2)在判断时,没有用到提前存储的结构,直接使用当遇到左括号是直接入栈,提高了执行效率。 134 | 135 | ``` 136 | var isValid = function(s) { 137 | let stack = []; 138 | var obj = { 139 | "]": "[", 140 | "}": "{", 141 | ")": "(", 142 | }; 143 | 144 | for(var i = 0; i < s.length; i++) { 145 | if(s[i] === "[" || s[i] === "{" || s[i] === "(") { 146 | stack.push(s[i]); 147 | } else { 148 | var key = stack.pop(); 149 | if(maps[key] !== s[i]) { 150 | return false; 151 | } 152 | } 153 | } 154 | if(!stack.length) { 155 | return true; 156 | } 157 | return false; 158 | }; 159 | ``` 160 | 161 | 162 | 163 | #### ▉ 复杂度分析 164 | 165 | > **时间复杂度: **O(n)。只需要遍历一遍字符串中的字符,入栈和出栈的时间复杂度为 O(1)。 166 | > 167 | > **空间复杂度:** O(n)。当只有左括号近栈,没有右括号进行匹配的时候是最糟糕的情况,所有括号都在栈内。例如:{{{{{{{{{ 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /images/LinkedListCycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JS-LeetCode/049417e76f84ad786a82410965f34bd53e1e6b13/images/LinkedListCycle.png -------------------------------------------------------------------------------- /images/LinkedListCycle2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JS-LeetCode/049417e76f84ad786a82410965f34bd53e1e6b13/images/LinkedListCycle2.png -------------------------------------------------------------------------------- /images/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JS-LeetCode/049417e76f84ad786a82410965f34bd53e1e6b13/images/title.png -------------------------------------------------------------------------------- /images/title2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JS-LeetCode/049417e76f84ad786a82410965f34bd53e1e6b13/images/title2.png -------------------------------------------------------------------------------- /sqrtx.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/4/17 3 | Title: sqrt(x) 4 | Difficulty: Easy 5 | Author: 小鹿 6 | --- 7 | 8 | 9 | 10 | ## 题目:sqrt(x) 11 | 12 | Implement `int sqrt(int x)`. 13 | 14 | Compute and return the square root of *x*, where *x* is guaranteed to be a non-negative integer. 15 | 16 | Since the return type is an integer, the decimal digits are truncated and only the integer part of the result is returned. 17 | 18 | > 实现 `int sqrt(int x)` 函数。 19 | > 20 | > 计算并返回 *x* 的平方根,其中 *x* 是非负整数。「」 21 | > 22 | > 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 23 | 24 | **Example 1:** 25 | 26 | ``` 27 | Input: 4 28 | Output: 2 29 | ``` 30 | 31 | **Example 2:** 32 | 33 | ``` 34 | Input: 8 35 | Output: 2 36 | Explanation: The square root of 8 is 2.82842..., and since 37 | the decimal part is truncated, 2 is returned. 38 | ``` 39 | 40 | 41 | 42 | ## Solve: 43 | 44 | ###### ▉ 问题分析 45 | 46 | > 1)根据题目要求,求一个指定树的平方根,第一要想到的是开平方根是没有规律可循的,可能想到一个暴力破解法,从 1 开始遍历,直到满足 `k^2 < x `且 `(k+1)^2 > x` 为止。 47 | > 48 | > 2) 你可能想到这种方法效率太低,需要从 1 开始,如果 x 很大,岂不是需要遍历很多?能不能规定一个范围,在这个范围中查找开平方根呢?你会想到,所有数的开平方根得到的值是永远小于等于自身的(0 是自身),所以 x 的开平方根的值的范围一定在 0 < k < x 之间。 49 | > 50 | > 3)要想在这个区间快速定位找到一个满足条件的 x ,最高效的方法莫过于二分查找,但是可能存在小数,这又涉及到二分查找的四个变体(二分查找的变形)过程。如果你之前没有连接过,没关系,请看我之前记载的一篇文章。 51 | > 52 | > 4)虽然我们已经确定了解题方法,但是这时候不要着急,想一想这个问题是否满足二分查找的四个适用条件?哪四个条件呢?你需要系统学习一下就 ok ! 53 | 54 | 55 | 56 | ###### ▉ 算法思路 57 | 58 | > 1)此过程分为两种情况,负数和正整数,所以要对输入的 x 进行判断。 59 | > 60 | > 2)然后开始根据二分查找应该注意的「三个重点」写出无 bug 的代码。 61 | > 62 | > 3)对二分查找进行稍微的变体,因为我们可能查找的数并不是一个正整数,我们取整数部分就可以了,小数部分省略。 63 | 64 | 65 | 66 | ###### ▉ 测试用例 67 | 68 | > 1)输入 0 69 | > 70 | > 2)输入1 71 | > 72 | > 3)输入负数的 x 73 | > 74 | > 4)输入平方根为正整数的 x 75 | > 76 | > 5)输入平方根为小数的 x 77 | 78 | 79 | 80 | ###### ▉ 代码实现 81 | 82 | > 写二分查找代码需要注意的三点: 83 | > 84 | > 1)循环退出条件。 85 | > 86 | > 2)mid 的取值。 87 | > 88 | > 3)low 和 hight 的更新。 89 | 90 | ```javascript 91 | var mySqrt = function(x) { 92 | let low = 1; 93 | let high = x; 94 | // 如果 x 小于 0 输出 -1 95 | if(x < 0) return -1; 96 | // 循环终止条件 97 | while(low <= high){ 98 | // mid 取值 99 | let mid = Math.floor(low + ((high - low)/2)); 100 | // 判断平方是否小于等于 101 | if(Math.pow(mid,2) <= x){ 102 | // 如果小于等于,如果下一值大于 x 则当前值为 x 平方根的最小整数值 103 | if(Math.pow(mid+1,2) > x || mid === high){ 104 | return mid; 105 | }else{ 106 | low = mid + 1; 107 | } 108 | }else{ 109 | high = mid - 1; 110 | } 111 | } 112 | return 0; 113 | }; 114 | ``` 115 | 116 | 117 | 118 | ###### ▉ 性能分析 119 | 120 | > 暴力破解: 121 | > 122 | > - 时间复杂度:O(n)。你需要从 1 遍历所有可能的数据,所以时间复杂度为O(n)。 123 | > - 空间复杂度:O(1)。不需要额外的内存空间。 124 | > 125 | > 二分法: 126 | > 127 | > - 时间复杂度:O(n)。每次都折半查找,所以查找一个元素时间复杂度为O(logn)。 128 | > - 空间复杂度:O(1)。不需要额外的内存空间。 129 | 130 | 131 | 132 | ###### ▉ 小结 133 | 134 | > 通过这个题我们可以总结一下: 135 | > 136 | > 1)如果问题涉及到查找,我们要想到使用二分查找来提高效率。 137 | > 138 | > 2)使用二分查找之前,判断问题是否满足二分查找的要求。 139 | 140 | --------------------------------------------------------------------------------