diff --git a/Week01/NOTE.md b/Week01/NOTE.md deleted file mode 100644 index 50de30414..000000000 --- a/Week01/NOTE.md +++ /dev/null @@ -1 +0,0 @@ -学习笔记 \ No newline at end of file diff --git a/Week01/README.md b/Week01/README.md new file mode 100644 index 000000000..1e0543dc5 --- /dev/null +++ b/Week01/README.md @@ -0,0 +1,337 @@ +# 数组 + +每次创建数组,计算机实际上是在内存中开辟了一段`连续`的地址,每个地址可通过内存管理器进行访问。 + +![array](static/img/array1.png) + +## 优点 + +- 随机访问某个地址的时间复杂度:`O(1)` + +## 缺点 + +- 插入 / 删除元素的时间复杂度为 `O(n)` + + - 每次向数组插入元素,需要依次挪动所插入元素的后续元素,故该操作的时间复杂度不再是常数级 + + - 最坏情况下,需要挪动整个数组,如:插入首个元素 + + - 最好情况下,插入的时间复杂度为 `O(1)`,即插入元素为最末元素 + + - 删除元素同理 + +## 时间复杂度 + +!> 注意:正常情况下数组的 prepend 操作的时间复杂度是 `O(n)`,但是可以进行特殊优化到 `O(1)`。采用的方式是申请稍大一些的内存空间,然后在数组最开始预留一部分空间,然后 prepend 的操作则是把头下标前移一个位置即可。 + +- prepend:`O(1)` + +- append:`O(1)` + +- lookup:`O(1)` + +- insert:`O(n)` + +- delete:`O(n)` + +# 链表 + +在频繁需要进行增删操作时,`数组` 这种数据结构并不好用,此时可以考虑 `链表`。 + +`链表` 的每个元素一般用一个 `class` 来定义,存放 `value` 及指向下个元素的 `next` 指针,如果再增加一个 `prev` 指针指向上一个元素,则称为 `双向链表`。 + +![linked-list](static/img/linked-list1.png) + +- Head:代表 `头指针` + +- Tail:代表 `尾指针`,最后一个元素的 `next` 指针指向 `null` + +!> 当 `单向链表` 的 `尾指针` 指向 `头指针` 时,称为 `循环链表`。 + +## 优点 + +- 插入 / 删除元素的时间复杂度:`O(1)` + +## 缺点 + +- 当访问首尾节点的时间复杂度为 `O(1)` + +- 当访问任意节点时,需要从头开始进行线性查找,时间复杂度为 `O(n)` + +## 时间复杂度(普通链表) + +- prepend:`O(1)` + +- append:`O(1)` + +- lookup:`O(n)` + +- insert:`O(1)` + +- delete:`O(1)` + +## 实际应用 + +- [LRU Cache](https://www.jianshu.com/p/b1ab4a170c3c) + +- [leetcode第146题:LRU缓存机制](https://leetcode-cn.com/problems/lru-cache/) +## 简单实现 + +``` javascript +// 节点 +class Node{ + constructor(data){ + this.data = data + this.next = null + } +} +// 单向链表 +class LinkList{ + constructor(){ + // 总长度 + this.length = 0 + // 首元素 + this.head = null + } + + // 新增 + append(data){ + let node = new Node(data) + let cur + if(this.head){ + // 遍历链表 + cur = this.head + // 寻找未绑节点 + while(cur.next) { + cur = cur.next + } + // 绑定新增节点 + cur.next = node + }else{ + this.head = node + } + this.length++ + } + + // 打印 + print(){ + let cur = this.head + let res = [] + while(cur){ + res.push(cur.data) + cur = cur.next + } + console.log(res.join(' => ')) + } + + // 删除指定索引的元素 + removeAt(index){ + if(index < 0 || index > this.length - 1){ + console.log('index: out of range!') + return + } + let cur = this.head + let prev + let i = 0 + if(index === 0){ + this.head = cur.next + }else{ + while(i < index){ + // 保存当前节点到 prev + prev = cur + // 将下一个节点指向当前节点 cur + cur = cur.next + i++ + } + prev.next = cur.next + cur.next = null + } + this.length -- + return cur.data + } +} + +// 测试 +let linkList = new LinkList() +linkList.append('你好') +linkList.append('我是') +linkList.append('一名') +linkList.append('前端工程师') +linkList.print() +console.log(linkList.length) +console.log(linkList.removeAt(3)) +linkList.print() +console.log(linkList.length) + +``` + +# 链表 + +在频繁需要进行增删操作时,`数组` 这种数据结构并不好用,此时可以考虑 `链表`。 + +`链表` 的每个元素一般用一个 `class` 来定义,存放 `value` 及指向下个元素的 `next` 指针,如果再增加一个 `prev` 指针指向上一个元素,则称为 `双向链表`。 + +![linked-list](static/img/linked-list1.png) + +- Head:代表 `头指针` + +- Tail:代表 `尾指针`,最后一个元素的 `next` 指针指向 `null` + +!> 当 `单向链表` 的 `尾指针` 指向 `头指针` 时,称为 `循环链表`。 + +## 优点 + +- 插入 / 删除元素的时间复杂度:`O(1)` + +## 缺点 + +- 当访问首尾节点的时间复杂度为 `O(1)` + +- 当访问任意节点时,需要从头开始进行线性查找,时间复杂度为 `O(n)` + +## 时间复杂度(普通链表) + +- prepend:`O(1)` + +- append:`O(1)` + +- lookup:`O(n)` + +- insert:`O(1)` + +- delete:`O(1)` + +## 实际应用 + +- [LRU Cache](https://www.jianshu.com/p/b1ab4a170c3c) + +- [leetcode第146题:LRU缓存机制](https://leetcode-cn.com/problems/lru-cache/) +## 简单实现 + +``` javascript +// 节点 +class Node{ + constructor(data){ + this.data = data + this.next = null + } +} +// 单向链表 +class LinkList{ + constructor(){ + // 总长度 + this.length = 0 + // 首元素 + this.head = null + } + + // 新增 + append(data){ + let node = new Node(data) + let cur + if(this.head){ + // 遍历链表 + cur = this.head + // 寻找未绑节点 + while(cur.next) { + cur = cur.next + } + // 绑定新增节点 + cur.next = node + }else{ + this.head = node + } + this.length++ + } + + // 打印 + print(){ + let cur = this.head + let res = [] + while(cur){ + res.push(cur.data) + cur = cur.next + } + console.log(res.join(' => ')) + } + + // 删除指定索引的元素 + removeAt(index){ + if(index < 0 || index > this.length - 1){ + console.log('index: out of range!') + return + } + let cur = this.head + let prev + let i = 0 + if(index === 0){ + this.head = cur.next + }else{ + while(i < index){ + // 保存当前节点到 prev + prev = cur + // 将下一个节点指向当前节点 cur + cur = cur.next + i++ + } + prev.next = cur.next + cur.next = null + } + this.length -- + return cur.data + } +} + +// 测试 +let linkList = new LinkList() +linkList.append('你好') +linkList.append('我是') +linkList.append('一名') +linkList.append('前端工程师') +linkList.print() +console.log(linkList.length) +console.log(linkList.removeAt(3)) +linkList.print() +console.log(linkList.length) + +``` + +# 跳表 + +跳表(skip list)对标的是平衡树(AVL Tree)和二分查找,是一种 插入 / 删除 / 搜索 都是 `O(log n)` 的数据结构(在 1989 年出现)。 + +!> 注意:只能用于元素有序的情况 + +![skip-list](static/img/skip-list1.png) + +## 优点 + +- 原理简单、容易实现方便扩展效率更高 + +- 在一些热门的项目里用来替代平衡树,如 Redis、LevelDB + +## 缺点 + +- 每次进行增删操作需要更新索引,维护成本较高 + +- 相比于普通链表,增删操作的时间复杂度降为 `O(log n)` + +- 相比于普通链表,要占用更大的空间,空间复杂度为 `O(n)` + +> 虽然跳表的空间复杂度和普通链表都为 `O(n)`,但由于添加的每一级索引都要占用额外空间,故跳表的空间复杂度相比原始链表而言要超出不少。 + +## 查询的时间复杂度分析 + +- `升维` + `空间换时间`:通过添加多级索引提升查找效率 + +- n/2、n/4、n/8、第 k 级索引结点的个数就是 n/(2^k) + +- 假设索引有 h 级,最高的索引有 2 个结点。 n/(2^h) = 2,从而求得 h = log2(n) -1 + +- 总体时间复杂度:`O(log n)` + +## 实际应用 + +- [跳跃表 - Redis设计与实现](https://redisbook.readthedocs.io/en/latest/internal-datastruct/skiplist.html) + +- [为啥 redis 使用跳表(skiplist)而不是使用 red-black?](https://www.zhihu.com/question/20202931) \ No newline at end of file diff --git "a/Week01/code/1.\344\270\244\346\225\260\344\271\213\345\222\214.js" "b/Week01/code/1.\344\270\244\346\225\260\344\271\213\345\222\214.js" new file mode 100644 index 000000000..7bcd079ab --- /dev/null +++ "b/Week01/code/1.\344\270\244\346\225\260\344\271\213\345\222\214.js" @@ -0,0 +1,25 @@ +/* + * @lc app=leetcode.cn id=1 lang=javascript + * + * [1] 两数之和 + */ + +// @lc code=start +/** + * @param {number[]} nums + * @param {number} target + * @return {number[]} + */ +var twoSum = function (nums, target) { + let obj = {} + for (let i = 0; i < nums.length; i++) { + let num = nums[i] + if (num in obj) { + return [obj[num], i] + } else { + obj[target - num] = i + } + } +}; +// @lc code=end + diff --git "a/Week01/code/144.\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.js" "b/Week01/code/144.\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.js" new file mode 100644 index 000000000..c19b16ef5 --- /dev/null +++ "b/Week01/code/144.\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.js" @@ -0,0 +1,71 @@ +/* + * @lc app=leetcode.cn id=144 lang=javascript + * + * [144] 二叉树的前序遍历 + * + * https://leetcode-cn.com/problems/binary-tree-preorder-traversal/description/ + * + * algorithms + * Medium (63.48%) + * Likes: 196 + * Dislikes: 0 + * Total Accepted: 62.9K + * Total Submissions: 98.8K + * Testcase Example: '[1,null,2,3]' + * + * 给定一个二叉树,返回它的 前序 遍历。 + * + * 示例: + * + * 输入: [1,null,2,3] + * ⁠ 1 + * ⁠ \ + * ⁠ 2 + * ⁠ / + * ⁠ 3 + * + * 输出: [1,2,3] + * + * + * 进阶: 递归算法很简单,你可以通过迭代算法完成吗? + * + */ + +// @lc code=start +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[]} + */ +var preorderTraversal = function (root, arr = []) { + // // 递归 + // if (root) { + // arr.push(root.val) + // preorderTraversal(root.left, arr) + // preorderTraversal(root.right, arr) + // } + // return arr + + // 迭代 + let result = [] + let stack = [] + let curr = root + while(curr || stack.length > 0){ + while(curr){ + result.push(curr.val) + stack.push(curr) // 节点入栈,通过curr寻找右子树 + curr = curr.left + } + curr = stack.pop() + curr = curr.right + } + return result +}; +// @lc code=end + diff --git "a/Week01/code/145.\344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.js" "b/Week01/code/145.\344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.js" new file mode 100644 index 000000000..30f026468 --- /dev/null +++ "b/Week01/code/145.\344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.js" @@ -0,0 +1,78 @@ +/* + * @lc app=leetcode.cn id=145 lang=javascript + * + * [145] 二叉树的后序遍历 + * + * https://leetcode-cn.com/problems/binary-tree-postorder-traversal/description/ + * + * algorithms + * Hard (69.61%) + * Likes: 217 + * Dislikes: 0 + * Total Accepted: 47.5K + * Total Submissions: 68K + * Testcase Example: '[1,null,2,3]' + * + * 给定一个二叉树,返回它的 后序 遍历。 + * + * 示例: + * + * 输入: [1,null,2,3] + * ⁠ 1 + * ⁠ \ + * ⁠ 2 + * ⁠ / + * ⁠ 3 + * + * 输出: [3,2,1] + * + * 进阶: 递归算法很简单,你可以通过迭代算法完成吗? + * + */ + +// @lc code=start +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[]} + */ +var postorderTraversal = function (root, arr = []) { + // // 递归 + // if (root) { + // postorderTraversal(root.left, arr) + // postorderTraversal(root.right, arr) + // arr.push(root.val) + // } + // return arr + + // 迭代 + const result = []; + const stack = []; + let last = null; // 标记上一个访问的节点 + let curr = root; + while (curr || stack.length > 0) { + while (curr) { + stack.push(curr); + curr = curr.left; + } + curr = stack[stack.length - 1]; + if (!curr.right || curr.right == last) { + curr = stack.pop(); + result.push(curr.val); + last = curr; + curr = null; // 继续弹栈 + } else { + curr = curr.right; + } + } + return result; + +}; +// @lc code=end + diff --git "a/Week01/code/189.\346\227\213\350\275\254\346\225\260\347\273\204.js" "b/Week01/code/189.\346\227\213\350\275\254\346\225\260\347\273\204.js" new file mode 100644 index 000000000..37de33a97 --- /dev/null +++ "b/Week01/code/189.\346\227\213\350\275\254\346\225\260\347\273\204.js" @@ -0,0 +1,65 @@ +/* + * @lc app=leetcode.cn id=189 lang=javascript + * + * [189] 旋转数组 + * + * https://leetcode-cn.com/problems/rotate-array/description/ + * + * algorithms + * Easy (41.76%) + * Likes: 593 + * Dislikes: 0 + * Total Accepted: 132.2K + * Total Submissions: 316.6K + * Testcase Example: '[1,2,3,4,5,6,7]\n3' + * + * 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。 + * + * 示例 1: + * + * 输入: [1,2,3,4,5,6,7] 和 k = 3 + * 输出: [5,6,7,1,2,3,4] + * 解释: + * 向右旋转 1 步: [7,1,2,3,4,5,6] + * 向右旋转 2 步: [6,7,1,2,3,4,5] + * 向右旋转 3 步: [5,6,7,1,2,3,4] + * + * + * 示例 2: + * + * 输入: [-1,-100,3,99] 和 k = 2 + * 输出: [3,99,-1,-100] + * 解释: + * 向右旋转 1 步: [99,-1,-100,3] + * 向右旋转 2 步: [3,99,-1,-100] + * + * 说明: + * + * + * 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。 + * 要求使用空间复杂度为 O(1) 的 原地 算法。 + * + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @param {number} k + * @return {void} Do not return anything, modify nums in-place instead. + */ +var rotate = function (nums, k) { + // // 解法一 + // // 时间复杂度:O(k²),数组的 unshift 方法时间复杂度为O(n) + // // 空间复杂度:O(1) + // for (let i = 0; i < k; i++) { + // nums.unshift(nums.pop()) + // } + + // 解法二 + // 时间复杂度:O(k) + // 空间复杂度:O(1) + nums.unshift(...nums.splice(nums.length - k, k)) +}; +// @lc code=end + diff --git "a/Week01/code/21.\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.js" "b/Week01/code/21.\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.js" new file mode 100644 index 000000000..bfc316960 --- /dev/null +++ "b/Week01/code/21.\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.js" @@ -0,0 +1,62 @@ +/* + * @lc app=leetcode.cn id=21 lang=javascript + * + * [21] 合并两个有序链表 + * + * https://leetcode-cn.com/problems/merge-two-sorted-lists/description/ + * + * algorithms + * Easy (62.81%) + * Likes: 1105 + * Dislikes: 0 + * Total Accepted: 288.4K + * Total Submissions: 459.1K + * Testcase Example: '[1,2,4]\n[1,3,4]' + * + * 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。  + * + * + * + * 示例: + * + * 输入:1->2->4, 1->3->4 + * 输出:1->1->2->3->4->4 + * + * + */ + +// @lc code=start +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} l1 + * @param {ListNode} l2 + * @return {ListNode} + */ +var mergeTwoLists = function (l1, l2) { + // 迭代 + const head = new ListNode() + let prev = head + while (l1 && l2) { + if (l1.val > l2.val) { + prev.next = l2 + // 向后移动一个节点 + l2 = l2.next + } else { + prev.next = l1 + l1 = l1.next + } + // prev移动到新节点 + prev = prev.next + } + // 当其中一个链表最后一个节点指向null时,prev指向还有节点未遍历完的链表 + prev.next = l1 ? l1 : l2 + return head.next +}; +// @lc code=end + diff --git "a/Week01/code/242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" "b/Week01/code/242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" new file mode 100644 index 000000000..98735b945 --- /dev/null +++ "b/Week01/code/242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" @@ -0,0 +1,48 @@ +/* + * @lc app=leetcode.cn id=242 lang=javascript + * + * [242] 有效的字母异位词 + * + * https://leetcode-cn.com/problems/valid-anagram/description/ + * + * algorithms + * Easy (60.16%) + * Likes: 203 + * Dislikes: 0 + * Total Accepted: 106.8K + * Total Submissions: 177.5K + * Testcase Example: '"anagram"\n"nagaram"' + * + * 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 + * + * 示例 1: + * + * 输入: s = "anagram", t = "nagaram" + * 输出: true + * + * + * 示例 2: + * + * 输入: s = "rat", t = "car" + * 输出: false + * + * 说明: + * 你可以假设字符串只包含小写字母。 + * + * 进阶: + * 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况? + * + */ + +// @lc code=start +/** + * @param {string} s + * @param {string} t + * @return {boolean} + */ +var isAnagram = function (s, t) { + if(s.length != t.length) return false + return Array.from(s).sort().toString() === Array.from(t).sort().toString() +}; +// @lc code=end + diff --git "a/Week01/code/26.\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.js" "b/Week01/code/26.\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.js" new file mode 100644 index 000000000..903bd3e8d --- /dev/null +++ "b/Week01/code/26.\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.js" @@ -0,0 +1,75 @@ +/* + * @lc app=leetcode.cn id=26 lang=javascript + * + * [26] 删除排序数组中的重复项 + * + * https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/description/ + * + * algorithms + * Easy (48.05%) + * Likes: 1345 + * Dislikes: 0 + * Total Accepted: 256.7K + * Total Submissions: 526.1K + * Testcase Example: '[1,1,2]' + * + * 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 + * + * 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 + * + * 示例 1: + * + * 给定数组 nums = [1,1,2], + * + * 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 + * + * 你不需要考虑数组中超出新长度后面的元素。 + * + * 示例 2: + * + * 给定 nums = [0,0,1,1,1,2,2,3,3,4], + * + * 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 + * + * 你不需要考虑数组中超出新长度后面的元素。 + * + * + * 说明: + * + * 为什么返回数值是整数,但输出的答案是数组呢? + * + * 请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 + * + * 你可以想象内部操作如下: + * + * // nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 + * int len = removeDuplicates(nums); + * + * // 在函数里修改输入数组对于调用者是可见的。 + * // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 + * for (int i = 0; i < len; i++) { + * print(nums[i]); + * } + * + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {number} + */ +var removeDuplicates = function (nums) { + // 双指针 + const len = nums.length + let slow = 0 + for (let fast = 0; fast < len; fast++) { + if (nums[fast] !== nums[slow]) { + slow++ + nums[slow] = nums[fast] + } + } + return slow + 1 +}; +// @lc code=end + diff --git "a/Week01/code/283.\347\247\273\345\212\250\351\233\266.js" "b/Week01/code/283.\347\247\273\345\212\250\351\233\266.js" new file mode 100644 index 000000000..0622dfa84 --- /dev/null +++ "b/Week01/code/283.\347\247\273\345\212\250\351\233\266.js" @@ -0,0 +1,114 @@ +/* + * @lc app=leetcode.cn id=283 lang=javascript + * + * [283] 移动零 + * + * https://leetcode-cn.com/problems/move-zeroes/description/ + * + * algorithms + * Easy (58.75%) + * Likes: 614 + * Dislikes: 0 + * Total Accepted: 158.7K + * Total Submissions: 259.6K + * Testcase Example: '[0,1,0,3,12]' + * + * 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 + * + * 示例: + * + * 输入: [0,1,0,3,12] + * 输出: [1,3,12,0,0] + * + * 说明: + * + * + * 必须在原数组上操作,不能拷贝额外的数组。 + * 尽量减少操作次数。 + * + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {void} Do not return anything, modify nums in-place instead. + */ +var moveZeroes = function (nums) { + // // 解法一 + // // 思路:两次循环、从头开始覆盖非0元素、末尾补0 + // // 时间复杂度:`O(n)`,两次非嵌套循环在最极端情况下的时间复杂度为 2n + // // 空间复杂度:`O(1)` + // let j = 0 + // // 首次遍历数组,从头开始覆盖非0元素 + // for (let i = 0; i < nums.length; i++) { + // if (nums[i] !== 0) nums[j++] = nums[i] + // } + // // 数组末尾补0 + // for (let i = j; i < nums.length; i++) { + // nums[i] = 0 + // } + + // // 解法二(优化) + // // 思路:一次循环、从头开始覆盖非0元素,非0元素向前移动时,原索引位置用0替换 + // // 时间复杂度:`O(n)` + // // 空间复杂度:`O(1)` + // // 记录非0元素下标 + // let j = 0 + // for (let i = 0; i < nums.length; i++) { + // if (nums[i] !== 0) { + // // 从头开始覆盖非0元素 + // nums[j] = nums[i] + // // 下标不同时,i 下标元素替换成 0,循环结束后,0 在数组末尾 + // if (i !== j++) nums[i] = 0 + // } + // // console.log(nums) + // /* 输入数组:[0,1,0,3,2,0],每次循环打印 nums + // i: 0 nums: [0,1,0,3,2,0] + // i: 1 nums: [1,0,0,3,2,0] + // i: 2 nums: [1,0,0,3,2,0] + // i: 3 nums: [1,3,0,0,2,0] + // i: 4 nums: [1,3,2,0,0,0] + // i: 5 nums: [1,3,2,0,0,0] + // */ + // } + + // 解法三 + // 思路:一次循环、双指针、交换两个元素 + // 时间复杂度:`O(n)` + // 空间复杂度:`O(1)` + let j = 0 + for (let i = 0; i < nums.length; i++) { + // 非0元素时,交换两个指针指向的元素 + if (nums[i] !== 0) [nums[j++], nums[i]] = [nums[i], nums[j]] + } + + // // 解法四(数组方法) + // // 思路:倒序遍历(正序遍历删除元素会改变原数组元素的索引)、调用数组 `splice` 和 `push` 方法 + // // 时间复杂度:`O(n²)`,数组 `splice` 方法的时间复杂度为 `O(n)` + // // 空间复杂度:`O(1)` + // for(let i= nums.length; i >= 0; i--){ + // if(nums[i] === 0){ + // nums.splice(i,1) + // nums.push(0) + // } + // } + + // // 解法五(不符题意) + // // 思路:创建一个新数组,利用索引 i 和 j 分别指向数组的首尾,将非0元素往前放,0往后放 + // // 时间复杂度:`O(n)` + // // 空间复杂度:`O(n)` + // const nums_new = [] + // let i = 0 + // let j = nums.length - 1 + // for (let k = 0; k < nums.length; k++) { + // if (nums[k] !== 0) { + // nums_new[i++] = nums[k] + // }else{ + // nums_new[j--] = 0 + // } + // } + // nums = nums_new +}; +// @lc code=end + diff --git "a/Week01/code/42.\346\216\245\351\233\250\346\260\264.js" "b/Week01/code/42.\346\216\245\351\233\250\346\260\264.js" new file mode 100644 index 000000000..b2a0fa33e --- /dev/null +++ "b/Week01/code/42.\346\216\245\351\233\250\346\260\264.js" @@ -0,0 +1,58 @@ +/* + * @lc app=leetcode.cn id=42 lang=javascript + * + * [42] 接雨水 + * + * https://leetcode-cn.com/problems/trapping-rain-water/description/ + * + * algorithms + * Hard (51.24%) + * Likes: 1354 + * Dislikes: 0 + * Total Accepted: 109.3K + * Total Submissions: 213.3K + * Testcase Example: '[0,1,0,2,1,0,1,3,2,1,2,1]' + * + * 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 + * + * + * + * 上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 + * Marcos 贡献此图。 + * + * 示例: + * + * 输入: [0,1,0,2,1,0,1,3,2,1,2,1] + * 输出: 6 + * + */ + +// @lc code=start +/** + * @param {number[]} height + * @return {number} + */ +var trap = function (height) { + let sum = 0 + for (let index = 1; index < height.length - 1; index++) { + // 左边最大高度 + let leftMax = 0 + for (let i = index - 1; i >= 0; i--) { + leftMax = height[i] >= leftMax ? height[i] : leftMax + } + // 右边最大高度 + let rightMax = 0 + for (let i = index + 1; i < height.length; i++) { + rightMax = height[i] >= rightMax ? height[i] : rightMax + } + // 获取两边最大高度中的较低高度 + let min = Math.min(leftMax, rightMax) + if (min > height[index]) { + //接水量 = 两边最大高度中的较低高度 - 当前项的高度 + sum = sum + min - height[index] + } + } + return sum +}; +// @lc code=end + diff --git "a/Week01/code/641.\350\256\276\350\256\241\345\276\252\347\216\257\345\217\214\347\253\257\351\230\237\345\210\227.js" "b/Week01/code/641.\350\256\276\350\256\241\345\276\252\347\216\257\345\217\214\347\253\257\351\230\237\345\210\227.js" new file mode 100644 index 000000000..0a6ded7d6 --- /dev/null +++ "b/Week01/code/641.\350\256\276\350\256\241\345\276\252\347\216\257\345\217\214\347\253\257\351\230\237\345\210\227.js" @@ -0,0 +1,194 @@ +/* + * @lc app=leetcode.cn id=641 lang=javascript + * + * [641] 设计循环双端队列 + * + * https://leetcode-cn.com/problems/design-circular-deque/description/ + * + * algorithms + * Medium (52.24%) + * Likes: 38 + * Dislikes: 0 + * Total Accepted: 8.2K + * Total Submissions: 15.7K + * Testcase Example: '["MyCircularDeque","insertLast","insertLast","insertFront","insertFront","getRear","isFull","deleteLast","insertFront","getFront"]\n[[3],[1],[2],[3],[4],[],[],[],[4],[]]' + * + * 设计实现双端队列。 + * 你的实现需要支持以下操作: + * + * + * MyCircularDeque(k):构造函数,双端队列的大小为k。 + * insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。 + * insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。 + * deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。 + * deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。 + * getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。 + * getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。 + * isEmpty():检查双端队列是否为空。 + * isFull():检查双端队列是否满了。 + * + * + * 示例: + * + * MyCircularDeque circularDeque = new MycircularDeque(3); // 设置容量大小为3 + * circularDeque.insertLast(1); // 返回 true + * circularDeque.insertLast(2); // 返回 true + * circularDeque.insertFront(3); // 返回 true + * circularDeque.insertFront(4); // 已经满了,返回 false + * circularDeque.getRear(); // 返回 2 + * circularDeque.isFull(); // 返回 true + * circularDeque.deleteLast(); // 返回 true + * circularDeque.insertFront(4); // 返回 true + * circularDeque.getFront(); // 返回 4 + * + * + * + * + * 提示: + * + * + * 所有值的范围为 [1, 1000] + * 操作次数的范围为 [1, 1000] + * 请不要使用内置的双端队列库。 + * + * + */ + +// @lc code=start +/** + * Initialize your data structure here. Set the size of the deque to be k. + * @param {number} k + */ +var MyCircularDeque = function (k) { + this.key = 0 //key + this.quenLength = 0 //队列长度 + this.firstKey = 0 //队列首个元素key + this.queueSize = k //队列大小 + this.queue = {} //队列 +}; + +/** + * Adds an item at the front of Deque. Return true if the operation is successful. + * @param {number} value + * @return {boolean} + */ +MyCircularDeque.prototype.insertFront = function (value) { + if (this.isEmpty()) { + return this.insertLast(value) + } else { + if (this.isFull()) { + return false + } else if (this.firstKey > 0) { + this.firstKey-- + this.queue[this.firstKey] = value + this.quenLength++ + return true + } else { + for (let i = this.key; i > 0; i--) { + this.queue[i] = this.queue[i - 1] + } + this.queue[0] = value + this.key++ + this.quenLength++ + return true + } + } +}; + +/** + * Adds an item at the rear of Deque. Return true if the operation is successful. + * @param {number} value + * @return {boolean} + */ +MyCircularDeque.prototype.insertLast = function (value) { + if (this.isFull()) { + return false + } + this.queue[this.key] = value + this.key++ + this.quenLength++ + return true +}; + +/** + * Deletes an item from the front of Deque. Return true if the operation is successful. + * @return {boolean} + */ +MyCircularDeque.prototype.deleteFront = function () { + if (this.isEmpty()) { + return false + } + delete this.queue[this.firstKey] + this.firstKey++ + this.quenLength-- + return true +}; + +/** + * Deletes an item from the rear of Deque. Return true if the operation is successful. + * @return {boolean} + */ +MyCircularDeque.prototype.deleteLast = function () { + if (this.isEmpty()) { + return false + } + this.key-- + this.quenLength-- + delete this.queue[this.key] + return true +}; + +/** + * Get the front item from the deque. + * @return {number} + */ +MyCircularDeque.prototype.getFront = function () { + if (this.isEmpty()) { + return -1 + } else { + return this.queue[this.firstKey] + } +}; + +/** + * Get the last item from the deque. + * @return {number} + */ +MyCircularDeque.prototype.getRear = function () { + if (this.isEmpty()) { + return -1 + } else { + return this.queue[this.key - 1] + } +}; + +/** + * Checks whether the circular deque is empty or not. + * @return {boolean} + */ +MyCircularDeque.prototype.isEmpty = function () { + return this.quenLength === 0 +}; + +/** + * Checks whether the circular deque is full or not. + * @return {boolean} + */ +MyCircularDeque.prototype.isFull = function () { + return this.quenLength === this.queueSize +}; + +/** + * Your MyCircularDeque object will be instantiated and called as such: + * var obj = new MyCircularDeque(k) + * var param_1 = obj.insertFront(value) + * var param_2 = obj.insertLast(value) + * var param_3 = obj.deleteFront() + * var param_4 = obj.deleteLast() + * var param_5 = obj.getFront() + * var param_6 = obj.getRear() + * var param_7 = obj.isEmpty() + * var param_8 = obj.isFull() + */ +// @lc code=end + diff --git "a/Week01/code/66.\345\212\240\344\270\200.js" "b/Week01/code/66.\345\212\240\344\270\200.js" new file mode 100644 index 000000000..a6ac101e5 --- /dev/null +++ "b/Week01/code/66.\345\212\240\344\270\200.js" @@ -0,0 +1,48 @@ +/* + * @lc app=leetcode.cn id=66 lang=javascript + * + * [66] 加一 + * + * https://leetcode-cn.com/problems/plus-one/description/ + * + * algorithms + * Easy (44.32%) + * Likes: 483 + * Dislikes: 0 + * Total Accepted: 163.6K + * Total Submissions: 369K + * Testcase Example: '[1,2,3]' + * + * 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。 + * + * 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 + * + * 你可以假设除了整数 0 之外,这个整数不会以零开头。 + * + * 示例 1: + * + * 输入: [1,2,3] + * 输出: [1,2,4] + * 解释: 输入数组表示数字 123。 + * + * + * 示例 2: + * + * 输入: [4,3,2,1] + * 输出: [4,3,2,2] + * 解释: 输入数组表示数字 4321。 + * + * + */ + +// @lc code=start +/** + * @param {number[]} digits + * @return {number[]} + */ +var plusOne = function (digits) { + // 使用ES10新特性 + return (BigInt(digits.join('')) + 1n).toString().split('').map(i => +i) +}; +// @lc code=end + diff --git "a/Week01/code/88.\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.js" "b/Week01/code/88.\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.js" new file mode 100644 index 000000000..0be88fe47 --- /dev/null +++ "b/Week01/code/88.\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.js" @@ -0,0 +1,60 @@ +/* + * @lc app=leetcode.cn id=88 lang=javascript + * + * [88] 合并两个有序数组 + * + * https://leetcode-cn.com/problems/merge-sorted-array/description/ + * + * algorithms + * Easy (47.90%) + * Likes: 530 + * Dislikes: 0 + * Total Accepted: 160K + * Total Submissions: 333.9K + * Testcase Example: '[1,2,3,0,0,0]\n3\n[2,5,6]\n3' + * + * 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。 + * + * + * + * 说明: + * + * + * 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 + * 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 + * + * + * + * + * 示例: + * + * 输入: + * nums1 = [1,2,3,0,0,0], m = 3 + * nums2 = [2,5,6], n = 3 + * + * 输出: [1,2,2,3,5,6] + * + */ + +// @lc code=start +/** + * @param {number[]} nums1 + * @param {number} m + * @param {number[]} nums2 + * @param {number} n + * @return {void} Do not return anything, modify nums1 in-place instead. + */ +var merge = function (nums1, m, nums2, n) { + // 时间复杂度:O(m+n) + // https://leetcode-cn.com/problems/merge-sorted-array/solution/tu-jie-he-bing-liang-ge-you-xu-shu-zu-by-user7746o/ + let length = m + n + while (n > 0) { + if (m <= 0) { + nums1[--length] = nums2[--n] + continue + } + nums1[--length] = nums1[m - 1] >= nums2[n - 1] ? nums1[--m] : nums2[--n] + } +}; +// @lc code=end + diff --git "a/Week01/code/94.\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.js" "b/Week01/code/94.\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.js" new file mode 100644 index 000000000..ae88be513 --- /dev/null +++ "b/Week01/code/94.\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.js" @@ -0,0 +1,72 @@ +/* + * @lc app=leetcode.cn id=94 lang=javascript + * + * [94] 二叉树的中序遍历 + * + * https://leetcode-cn.com/problems/binary-tree-inorder-traversal/description/ + * + * algorithms + * Medium (69.62%) + * Likes: 378 + * Dislikes: 0 + * Total Accepted: 97.4K + * Total Submissions: 139.4K + * Testcase Example: '[1,null,2,3]' + * + * 给定一个二叉树,返回它的中序 遍历。 + * + * 示例: + * + * 输入: [1,null,2,3] + * ⁠ 1 + * ⁠ \ + * ⁠ 2 + * ⁠ / + * ⁠ 3 + * + * 输出: [1,3,2] + * + * 进阶: 递归算法很简单,你可以通过迭代算法完成吗? + * + */ + +// @lc code=start +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[]} + */ +var inorderTraversal = function (root, arr = []) { + // // 递归 + // if (root) { + // inorderTraversal(root.left, arr) + // arr.push(root.val) + // inorderTraversal(root.right, arr) + // } + // return arr + + // 迭代 + let result = [] + let stack = [] + let curr = root + while (curr || stack.length > 0) { + if (curr) { + stack.push(curr); // 节点入栈,通过curr寻找右子树 + curr = curr.left; + } else { + curr = stack.pop(); + result.push(curr.val); + curr = curr.right; + } + } + return result + +}; +// @lc code=end + diff --git a/Week01/static/img/array1.png b/Week01/static/img/array1.png new file mode 100644 index 000000000..5b51b4cbb Binary files /dev/null and b/Week01/static/img/array1.png differ diff --git a/Week01/static/img/linked-list1.png b/Week01/static/img/linked-list1.png new file mode 100644 index 000000000..90364505d Binary files /dev/null and b/Week01/static/img/linked-list1.png differ diff --git a/Week01/static/img/skip-list1.png b/Week01/static/img/skip-list1.png new file mode 100644 index 000000000..a192eabff Binary files /dev/null and b/Week01/static/img/skip-list1.png differ diff --git a/Week02/NOTE.md b/Week02/NOTE.md deleted file mode 100644 index 50de30414..000000000 --- a/Week02/NOTE.md +++ /dev/null @@ -1 +0,0 @@ -学习笔记 \ No newline at end of file diff --git a/Week02/README.md b/Week02/README.md new file mode 100644 index 000000000..6f6b009e9 --- /dev/null +++ b/Week02/README.md @@ -0,0 +1,149 @@ +# 树 + +特殊化的图 + +- 树是包含 n(n>=0)个结点的有穷集 +- 每个元素称为结点 +- 有一个特定的结点被称为根结点或树根 +- 除根结点外的其余元素被分为 m(m>=0)个互不相交的集合 + +## 基本属性 + +- 树是按层级构建的 +- 一个节点的所有子节点与另一个节点的所有子节点无关 +- 叶子节点都是独一无二的 + +## 相关概念 + +- 节点的高度 +- 节点的深度 +- 节点的层数 +- 树的高度 + +# 二叉树 + +每个结点最多有两个子树的树结构 + +## 五种形态 + +- 空二叉树 +- 只有根节点二叉树 +- 只有左子树 +- 只有右子树 +- 完全二叉树 + +## 遍历方式 + +- 广度优先搜索 +- 深度优先搜索 + +``` +def preorder(self, root): + if root: + self.traverse_path.append(root.val) + self.preorder(root.right) + self.preorder(root.left) + +def inorder(self, root): + if root: + self.inorder(root.left) + self.traverse_path.append(root.val) + self.inorder(root.right) + +def postorder(self, root): + if root: + self.postorder(root.left) + self.postorder(root.right) + self.traverse_path.append(root.val) +``` + + + +# 二叉堆 + +- 一个完全二叉树 + +1. 除了最底层其他每一层的节点都是满的 +2. 在最底层从左往右填充节点 + +- 每个结点的值都必须大于等于(或小于等于)其子树中每个结点的值 + +1. 大于等于 -> 大顶堆 +2. 小于等于 -> 小顶堆 + +## 常见操作 + +- 堆化:O(n) + +1. 从下往上 +2. 从上往下 + +- 插入:O(longn) + +1. 新元素一律先插入到堆的尾部 +2. 依次从下向上调整整个堆的结构直到堆顶(HeapifyUp) + +- 删最:O(logn)/O(1) + +1. 将堆尾元素替换到顶部(即对堆顶被替代删除掉) +2. 依次从根向下调整整个堆的结构直到堆尾(HeapifyDown) + +- 查最:O(1) + + + +# 二叉搜索树 + +- 概念解析 + +1. 左子树上所有节点的值均小于它的根节点的值 +2. 右子树上所有节点的值均大于它的根节点的值 +3. 依次类推:左右子树也分别为二叉搜索树 + +- 常见操作 +- 基本实现 +- 简单分析 + + + +# 平衡二叉树 + +1. 左子树和右子树的深度差(平衡因子)的绝对值不超过 1 +2. 左右子树也分别为平衡二叉树 + + + +# 图 + +由顶点的有穷非空集合和顶点之间边的集合组成:G(V, E) + +- G(Graph):表示一个图 +- V(Vertex):图 G 中的顶点的集合 + +1. 度:入度和出度 +2. 点与点之间:连通与否 + +- E(Edge):图 G 中边的集合 + +1. 有向和无向(单行线) +2. 权重(边长) + +## 图的类型 + +- 无向图 +- 有向图 +- 完全图 + +## 图的存储 + +- 邻接矩阵 -> 很多条边 +- 邻接表 -> 稀疏连接 + +## 适用场景 + +- 微信等社交网络中的好友关系 +- 地图导航及交通网络 +- 游戏地图与迷宫 +- 计算机网络 +- 人际关系推荐系统 +- 知识图谱 \ No newline at end of file diff --git "a/Week02/code/1.\344\270\244\346\225\260\344\271\213\345\222\214.js" "b/Week02/code/1.\344\270\244\346\225\260\344\271\213\345\222\214.js" new file mode 100644 index 000000000..7bcd079ab --- /dev/null +++ "b/Week02/code/1.\344\270\244\346\225\260\344\271\213\345\222\214.js" @@ -0,0 +1,25 @@ +/* + * @lc app=leetcode.cn id=1 lang=javascript + * + * [1] 两数之和 + */ + +// @lc code=start +/** + * @param {number[]} nums + * @param {number} target + * @return {number[]} + */ +var twoSum = function (nums, target) { + let obj = {} + for (let i = 0; i < nums.length; i++) { + let num = nums[i] + if (num in obj) { + return [obj[num], i] + } else { + obj[target - num] = i + } + } +}; +// @lc code=end + diff --git "a/Week02/code/144.\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.js" "b/Week02/code/144.\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.js" new file mode 100644 index 000000000..c19b16ef5 --- /dev/null +++ "b/Week02/code/144.\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.js" @@ -0,0 +1,71 @@ +/* + * @lc app=leetcode.cn id=144 lang=javascript + * + * [144] 二叉树的前序遍历 + * + * https://leetcode-cn.com/problems/binary-tree-preorder-traversal/description/ + * + * algorithms + * Medium (63.48%) + * Likes: 196 + * Dislikes: 0 + * Total Accepted: 62.9K + * Total Submissions: 98.8K + * Testcase Example: '[1,null,2,3]' + * + * 给定一个二叉树,返回它的 前序 遍历。 + * + * 示例: + * + * 输入: [1,null,2,3] + * ⁠ 1 + * ⁠ \ + * ⁠ 2 + * ⁠ / + * ⁠ 3 + * + * 输出: [1,2,3] + * + * + * 进阶: 递归算法很简单,你可以通过迭代算法完成吗? + * + */ + +// @lc code=start +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[]} + */ +var preorderTraversal = function (root, arr = []) { + // // 递归 + // if (root) { + // arr.push(root.val) + // preorderTraversal(root.left, arr) + // preorderTraversal(root.right, arr) + // } + // return arr + + // 迭代 + let result = [] + let stack = [] + let curr = root + while(curr || stack.length > 0){ + while(curr){ + result.push(curr.val) + stack.push(curr) // 节点入栈,通过curr寻找右子树 + curr = curr.left + } + curr = stack.pop() + curr = curr.right + } + return result +}; +// @lc code=end + diff --git "a/Week02/code/22.\346\213\254\345\217\267\347\224\237\346\210\220.js" "b/Week02/code/22.\346\213\254\345\217\267\347\224\237\346\210\220.js" new file mode 100644 index 000000000..982ef48d3 --- /dev/null +++ "b/Week02/code/22.\346\213\254\345\217\267\347\224\237\346\210\220.js" @@ -0,0 +1,50 @@ +/* + * @lc app=leetcode.cn id=22 lang=javascript + * + * [22] 括号生成 + * + * https://leetcode-cn.com/problems/generate-parentheses/description/ + * + * algorithms + * Medium (75.55%) + * Likes: 1109 + * Dislikes: 0 + * Total Accepted: 138.8K + * Total Submissions: 183.7K + * Testcase Example: '3' + * + * 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 + * + * + * + * 示例: + * + * 输入:n = 3 + * 输出:[ + * ⁠ "((()))", + * ⁠ "(()())", + * ⁠ "(())()", + * ⁠ "()(())", + * ⁠ "()()()" + * ⁠ ] + * + * + */ + +// @lc code=start +/** + * @param {number} n + * @return {string[]} + */ +var generateParenthesis = function (n) { + let res = [] + let dfs = (s, left, right) => { + if (left == n && right == n) return res.push(s) + if (left < n) dfs(s + '(', left + 1, right) + if (right < left) dfs(s + ')', left, right + 1) + } + dfs('', 0, 0) + return res +}; +// @lc code=end + diff --git "a/Week02/code/242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" "b/Week02/code/242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" new file mode 100644 index 000000000..98735b945 --- /dev/null +++ "b/Week02/code/242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" @@ -0,0 +1,48 @@ +/* + * @lc app=leetcode.cn id=242 lang=javascript + * + * [242] 有效的字母异位词 + * + * https://leetcode-cn.com/problems/valid-anagram/description/ + * + * algorithms + * Easy (60.16%) + * Likes: 203 + * Dislikes: 0 + * Total Accepted: 106.8K + * Total Submissions: 177.5K + * Testcase Example: '"anagram"\n"nagaram"' + * + * 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 + * + * 示例 1: + * + * 输入: s = "anagram", t = "nagaram" + * 输出: true + * + * + * 示例 2: + * + * 输入: s = "rat", t = "car" + * 输出: false + * + * 说明: + * 你可以假设字符串只包含小写字母。 + * + * 进阶: + * 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况? + * + */ + +// @lc code=start +/** + * @param {string} s + * @param {string} t + * @return {boolean} + */ +var isAnagram = function (s, t) { + if(s.length != t.length) return false + return Array.from(s).sort().toString() === Array.from(t).sort().toString() +}; +// @lc code=end + diff --git "a/Week02/code/263.\344\270\221\346\225\260.js" "b/Week02/code/263.\344\270\221\346\225\260.js" new file mode 100644 index 000000000..e43ce868c --- /dev/null +++ "b/Week02/code/263.\344\270\221\346\225\260.js" @@ -0,0 +1,68 @@ +/* + * @lc app=leetcode.cn id=263 lang=javascript + * + * [263] 丑数 + * + * https://leetcode-cn.com/problems/ugly-number/description/ + * + * algorithms + * Easy (49.28%) + * Likes: 130 + * Dislikes: 0 + * Total Accepted: 34.6K + * Total Submissions: 70.3K + * Testcase Example: '6' + * + * 编写一个程序判断给定的数是否为丑数。 + * + * 丑数就是只包含质因数 2, 3, 5 的正整数。 + * + * 示例 1: + * + * 输入: 6 + * 输出: true + * 解释: 6 = 2 × 3 + * + * 示例 2: + * + * 输入: 8 + * 输出: true + * 解释: 8 = 2 × 2 × 2 + * + * + * 示例 3: + * + * 输入: 14 + * 输出: false + * 解释: 14 不是丑数,因为它包含了另外一个质因数 7。 + * + * 说明: + * + * + * 1 是丑数。 + * 输入不会超过 32 位有符号整数的范围: [−2^31,  2^31 − 1]。 + * + * + */ + +// @lc code=start +/** + * @param {number} num + * @return {boolean} + */ +var isUgly = function (num) { + if (num <= 0) return false + while (num % 2 === 0) { + num = num / 2; + } + while (num % 3 === 0) { + num = num / 3; + } + while (num % 5 === 0) { + num = num / 5; + } + return num === 1 + +}; +// @lc code=end + diff --git "a/Week02/code/264.\344\270\221\346\225\260-ii.js" "b/Week02/code/264.\344\270\221\346\225\260-ii.js" new file mode 100644 index 000000000..06c8bfe13 --- /dev/null +++ "b/Week02/code/264.\344\270\221\346\225\260-ii.js" @@ -0,0 +1,124 @@ +/* + * @lc app=leetcode.cn id=264 lang=javascript + * + * [264] 丑数 II + * + * https://leetcode-cn.com/problems/ugly-number-ii/description/ + * + * algorithms + * Medium (52.19%) + * Likes: 307 + * Dislikes: 0 + * Total Accepted: 27.3K + * Total Submissions: 52.1K + * Testcase Example: '10' + * + * 编写一个程序,找出第 n 个丑数。 + * + * 丑数就是质因数只包含 2, 3, 5 的正整数。 + * + * 示例: + * + * 输入: n = 10 + * 输出: 12 + * 解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。 + * + * 说明:   + * + * + * 1 是丑数。 + * n 不超过1690。 + * + * + */ + +// @lc code=start +/** + * @param {number} n + * @return {number} + */ +var nthUglyNumber = function (n) { + // https://leetcode-cn.com/problems/chou-shu-lcof/solution/shuang-jie-fa-dong-tai-gui-hua-zui-xiao-dui-javasc/ + const defaultCmp = (x, y) => x > y; + const swap = (arr, i, j) => ([arr[i], arr[j]] = [arr[j], arr[i]]); + class Heap { + /** + * 默认是最大堆 + * @param {Function} cmp + */ + constructor(cmp = defaultCmp) { + this.container = []; + this.cmp = cmp; + } + + insert(data) { + const { container, cmp } = this; + + container.push(data); + let index = container.length - 1; + while (index) { + let parent = Math.floor((index - 1) / 2); + if (!cmp(container[index], container[parent])) { + return; + } + swap(container, index, parent); + index = parent; + } + } + + extract() { + const { container, cmp } = this; + if (!container.length) { + return null; + } + + swap(container, 0, container.length - 1); + const res = container.pop(); + const length = container.length; + let index = 0, + exchange = index * 2 + 1; + + while (exchange < length) { + // 如果有右节点,并且右节点的值大于左节点的值 + let right = index * 2 + 2; + if (right < length && cmp(container[right], container[exchange])) { + exchange = right; + } + if (!cmp(container[exchange], container[index])) { + break; + } + swap(container, exchange, index); + index = exchange; + exchange = index * 2 + 1; + } + + return res; + } + + top() { + if (this.container.length) return this.container[0]; + return null; + } + } + const heap = new Heap((x, y) => x < y); + const res = new Array(n); + const map = {}; + const primes = [2, 3, 5]; + + heap.insert(1); + map[1] = true; + for (let i = 0; i < n; ++i) { + res[i] = heap.extract(); + + for (const prime of primes) { + let tmp = res[i] * prime; + if (!map[tmp]) { + heap.insert(tmp); + map[tmp] = true; + } + } + } + return res[n - 1]; +}; +// @lc code=end + diff --git "a/Week02/code/347.\345\211\215-k-\344\270\252\351\253\230\351\242\221\345\205\203\347\264\240.js" "b/Week02/code/347.\345\211\215-k-\344\270\252\351\253\230\351\242\221\345\205\203\347\264\240.js" new file mode 100644 index 000000000..651dfb344 --- /dev/null +++ "b/Week02/code/347.\345\211\215-k-\344\270\252\351\253\230\351\242\221\345\205\203\347\264\240.js" @@ -0,0 +1,60 @@ +/* + * @lc app=leetcode.cn id=347 lang=javascript + * + * [347] 前 K 个高频元素 + * + * https://leetcode-cn.com/problems/top-k-frequent-elements/description/ + * + * algorithms + * Medium (60.63%) + * Likes: 364 + * Dislikes: 0 + * Total Accepted: 60.2K + * Total Submissions: 99.4K + * Testcase Example: '[1,1,1,2,2,3]\n2' + * + * 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 + * + * + * + * 示例 1: + * + * 输入: nums = [1,1,1,2,2,3], k = 2 + * 输出: [1,2] + * + * + * 示例 2: + * + * 输入: nums = [1], k = 1 + * 输出: [1] + * + * + * + * 提示: + * + * + * 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。 + * 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。 + * 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。 + * 你可以按任意顺序返回答案。 + * + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @param {number} k + * @return {number[]} + */ +var topKFrequent = function (nums, k) { + let map = new Map(), arr = [...new Set(nums)] + nums.map((num) => { + if (map.has(num)) map.set(num, map.get(num) + 1) + else map.set(num, 1) + }) + + return arr.sort((a, b) => map.get(b) - map.get(a)).slice(0, k); +}; +// @lc code=end + diff --git "a/Week02/code/429.n\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.js" "b/Week02/code/429.n\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.js" new file mode 100644 index 000000000..e795eafd2 --- /dev/null +++ "b/Week02/code/429.n\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.js" @@ -0,0 +1,77 @@ +/* + * @lc app=leetcode.cn id=429 lang=javascript + * + * [429] N叉树的层序遍历 + * + * https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/description/ + * + * algorithms + * Medium (65.54%) + * Likes: 94 + * Dislikes: 0 + * Total Accepted: 22.6K + * Total Submissions: 34.4K + * Testcase Example: '[1,null,3,2,4,null,5,6]' + * + * 给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。 + * + * 例如,给定一个 3叉树 : + * + * + * + * + * + * + * + * 返回其层序遍历: + * + * [ + * ⁠ [1], + * ⁠ [3,2,4], + * ⁠ [5,6] + * ] + * + * + * + * + * 说明: + * + * + * 树的深度不会超过 1000。 + * 树的节点总数不会超过 5000。 + * + */ + +// @lc code=start +/** + * // Definition for a Node. + * function Node(val,children) { + * this.val = val; + * this.children = children; + * }; + */ + +/** + * @param {Node} root + * @return {number[][]} + */ +var levelOrder = function (root) { + if (!root) return []; + let queue = [root]; + let res = []; + while (queue.length) { + let level = []; + let len = queue.length; + for (let i = 0; i < len; i++) { + let current = queue.shift(); + level.push(current.val); + if (current.children && current.children.length > 0) { + queue.push(...current.children); + } + } + res.push(level); + } + return res +}; +// @lc code=end + diff --git "a/Week02/code/49.\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.js" "b/Week02/code/49.\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.js" new file mode 100644 index 000000000..9c15aeed3 --- /dev/null +++ "b/Week02/code/49.\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.js" @@ -0,0 +1,57 @@ +/* + * @lc app=leetcode.cn id=49 lang=javascript + * + * [49] 字母异位词分组 + * + * https://leetcode-cn.com/problems/group-anagrams/description/ + * + * algorithms + * Medium (61.88%) + * Likes: 370 + * Dislikes: 0 + * Total Accepted: 81.5K + * Total Submissions: 131.3K + * Testcase Example: '["eat","tea","tan","ate","nat","bat"]' + * + * 给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。 + * + * 示例: + * + * 输入: ["eat", "tea", "tan", "ate", "nat", "bat"] + * 输出: + * [ + * ⁠ ["ate","eat","tea"], + * ⁠ ["nat","tan"], + * ⁠ ["bat"] + * ] + * + * 说明: + * + * + * 所有输入均为小写字母。 + * 不考虑答案输出的顺序。 + * + * + */ + +// @lc code=start +/** + * @param {string[]} strs + * @return {string[][]} + */ +var groupAnagrams = function (strs) { + let hash = new Map() + for (let i = 0; i < strs.length; i++) { + let str = strs[i].split('').sort().join() + if (hash.has(str)) { + let tmp = hash.get(str) + tmp.push(strs[i]) + hash.set(str, tmp) + } else { + hash.set(str, [strs[i]]) + } + } + return [...hash.values()] +}; +// @lc code=end + diff --git a/Week02/code/50.pow-x-n.js b/Week02/code/50.pow-x-n.js new file mode 100644 index 000000000..a7a001226 --- /dev/null +++ b/Week02/code/50.pow-x-n.js @@ -0,0 +1,58 @@ +/* + * @lc app=leetcode.cn id=50 lang=javascript + * + * [50] Pow(x, n) + * + * https://leetcode-cn.com/problems/powx-n/description/ + * + * algorithms + * Medium (33.62%) + * Likes: 258 + * Dislikes: 0 + * Total Accepted: 51.2K + * Total Submissions: 151.4K + * Testcase Example: '2.00000\n10' + * + * 实现 pow(x, n) ,即计算 x 的 n 次幂函数。 + * + * 示例 1: + * + * 输入: 2.00000, 10 + * 输出: 1024.00000 + * + * + * 示例 2: + * + * 输入: 2.10000, 3 + * 输出: 9.26100 + * + * + * 示例 3: + * + * 输入: 2.00000, -2 + * 输出: 0.25000 + * 解释: 2^-2 = 1/2^2 = 1/4 = 0.25 + * + * 说明: + * + * + * -100.0 < x < 100.0 + * n 是 32 位有符号整数,其数值范围是 [−2^31, 2^31 − 1] 。 + * + * + */ + +// @lc code=start +/** + * @param {number} x + * @param {number} n + * @return {number} + */ +var myPow = function (x, n) { + if (n < 0) return 1 / myPow(x, -n); + if (n === 0) return 1; + if (n % 2 === 0) return myPow(x * x, Math.floor(n / 2)); + return myPow(x * x, Math.floor(n / 2)) * x; +}; +// @lc code=end + diff --git "a/Week02/code/51.n\347\232\207\345\220\216.js" "b/Week02/code/51.n\347\232\207\345\220\216.js" new file mode 100644 index 000000000..1c5e806af --- /dev/null +++ "b/Week02/code/51.n\347\232\207\345\220\216.js" @@ -0,0 +1,99 @@ +/* + * @lc app=leetcode.cn id=51 lang=javascript + * + * [51] N皇后 + * + * https://leetcode-cn.com/problems/n-queens/description/ + * + * algorithms + * Hard (67.42%) + * Likes: 328 + * Dislikes: 0 + * Total Accepted: 25.9K + * Total Submissions: 38K + * Testcase Example: '4' + * + * n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 + * + * + * + * 上图为 8 皇后问题的一种解法。 + * + * 给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。 + * + * 每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 + * + * 示例: + * + * 输入: 4 + * 输出: [ + * ⁠[".Q..", // 解法 1 + * ⁠ "...Q", + * ⁠ "Q...", + * ⁠ "..Q."], + * + * ⁠["..Q.", // 解法 2 + * ⁠ "Q...", + * ⁠ "...Q", + * ⁠ ".Q.."] + * ] + * 解释: 4 皇后问题存在两个不同的解法。 + * + * + */ + +// @lc code=start +/** + * @param {number} n + * @return {string[][]} + */ +var solveNQueens = function (n) { + + // 1. 行不能一样 按照行查找 + + // 2. 列不能一样 + + // 3. 行-列(索引值)不能一样 + + // 4. 行+列(索引值)不能一样 + + // tmp 用于记录之前的路径 + // tmp:当行索引为n时,摆放的棋子列的索引值 + // [1,3,0,2] + // 比如行索引是0时,摆放列索引是1 + // 比如行索引是1时,摆放列索引是3 + let ret = [] + // 查找第0行 + find(0) + return ret + function find(row, tmp = []) { + // 终止条件 + if (row === n) { + // n-1 已经是最后一行了 tmp是所有拜访的位置 + ret.push(tmp.map(c => { + let arr = new Array(n).fill('.') + arr[c] = 'Q' + return arr.join('') + })) + } + // 查找 + for (let col = 0; col < n; col++) { + // 是否无法放置 + let canNotSet = tmp.some((colIndex, rowIndex) => { + // colIndex和rowIndex是之前摆放棋子的行列索引 + // col和row是当前所在位置的索引 + return colIndex === col || + (rowIndex - colIndex) === (row - col) || + (rowIndex + colIndex) === (row + col) + }) + if (canNotSet) { + continue + } + // 如果能放置,直接查找下一行 + find(row + 1, [...tmp, col]) + } + } + +}; +// @lc code=end + diff --git "a/Week02/code/589.n\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.js" "b/Week02/code/589.n\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.js" new file mode 100644 index 000000000..797f977bf --- /dev/null +++ "b/Week02/code/589.n\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.js" @@ -0,0 +1,77 @@ +/* + * @lc app=leetcode.cn id=589 lang=javascript + * + * [589] N叉树的前序遍历 + * + * https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal/description/ + * + * algorithms + * Easy (73.24%) + * Likes: 82 + * Dislikes: 0 + * Total Accepted: 30.3K + * Total Submissions: 41.3K + * Testcase Example: '[1,null,3,2,4,null,5,6]' + * + * 给定一个 N 叉树,返回其节点值的前序遍历。 + * + * 例如,给定一个 3叉树 : + * + * + * + * + * + * + * + * 返回其前序遍历: [1,3,5,6,2,4]。 + * + * + * + * 说明: 递归法很简单,你可以使用迭代法完成此题吗? + */ + +// @lc code=start +/** + * // Definition for a Node. + * function Node(val, children) { + * this.val = val; + * this.children = children; + * }; + */ + +/** + * @param {Node} root + * @return {number[]} + */ +var preorder = function (root) { + // 递归 + // if (!root) return []; + // const res = []; + // const recusion = root => { + // if (!root) return; + // res.push(root.val); + // for (let i = 0; i < root.children.length; i++) { + // recusion(root.children[i]); + // } + // } + // recusion(root); + // return res; + + // 迭代 + let result = [] + let current = root; + while (current) { + result.push(current.val); + let tmp = current.children; + if (!tmp.length) return result; + current = current.children.shift(); + let next = current; + while (next.children.length) { + next = next.children[next.children.length - 1]; + } + next.children = tmp; + } + return result; +} +// @lc code=end + diff --git "a/Week02/code/70.\347\210\254\346\245\274\346\242\257.js" "b/Week02/code/70.\347\210\254\346\245\274\346\242\257.js" new file mode 100644 index 000000000..771944eed --- /dev/null +++ "b/Week02/code/70.\347\210\254\346\245\274\346\242\257.js" @@ -0,0 +1,89 @@ +/* + * @lc app=leetcode.cn id=70 lang=javascript + * + * [70] 爬楼梯 + * + * https://leetcode-cn.com/problems/climbing-stairs/description/ + * + * algorithms + * Easy (49.72%) + * Likes: 1074 + * Dislikes: 0 + * Total Accepted: 221.6K + * Total Submissions: 445.7K + * Testcase Example: '2' + * + * 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 + * + * 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? + * + * 注意:给定 n 是一个正整数。 + * + * 示例 1: + * + * 输入: 2 + * 输出: 2 + * 解释: 有两种方法可以爬到楼顶。 + * 1. 1 阶 + 1 阶 + * 2. 2 阶 + * + * 示例 2: + * + * 输入: 3 + * 输出: 3 + * 解释: 有三种方法可以爬到楼顶。 + * 1. 1 阶 + 1 阶 + 1 阶 + * 2. 1 阶 + 2 阶 + * 3. 2 阶 + 1 阶 + * + * + */ + +// @lc code=start +/** + * @param {number} n + * @return {number} + */ +var climbStairs = function (n) { + // 递推思想,找最近重复子问题 + // 1:1 + // 2:2 + // 3:f(1)+f(2) + // 4:f(2)+f(3) + // n:f(n-2)+f(n-1) + + // // 解法一 + // // 时间复杂度:O(n) + // // 空间复杂度:O(n) + // if (n <= 2) return n + // const a = [1, 2] + // for (let i = 2; i < n; i++) { + // a[i] = a[i - 1] + a[i - 2] + // } + // return a[n - 1] + + // 解法二 + // 思路:循环、只保存最后三个值 + // 时间复杂度:O(n) + // 空间复杂度:O(1) + if (n <= 2) return n + let f1 = 1 + let f2 = 2 + let f3 = 3 + for (let i = 2; i < n; i++) { + f3 = f1 + f2 + f1 = f2 + f2 = f3 + } + return f3 + + // // 解法三 + // // 思路:斐波那契数列公式 + // // 时间复杂度:O(log n) + // // 空间复杂度:O(1) + // const sqrt_5 = Math.sqrt(5); + // const fib_n = Math.pow((1 + sqrt_5) / 2, n + 1) - Math.pow((1 - sqrt_5) / 2, n + 1); + // return Math.round(fib_n / sqrt_5); +}; +// @lc code=end + diff --git "a/Week02/code/78.\345\255\220\351\233\206.js" "b/Week02/code/78.\345\255\220\351\233\206.js" new file mode 100644 index 000000000..ca3a24617 --- /dev/null +++ "b/Week02/code/78.\345\255\220\351\233\206.js" @@ -0,0 +1,58 @@ +/* + * @lc app=leetcode.cn id=78 lang=javascript + * + * [78] 子集 + * + * https://leetcode-cn.com/problems/subsets/description/ + * + * algorithms + * Medium (77.41%) + * Likes: 608 + * Dislikes: 0 + * Total Accepted: 97.9K + * Total Submissions: 126.5K + * Testcase Example: '[1,2,3]' + * + * 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 + * + * 说明:解集不能包含重复的子集。 + * + * 示例: + * + * 输入: nums = [1,2,3] + * 输出: + * [ + * ⁠ [3], + * [1], + * [2], + * [1,2,3], + * [1,3], + * [2,3], + * [1,2], + * [] + * ] + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {number[][]} + */ +var subsets = function (nums) { + let n = nums.length; + let tmpPath = []; + let res = []; + let backtrack = (tmpPath, start) => { + res.push(tmpPath); + for (let i = start; i < n; i++) { + tmpPath.push(nums[i]); + backtrack(tmpPath.slice(), i + 1); + tmpPath.pop(); + } + } + backtrack(tmpPath, 0); + return res; +}; +// @lc code=end + diff --git "a/Week02/code/94.\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.js" "b/Week02/code/94.\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.js" new file mode 100644 index 000000000..ae88be513 --- /dev/null +++ "b/Week02/code/94.\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.js" @@ -0,0 +1,72 @@ +/* + * @lc app=leetcode.cn id=94 lang=javascript + * + * [94] 二叉树的中序遍历 + * + * https://leetcode-cn.com/problems/binary-tree-inorder-traversal/description/ + * + * algorithms + * Medium (69.62%) + * Likes: 378 + * Dislikes: 0 + * Total Accepted: 97.4K + * Total Submissions: 139.4K + * Testcase Example: '[1,null,2,3]' + * + * 给定一个二叉树,返回它的中序 遍历。 + * + * 示例: + * + * 输入: [1,null,2,3] + * ⁠ 1 + * ⁠ \ + * ⁠ 2 + * ⁠ / + * ⁠ 3 + * + * 输出: [1,3,2] + * + * 进阶: 递归算法很简单,你可以通过迭代算法完成吗? + * + */ + +// @lc code=start +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[]} + */ +var inorderTraversal = function (root, arr = []) { + // // 递归 + // if (root) { + // inorderTraversal(root.left, arr) + // arr.push(root.val) + // inorderTraversal(root.right, arr) + // } + // return arr + + // 迭代 + let result = [] + let stack = [] + let curr = root + while (curr || stack.length > 0) { + if (curr) { + stack.push(curr); // 节点入栈,通过curr寻找右子树 + curr = curr.left; + } else { + curr = stack.pop(); + result.push(curr.val); + curr = curr.right; + } + } + return result + +}; +// @lc code=end + diff --git a/Week03/NOTE.md b/Week03/NOTE.md deleted file mode 100644 index 50de30414..000000000 --- a/Week03/NOTE.md +++ /dev/null @@ -1 +0,0 @@ -学习笔记 \ No newline at end of file diff --git a/Week03/README.md b/Week03/README.md new file mode 100644 index 000000000..d8323dc65 --- /dev/null +++ b/Week03/README.md @@ -0,0 +1,41 @@ +# 泛型递归 + +```javascript +function recursion (level, param) { + // terminator + if (level > MAX_LEVEL) { + // process result + return result; + } + // process current logic + process(level, param); + // drill down + recursion(level + 1, newParam); + // revert the current level states +} +``` + + +# 分治 + +```javascript +function divide_conquer(problem, param1, param2, ...) { + // recursion terminator + if (problem is None) { + return result; + } + // prepare data + const data = prepare_data(problem); + subProblems = split_problem(problem, data); + // conquer subProblems + subResult1 = divide_conquer(subProblems[0], p1, ...); + subResult2 = divide_conquer(subProblems[1], p1, ...); + subResult3 = divide_conquer(subProblems[2], p1, ...); + ... + // process and generate the final result + result = process_result(subResult1, subResult2, subResult3, ...); + + // revert the current level states +} +``` + diff --git "a/Week03/code/102.\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.js" "b/Week03/code/102.\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.js" new file mode 100644 index 000000000..382688825 --- /dev/null +++ "b/Week03/code/102.\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.js" @@ -0,0 +1,71 @@ +/* + * @lc app=leetcode.cn id=102 lang=javascript + * + * [102] 二叉树的层序遍历 + * + * https://leetcode-cn.com/problems/binary-tree-level-order-traversal/description/ + * + * algorithms + * Medium (62.92%) + * Likes: 547 + * Dislikes: 0 + * Total Accepted: 151K + * Total Submissions: 239.7K + * Testcase Example: '[3,9,20,null,null,15,7]' + * + * 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 + * + * + * + * 示例: + * 二叉树:[3,9,20,null,null,15,7], + * + * ⁠ 3 + * ⁠ / \ + * ⁠ 9 20 + * ⁠ / \ + * ⁠ 15 7 + * + * + * 返回其层次遍历结果: + * + * [ + * ⁠ [3], + * ⁠ [9,20], + * ⁠ [15,7] + * ] + * + * + */ + +// @lc code=start +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[][]} + */ +var levelOrder = function (root) { + const ret = []; + if (!root) return ret + const q = [] + q.push(root) + while (q.length !== 0) { + const currentLevelSize = q.length; + ret.push([]) + for (let i = 1; i <= currentLevelSize; ++i) { + const node = q.shift() + ret[ret.length - 1].push(node.val) + if (node.left) q.push(node.left) + if (node.right) q.push(node.right) + } + } + return ret +}; +// @lc code=end + diff --git "a/Week03/code/105.\344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.js" "b/Week03/code/105.\344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.js" new file mode 100644 index 000000000..27de42e3f --- /dev/null +++ "b/Week03/code/105.\344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.js" @@ -0,0 +1,58 @@ +/* + * @lc app=leetcode.cn id=105 lang=javascript + * + * [105] 从前序与中序遍历序列构造二叉树 + * + * https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/description/ + * + * algorithms + * Medium (67.14%) + * Likes: 543 + * Dislikes: 0 + * Total Accepted: 89.8K + * Total Submissions: 133.4K + * Testcase Example: '[3,9,20,15,7]\n[9,3,15,20,7]' + * + * 根据一棵树的前序遍历与中序遍历构造二叉树。 + * + * 注意: + * 你可以假设树中没有重复的元素。 + * + * 例如,给出 + * + * 前序遍历 preorder = [3,9,20,15,7] + * 中序遍历 inorder = [9,3,15,20,7] + * + * 返回如下的二叉树: + * + * ⁠ 3 + * ⁠ / \ + * ⁠ 9 20 + * ⁠ / \ + * ⁠ 15 7 + * + */ + +// @lc code=start +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {number[]} preorder + * @param {number[]} inorder + * @return {TreeNode} + */ +var buildTree = function(preorder, inorder) { + if (inorder.length === 0) return null + let root = new TreeNode(preorder[0]) + let mid = inorder.indexOf(preorder[0]) + root.left = buildTree(preorder.slice(1, mid + 1), inorder.slice(0, mid)) + root.right = buildTree(preorder.slice(mid + 1), inorder.slice(mid + 1)) + return root +}; +// @lc code=end + diff --git "a/Week03/code/122.\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272-ii.js" "b/Week03/code/122.\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272-ii.js" new file mode 100644 index 000000000..8f0d81f6c --- /dev/null +++ "b/Week03/code/122.\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272-ii.js" @@ -0,0 +1,72 @@ +/* + * @lc app=leetcode.cn id=122 lang=javascript + * + * [122] 买卖股票的最佳时机 II + * + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/description/ + * + * algorithms + * Easy (60.42%) + * Likes: 749 + * Dislikes: 0 + * Total Accepted: 174.5K + * Total Submissions: 287K + * Testcase Example: '[7,1,5,3,6,4]' + * + * 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 + * + * 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 + * + * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + * + * + * + * 示例 1: + * + * 输入: [7,1,5,3,6,4] + * 输出: 7 + * 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 + * 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 + * + * + * 示例 2: + * + * 输入: [1,2,3,4,5] + * 输出: 4 + * 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 + * 。 + * 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 + * 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 + * + * + * 示例 3: + * + * 输入: [7,6,4,3,1] + * 输出: 0 + * 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 + * + * + * + * 提示: + * + * + * 1 <= prices.length <= 3 * 10 ^ 4 + * 0 <= prices[i] <= 10 ^ 4 + * + * + */ + +// @lc code=start +/** + * @param {number[]} prices + * @return {number} + */ +var maxProfit = function (prices) { + let sum = 0 + for (let i = 1; i < prices.length; i++) { + sum += Math.max(prices[i] - prices[i - 1], 0) + } + return sum +}; +// @lc code=end + diff --git "a/Week03/code/236.\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.js" "b/Week03/code/236.\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.js" new file mode 100644 index 000000000..1cdb6810d --- /dev/null +++ "b/Week03/code/236.\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.js" @@ -0,0 +1,86 @@ +/* + * @lc app=leetcode.cn id=236 lang=javascript + * + * [236] 二叉树的最近公共祖先 + * + * https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/description/ + * + * algorithms + * Medium (59.04%) + * Likes: 343 + * Dislikes: 0 + * Total Accepted: 40.7K + * Total Submissions: 68.6K + * Testcase Example: '[3,5,1,6,2,0,8,null,null,7,4]\n5\n1' + * + * 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 + * + * 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x + * 的深度尽可能大(一个节点也可以是它自己的祖先)。” + * + * 例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4] + * + * + * + * + * + * 示例 1: + * + * 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 + * 输出: 3 + * 解释: 节点 5 和节点 1 的最近公共祖先是节点 3。 + * + * + * 示例 2: + * + * 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 + * 输出: 5 + * 解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。 + * + * + * + * + * 说明: + * + * + * 所有节点的值都是唯一的。 + * p、q 为不同节点且均存在于给定的二叉树中。 + * + * + */ + +// @lc code=start +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @param {TreeNode} p + * @param {TreeNode} q + * @return {TreeNode} + */ +var lowestCommonAncestor = function (root, p, q) { + // 如果左子树没有找到p和q,那就去右边找 + // 如果两边都找到,那root就是了 + // findPorQ + if (root == null || root == p || root == q) { + return root + } + const left = lowestCommonAncestor(root.left, p, q) + const right = lowestCommonAncestor(root.right, p, q) + // if(left == null){ + // return right + // } + // if(right == null){ + // return left + // } + // // 左右都找到了p或q,当前root为结果 + // return root + return left == null ? right : right == null ? left : root +}; +// @lc code=end + diff --git "a/Week03/code/367.\346\234\211\346\225\210\347\232\204\345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.js" "b/Week03/code/367.\346\234\211\346\225\210\347\232\204\345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.js" new file mode 100644 index 000000000..67ee59a8f --- /dev/null +++ "b/Week03/code/367.\346\234\211\346\225\210\347\232\204\345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.js" @@ -0,0 +1,55 @@ +/* + * @lc app=leetcode.cn id=367 lang=javascript + * + * [367] 有效的完全平方数 + * + * https://leetcode-cn.com/problems/valid-perfect-square/description/ + * + * algorithms + * Easy (43.38%) + * Likes: 140 + * Dislikes: 0 + * Total Accepted: 35.4K + * Total Submissions: 81.7K + * Testcase Example: '16' + * + * 给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False。 + * + * 说明:不要使用任何内置的库函数,如  sqrt。 + * + * 示例 1: + * + * 输入:16 + * 输出:True + * + * 示例 2: + * + * 输入:14 + * 输出:False + * + * + */ + +// @lc code=start +/** + * @param {number} num + * @return {boolean} + */ +var isPerfectSquare = function (num) { + if (num == 1) return true + let left = 1, right = num + while (left <= right) { + let mid = parseInt((left + right) / 2) + let temp = mid * mid + if (temp == num) { + return true + } else if (temp > num) { + right = mid - 1 + } else { + left = mid + 1 + } + } + return false +}; +// @lc code=end + diff --git "a/Week03/code/455.\345\210\206\345\217\221\351\245\274\345\271\262.js" "b/Week03/code/455.\345\210\206\345\217\221\351\245\274\345\271\262.js" new file mode 100644 index 000000000..a034c1c94 --- /dev/null +++ "b/Week03/code/455.\345\210\206\345\217\221\351\245\274\345\271\262.js" @@ -0,0 +1,73 @@ +/* + * @lc app=leetcode.cn id=455 lang=javascript + * + * [455] 分发饼干 + * + * https://leetcode-cn.com/problems/assign-cookies/description/ + * + * algorithms + * Easy (54.57%) + * Likes: 179 + * Dislikes: 0 + * Total Accepted: 41.7K + * Total Submissions: 76K + * Testcase Example: '[1,2,3]\n[1,1]' + * + * 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi + * ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i + * ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。 + * + * 注意: + * + * 你可以假设胃口值为正。 + * 一个小朋友最多只能拥有一块饼干。 + * + * 示例 1: + * + * + * 输入: [1,2,3], [1,1] + * + * 输出: 1 + * + * 解释: + * 你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 + * 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 + * 所以你应该输出1。 + * + * + * 示例 2: + * + * + * 输入: [1,2], [1,2,3] + * + * 输出: 2 + * + * 解释: + * 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 + * 你拥有的饼干数量和尺寸都足以让所有孩子满足。 + * 所以你应该输出2. + * + * + */ + +// @lc code=start +/** + * @param {number[]} g + * @param {number[]} s + * @return {number} + */ +var findContentChildren = function (g, s) { + let count = 0 + g.sort((a, b) => a - b) + s.sort((a, b) => a - b) + for (const cookie of s) { + const childIndex = g.findIndex((child) => child <= cookie) + if (childIndex !== -1) { + count++ + g.splice(childIndex, 1) + } + } + return count +}; +// @lc code=end + diff --git "a/Week03/code/46.\345\205\250\346\216\222\345\210\227.js" "b/Week03/code/46.\345\205\250\346\216\222\345\210\227.js" new file mode 100644 index 000000000..709b14af5 --- /dev/null +++ "b/Week03/code/46.\345\205\250\346\216\222\345\210\227.js" @@ -0,0 +1,57 @@ +/* + * @lc app=leetcode.cn id=46 lang=javascript + * + * [46] 全排列 + * + * https://leetcode-cn.com/problems/permutations/description/ + * + * algorithms + * Medium (73.60%) + * Likes: 530 + * Dislikes: 0 + * Total Accepted: 77.2K + * Total Submissions: 104.3K + * Testcase Example: '[1,2,3]' + * + * 给定一个没有重复数字的序列,返回其所有可能的全排列。 + * + * 示例: + * + * 输入: [1,2,3] + * 输出: + * [ + * ⁠ [1,2,3], + * ⁠ [1,3,2], + * ⁠ [2,1,3], + * ⁠ [2,3,1], + * ⁠ [3,1,2], + * ⁠ [3,2,1] + * ] + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {number[][]} + */ +function backtrack(list, temp, nums) { + if(temp.length == nums.length){ + return list.push([...temp]) + } + for (let i = 0; i < nums.length; i++) { + if (temp.includes(nums[i])) { + continue + } + temp.push(nums[i]) + backtrack(list, temp, nums) + temp.pop() + } +} +var permute = function (nums) { + let list = [] + backtrack(list, [], nums) + return list +}; +// @lc code=end + diff --git "a/Week03/code/47.\345\205\250\346\216\222\345\210\227-ii.js" "b/Week03/code/47.\345\205\250\346\216\222\345\210\227-ii.js" new file mode 100644 index 000000000..faeedf628 --- /dev/null +++ "b/Week03/code/47.\345\205\250\346\216\222\345\210\227-ii.js" @@ -0,0 +1,65 @@ +/* + * @lc app=leetcode.cn id=47 lang=javascript + * + * [47] 全排列 II + * + * https://leetcode-cn.com/problems/permutations-ii/description/ + * + * algorithms + * Medium (59.05%) + * Likes: 325 + * Dislikes: 0 + * Total Accepted: 66.8K + * Total Submissions: 112.8K + * Testcase Example: '[1,1,2]' + * + * 给定一个可包含重复数字的序列,返回所有不重复的全排列。 + * + * 示例: + * + * 输入: [1,1,2] + * 输出: + * [ + * ⁠ [1,1,2], + * ⁠ [1,2,1], + * ⁠ [2,1,1] + * ] + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {number[][]} + */ +var permuteUnique = function (nums) { + const swap = function (nums, i, j) { + if (i === j) return + [nums[i], nums[j]] = [nums[j], nums[i]] + } + + const cal = function (nums, first, result) { + if (nums.length === first) { + result.push([...nums]) + return + } + const map = new Map() + for (let i = first; i < nums.length; i++) { + if (!map.get(nums[i])) { + map.set(nums[i], true) + swap(nums, first, i) + cal(nums, first + 1, result) + swap(nums, first, i) + } + } + } + + if (nums == null) return + nums.sort((a, b) => a - b) + const res = [] + cal(nums, 0, res) + return res + +}; +// @lc code=end + diff --git "a/Week03/code/55.\350\267\263\350\267\203\346\270\270\346\210\217.js" "b/Week03/code/55.\350\267\263\350\267\203\346\270\270\346\210\217.js" new file mode 100644 index 000000000..a231fb184 --- /dev/null +++ "b/Week03/code/55.\350\267\263\350\267\203\346\270\270\346\210\217.js" @@ -0,0 +1,52 @@ +/* + * @lc app=leetcode.cn id=55 lang=javascript + * + * [55] 跳跃游戏 + * + * https://leetcode-cn.com/problems/jump-game/description/ + * + * algorithms + * Medium (40.11%) + * Likes: 713 + * Dislikes: 0 + * Total Accepted: 123.2K + * Total Submissions: 305.8K + * Testcase Example: '[2,3,1,1,4]' + * + * 给定一个非负整数数组,你最初位于数组的第一个位置。 + * + * 数组中的每个元素代表你在该位置可以跳跃的最大长度。 + * + * 判断你是否能够到达最后一个位置。 + * + * 示例 1: + * + * 输入: [2,3,1,1,4] + * 输出: true + * 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。 + * + * + * 示例 2: + * + * 输入: [3,2,1,0,4] + * 输出: false + * 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。 + * + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {boolean} + */ +var canJump = function (nums) { + let max = 0 // 能够走到的数组下标 + for (let i = 0; i < nums.length; i++) { + if (max < i) return false // 当前这一步都走不到,后面更走不到了 + max = Math.max(nums[i] + i, max) + } + return max >= nums.length - 1 +}; +// @lc code=end + diff --git "a/Week03/code/69.x-\347\232\204\345\271\263\346\226\271\346\240\271.js" "b/Week03/code/69.x-\347\232\204\345\271\263\346\226\271\346\240\271.js" new file mode 100644 index 000000000..3beda2596 --- /dev/null +++ "b/Week03/code/69.x-\347\232\204\345\271\263\346\226\271\346\240\271.js" @@ -0,0 +1,61 @@ +/* + * @lc app=leetcode.cn id=69 lang=javascript + * + * [69] x 的平方根 + * + * https://leetcode-cn.com/problems/sqrtx/description/ + * + * algorithms + * Easy (37.38%) + * Likes: 302 + * Dislikes: 0 + * Total Accepted: 93.4K + * Total Submissions: 248.8K + * Testcase Example: '4' + * + * 实现 int sqrt(int x) 函数。 + * + * 计算并返回 x 的平方根,其中 x 是非负整数。 + * + * 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 + * + * 示例 1: + * + * 输入: 4 + * 输出: 2 + * + * + * 示例 2: + * + * 输入: 8 + * 输出: 2 + * 说明: 8 的平方根是 2.82842..., + * 由于返回类型是整数,小数部分将被舍去。 + * + * + */ + +// @lc code=start +/** + * @param {number} x + * @return {number} + */ +var mySqrt = function (x) { + let left = 0 + let right = x + let mid = 0 + while (left <= right) { + mid = parseInt((left + right) / 2) + if (Math.pow(mid, 2) == x) { + return mid; + } else if (Math.pow(mid, 2) < x) { + left = mid + 1 + } else { + right = mid - 1 + } + } + return right + +}; +// @lc code=end + diff --git "a/Week03/code/77.\347\273\204\345\220\210.js" "b/Week03/code/77.\347\273\204\345\220\210.js" new file mode 100644 index 000000000..6e1013284 --- /dev/null +++ "b/Week03/code/77.\347\273\204\345\220\210.js" @@ -0,0 +1,59 @@ +/* + * @lc app=leetcode.cn id=77 lang=javascript + * + * [77] 组合 + * + * https://leetcode-cn.com/problems/combinations/description/ + * + * algorithms + * Medium (73.84%) + * Likes: 293 + * Dislikes: 0 + * Total Accepted: 57.3K + * Total Submissions: 77.5K + * Testcase Example: '4\n2' + * + * 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 + * + * 示例: + * + * 输入: n = 4, k = 2 + * 输出: + * [ + * ⁠ [2,4], + * ⁠ [3,4], + * ⁠ [2,3], + * ⁠ [1,2], + * ⁠ [1,3], + * ⁠ [1,4], + * ] + * + */ + +// @lc code=start +/** + * @param {number} n + * @param {number} k + * @return {number[][]} + */ +var combine = function (n, k) { + const res = []; + const path = []; + if (k == 0) return [[]] + + function dfs(start, n, k, path) { + if (path.length === k) { + res.push(path.slice(0)) + return + } + for (let i = start; i <= n; i++) { + path.push(i); + dfs(i + 1, n, k, path); + path.pop(); + } + } + dfs(1, n, k, path) + return res; +}; +// @lc code=end + diff --git a/Week04/NOTE.md b/Week04/NOTE.md deleted file mode 100644 index 50de30414..000000000 --- a/Week04/NOTE.md +++ /dev/null @@ -1 +0,0 @@ -学习笔记 \ No newline at end of file diff --git a/Week04/README.md b/Week04/README.md new file mode 100644 index 000000000..8a4a2bff4 --- /dev/null +++ b/Week04/README.md @@ -0,0 +1,134 @@ +# 深度优先搜索 + +## Depth-First-Search 模板 + +- 递归 + +``` +visited = set() +def dfs(node, visited): +if node in visited: # terminator +# already visited +return +visited.add(node) +# process current node here. +... +for next_node in node.children(): +if not next_node in visited: +dfs(next_node, visited) +``` + +- 非递归 + +``` +def DFS(self, tree): +if tree.root is None: +return [] +visited, stack = [], [tree.root] +while stack: +node = stack.pop() +visited.add(node) +process (node) +nodes = generate_related_nodes(node) +stack.push(nodes) +# other processing work +... +``` + + + +# 广度优先搜索 + +## Breadth-First-Search 模板 + +``` +def BFS(graph, start, end): +queue = [] +queue.append([start]) +visited.add(start) +while queue: +node = queue.pop() +visited.add(node) +process(node) +nodes = generate_related_nodes(node) +queue.push(nodes) +# other +``` + + + +# 贪心算法 Greedy + +- 贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。 + + + +## 与动态规划的区别 + +- 贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。 +- 动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。 + + + +## 使用场景 + +- 简单地说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。这种子问题最优解称为最优子结构。 + + + +# 二分查找 + +## 前提 + +1. 目标函数单调性(单调递增或者递减) +2. 存在上下界(bounded) +3. 能够通过索引访问(index accessible) + + + +## 代码模板 + +- 单调递增 +- 整数 + +``` +left, right = 0, len(array) - 1 +while left <= right: +mid = (left + right) / 2 +if array[mid] == target: +# find the target!! +break or return result +elif array[mid] < target: +left = mid + 1 +else: +right = mid - 1 +``` + + + +# 思考题 + +使用二分查找,寻找一个半有序数组 [4, 5, 6, 7, 0, 1, 2] 中间无序的地方 + +即求153题:寻找旋转排序数组中的最小值 + +```javascript +var find = function (nums) { + // 思路:二分,单调递增、且有索引和边界 + // 无旋转:nums[left] <= nums[mid] <= nums[right] min = nums[left] + // 有旋转:nums[left] >= nums[mid] <= nums[right] 收缩右边界 + // 有旋转:nums[left] <= nums[mid] >= nums[right] 收缩左边界 + let left = 0, right = nums.length - 1 + while (left < right) { + let mid = (left + right) / 2 | 0 + if (nums[mid] > nums[right]) { + left = mid + 1 + } else { + right = mid + } + } + // left 即为无序位置的下标 + return left +}; +``` + diff --git "a/Week04/code/1143.\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.js" "b/Week04/code/1143.\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.js" new file mode 100644 index 000000000..cfacd331c --- /dev/null +++ "b/Week04/code/1143.\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.js" @@ -0,0 +1,89 @@ +/* + * @lc app=leetcode.cn id=1143 lang=javascript + * + * [1143] 最长公共子序列 + * + * https://leetcode-cn.com/problems/longest-common-subsequence/description/ + * + * algorithms + * Medium (59.75%) + * Likes: 174 + * Dislikes: 0 + * Total Accepted: 28.6K + * Total Submissions: 47.7K + * Testcase Example: '"abcde"\n"ace"' + * + * 给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。 + * + * 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 + * 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" + * 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 + * + * 若这两个字符串没有公共子序列,则返回 0。 + * + * + * + * 示例 1: + * + * 输入:text1 = "abcde", text2 = "ace" + * 输出:3 + * 解释:最长公共子序列是 "ace",它的长度为 3。 + * + * + * 示例 2: + * + * 输入:text1 = "abc", text2 = "abc" + * 输出:3 + * 解释:最长公共子序列是 "abc",它的长度为 3。 + * + * + * 示例 3: + * + * 输入:text1 = "abc", text2 = "def" + * 输出:0 + * 解释:两个字符串没有公共子序列,返回 0。 + * + * + * + * + * 提示: + * + * + * 1 <= text1.length <= 1000 + * 1 <= text2.length <= 1000 + * 输入的字符串只含有小写英文字符。 + * + * + */ + +// @lc code=start +/** + * @param {string} text1 + * @param {string} text2 + * @return {number} + */ +var longestCommonSubsequence = function (text1, text2) { + const m = text1.length + const n = text2.length + let dp = [] + for (let i = 0; i <= m; i++) { + dp[i] = [] + for (let j = 0; j <= n; j++) { + dp[i][j] = 0 + } + } + for (let i = 0; i <= m; i++) { + for (let j = 0; j <= n; j++) { + if (i == 0 || j == 0) { + dp[i][j] = 0 + } else if (text1[i - 1] == text2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1 + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + } + } + }; + return dp[m][n] +}; +// @lc code=end + diff --git "a/Week04/code/120.\344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.js" "b/Week04/code/120.\344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.js" new file mode 100644 index 000000000..dc2920a22 --- /dev/null +++ "b/Week04/code/120.\344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.js" @@ -0,0 +1,56 @@ +/* + * @lc app=leetcode.cn id=120 lang=javascript + * + * [120] 三角形最小路径和 + * + * https://leetcode-cn.com/problems/triangle/description/ + * + * algorithms + * Medium (64.79%) + * Likes: 433 + * Dislikes: 0 + * Total Accepted: 65.7K + * Total Submissions: 100.9K + * Testcase Example: '[[2],[3,4],[6,5,7],[4,1,8,3]]' + * + * 给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 + * + * 相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。 + * + * + * + * 例如,给定三角形: + * + * [ + * ⁠ [2], + * ⁠ [3,4], + * ⁠ [6,5,7], + * ⁠ [4,1,8,3] + * ] + * + * + * 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 + * + * + * + * 说明: + * + * 如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。 + * + */ + +// @lc code=start +/** + * @param {number[][]} triangle + * @return {number} + */ +var minimumTotal = function (triangle) { + for (var i = triangle.length - 2; i >= 0; i--) { + for (var j = 0; j < triangle[i].length; j++) { + triangle[i][j] = Math.min(triangle[i + 1][j], triangle[i + 1][j + 1]) + triangle[i][j] + } + } + return triangle[0][0] +}; +// @lc code=end + diff --git "a/Week04/code/122.\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272-ii.js" "b/Week04/code/122.\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272-ii.js" new file mode 100644 index 000000000..f0aa85744 --- /dev/null +++ "b/Week04/code/122.\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272-ii.js" @@ -0,0 +1,74 @@ +/* + * @lc app=leetcode.cn id=122 lang=javascript + * + * [122] 买卖股票的最佳时机 II + * + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/description/ + * + * algorithms + * Easy (60.42%) + * Likes: 749 + * Dislikes: 0 + * Total Accepted: 174.5K + * Total Submissions: 287K + * Testcase Example: '[7,1,5,3,6,4]' + * + * 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 + * + * 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 + * + * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + * + * + * + * 示例 1: + * + * 输入: [7,1,5,3,6,4] + * 输出: 7 + * 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 + * 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 + * + * + * 示例 2: + * + * 输入: [1,2,3,4,5] + * 输出: 4 + * 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 + * 。 + * 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 + * 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 + * + * + * 示例 3: + * + * 输入: [7,6,4,3,1] + * 输出: 0 + * 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 + * + * + * + * 提示: + * + * + * 1 <= prices.length <= 3 * 10 ^ 4 + * 0 <= prices[i] <= 10 ^ 4 + * + * + */ + +// @lc code=start +/** + * @param {number[]} prices + * @return {number} + */ +var maxProfit = function (prices) { + // 贪心 + let sum = 0 + for (let i = 1; i < prices.length; i++) { + // 只要次日价格大于当日价格,计入总收益 + sum += Math.max(prices[i] - prices[i - 1], 0) + } + return sum +}; +// @lc code=end + diff --git "a/Week04/code/126.\345\215\225\350\257\215\346\216\245\351\276\231-ii.js" "b/Week04/code/126.\345\215\225\350\257\215\346\216\245\351\276\231-ii.js" new file mode 100644 index 000000000..68b44d844 --- /dev/null +++ "b/Week04/code/126.\345\215\225\350\257\215\346\216\245\351\276\231-ii.js" @@ -0,0 +1,134 @@ +/* + * @lc app=leetcode.cn id=126 lang=javascript + * + * [126] 单词接龙 II + * + * https://leetcode-cn.com/problems/word-ladder-ii/description/ + * + * algorithms + * Hard (38.42%) + * Likes: 284 + * Dislikes: 0 + * Total Accepted: 20.5K + * Total Submissions: 53.3K + * Testcase Example: '"hit"\n"cog"\n["hot","dot","dog","lot","log","cog"]' + * + * 给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord + * 的最短转换序列。转换需遵循如下规则: + * + * + * 每次转换只能改变一个字母。 + * 转换后得到的单词必须是字典中的单词。 + * + * + * 说明: + * + * + * 如果不存在这样的转换序列,返回一个空列表。 + * 所有单词具有相同的长度。 + * 所有单词只由小写字母组成。 + * 字典中不存在重复的单词。 + * 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。 + * + * + * 示例 1: + * + * 输入: + * beginWord = "hit", + * endWord = "cog", + * wordList = ["hot","dot","dog","lot","log","cog"] + * + * 输出: + * [ + * ⁠ ["hit","hot","dot","dog","cog"], + * ["hit","hot","lot","log","cog"] + * ] + * + * + * 示例 2: + * + * 输入: + * beginWord = "hit" + * endWord = "cog" + * wordList = ["hot","dot","dog","lot","log"] + * + * 输出: [] + * + * 解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。 + * + */ + +// @lc code=start +/** + * @param {string} beginWord + * @param {string} endWord + * @param {string[]} wordList + * @return {string[][]} + */ +var findLadders = function (beginWord, endWord, wordList) { + // https://leetcode-cn.com/problems/word-ladder-ii/solution/ru-guo-ni-fa-xian-kan-bie-ren-de-ti-jie-kan-bu-don/ + const wordSet = new Set(wordList) // 将单词表存入Set,Set的查找是O(1) + wordSet.add(beginWord) // 这个其实要不要都行 + if (!wordSet.has(endWord)) return [] // 单词表中没有endWord,无法变到endWord + + const levelMap = new Map() // 存放图中的单词对应的level + const wordMap = new Map() // 存放图中的单词是从哪些单词变来的 + const queue = [beginWord] // BFS需要维护一个队列,初始放入level0的beginWord + const visited = new Set() // 存放访问过的节点,避免重复 + + let finished = false // true代表存在变化到endWord的路径,false表示不存在 + + let level = 0 // 路径的深度,初始化0 + + levelMap.set(beginWord, 0) // beginWord的level为0 + visited.add(beginWord) // 起点是beginWord,后面不能再出现它,存入visited + + while (queue.length) { // 队列空了,代表所有邻接节点遍历完了 + let levelSize = queue.length // 当前level的单词个数 + level++ // 一次遍历一层的单词,level+1 + for (let i = 0; i < levelSize; i++) { // 当前层的单词逐个出列,同时逐个考察 + const word = queue.shift() // 当前出列的单词 + for (let i = 0; i < word.length; i++) { // 遍历当前单词的所有字符 + for (let code = 97; code <= 122; code++) { // 将当前字符替换为26个字符,生成新单词逐个试 + + const newWord = word.slice(0, i) + String.fromCharCode(code) + word.slice(i + 1) + if (!wordSet.has(newWord)) continue // 如果不是单词表中的单词,continue + + if (wordMap.has(newWord)) { // 如果已经存在于wordMap,有自己的数组 + wordMap.get(newWord).push(word) // 对应的数组推入它的“父单词”,即出列的单词 + } else { // 新单词不存在于wordMap,初始化一个数组 + wordMap.set(newWord, [word]) // 并放入“父单词” + } + + if (visited.has(newWord)) continue // 这个新单词已经存在于路径中,不重复考察 + if (newWord === endWord) finished = true // 遇到了终点词,说明存在抵达终点词的路径 + + levelMap.set(newWord, level) // 记录这个单词所处于的level + queue.push(newWord) // 这个新单词是下一层的,让下一层的单词入列 + visited.add(newWord) // 这个新单词访问过了,记录一下 + } + } + } + } + if (!finished) return [] // 无法从到达终点词,返回一个[] + const res = [] + dfs(res, [], beginWord, endWord, wordMap, levelMap) // dfs的入口 + return res +} +function dfs(res, path, beginWord, word, wordMap, levelMap) { + if (word === beginWord) { // dfs的出口,如果dfs传入的word,已经和起始词相同 + res.push([beginWord, ...path]) // 将当前路径推入res数组,别忘了加上起始词 + return // path在dfs中是引用传递,要深拷贝一下再推入res + } + path.unshift(word) // 将当前单词加入到path数组的开头 + if (wordMap.get(word)) { // 当前单词有对应的“父单词”们 + for (let parent of wordMap.get(word)) { // 遍历当前单词对应的“父单词”们 + if (levelMap.get(parent) + 1 === levelMap.get(word)) { // 找出其中满足层次要求的 + dfs(res, path, beginWord, parent, wordMap, levelMap) // 即找出满足要求的“父单词” 递归dfs + } + } + } + path.shift() // 回溯,撤销选择,将path数组开头的单词弹出 +}; +// @lc code=end + diff --git "a/Week04/code/127.\345\215\225\350\257\215\346\216\245\351\276\231.js" "b/Week04/code/127.\345\215\225\350\257\215\346\216\245\351\276\231.js" new file mode 100644 index 000000000..31cd95dc8 --- /dev/null +++ "b/Week04/code/127.\345\215\225\350\257\215\346\216\245\351\276\231.js" @@ -0,0 +1,109 @@ +/* + * @lc app=leetcode.cn id=127 lang=javascript + * + * [127] 单词接龙 + * + * https://leetcode-cn.com/problems/word-ladder/description/ + * + * algorithms + * Medium (42.36%) + * Likes: 365 + * Dislikes: 0 + * Total Accepted: 47.7K + * Total Submissions: 111.7K + * Testcase Example: '"hit"\n"cog"\n["hot","dot","dog","lot","log","cog"]' + * + * 给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord + * 的最短转换序列的长度。转换需遵循如下规则: + * + * + * 每次转换只能改变一个字母。 + * 转换过程中的中间单词必须是字典中的单词。 + * + * + * 说明: + * + * + * 如果不存在这样的转换序列,返回 0。 + * 所有单词具有相同的长度。 + * 所有单词只由小写字母组成。 + * 字典中不存在重复的单词。 + * 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。 + * + * + * 示例 1: + * + * 输入: + * beginWord = "hit", + * endWord = "cog", + * wordList = ["hot","dot","dog","lot","log","cog"] + * + * 输出: 5 + * + * 解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", + * ⁠ 返回它的长度 5。 + * + * + * 示例 2: + * + * 输入: + * beginWord = "hit" + * endWord = "cog" + * wordList = ["hot","dot","dog","lot","log"] + * + * 输出: 0 + * + * 解释: endWord "cog" 不在字典中,所以无法进行转换。 + * + */ + +// @lc code=start +/** + * @param {string} beginWord + * @param {string} endWord + * @param {string[]} wordList + * @return {number} + */ +var ladderLength = function (beginWord, endWord, wordList) { + if (!wordList.includes(endWord)) { + return 0 + } + const wordLength = beginWord.length // 每个单词长度都是相同的 + const comboDict = new Map() + const visited = new Map() + wordList.forEach((word) => { + for (let i = 0; i < wordLength; i++) { + let genericWord = word.substring(0, i) + '*' + word.substring(i + 1) + if (!comboDict.get(genericWord)) { + comboDict.set(genericWord, []) + } + comboDict.get(genericWord).push(word) + } + }) + const queue = [] + queue.push({ [beginWord]: 1 }) + visited.set(beginWord, true) + while (queue.length > 0) { + let node = queue.shift() + let word = Object.keys(node)[0] + let level = node[word] + for (let i = 0; i < wordLength; i++) { + const generic = word.substring(0, i) + '*' + word.substring(i + 1) + const list = comboDict.get(generic) + if (list) { + for (let j = 0; j < list.length; j++) { + if (list[j] === endWord) { + return level + 1 + } + if (!visited.get(list[j])) { + visited.set(list[j], true) + queue.push({ [list[j]]: level + 1 }) + } + } + } + } + } + return 0 +}; +// @lc code=end + diff --git "a/Week04/code/153.\345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274.js" "b/Week04/code/153.\345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274.js" new file mode 100644 index 000000000..c8e05335e --- /dev/null +++ "b/Week04/code/153.\345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274.js" @@ -0,0 +1,68 @@ +/* + * @lc app=leetcode.cn id=153 lang=javascript + * + * [153] 寻找旋转排序数组中的最小值 + * + * https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/description/ + * + * algorithms + * Medium (50.94%) + * Likes: 202 + * Dislikes: 0 + * Total Accepted: 57.4K + * Total Submissions: 112.4K + * Testcase Example: '[3,4,5,1,2]' + * + * 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 + * + * ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。 + * + * 请找出其中最小的元素。 + * + * 你可以假设数组中不存在重复元素。 + * + * 示例 1: + * + * 输入: [3,4,5,1,2] + * 输出: 1 + * + * 示例 2: + * + * 输入: [4,5,6,7,0,1,2] + * 输出: 0 + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {number} + */ +var findMin = function (nums) { + // 思路:二分,单调递增、且有索引和边界 + // 无旋转:nums[left] <= nums[mid] <= nums[right] min = nums[left] + // 有旋转:nums[left] >= nums[mid] <= nums[right] 收缩右边界 + // 有旋转:nums[left] <= nums[mid] >= nums[right] 收缩左边界 + let left = 0, right = nums.length - 1 + while (left < right) { + let mid = (left + right) / 2 | 0 + if (nums[mid] > nums[right]) { + left = mid + 1 + } else { + right = mid + } + } + return nums[left] + + // while (left <= right) { + // if (nums[left] <= nums[right]) return nums[left] + // let mid = (left + right) / 2 | 0 + // if (nums[left] <= nums[mid]) { + // left = mid + 1 + // } else { + // right = mid + // } + // } +}; +// @lc code=end + diff --git "a/Week04/code/198.\346\211\223\345\256\266\345\212\253\350\210\215.js" "b/Week04/code/198.\346\211\223\345\256\266\345\212\253\350\210\215.js" new file mode 100644 index 000000000..b88f5370a --- /dev/null +++ "b/Week04/code/198.\346\211\223\345\256\266\345\212\253\350\210\215.js" @@ -0,0 +1,66 @@ +/* + * @lc app=leetcode.cn id=198 lang=javascript + * + * [198] 打家劫舍 + * + * https://leetcode-cn.com/problems/house-robber/description/ + * + * algorithms + * Easy (45.61%) + * Likes: 918 + * Dislikes: 0 + * Total Accepted: 147.4K + * Total Submissions: 321.6K + * Testcase Example: '[1,2,3,1]' + * + * + * 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 + * + * 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 + * + * + * + * 示例 1: + * + * 输入:[1,2,3,1] + * 输出:4 + * 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 + * 偷窃到的最高金额 = 1 + 3 = 4 。 + * + * 示例 2: + * + * 输入:[2,7,9,3,1] + * 输出:12 + * 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 + * 偷窃到的最高金额 = 2 + 9 + 1 = 12 。 + * + * + * + * + * 提示: + * + * + * 0 <= nums.length <= 100 + * 0 <= nums[i] <= 400 + * + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {number} + */ +var rob = function (nums) { + const len = nums.length + if (len == 0) return 0 + const dp = new Array(len + 1) + dp[0] = 0 + dp[1] = nums[0] + for (let i = 2; i <= len; i++) { + dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]) + } + return dp[len] +}; +// @lc code=end + diff --git "a/Week04/code/200.\345\262\233\345\261\277\346\225\260\351\207\217.js" "b/Week04/code/200.\345\262\233\345\261\277\346\225\260\351\207\217.js" new file mode 100644 index 000000000..ed870c0ea --- /dev/null +++ "b/Week04/code/200.\345\262\233\345\261\277\346\225\260\351\207\217.js" @@ -0,0 +1,85 @@ +/* + * @lc app=leetcode.cn id=200 lang=javascript + * + * [200] 岛屿数量 + * + * https://leetcode-cn.com/problems/number-of-islands/description/ + * + * algorithms + * Medium (49.56%) + * Likes: 640 + * Dislikes: 0 + * Total Accepted: 124K + * Total Submissions: 249.5K + * Testcase Example: '[["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]]' + * + * 给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。 + * + * 岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。 + * + * 此外,你可以假设该网格的四条边均被水包围。 + * + * + * + * 示例 1: + * + * 输入: + * [ + * ['1','1','1','1','0'], + * ['1','1','0','1','0'], + * ['1','1','0','0','0'], + * ['0','0','0','0','0'] + * ] + * 输出: 1 + * + * + * 示例 2: + * + * 输入: + * [ + * ['1','1','0','0','0'], + * ['1','1','0','0','0'], + * ['0','0','1','0','0'], + * ['0','0','0','1','1'] + * ] + * 输出: 3 + * 解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。 + * + * + */ + +// @lc code=start +/** + * @param {character[][]} grid + * @return {number} + */ +var numIslands = function (grid) { + // dfs + function dfsmarking(grid, i, j) { + if (i < 0 || + j < 0 || + i >= n || + j >= m || + grid[i][j] !== '1') return + grid[i][j] = '0' + dfsmarking(grid, i + 1, j) + dfsmarking(grid, i - 1, j) + dfsmarking(grid, i, j + 1) + dfsmarking(grid, i, j - 1) + } + let sum = 0 + let n = grid.length + if (n === 0) return 0 + m = grid[0].length + for (let i = 0; i < n; i++) { + for (let j = 0; j < m; j++) { + if (grid[i][j] === '1') { + dfsmarking(grid, i, j) + sum++ + } + } + } + return sum +}; +// @lc code=end + diff --git "a/Week04/code/33.\346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204.js" "b/Week04/code/33.\346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204.js" new file mode 100644 index 000000000..71b4df4af --- /dev/null +++ "b/Week04/code/33.\346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204.js" @@ -0,0 +1,65 @@ +/* + * @lc app=leetcode.cn id=33 lang=javascript + * + * [33] 搜索旋转排序数组 + * + * https://leetcode-cn.com/problems/search-in-rotated-sorted-array/description/ + * + * algorithms + * Medium (38.05%) + * Likes: 811 + * Dislikes: 0 + * Total Accepted: 139K + * Total Submissions: 363.8K + * Testcase Example: '[4,5,6,7,0,1,2]\n0' + * + * 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 + * + * ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。 + * + * 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。 + * + * 你可以假设数组中不存在重复的元素。 + * + * 你的算法时间复杂度必须是 O(log n) 级别。 + * + * 示例 1: + * + * 输入: nums = [4,5,6,7,0,1,2], target = 0 + * 输出: 4 + * + * + * 示例 2: + * + * 输入: nums = [4,5,6,7,0,1,2], target = 3 + * 输出: -1 + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @param {number} target + * @return {number} + */ +var search = function (nums, target) { + // https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/solution/yi-wen-jie-jue-4-dao-sou-suo-xuan-zhuan-pai-xu-s-3/ + // 二分查找 + let left = 0, right = nums.length - 1 + while (left < right) { + let mid = (left + right) / 2 | 0 + // 判断单调区间 + if (target === nums[mid]) { + return mid + } else if (nums[0] <= nums[mid] && (target > nums[mid] || target < nums[0])) { + left = mid + 1 + } else if (target > nums[mid] && target < nums[0]) { + left = mid + 1 + } else { + right = mid + } + } + return nums[left] === target ? left : -1 +}; +// @lc code=end + diff --git "a/Week04/code/45.\350\267\263\350\267\203\346\270\270\346\210\217-ii.js" "b/Week04/code/45.\350\267\263\350\267\203\346\270\270\346\210\217-ii.js" new file mode 100644 index 000000000..6a5286e95 --- /dev/null +++ "b/Week04/code/45.\350\267\263\350\267\203\346\270\270\346\210\217-ii.js" @@ -0,0 +1,58 @@ +/* + * @lc app=leetcode.cn id=45 lang=javascript + * + * [45] 跳跃游戏 II + * + * https://leetcode-cn.com/problems/jump-game-ii/description/ + * + * algorithms + * Hard (36.58%) + * Likes: 612 + * Dislikes: 0 + * Total Accepted: 68.4K + * Total Submissions: 185.5K + * Testcase Example: '[2,3,1,1,4]' + * + * 给定一个非负整数数组,你最初位于数组的第一个位置。 + * + * 数组中的每个元素代表你在该位置可以跳跃的最大长度。 + * + * 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 + * + * 示例: + * + * 输入: [2,3,1,1,4] + * 输出: 2 + * 解释: 跳到最后一个位置的最小跳跃数是 2。 + * 从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。 + * + * + * 说明: + * + * 假设你总是可以到达数组的最后一个位置。 + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {number} + */ +var jump = function (nums) { + let farthestPos = 0 // 记录当前能去到的最远的位置,遍历每个点都会求能跳到的最远位置,与它比较,如果把它大就更新它 + let endOfCanReach = 0 + let steps = 0 + for (let i = 0; i < nums.length - 1; i++) { + farthestPos = Math.max(farthestPos, i + nums[i]) + if (i === endOfCanReach) { + endOfCanReach = farthestPos // 可抵达区间的右端位置 + steps++ + } + if (endOfCanReach >= nums.length - 1) { // 一旦新的可抵达区间触碰到nums数组的边界,则直接break,不用对区间的点遍历了 + break + } + } + return steps +}; +// @lc code=end + diff --git "a/Week04/code/455.\345\210\206\345\217\221\351\245\274\345\271\262.js" "b/Week04/code/455.\345\210\206\345\217\221\351\245\274\345\271\262.js" new file mode 100644 index 000000000..9e6923e6a --- /dev/null +++ "b/Week04/code/455.\345\210\206\345\217\221\351\245\274\345\271\262.js" @@ -0,0 +1,82 @@ +/* + * @lc app=leetcode.cn id=455 lang=javascript + * + * [455] 分发饼干 + * + * https://leetcode-cn.com/problems/assign-cookies/description/ + * + * algorithms + * Easy (54.57%) + * Likes: 179 + * Dislikes: 0 + * Total Accepted: 41.7K + * Total Submissions: 76K + * Testcase Example: '[1,2,3]\n[1,1]' + * + * 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi + * ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i + * ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。 + * + * 注意: + * + * 你可以假设胃口值为正。 + * 一个小朋友最多只能拥有一块饼干。 + * + * 示例 1: + * + * + * 输入: [1,2,3], [1,1] + * + * 输出: 1 + * + * 解释: + * 你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 + * 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 + * 所以你应该输出1。 + * + * + * 示例 2: + * + * + * 输入: [1,2], [1,2,3] + * + * 输出: 2 + * + * 解释: + * 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 + * 你拥有的饼干数量和尺寸都足以让所有孩子满足。 + * 所以你应该输出2. + * + * + */ + +// @lc code=start +/** + * @param {number[]} g + * @param {number[]} s + * @return {number} + */ +var findContentChildren = function (g, s) { + // let count = 0 + // g.sort((a, b) => a - b) + // s.sort((a, b) => a - b) + // for (const cookie of s) { + // const childIndex = g.findIndex((child) => child <= cookie) + // if (childIndex !== -1) { + // count++ + // g.splice(childIndex, 1) + // } + // } + // return count + + g.sort((a, b) => a - b) + s.sort((a, b) => a - b) + let res = i = j = 0 + while (i < g.length && j < s.length) { + if (g[i] <= s[j]) ++res && ++i + ++j + } + return res +}; +// @lc code=end + diff --git "a/Week04/code/529.\346\211\253\351\233\267\346\270\270\346\210\217.js" "b/Week04/code/529.\346\211\253\351\233\267\346\270\270\346\210\217.js" new file mode 100644 index 000000000..d11594e7e --- /dev/null +++ "b/Week04/code/529.\346\211\253\351\233\267\346\270\270\346\210\217.js" @@ -0,0 +1,136 @@ +/* + * @lc app=leetcode.cn id=529 lang=javascript + * + * [529] 扫雷游戏 + * + * https://leetcode-cn.com/problems/minesweeper/description/ + * + * algorithms + * Medium (60.86%) + * Likes: 82 + * Dislikes: 0 + * Total Accepted: 9K + * Total Submissions: 14.8K + * Testcase Example: '[["E","E","E","E","E"],["E","E","M","E","E"],["E","E","E","E","E"],["E","E","E","E","E"]]\n[3,0]' + * + * 让我们一起来玩扫雷游戏! + * + * 给定一个代表游戏板的二维字符矩阵。 'M' 代表一个未挖出的地雷,'E' 代表一个未挖出的空方块,'B' + * 代表没有相邻(上,下,左,右,和所有4个对角线)地雷的已挖出的空白方块,数字('1' 到 '8')表示有多少地雷与这块已挖出的方块相邻,'X' + * 则表示一个已挖出的地雷。 + * + * 现在给出在所有未挖出的方块中('M'或者'E')的下一个点击位置(行和列索引),根据以下规则,返回相应位置被点击后对应的面板: + * + * + * 如果一个地雷('M')被挖出,游戏就结束了- 把它改为 'X'。 + * 如果一个没有相邻地雷的空方块('E')被挖出,修改它为('B'),并且所有和其相邻的方块都应该被递归地揭露。 + * 如果一个至少与一个地雷相邻的空方块('E')被挖出,修改它为数字('1'到'8'),表示相邻地雷的数量。 + * 如果在此次点击中,若无更多方块可被揭露,则返回面板。 + * + * + * + * + * 示例 1: + * + * 输入: + * + * [['E', 'E', 'E', 'E', 'E'], + * ⁠['E', 'E', 'M', 'E', 'E'], + * ⁠['E', 'E', 'E', 'E', 'E'], + * ⁠['E', 'E', 'E', 'E', 'E']] + * + * Click : [3,0] + * + * 输出: + * + * [['B', '1', 'E', '1', 'B'], + * ⁠['B', '1', 'M', '1', 'B'], + * ⁠['B', '1', '1', '1', 'B'], + * ⁠['B', 'B', 'B', 'B', 'B']] + * + * 解释: + * + * + * + * 示例 2: + * + * 输入: + * + * [['B', '1', 'E', '1', 'B'], + * ⁠['B', '1', 'M', '1', 'B'], + * ⁠['B', '1', '1', '1', 'B'], + * ⁠['B', 'B', 'B', 'B', 'B']] + * + * Click : [1,2] + * + * 输出: + * + * [['B', '1', 'E', '1', 'B'], + * ⁠['B', '1', 'X', '1', 'B'], + * ⁠['B', '1', '1', '1', 'B'], + * ⁠['B', 'B', 'B', 'B', 'B']] + * + * 解释: + * + * + * + * + * + * 注意: + * + * + * 输入矩阵的宽和高的范围为 [1,50]。 + * 点击的位置只能是未被挖出的方块 ('M' 或者 'E'),这也意味着面板至少包含一个可点击的方块。 + * 输入面板不会是游戏结束的状态(即有地雷已被挖出)。 + * 简单起见,未提及的规则在这个问题中可被忽略。例如,当游戏结束时你不需要挖出所有地雷,考虑所有你可能赢得游戏或标记方块的情况。 + * + */ + +// @lc code=start +/** + * @param {character[][]} board + * @param {number[]} click + * @return {character[][]} + */ +var updateBoard = function (board, click) { + let x = click[0] + let y = click[1] + if (board[x][y] == 'M') { + board[x][y] = 'X' + return board + } + let dx = [-1, -1, -1, 1, 1, 1, 0, 0] + let dy = [-1, 1, 0, -1, 1, 0, -1, 1] + let getNumsBombs = (board, x, y) => { + let num = 0 + for (let i = 0; i < 8; i++) { + let newX = x + dx[i] + let newY = y + dy[i] + if (newX < 0 || newX >= board.length || newY < 0 || newY >= board[0].length) { + continue + } + if (board[newX][newY] == 'M' || board[newX][newY] == 'X') { + num++ + } + } + return num + } + let dfs = (board, x, y) => { + if (x < 0 || x >= board.length || y < 0 || y >= board[0].length || board[x][y] != 'E') return + let num = getNumsBombs(board, x, y) + if (num == 0) { + board[x][y] = 'B' + for (let i = 0; i < 8; i++) { + let newX = x + dx[i] + let newY = y + dy[i] + dfs(board, newX, newY) + } + } else { + board[x][y] = num + '' + } + } + dfs(board, x, y) + return board +}; +// @lc code=end + diff --git "a/Week04/code/53.\346\234\200\345\244\247\345\255\220\345\272\217\345\222\214.js" "b/Week04/code/53.\346\234\200\345\244\247\345\255\220\345\272\217\345\222\214.js" new file mode 100644 index 000000000..f85e889bf --- /dev/null +++ "b/Week04/code/53.\346\234\200\345\244\247\345\255\220\345\272\217\345\222\214.js" @@ -0,0 +1,45 @@ +/* + * @lc app=leetcode.cn id=53 lang=javascript + * + * [53] 最大子序和 + * + * https://leetcode-cn.com/problems/maximum-subarray/description/ + * + * algorithms + * Easy (51.36%) + * Likes: 2143 + * Dislikes: 0 + * Total Accepted: 270.5K + * Total Submissions: 524.1K + * Testcase Example: '[-2,1,-3,4,-1,2,1,-5,4]' + * + * 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 + * + * 示例: + * + * 输入: [-2,1,-3,4,-1,2,1,-5,4], + * 输出: 6 + * 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 + * + * + * 进阶: + * + * 如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。 + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {number} + */ +var maxSubArray = function (nums) { + let pre = 0, maxAns = nums[0] + nums.forEach(x => { + pre = Math.max(pre + x, x) + maxAns = Math.max(maxAns, pre) + }) + return maxAns +}; +// @lc code=end + diff --git "a/Week04/code/55.\350\267\263\350\267\203\346\270\270\346\210\217.js" "b/Week04/code/55.\350\267\263\350\267\203\346\270\270\346\210\217.js" new file mode 100644 index 000000000..9a14cbdbe --- /dev/null +++ "b/Week04/code/55.\350\267\263\350\267\203\346\270\270\346\210\217.js" @@ -0,0 +1,76 @@ +/* + * @lc app=leetcode.cn id=55 lang=javascript + * + * [55] 跳跃游戏 + * + * https://leetcode-cn.com/problems/jump-game/description/ + * + * algorithms + * Medium (40.11%) + * Likes: 713 + * Dislikes: 0 + * Total Accepted: 123.2K + * Total Submissions: 305.8K + * Testcase Example: '[2,3,1,1,4]' + * + * 给定一个非负整数数组,你最初位于数组的第一个位置。 + * + * 数组中的每个元素代表你在该位置可以跳跃的最大长度。 + * + * 判断你是否能够到达最后一个位置。 + * + * 示例 1: + * + * 输入: [2,3,1,1,4] + * 输出: true + * 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。 + * + * + * 示例 2: + * + * 输入: [3,2,1,0,4] + * 输出: false + * 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。 + * + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {boolean} + */ +var canJump = function (nums) { + // let max = 0 // 能够走到的数组下标 + // for (let i = 0; i < nums.length; i++) { + // if (max < i) return false // 当前这一步都走不到,后面更走不到了 + // max = Math.max(nums[i] + i, max) + // } + // return true + + // O(n²) + // const res = new Array(nums.length) + // if (res[0] !== 0 || nums.length === 1) res[0] = true + // for (let i = 0; i < nums.length; i++) { + // for (let j = 1; j <= nums[i]; j++) { + // if (i + j > nums.length - 1) break + // res[i + j] = true + // } + // } + // for (let k = 0; k < res.length; k++) { + // if (!res[k]) return false + // } + // return true + + // 贪心 + if (!nums.length) return false + let canReachable = nums.length - 1 + for (let i = nums.length - 1; i >= 0; i--) { + if (nums[i] + i >= canReachable) { + canReachable = i + } + } + return canReachable === 0 +}; +// @lc code=end + diff --git "a/Week04/code/74.\346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265.js" "b/Week04/code/74.\346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265.js" new file mode 100644 index 000000000..9d15ebf56 --- /dev/null +++ "b/Week04/code/74.\346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265.js" @@ -0,0 +1,76 @@ +/* + * @lc app=leetcode.cn id=74 lang=javascript + * + * [74] 搜索二维矩阵 + * + * https://leetcode-cn.com/problems/search-a-2d-matrix/description/ + * + * algorithms + * Medium (38.07%) + * Likes: 201 + * Dislikes: 0 + * Total Accepted: 49.8K + * Total Submissions: 130K + * Testcase Example: '[[1,3,5,7],[10,11,16,20],[23,30,34,50]]\n3' + * + * 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性: + * + * + * 每行中的整数从左到右按升序排列。 + * 每行的第一个整数大于前一行的最后一个整数。 + * + * + * 示例 1: + * + * 输入: + * matrix = [ + * ⁠ [1, 3, 5, 7], + * ⁠ [10, 11, 16, 20], + * ⁠ [23, 30, 34, 50] + * ] + * target = 3 + * 输出: true + * + * + * 示例 2: + * + * 输入: + * matrix = [ + * ⁠ [1, 3, 5, 7], + * ⁠ [10, 11, 16, 20], + * ⁠ [23, 30, 34, 50] + * ] + * target = 13 + * 输出: false + * + */ + +// @lc code=start +/** + * @param {number[][]} matrix + * @param {number} target + * @return {boolean} + */ +var searchMatrix = function (matrix, target) { + let m = matrix.length + if (m == 0) return false + let n = matrix[0].length + let low = 0 + let high = m * n - 1 + while (low <= high) { + let mid = (low + high) >> 1 + let row = mid / n | 0 + let col = mid % n + let matrixMid = matrix[row][col] + if (matrixMid < target) { + low = mid + 1 + } else if (matrixMid > target) { + high = mid - 1 + } else if (matrixMid == target) { + return true + } + } + return false +}; +// @lc code=end + diff --git "a/Week04/code/860.\346\237\240\346\252\254\346\260\264\346\211\276\351\233\266.js" "b/Week04/code/860.\346\237\240\346\252\254\346\260\264\346\211\276\351\233\266.js" new file mode 100644 index 000000000..8ffe50b8a --- /dev/null +++ "b/Week04/code/860.\346\237\240\346\252\254\346\260\264\346\211\276\351\233\266.js" @@ -0,0 +1,103 @@ +/* + * @lc app=leetcode.cn id=860 lang=javascript + * + * [860] 柠檬水找零 + * + * https://leetcode-cn.com/problems/lemonade-change/description/ + * + * algorithms + * Easy (55.25%) + * Likes: 121 + * Dislikes: 0 + * Total Accepted: 25.1K + * Total Submissions: 45.2K + * Testcase Example: '[5,5,5,10,20]' + * + * 在柠檬水摊上,每一杯柠檬水的售价为 5 美元。 + * + * 顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。 + * + * 每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。 + * + * 注意,一开始你手头没有任何零钱。 + * + * 如果你能给每位顾客正确找零,返回 true ,否则返回 false 。 + * + * 示例 1: + * + * 输入:[5,5,5,10,20] + * 输出:true + * 解释: + * 前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。 + * 第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。 + * 第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。 + * 由于所有客户都得到了正确的找零,所以我们输出 true。 + * + * + * 示例 2: + * + * 输入:[5,5,10] + * 输出:true + * + * + * 示例 3: + * + * 输入:[10,10] + * 输出:false + * + * + * 示例 4: + * + * 输入:[5,5,10,10,20] + * 输出:false + * 解释: + * 前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。 + * 对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。 + * 对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。 + * 由于不是每位顾客都得到了正确的找零,所以答案是 false。 + * + * + * + * + * 提示: + * + * + * 0 <= bills.length <= 10000 + * bills[i] 不是 5 就是 10 或是 20  + * + * + */ + +// @lc code=start +/** + * @param {number[]} bills + * @return {boolean} + */ +var lemonadeChange = function (bills) { + // 贪心 + let five = 0 + let ten = 0 + for (var i = 0; i < bills.length; i++) { + if (bills[i] == 5) { + five++ + } else if (bills[i] == 10) { + if (five == 0) { + return false + } + five-- + ten++ + } else if (bills[i] == 20) { + if (ten > 0 && five > 0) { + ten-- + five-- + } else if (five >= 3) { + five -= 3 + } else { + return false + } + } + } + return true +}; +// @lc code=end + diff --git "a/Week04/code/874.\346\250\241\346\213\237\350\241\214\350\265\260\346\234\272\345\231\250\344\272\272.js" "b/Week04/code/874.\346\250\241\346\213\237\350\241\214\350\265\260\346\234\272\345\231\250\344\272\272.js" new file mode 100644 index 000000000..8fd93d38a --- /dev/null +++ "b/Week04/code/874.\346\250\241\346\213\237\350\241\214\350\265\260\346\234\272\345\231\250\344\272\272.js" @@ -0,0 +1,105 @@ +/* + * @lc app=leetcode.cn id=874 lang=javascript + * + * [874] 模拟行走机器人 + * + * https://leetcode-cn.com/problems/walking-robot-simulation/description/ + * + * algorithms + * Easy (35.29%) + * Likes: 95 + * Dislikes: 0 + * Total Accepted: 9.1K + * Total Submissions: 25.2K + * Testcase Example: '[4,-1,3]\n[]' + * + * 机器人在一个无限大小的网格上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令: + * + * + * -2:向左转 90 度 + * -1:向右转 90 度 + * 1 <= x <= 9:向前移动 x 个单位长度 + * + * + * 在网格上有一些格子被视为障碍物。 + * + * 第 i 个障碍物位于网格点  (obstacles[i][0], obstacles[i][1]) + * + * 机器人无法走到障碍物上,它将会停留在障碍物的前一个网格方块上,但仍然可以继续该路线的其余部分。 + * + * 返回从原点到机器人所有经过的路径点(坐标为整数)的最大欧式距离的平方。 + * + * + * + * 示例 1: + * + * 输入: commands = [4,-1,3], obstacles = [] + * 输出: 25 + * 解释: 机器人将会到达 (3, 4) + * + * + * 示例 2: + * + * 输入: commands = [4,-1,4,-2,4], obstacles = [[2,4]] + * 输出: 65 + * 解释: 机器人在左转走到 (1, 8) 之前将被困在 (1, 4) 处 + * + * + * + * + * 提示: + * + * + * 0 <= commands.length <= 10000 + * 0 <= obstacles.length <= 10000 + * -30000 <= obstacle[i][0] <= 30000 + * -30000 <= obstacle[i][1] <= 30000 + * 答案保证小于 2 ^ 31 + * + * + */ + +// @lc code=start +/** + * @param {number[]} commands + * @param {number[][]} obstacles + * @return {number} + */ +var robotSim = function (commands, obstacles) { + let obstacleMap = {} + obstacles.forEach(o => { + if (o.length > 0) obstacleMap[o[0] + ',' + o[1]] = true + }) + let res = 0 + // 以上右下左顺时针的方向定义x和y的方向向量 + let dx = [0, 1, 0, -1] // x的方向向量 + let dy = [1, 0, -1, 0] // y的方向向量 + let [x, y, di] = [0, 0, 0]// x,y分别代表当前坐标,di代表方向向量的索引位置 + commands.forEach((c, index) => { + /* + 这个地方,方向的控制可以这样理解,根据题意向北出发也就是从[0,0]开始 + 沿y轴的正方向移动所以是 x + 0,y + 1,当前的方向向量是[dx[di],dy[di]],也就是 + [0,1],当向右转时根据初始方向和我们顺时针定义方向向量数组,此时右转是顺时针转向 + 所以我们的方向向量索引加1,同理左转便是逆时针索引减1,(但是js好像无法负数取模,只能向前前进3位)()%4为在长度为4的数组上去模 + 避免不然一直右转就会数组越界 + */ + // 向右转 + if (c == -1) di = (di + 1) % 4 + // 向左转(di - 1) % 4........ + if (c == -2) di = (di + 3) % 4 + + if (c > 0) { + // 每走一步计算一次最大值 + for (let z = 0; z < c; z++) { + if (!obstacleMap[(x + dx[di]) + ',' + (y + dy[di])]) { + x += dx[di] + y += dy[di] + res = Math.max(res, x * x + y * y) + } + } + } + }) + return res +}; +// @lc code=end + diff --git a/Week06/NOTE.md b/Week06/NOTE.md deleted file mode 100644 index 50de30414..000000000 --- a/Week06/NOTE.md +++ /dev/null @@ -1 +0,0 @@ -学习笔记 \ No newline at end of file diff --git a/Week06/README.md b/Week06/README.md new file mode 100644 index 000000000..10c533eed --- /dev/null +++ b/Week06/README.md @@ -0,0 +1,19 @@ +# 动态规划 + +**动态规划 和 递归或者分治 没有根本上的区别(关键看有无最优的子结构)** + +**共性:** 找到重复子问题 + +**差异性:** 最优子结构、中途可以淘汰次优解关键点 + +**关键点:** + +1.最优子结构 opt[n] = best_of(opt[n-1], opt[n-2], ...) + +2.储存中间状态:opt[i] + +3.递推公式(状态转移方程 或 DP 方程) + + Fib: opt[i] = opt[n-1] + opt[n-2] + + 二维路径:opt[i,j] = opt[i+1] [j] + opt[i] [j+1] (且判断a[i,j]是否空地) diff --git "a/Week06/code/1091.\344\272\214\350\277\233\345\210\266\347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.js" "b/Week06/code/1091.\344\272\214\350\277\233\345\210\266\347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.js" new file mode 100644 index 000000000..9f8ca5147 --- /dev/null +++ "b/Week06/code/1091.\344\272\214\350\277\233\345\210\266\347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.js" @@ -0,0 +1,89 @@ +/* + * @lc app=leetcode.cn id=1091 lang=javascript + * + * [1091] 二进制矩阵中的最短路径 + * + * https://leetcode-cn.com/problems/shortest-path-in-binary-matrix/description/ + * + * algorithms + * Medium (33.10%) + * Likes: 47 + * Dislikes: 0 + * Total Accepted: 9.2K + * Total Submissions: 27.1K + * Testcase Example: '[[0,1],[1,0]]' + * + * 在一个 N × N 的方形网格中,每个单元格有两种状态:空(0)或者阻塞(1)。 + * + * 一条从左上角到右下角、长度为 k 的畅通路径,由满足下述条件的单元格 C_1, C_2, ..., C_k 组成: + * + * + * 相邻单元格 C_i 和 C_{i+1} 在八个方向之一上连通(此时,C_i 和 C_{i+1} 不同且共享边或角) + * C_1 位于 (0, 0)(即,值为 grid[0][0]) + * C_k 位于 (N-1, N-1)(即,值为 grid[N-1][N-1]) + * 如果 C_i 位于 (r, c),则 grid[r][c] 为空(即,grid[r][c] == 0) + * + * + * 返回这条从左上角到右下角的最短畅通路径的长度。如果不存在这样的路径,返回 -1 。 + * + * + * + * 示例 1: + * + * 输入:[[0,1],[1,0]] + * + * 输出:2 + * + * + * + * 示例 2: + * + * 输入:[[0,0,0],[1,1,0],[1,1,0]] + * + * 输出:4 + * + * + * + * + * + * 提示: + * + * + * 1 <= grid.length == grid[0].length <= 100 + * grid[i][j] 为 0 或 1 + * + * + */ + +// @lc code=start +/** + * @param {number[][]} grid + * @return {number} + */ +var shortestPathBinaryMatrix = function (grid) { + if (grid[0][0]) return -1 + const row = grid.length - 1, col = grid[0].length - 1 + if (grid[row][col]) return -1 + const dirs = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]] + const queue = [[0, 0]] + grid[0][0] = 1 + let res = 0, i = queue.length, newX, newY, temp, x, y + while (i) { + res++; + while (i--) { + temp = queue.shift() + if (temp[0] === row && temp[1] === col) return res + dirs.forEach(dir => { + newX = temp[0] + dir[0] + newY = temp[1] + dir[1] + if (newX < 0 || newX > row || newY < 0 || newY > col || grid[newX][newY]) return + queue.push([newX, newY]) + grid[newX][newY] = 1 + }) + } + i = queue.length + } + return -1 +}; +// @lc code=end + diff --git "a/Week06/code/127.\345\215\225\350\257\215\346\216\245\351\276\231.js" "b/Week06/code/127.\345\215\225\350\257\215\346\216\245\351\276\231.js" new file mode 100644 index 000000000..31cd95dc8 --- /dev/null +++ "b/Week06/code/127.\345\215\225\350\257\215\346\216\245\351\276\231.js" @@ -0,0 +1,109 @@ +/* + * @lc app=leetcode.cn id=127 lang=javascript + * + * [127] 单词接龙 + * + * https://leetcode-cn.com/problems/word-ladder/description/ + * + * algorithms + * Medium (42.36%) + * Likes: 365 + * Dislikes: 0 + * Total Accepted: 47.7K + * Total Submissions: 111.7K + * Testcase Example: '"hit"\n"cog"\n["hot","dot","dog","lot","log","cog"]' + * + * 给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord + * 的最短转换序列的长度。转换需遵循如下规则: + * + * + * 每次转换只能改变一个字母。 + * 转换过程中的中间单词必须是字典中的单词。 + * + * + * 说明: + * + * + * 如果不存在这样的转换序列,返回 0。 + * 所有单词具有相同的长度。 + * 所有单词只由小写字母组成。 + * 字典中不存在重复的单词。 + * 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。 + * + * + * 示例 1: + * + * 输入: + * beginWord = "hit", + * endWord = "cog", + * wordList = ["hot","dot","dog","lot","log","cog"] + * + * 输出: 5 + * + * 解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", + * ⁠ 返回它的长度 5。 + * + * + * 示例 2: + * + * 输入: + * beginWord = "hit" + * endWord = "cog" + * wordList = ["hot","dot","dog","lot","log"] + * + * 输出: 0 + * + * 解释: endWord "cog" 不在字典中,所以无法进行转换。 + * + */ + +// @lc code=start +/** + * @param {string} beginWord + * @param {string} endWord + * @param {string[]} wordList + * @return {number} + */ +var ladderLength = function (beginWord, endWord, wordList) { + if (!wordList.includes(endWord)) { + return 0 + } + const wordLength = beginWord.length // 每个单词长度都是相同的 + const comboDict = new Map() + const visited = new Map() + wordList.forEach((word) => { + for (let i = 0; i < wordLength; i++) { + let genericWord = word.substring(0, i) + '*' + word.substring(i + 1) + if (!comboDict.get(genericWord)) { + comboDict.set(genericWord, []) + } + comboDict.get(genericWord).push(word) + } + }) + const queue = [] + queue.push({ [beginWord]: 1 }) + visited.set(beginWord, true) + while (queue.length > 0) { + let node = queue.shift() + let word = Object.keys(node)[0] + let level = node[word] + for (let i = 0; i < wordLength; i++) { + const generic = word.substring(0, i) + '*' + word.substring(i + 1) + const list = comboDict.get(generic) + if (list) { + for (let j = 0; j < list.length; j++) { + if (list[j] === endWord) { + return level + 1 + } + if (!visited.get(list[j])) { + visited.set(list[j], true) + queue.push({ [list[j]]: level + 1 }) + } + } + } + } + } + return 0 +}; +// @lc code=end + diff --git "a/Week06/code/200.\345\262\233\345\261\277\346\225\260\351\207\217.js" "b/Week06/code/200.\345\262\233\345\261\277\346\225\260\351\207\217.js" new file mode 100644 index 000000000..ed870c0ea --- /dev/null +++ "b/Week06/code/200.\345\262\233\345\261\277\346\225\260\351\207\217.js" @@ -0,0 +1,85 @@ +/* + * @lc app=leetcode.cn id=200 lang=javascript + * + * [200] 岛屿数量 + * + * https://leetcode-cn.com/problems/number-of-islands/description/ + * + * algorithms + * Medium (49.56%) + * Likes: 640 + * Dislikes: 0 + * Total Accepted: 124K + * Total Submissions: 249.5K + * Testcase Example: '[["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]]' + * + * 给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。 + * + * 岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。 + * + * 此外,你可以假设该网格的四条边均被水包围。 + * + * + * + * 示例 1: + * + * 输入: + * [ + * ['1','1','1','1','0'], + * ['1','1','0','1','0'], + * ['1','1','0','0','0'], + * ['0','0','0','0','0'] + * ] + * 输出: 1 + * + * + * 示例 2: + * + * 输入: + * [ + * ['1','1','0','0','0'], + * ['1','1','0','0','0'], + * ['0','0','1','0','0'], + * ['0','0','0','1','1'] + * ] + * 输出: 3 + * 解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。 + * + * + */ + +// @lc code=start +/** + * @param {character[][]} grid + * @return {number} + */ +var numIslands = function (grid) { + // dfs + function dfsmarking(grid, i, j) { + if (i < 0 || + j < 0 || + i >= n || + j >= m || + grid[i][j] !== '1') return + grid[i][j] = '0' + dfsmarking(grid, i + 1, j) + dfsmarking(grid, i - 1, j) + dfsmarking(grid, i, j + 1) + dfsmarking(grid, i, j - 1) + } + let sum = 0 + let n = grid.length + if (n === 0) return 0 + m = grid[0].length + for (let i = 0; i < n; i++) { + for (let j = 0; j < m; j++) { + if (grid[i][j] === '1') { + dfsmarking(grid, i, j) + sum++ + } + } + } + return sum +}; +// @lc code=end + diff --git "a/Week06/code/208.\345\256\236\347\216\260-trie-\345\211\215\347\274\200\346\240\221.js" "b/Week06/code/208.\345\256\236\347\216\260-trie-\345\211\215\347\274\200\346\240\221.js" new file mode 100644 index 000000000..1c86780ba --- /dev/null +++ "b/Week06/code/208.\345\256\236\347\216\260-trie-\345\211\215\347\274\200\346\240\221.js" @@ -0,0 +1,113 @@ +/* + * @lc app=leetcode.cn id=208 lang=javascript + * + * [208] 实现 Trie (前缀树) + * + * https://leetcode-cn.com/problems/implement-trie-prefix-tree/description/ + * + * algorithms + * Medium (67.36%) + * Likes: 351 + * Dislikes: 0 + * Total Accepted: 45K + * Total Submissions: 66.3K + * Testcase Example: '["Trie","insert","search","search","startsWith","insert","search"]\n[[],["apple"],["apple"],["app"],["app"],["app"],["app"]]' + * + * 实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。 + * + * 示例: + * + * Trie trie = new Trie(); + * + * trie.insert("apple"); + * trie.search("apple"); // 返回 true + * trie.search("app"); // 返回 false + * trie.startsWith("app"); // 返回 true + * trie.insert("app"); + * trie.search("app"); // 返回 true + * + * 说明: + * + * + * 你可以假设所有的输入都是由小写字母 a-z 构成的。 + * 保证所有输入均为非空字符串。 + * + * + */ + +// @lc code=start +/** + * Initialize your data structure here. + */ +var TrieNode = function () { + this.next = {} + this.isEnd = false +}; + +var Trie = function () { + this.root = new TrieNode() +}; + +/** + * Inserts a word into the trie. + * @param {string} word + * @return {void} + */ +Trie.prototype.insert = function (word) { + if (!word) return false + let node = this.root + for (let i = 0; i < word.length; ++i) { + if (!node.next[word[i]]) { + node.next[word[i]] = new TrieNode() + } + node = node.next[word[i]] + } + node.isEnd = true + return true +}; + +/** + * Returns if the word is in the trie. + * @param {string} word + * @return {boolean} + */ +Trie.prototype.search = function (word) { + if (!word) return false + let node = this.root + for (let i = 0; i < word.length; ++i) { + if (node.next[word[i]]) { + node = node.next[word[i]] + } else { + return false + } + } + return node.isEnd +}; + +/** + * Returns if there is any word in the trie that starts with the given prefix. + * @param {string} prefix + * @return {boolean} + */ +Trie.prototype.startsWith = function (prefix) { + if (!prefix) return true + let node = this.root + for (let i = 0; i < prefix.length; ++i) { + if (node.next[prefix[i]]) { + node = node.next[prefix[i]] + } else { + return false + } + } + return true +}; + +/** + * Your Trie object will be instantiated and called as such: + * var obj = new Trie() + * obj.insert(word) + * var param_2 = obj.search(word) + * var param_3 = obj.startsWith(prefix) + */ +// @lc code=end + diff --git "a/Week06/code/212.\345\215\225\350\257\215\346\220\234\347\264\242-ii.js" "b/Week06/code/212.\345\215\225\350\257\215\346\220\234\347\264\242-ii.js" new file mode 100644 index 000000000..52ea9571d --- /dev/null +++ "b/Week06/code/212.\345\215\225\350\257\215\346\220\234\347\264\242-ii.js" @@ -0,0 +1,117 @@ +/* + * @lc app=leetcode.cn id=212 lang=javascript + * + * [212] 单词搜索 II + * + * https://leetcode-cn.com/problems/word-search-ii/description/ + * + * algorithms + * Hard (40.79%) + * Likes: 196 + * Dislikes: 0 + * Total Accepted: 17.1K + * Total Submissions: 41.2K + * Testcase Example: '[["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]]\n["oath","pea","eat","rain"]' + * + * 给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词。 + * + * + * 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。 + * + * 示例: + * + * 输入: + * words = ["oath","pea","eat","rain"] and board = + * [ + * ⁠ ['o','a','a','n'], + * ⁠ ['e','t','a','e'], + * ⁠ ['i','h','k','r'], + * ⁠ ['i','f','l','v'] + * ] + * + * 输出: ["eat","oath"] + * + * 说明: + * 你可以假设所有输入都由小写字母 a-z 组成。 + * + * 提示: + * + * + * 你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯? + * 如果当前单词不存在于所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? + * 前缀树如何?如果你想学习如何实现一个基本的前缀树,请先查看这个问题: 实现Trie(前缀树)。 + * + * + */ + +// @lc code=start +/** + * @param {character[][]} board + * @param {string[]} words + * @return {string[]} + */ +var findWords = function (board, words) { + // 构建字典树 + class TrieNode { + constructor() { + this.END = false + this.children = {} + } + } + let root = null + let Trie = function () { + root = new TrieNode() + }; + Trie.prototype.insert = function (word) { + let currNode = root + for (let i = 0; i < word.length; i++) { + if (currNode.children[word[i]] == undefined) { + currNode.children[word[i]] = new TrieNode() + } + currNode = currNode.children[word[i]] + } + currNode.END = true + }; + // 初始化变量 + let m = board.length + let n = board[0].length + // 初始化字典树 + let wordsTrie = new Trie() + for (let i = 0; i < words.length; i++) { + wordsTrie.insert(words[i]) + } + // DFS 搜索 + let boardDFS = (i, j, curStr, currNode) => { + // 字典树中找到了 + if (currNode.END) { + result.push(curStr) + currNode.END = false + } + if (i < 0 || j < 0 || i == m || j == n) { + return + } + const restore = board[i][j]; + if (restore == '#' || !currNode.children[restore]) { + return + } + // 前进 + board[i][j] = '#' + curStr += restore + boardDFS(i - 1, j, curStr, currNode.children[restore]) + boardDFS(i + 1, j, curStr, currNode.children[restore]) + boardDFS(i, j - 1, curStr, currNode.children[restore]) + boardDFS(i, j + 1, curStr, currNode.children[restore]) + // 还原(回溯) + board[i][j] = restore + } + // 寻找结果 + let result = [] + for (let i = 0; i < m; i++) { + for (let j = 0; j < n; j++) { + boardDFS(i, j, '', root) + } + } + return result +}; +// @lc code=end + diff --git "a/Week06/code/221.\346\234\200\345\244\247\346\255\243\346\226\271\345\275\242.js" "b/Week06/code/221.\346\234\200\345\244\247\346\255\243\346\226\271\345\275\242.js" new file mode 100644 index 000000000..265a5f615 --- /dev/null +++ "b/Week06/code/221.\346\234\200\345\244\247\346\255\243\346\226\271\345\275\242.js" @@ -0,0 +1,62 @@ +/* + * @lc app=leetcode.cn id=221 lang=javascript + * + * [221] 最大正方形 + * + * https://leetcode-cn.com/problems/maximal-square/description/ + * + * algorithms + * Medium (42.47%) + * Likes: 486 + * Dislikes: 0 + * Total Accepted: 61.6K + * Total Submissions: 144.9K + * Testcase Example: '[["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]' + * + * 在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。 + * + * 示例: + * + * 输入: + * + * 1 0 1 0 0 + * 1 0 1 1 1 + * 1 1 1 1 1 + * 1 0 0 1 0 + * + * 输出: 4 + * + */ + +// @lc code=start +/** + * @param {character[][]} matrix + * @return {number} + */ +var maximalSquare = function (matrix) { + if (matrix.length === 0) return 0 + const dp = [] + const rows = matrix.length + const cols = matrix[0].length + let max = Number.MIN_VALUE + for (let i = 0; i < rows + 1; i++) { + if (i === 0) { + dp[i] = Array(cols + 1).fill(0) + } else { + dp[i] = [0] + } + } + for (let i = 1; i < rows + 1; i++) { + for (let j = 1; j < cols + 1; j++) { + if (matrix[i - 1][j - 1] === "1") { + dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 + max = Math.max(max, dp[i][j]) + } else { + dp[i][j] = 0 + } + } + } + return max * max +}; +// @lc code=end + diff --git "a/Week06/code/312.\346\210\263\346\260\224\347\220\203.js" "b/Week06/code/312.\346\210\263\346\260\224\347\220\203.js" new file mode 100644 index 000000000..cfe4f9fae --- /dev/null +++ "b/Week06/code/312.\346\210\263\346\260\224\347\220\203.js" @@ -0,0 +1,62 @@ +/* + * @lc app=leetcode.cn id=312 lang=javascript + * + * [312] 戳气球 + * + * https://leetcode-cn.com/problems/burst-balloons/description/ + * + * algorithms + * Hard (60.44%) + * Likes: 429 + * Dislikes: 0 + * Total Accepted: 22.3K + * Total Submissions: 34.4K + * Testcase Example: '[3,1,5,8]' + * + * 有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。 + * + * 现在要求你戳破所有的气球。如果你戳破气球 i ,就可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 + * left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。 + * + * 求所能获得硬币的最大数量。 + * + * 说明: + * + * + * 你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。 + * 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100 + * + * + * 示例: + * + * 输入: [3,1,5,8] + * 输出: 167 + * 解释: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> [] + * coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167 + * + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {number} + */ +var maxCoins = function (nums) { + let n = nums.length + // 添加两侧的虚拟气球 + let points = [1, ...nums, 1] + let dp = Array.from(Array(n + 2), () => Array(n + 2).fill(0)) + // 最后一行开始遍历,从下往上 + for (let i = n; i >= 0; i--) { + // 从左往右 + for (let j = i + 1; j < n + 2; j++) { + for (let k = i + 1; k < j; k++) { + dp[i][j] = Math.max(dp[i][j], points[j] * points[k] * points[i] + dp[i][k] + dp[k][j]) + } + } + } + return dp[0][n + 1] +}; +// @lc code=end + diff --git "a/Week06/code/32.\346\234\200\351\225\277\346\234\211\346\225\210\346\213\254\345\217\267.js" "b/Week06/code/32.\346\234\200\351\225\277\346\234\211\346\225\210\346\213\254\345\217\267.js" new file mode 100644 index 000000000..ea9948311 --- /dev/null +++ "b/Week06/code/32.\346\234\200\351\225\277\346\234\211\346\225\210\346\213\254\345\217\267.js" @@ -0,0 +1,64 @@ +/* + * @lc app=leetcode.cn id=32 lang=javascript + * + * [32] 最长有效括号 + * + * https://leetcode-cn.com/problems/longest-valid-parentheses/description/ + * + * algorithms + * Hard (30.73%) + * Likes: 853 + * Dislikes: 0 + * Total Accepted: 87.3K + * Total Submissions: 262.8K + * Testcase Example: '"(()"' + * + * 给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度。 + * + * 示例 1: + * + * 输入: "(()" + * 输出: 2 + * 解释: 最长有效括号子串为 "()" + * + * + * 示例 2: + * + * 输入: ")()())" + * 输出: 4 + * 解释: 最长有效括号子串为 "()()" + * + * + */ + +// @lc code=start +/** + * @param {string} s + * @return {number} + */ +var longestValidParentheses = function (s) { + let maxLen = 0 + const len = s.length + const dp = new Array(len).fill(0) + for (let i = 1; i < len; i++) { + if (s[i] == ')') { + if (s[i - 1] == '(') { + if (i - 2 >= 0) { + dp[i] = dp[i - 2] + 2 + } else { + dp[i] = 2 + } + } else if (s[i - dp[i - 1] - 1] == '(') { + if (i - dp[i - 1] - 2 >= 0) { + dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2] + } else { + dp[i] = dp[i - 1] + 2 + } + } + } + maxLen = Math.max(maxLen, dp[i]) + } + return maxLen +}; +// @lc code=end + diff --git "a/Week06/code/36.\346\234\211\346\225\210\347\232\204\346\225\260\347\213\254.js" "b/Week06/code/36.\346\234\211\346\225\210\347\232\204\346\225\260\347\213\254.js" new file mode 100644 index 000000000..01a122af9 --- /dev/null +++ "b/Week06/code/36.\346\234\211\346\225\210\347\232\204\346\225\260\347\213\254.js" @@ -0,0 +1,106 @@ +/* + * @lc app=leetcode.cn id=36 lang=javascript + * + * [36] 有效的数独 + * + * https://leetcode-cn.com/problems/valid-sudoku/description/ + * + * algorithms + * Medium (59.74%) + * Likes: 375 + * Dislikes: 0 + * Total Accepted: 82.9K + * Total Submissions: 137.8K + * Testcase Example: '[["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]' + * + * 判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。 + * + * + * 数字 1-9 在每一行只能出现一次。 + * 数字 1-9 在每一列只能出现一次。 + * 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 + * + * + * + * + * 上图是一个部分填充的有效的数独。 + * + * 数独部分空格内已填入了数字,空白格用 '.' 表示。 + * + * 示例 1: + * + * 输入: + * [ + * ⁠ ["5","3",".",".","7",".",".",".","."], + * ⁠ ["6",".",".","1","9","5",".",".","."], + * ⁠ [".","9","8",".",".",".",".","6","."], + * ⁠ ["8",".",".",".","6",".",".",".","3"], + * ⁠ ["4",".",".","8",".","3",".",".","1"], + * ⁠ ["7",".",".",".","2",".",".",".","6"], + * ⁠ [".","6",".",".",".",".","2","8","."], + * ⁠ [".",".",".","4","1","9",".",".","5"], + * ⁠ [".",".",".",".","8",".",".","7","9"] + * ] + * 输出: true + * + * + * 示例 2: + * + * 输入: + * [ + * ["8","3",".",".","7",".",".",".","."], + * ["6",".",".","1","9","5",".",".","."], + * [".","9","8",".",".",".",".","6","."], + * ["8",".",".",".","6",".",".",".","3"], + * ["4",".",".","8",".","3",".",".","1"], + * ["7",".",".",".","2",".",".",".","6"], + * [".","6",".",".",".",".","2","8","."], + * [".",".",".","4","1","9",".",".","5"], + * [".",".",".",".","8",".",".","7","9"] + * ] + * 输出: false + * 解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。 + * ⁠ 但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。 + * + * 说明: + * + * + * 一个有效的数独(部分已被填充)不一定是可解的。 + * 只需要根据以上规则,验证已经填入的数字是否有效即可。 + * 给定数独序列只包含数字 1-9 和字符 '.' 。 + * 给定数独永远是 9x9 形式的。 + * + * + */ + +// @lc code=start +/** + * @param {character[][]} board + * @return {boolean} + */ +var isValidSudoku = function (board) { + // 三个方向判重 + let rows = {} + let columns = {} + let boxes = {} + // 遍历数独 + for (let i = 0; i < 9; i++) { + for (let j = 0; j < 9; j++) { + let num = board[i][j] + if (num != '.') { + // 子数独序号 + let boxIndex = parseInt((i / 3)) * 3 + parseInt(j / 3) + if (rows[i + '-' + num] || columns[j + '-' + num] || boxes[boxIndex + '-' + num]) { + return false + } + // 以各自方向 + 不能出现重复的数字 组成唯一键值,若出现第二次,即为重复 + rows[i + '-' + num] = true + columns[j + '-' + num] = true + boxes[boxIndex + '-' + num] = true + } + } + } + return true +}; +// @lc code=end + diff --git "a/Week06/code/363.\347\237\251\345\275\242\345\214\272\345\237\237\344\270\215\350\266\205\350\277\207-k-\347\232\204\346\234\200\345\244\247\346\225\260\345\200\274\345\222\214.js" "b/Week06/code/363.\347\237\251\345\275\242\345\214\272\345\237\237\344\270\215\350\266\205\350\277\207-k-\347\232\204\346\234\200\345\244\247\346\225\260\345\200\274\345\222\214.js" new file mode 100644 index 000000000..3efcfb5c2 --- /dev/null +++ "b/Week06/code/363.\347\237\251\345\275\242\345\214\272\345\237\237\344\270\215\350\266\205\350\277\207-k-\347\232\204\346\234\200\345\244\247\346\225\260\345\200\274\345\222\214.js" @@ -0,0 +1,89 @@ +/* + * @lc app=leetcode.cn id=363 lang=javascript + * + * [363] 矩形区域不超过 K 的最大数值和 + * + * https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/description/ + * + * algorithms + * Hard (36.44%) + * Likes: 102 + * Dislikes: 0 + * Total Accepted: 4K + * Total Submissions: 10.6K + * Testcase Example: '[[1,0,1],[0,-2,3]]\n2' + * + * 给定一个非空二维矩阵 matrix 和一个整数 k,找到这个矩阵内部不大于 k 的最大矩形和。 + * + * 示例: + * + * 输入: matrix = [[1,0,1],[0,-2,3]], k = 2 + * 输出: 2 + * 解释: 矩形区域 [[0, 1], [-2, 3]] 的数值和是 2,且 2 是不超过 k 的最大数字(k = 2)。 + * + * + * 说明: + * + * + * 矩阵内的矩形区域面积必须大于 0。 + * 如果行数远大于列数,你将如何解答呢? + * + * + */ + +// @lc code=start +/** + * @param {number[][]} matrix + * @param {number} k + * @return {number} + */ +var maxSumSubmatrix = function (matrix, k) { + const insertIndex = function (nums, target) { + let low = 0 + let high = nums.length - 1 + let mid + while (low <= high) { + mid = (low + high) >> 1 + if (nums[mid] === target) { + return mid + } else if (nums[mid] > target) { + high = mid - 1 + } else { + low = mid + 1 + } + } + return low + } + let max = -Infinity + const m = matrix.length + const n = matrix[0].length + for (let i = 0; i < n; i++) { + const rowSum = Array(m).fill(0) + for (let j = i; j < n; j++) { + for (let k = 0; k < m; k++) { + rowSum[k] += matrix[k][j]; + } + let sum = 0 + const arr = [0] + for (let r = 0; r < m; r++) { + sum += rowSum[r] + let idx = insertIndex(arr, sum - k) + idx = idx >= arr.length ? arr.length - 1 : idx + const val = sum - arr[idx] + if (idx > -1 && val <= k) { + if (val === k) return k + else max = Math.max(max, val) + } + const insertIdx = insertIndex(arr, sum) + if (arr[insertIdx] !== sum) { + // 在合适的index位置插入该值,保证arr是个有序数组 + arr.splice(insertIdx, 0, sum) + } + } + + } + } + return max +}; +// @lc code=end + diff --git "a/Week06/code/403.\351\235\222\350\233\231\350\277\207\346\262\263.js" "b/Week06/code/403.\351\235\222\350\233\231\350\277\207\346\262\263.js" new file mode 100644 index 000000000..d13966ca4 --- /dev/null +++ "b/Week06/code/403.\351\235\222\350\233\231\350\277\207\346\262\263.js" @@ -0,0 +1,98 @@ +/* + * @lc app=leetcode.cn id=403 lang=javascript + * + * [403] 青蛙过河 + * + * https://leetcode-cn.com/problems/frog-jump/description/ + * + * algorithms + * Hard (35.35%) + * Likes: 83 + * Dislikes: 0 + * Total Accepted: 7.4K + * Total Submissions: 20.3K + * Testcase Example: '[0,1,3,4,5,7,9,10,12]' + * + * 一只青蛙想要过河。 假定河流被等分为 x 个单元格,并且在每一个单元格内都有可能放有一石子(也有可能没有)。 青蛙可以跳上石头,但是不可以跳入水中。 + * + * 给定石子的位置列表(用单元格序号升序表示), 请判定青蛙能否成功过河(即能否在最后一步跳至最后一个石子上)。 开始时, + * 青蛙默认已站在第一个石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格1跳至单元格2)。 + * + * 如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1个单位。 + * 另请注意,青蛙只能向前方(终点的方向)跳跃。 + * + * 请注意: + * + * + * 石子的数量 ≥ 2 且 < 1100; + * 每一个石子的位置序号都是一个非负整数,且其 < 2^31; + * 第一个石子的位置永远是0。 + * + * + * 示例 1: + * + * + * [0,1,3,5,6,8,12,17] + * + * 总共有8个石子。 + * 第一个石子处于序号为0的单元格的位置, 第二个石子处于序号为1的单元格的位置, + * 第三个石子在序号为3的单元格的位置, 以此定义整个数组... + * 最后一个石子处于序号为17的单元格的位置。 + * + * 返回 true。即青蛙可以成功过河,按照如下方案跳跃: + * 跳1个单位到第2块石子, 然后跳2个单位到第3块石子, 接着 + * 跳2个单位到第4块石子, 然后跳3个单位到第6块石子, + * 跳4个单位到第7块石子, 最后,跳5个单位到第8个石子(即最后一块石子)。 + * + * + * 示例 2: + * + * + * [0,1,2,3,4,8,9,11] + * + * 返回 false。青蛙没有办法过河。 + * 这是因为第5和第6个石子之间的间距太大,没有可选的方案供青蛙跳跃过去。 + * + * + */ + +// @lc code=start +/** + * @param {number[]} stones + * @return {boolean} + */ +var canCross = function (stones) { + // 确定状态 + let ability = new Array(stones.length) // 存每一个单元格是否有解 + let step = {} // 存跳到每一个单元格的步跳跃距离可能性 + // 初始化数据 + ability[0] = true + step[stones[0]] = new Set().add(0) + // 第二个单位格开始遍历 + for (let i = 1; i < ability.length; i++) { + ability[i] = false + step[stones[i]] = new Set() + // 遍历之前的单位看是否有满足条件的单位 + for (let j = 0; j < i; j++) { + // 取出在这之前每一个单元格基于前面单元格跳跃步数的可能性 + for (let k of step[stones[j]]) { + /* + 只要之前的所有单元格其中一个满足两个条件 + 1.所遍历到的之前的单元格可以到达 + 2.此单元格对应跳跃步数的可能性满足题目中所描述的 "如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1个单位。" + + 则当前单元格可到达 + */ + if (ability[j] && (stones[i] - stones[j] >= k - 1 && stones[i] - stones[j] <= k + 1)) { + ability[i] = true + // 存储所有可以到达该单元格的步数 + step[stones[i]].add(stones[i] - stones[j]) + } + } + } + } + // 返回最后一个点是否到达情况 + return ability[ability.length - 1] +}; +// @lc code=end + diff --git "a/Week06/code/410.\345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.js" "b/Week06/code/410.\345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.js" new file mode 100644 index 000000000..b3948a009 --- /dev/null +++ "b/Week06/code/410.\345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.js" @@ -0,0 +1,78 @@ +/* + * @lc app=leetcode.cn id=410 lang=javascript + * + * [410] 分割数组的最大值 + * + * https://leetcode-cn.com/problems/split-array-largest-sum/description/ + * + * algorithms + * Hard (43.22%) + * Likes: 183 + * Dislikes: 0 + * Total Accepted: 7.9K + * Total Submissions: 17.9K + * Testcase Example: '[7,2,5,10,8]\n2' + * + * 给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。 + * + * 注意: + * 数组长度 n 满足以下条件: + * + * + * 1 ≤ n ≤ 1000 + * 1 ≤ m ≤ min(50, n) + * + * + * 示例: + * + * + * 输入: + * nums = [7,2,5,10,8] + * m = 2 + * + * 输出: + * 18 + * + * 解释: + * 一共有四种方法将nums分割为2个子数组。 + * 其中最好的方式是将其分为[7,2,5] 和 [10,8], + * 因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。 + * + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @param {number} m + * @return {number} + */ +var splitArray = function (nums, m) { + let start = 0 + let end = 0 + for (let i = 0; i < nums.length; ++i) { + end += nums[i] + if (start < nums[i]) start = nums[i] + } + while (start < end) { + let mid = parseInt((end - start) / 2 + start) + let count = 0 + let sum = 0 + for (let i = 0; i < nums.length; ++i) { + if (sum + nums[i] > mid) { + ++count + sum = nums[i] + } else { + sum += nums[i] + } + } + if (count < m) { + end = mid + } else { + start = mid + 1 + } + } + return start +}; +// @lc code=end + diff --git "a/Week06/code/51.n\347\232\207\345\220\216.js" "b/Week06/code/51.n\347\232\207\345\220\216.js" new file mode 100644 index 000000000..1c5e806af --- /dev/null +++ "b/Week06/code/51.n\347\232\207\345\220\216.js" @@ -0,0 +1,99 @@ +/* + * @lc app=leetcode.cn id=51 lang=javascript + * + * [51] N皇后 + * + * https://leetcode-cn.com/problems/n-queens/description/ + * + * algorithms + * Hard (67.42%) + * Likes: 328 + * Dislikes: 0 + * Total Accepted: 25.9K + * Total Submissions: 38K + * Testcase Example: '4' + * + * n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 + * + * + * + * 上图为 8 皇后问题的一种解法。 + * + * 给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。 + * + * 每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 + * + * 示例: + * + * 输入: 4 + * 输出: [ + * ⁠[".Q..", // 解法 1 + * ⁠ "...Q", + * ⁠ "Q...", + * ⁠ "..Q."], + * + * ⁠["..Q.", // 解法 2 + * ⁠ "Q...", + * ⁠ "...Q", + * ⁠ ".Q.."] + * ] + * 解释: 4 皇后问题存在两个不同的解法。 + * + * + */ + +// @lc code=start +/** + * @param {number} n + * @return {string[][]} + */ +var solveNQueens = function (n) { + + // 1. 行不能一样 按照行查找 + + // 2. 列不能一样 + + // 3. 行-列(索引值)不能一样 + + // 4. 行+列(索引值)不能一样 + + // tmp 用于记录之前的路径 + // tmp:当行索引为n时,摆放的棋子列的索引值 + // [1,3,0,2] + // 比如行索引是0时,摆放列索引是1 + // 比如行索引是1时,摆放列索引是3 + let ret = [] + // 查找第0行 + find(0) + return ret + function find(row, tmp = []) { + // 终止条件 + if (row === n) { + // n-1 已经是最后一行了 tmp是所有拜访的位置 + ret.push(tmp.map(c => { + let arr = new Array(n).fill('.') + arr[c] = 'Q' + return arr.join('') + })) + } + // 查找 + for (let col = 0; col < n; col++) { + // 是否无法放置 + let canNotSet = tmp.some((colIndex, rowIndex) => { + // colIndex和rowIndex是之前摆放棋子的行列索引 + // col和row是当前所在位置的索引 + return colIndex === col || + (rowIndex - colIndex) === (row - col) || + (rowIndex + colIndex) === (row + col) + }) + if (canNotSet) { + continue + } + // 如果能放置,直接查找下一行 + find(row + 1, [...tmp, col]) + } + } + +}; +// @lc code=end + diff --git "a/Week06/code/552.\345\255\246\347\224\237\345\207\272\345\213\244\350\256\260\345\275\225-ii.js" "b/Week06/code/552.\345\255\246\347\224\237\345\207\272\345\213\244\350\256\260\345\275\225-ii.js" new file mode 100644 index 000000000..d569cd1f9 --- /dev/null +++ "b/Week06/code/552.\345\255\246\347\224\237\345\207\272\345\213\244\350\256\260\345\275\225-ii.js" @@ -0,0 +1,69 @@ +/* + * @lc app=leetcode.cn id=552 lang=javascript + * + * [552] 学生出勤记录 II + * + * https://leetcode-cn.com/problems/student-attendance-record-ii/description/ + * + * algorithms + * Hard (40.58%) + * Likes: 82 + * Dislikes: 0 + * Total Accepted: 2.8K + * Total Submissions: 6.9K + * Testcase Example: '1' + * + * 给定一个正整数 n,返回长度为 n 的所有可被视为可奖励的出勤记录的数量。 答案可能非常大,你只需返回结果mod 10^9 + 7的值。 + * + * 学生出勤记录是只包含以下三个字符的字符串: + * + * + * 'A' : Absent,缺勤 + * 'L' : Late,迟到 + * 'P' : Present,到场 + * + * + * 如果记录不包含多于一个'A'(缺勤)或超过两个连续的'L'(迟到),则该记录被视为可奖励的。 + * + * 示例 1: + * + * + * 输入: n = 2 + * 输出: 8 + * 解释: + * 有8个长度为2的记录将被视为可奖励: + * "PP" , "AP", "PA", "LP", "PL", "AL", "LA", "LL" + * 只有"AA"不会被视为可奖励,因为缺勤次数超过一次。 + * + * 注意:n 的值不会超过100000。 + * + */ + +// @lc code=start +/** + * @param {number} n + * @return {number} + */ +var checkRecord = function (n) { + let P = 1 // 不含A以P结尾的数量 + let L = 1 // 不含A以L结尾不以LL结尾的数量 + let LL = 0 // 不含A以LL结尾的数量 + let A = 1 // 含有A并且以A结尾的数量 + let AP = 0 // 含有A并且以P结尾的数量 + let AL = 0 // 含有A并且以L结尾不以LL结尾的数量 + let ALL = 0 // 含有A并且以LL结尾的数量 + for (let i = 1; i < n; ++i) { + [P, L, LL, A, AP, AL, ALL] = [ + (P + L + LL) % 1000000007, + P, + L, + (P + L + LL) % 1000000007, + (A + AP + AL + ALL) % 1000000007, + (A + AP) % 1000000007, + AL + ] + } + return (P + L + LL + A + AP + AL + ALL) % 1000000007 +}; +// @lc code=end + diff --git "a/Week06/code/621.\344\273\273\345\212\241\350\260\203\345\272\246\345\231\250.js" "b/Week06/code/621.\344\273\273\345\212\241\350\260\203\345\272\246\345\231\250.js" new file mode 100644 index 000000000..ad77dc590 --- /dev/null +++ "b/Week06/code/621.\344\273\273\345\212\241\350\260\203\345\272\246\345\231\250.js" @@ -0,0 +1,74 @@ +/* + * @lc app=leetcode.cn id=621 lang=javascript + * + * [621] 任务调度器 + * + * https://leetcode-cn.com/problems/task-scheduler/description/ + * + * algorithms + * Medium (49.34%) + * Likes: 317 + * Dislikes: 0 + * Total Accepted: 26.6K + * Total Submissions: 53.2K + * Testcase Example: '["A","A","A","B","B","B"]\n2' + * + * 给定一个用字符数组表示的 CPU 需要执行的任务列表。其中包含使用大写的 A - Z 字母表示的26 + * 种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。CPU + * 在任何一个单位时间内都可以执行一个任务,或者在待命状态。 + * + * 然而,两个相同种类的任务之间必须有长度为 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。 + * + * 你需要计算完成所有任务所需要的最短时间。 + * + * + * + * 示例 : + * + * 输入:tasks = ["A","A","A","B","B","B"], n = 2 + * 输出:8 + * 解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B. + * ⁠ 在本示例中,两个相同类型任务之间必须间隔长度为 n = 2 + * 的冷却时间,而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。 + * + * + * + * 提示: + * + * + * 任务的总个数为 [1, 10000]。 + * n 的取值范围为 [0, 100]。 + * + * + */ + +// @lc code=start +/** + * @param {character[]} tasks + * @param {number} n + * @return {number} + */ +var leastInterval = function (tasks, n) { + let map = new Map() + // 遍历计算所有任务出现的次数 + for (let i = 0; i < tasks.length; i++) { + if (map.has(tasks[i])) { + map.set(tasks[i], map.get(tasks[i]) + 1) + } else { + map.set(tasks[i], 1) + } + } + // 对次数进行递减排序 + let arr = [...map.values()].sort((a, b) => b - a) + let maxNum = arr[0] + let res = (maxNum - 1) * (n + 1) + 1 + let i = 1; + while (i < arr.length && arr[i] === maxNum) { + // 如果存在其他任务的出现次数跟最大次数相同 + res++ + i++ + } + return Math.max(tasks.length, res) +}; +// @lc code=end + diff --git "a/Week06/code/64.\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.js" "b/Week06/code/64.\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.js" new file mode 100644 index 000000000..469b76371 --- /dev/null +++ "b/Week06/code/64.\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.js" @@ -0,0 +1,56 @@ +/* + * @lc app=leetcode.cn id=64 lang=javascript + * + * [64] 最小路径和 + * + * https://leetcode-cn.com/problems/minimum-path-sum/description/ + * + * algorithms + * Medium (65.68%) + * Likes: 523 + * Dislikes: 0 + * Total Accepted: 101.5K + * Total Submissions: 153.7K + * Testcase Example: '[[1,3,1],[1,5,1],[4,2,1]]' + * + * 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 + * + * 说明:每次只能向下或者向右移动一步。 + * + * 示例: + * + * 输入: + * [ + * [1,3,1], + * ⁠ [1,5,1], + * ⁠ [4,2,1] + * ] + * 输出: 7 + * 解释: 因为路径 1→3→1→1→1 的总和最小。 + * + * + */ + +// @lc code=start +/** + * @param {number[][]} grid + * @return {number} + */ +var minPathSum = function (grid) { + for (var i = 0; i < grid.length; i++) { + for (var j = 0; j < grid[0].length; j++) { + if (i != 0 && j != 0) { + grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j] + } else if (i == 0 && j != 0) { + grid[i][j] = grid[i][j - 1] + grid[i][j] + } else if (i != 0 && j == 0) { + grid[i][j] = grid[i - 1][j] + grid[i][j] + } else if (i == 0 && j == 0) { + continue + } + } + } + return grid[grid.length - 1][grid[0].length - 1] +}; +// @lc code=end + diff --git "a/Week06/code/647.\345\233\236\346\226\207\345\255\220\344\270\262.js" "b/Week06/code/647.\345\233\236\346\226\207\345\255\220\344\270\262.js" new file mode 100644 index 000000000..b0312c752 --- /dev/null +++ "b/Week06/code/647.\345\233\236\346\226\207\345\255\220\344\270\262.js" @@ -0,0 +1,66 @@ +/* + * @lc app=leetcode.cn id=647 lang=javascript + * + * [647] 回文子串 + * + * https://leetcode-cn.com/problems/palindromic-substrings/description/ + * + * algorithms + * Medium (61.75%) + * Likes: 282 + * Dislikes: 0 + * Total Accepted: 32.6K + * Total Submissions: 52.5K + * Testcase Example: '"abc"' + * + * 给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。 + * + * 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。 + * + * 示例 1: + * + * + * 输入: "abc" + * 输出: 3 + * 解释: 三个回文子串: "a", "b", "c". + * + * + * 示例 2: + * + * + * 输入: "aaa" + * 输出: 6 + * 说明: 6个回文子串: "a", "a", "a", "aa", "aa", "aaa". + * + * + * 注意: + * + * + * 输入的字符串长度不会超过1000。 + * + * + */ + +// @lc code=start +/** + * @param {string} s + * @return {number} + */ +var countSubstrings = function (s) { + let dp = new Array(s.length).fill('').map(() => new Array(s.length).fill(false)) + let num = 0 + // 外层循环是计算的字符串长度 + for (let i = 0; i < s.length; i++) { + // 内层循环是开始位置 + for (let j = 0; j <= i; j++) { + // 在首位相等时,如果长度是1(如a)或2(如aa),或去收尾字符串是回文 + if (s[i] === s[j] && (i - j < 2 || dp[j + 1][i - 1])) { + dp[j][i] = true + num++ + } + } + } + return num +}; +// @lc code=end + diff --git "a/Week06/code/72.\347\274\226\350\276\221\350\267\235\347\246\273.js" "b/Week06/code/72.\347\274\226\350\276\221\350\267\235\347\246\273.js" new file mode 100644 index 000000000..f391533d0 --- /dev/null +++ "b/Week06/code/72.\347\274\226\350\276\221\350\267\235\347\246\273.js" @@ -0,0 +1,84 @@ +/* + * @lc app=leetcode.cn id=72 lang=javascript + * + * [72] 编辑距离 + * + * https://leetcode-cn.com/problems/edit-distance/description/ + * + * algorithms + * Hard (59.33%) + * Likes: 981 + * Dislikes: 0 + * Total Accepted: 69.8K + * Total Submissions: 117.2K + * Testcase Example: '"horse"\n"ros"' + * + * 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 + * + * 你可以对一个单词进行如下三种操作: + * + * + * 插入一个字符 + * 删除一个字符 + * 替换一个字符 + * + * + * + * + * 示例 1: + * + * 输入:word1 = "horse", word2 = "ros" + * 输出:3 + * 解释: + * horse -> rorse (将 'h' 替换为 'r') + * rorse -> rose (删除 'r') + * rose -> ros (删除 'e') + * + * + * 示例 2: + * + * 输入:word1 = "intention", word2 = "execution" + * 输出:5 + * 解释: + * intention -> inention (删除 't') + * inention -> enention (将 'i' 替换为 'e') + * enention -> exention (将 'n' 替换为 'x') + * exention -> exection (将 'n' 替换为 'c') + * exection -> execution (插入 'u') + * + * + */ + +// @lc code=start +/** + * @param {string} word1 + * @param {string} word2 + * @return {number} + */ +var minDistance = function (word1, word2) { + let m = word1.length + 1 + let n = word2.length + 1 + let dp = new Array(m) + for (let k = 0; k < m; k++) { + dp[k] = new Array(n) + } + for (let i = 0; i < m; i++) { + dp[i][0] = i + } + for (let j = 0; j < n; j++) { + dp[0][j] = j + } + for (let i = 1; i < m; i++) { + for (let j = 1; j < n; j++) { + if (word1[i - 1] == word2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + } + else { + dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + } + } + } + return dp[m - 1][n - 1] +}; +// @lc code=end + diff --git "a/Week06/code/76.\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.js" "b/Week06/code/76.\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.js" new file mode 100644 index 000000000..88e5cebbb --- /dev/null +++ "b/Week06/code/76.\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.js" @@ -0,0 +1,69 @@ +/* + * @lc app=leetcode.cn id=76 lang=javascript + * + * [76] 最小覆盖子串 + * + * https://leetcode-cn.com/problems/minimum-window-substring/description/ + * + * algorithms + * Hard (37.88%) + * Likes: 649 + * Dislikes: 0 + * Total Accepted: 63.9K + * Total Submissions: 167.1K + * Testcase Example: '"ADOBECODEBANC"\n"ABC"' + * + * 给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。 + * + * 示例: + * + * 输入: S = "ADOBECODEBANC", T = "ABC" + * 输出: "BANC" + * + * 说明: + * + * + * 如果 S 中不存这样的子串,则返回空字符串 ""。 + * 如果 S 中存在这样的子串,我们保证它是唯一的答案。 + * + * + */ + +// @lc code=start +/** + * @param {string} s + * @param {string} t + * @return {string} + */ +var minWindow = function (s, t) { + let minLen = Infinity, resL // minLen初始尽量大,第一次就能被改写 + let map = {} // 存储目标字符,和对应的缺失个数 + let missingType = 0 // 当前缺失的字符种类数 + for (const char of t) { // t为baac的话,map为{a:2,b:1,c:1} + if (!map[char]) { + missingType++ // 需要找齐的种类数 +1 + map[char] = 1 + } else { + map[char]++ + } + } + let left = 0, right = 0 // 左右指针 + for (; right < s.length; right++) { // right++ 扩张窗口,超出s串就结束循环 + let rightChar = s[right] // 获取right指向的新字符 + if (map[rightChar] !== undefined) map[rightChar]-- // 是目标字符,它的缺失个数-1 + if (map[rightChar] === 0) missingType-- // 它的缺失个数变0,缺失的种类就-1 + while (missingType === 0) { //只要满足当前窗口包含所有字符,就一直收缩窗口 + if (right - left + 1 < minLen) { // 计算长度,和minLen比较 + minLen = right - left + 1 // 更新minLen + resL = left // 更新最小子串的起点 + } + let leftChar = s[left] // 获取左指针指向的字符 + if (map[leftChar] !== undefined) map[leftChar]++ //目标字符被舍弃,缺失个数+1 + if (map[leftChar] > 0) missingType++ // 缺失个数变>0,缺失的种类+1 + left++ // 左指针步进,收缩窗口 + } + } + return s.substring(resL, resL + minLen) // 根据起点和minLen截取子串 +}; +// @lc code=end + diff --git "a/Week06/code/91.\350\247\243\347\240\201\346\226\271\346\263\225.js" "b/Week06/code/91.\350\247\243\347\240\201\346\226\271\346\263\225.js" new file mode 100644 index 000000000..bc22e7f06 --- /dev/null +++ "b/Week06/code/91.\350\247\243\347\240\201\346\226\271\346\263\225.js" @@ -0,0 +1,64 @@ +/* + * @lc app=leetcode.cn id=91 lang=javascript + * + * [91] 解码方法 + * + * https://leetcode-cn.com/problems/decode-ways/description/ + * + * algorithms + * Medium (23.72%) + * Likes: 441 + * Dislikes: 0 + * Total Accepted: 56.6K + * Total Submissions: 235.8K + * Testcase Example: '"12"' + * + * 一条包含字母 A-Z 的消息通过以下方式进行了编码: + * + * 'A' -> 1 + * 'B' -> 2 + * ... + * 'Z' -> 26 + * + * + * 给定一个只包含数字的非空字符串,请计算解码方法的总数。 + * + * 示例 1: + * + * 输入: "12" + * 输出: 2 + * 解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。 + * + * + * 示例 2: + * + * 输入: "226" + * 输出: 3 + * 解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 + * + * + */ + +// @lc code=start +/** + * @param {string} s + * @return {number} + */ +var numDecodings = function (s) { + if (!s) return 0 + let len = s.length + let dp = Array(len + 1).fill(0) + dp[0] = 1 + dp[1] = s[0] === '0' ? 0 : 1 + for (let i = 2; i <= len; i++) { + if (s[i - 1] !== '0') { + dp[i] += dp[i - 1] + } + if (s[i - 2] === '1' || (s[i - 2] === '2' && s[i - 1] >= 0 && s[i - 1] <= 6)) { + dp[i] += dp[i - 2] + } + } + return dp[len] +}; +// @lc code=end + diff --git a/Week07/NOTE.md b/Week07/NOTE.md deleted file mode 100644 index 50de30414..000000000 --- a/Week07/NOTE.md +++ /dev/null @@ -1 +0,0 @@ -学习笔记 \ No newline at end of file diff --git a/Week07/README.md b/Week07/README.md new file mode 100644 index 000000000..ab67a4793 --- /dev/null +++ b/Week07/README.md @@ -0,0 +1,166 @@ +## 二叉搜索树 + +- 左子树上所有节点的值均小于它的根节点的值 + +- 右子树上所有节点的值均大于它的根节点的值 +- 依次类推:左右子树也分别为二叉搜索树 +- 中序遍历:升序遍历 + +## 平衡二叉树 + +1. 概念解析 + - 左子树和右子树的深度差(平衡因子)的绝对值不超过 1 + - 左右子树也分别为平衡二叉树 +2. 基本实现 + - 左旋 + - 右旋 + - 左右旋 + - 右左旋 +3. 性能分析 + - 节点需要存储额外信息 + - 调整次数频繁 + +## 红黑树 + +1. 概念解析 + - 每个节点要么是红色要么是黑色 + - 根节点是黑色的 + - 每个叶节点(NIL 节点/空节点)都是黑色的 + - 任何相邻的阶段都不能同时为红色 + - 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点 +2. 实现技巧 + - 把红黑树的平衡调整的过程比作魔方复原 + - 找准关注节点:不要搞丢、搞错关注节点 + - 插入操作的平衡调整比较简单而删除操作就比较复杂 +3. 适用场景 + - 解决普通二叉树在频繁插入、删除等动态更新操作时时间复杂度退化问题 + - 实际工程中更倾向用跳表来替代 + + +## 字典树 +1. 概念解析 + - 节点本身不存在完整单词 + - 从根节点到某一节点路径经过的字符串连接起来为该节点对应的字符串 + - 每个节点的所有子节点路径代表的字符都不相同 +2. 适用场景 + - 统计和排序大量的字符串 + - 搜索引擎词频统计及关键字提醒 + + +## 并查集 +1. 概念解析 + - 一种树型数据结构在使用中以森林来表示 + - 用于处理不交集的合并及查询问题 +2. 常见操作 + - 初始化:把每个点所在的集合测试为其自身 -> O(n) + - 合并:将两个元素所在的集合合并为一个集合 -> 建立关系 +3. 查找:查找元素所在集合(即根节点) +4. 优化方式 + - 将 rank 浅的合并到 rank 深的 + - 路径压缩 +5. 并查集代码模板 +``` +def init(p): + # for i = 0 .. n: p[i] = i + p = [i for i in range(n)] + +def union(self, p, i, j): + p1 = self.parent(p, i) + p2 = self.parent(p, j) + p[p1] = p2 + +def parent(self, p, i): + root = i + while p[root] != root: + root = p[root] + # 路径压缩 + while p[i] != i: + x = i; i = p[i]; p[x] = root + return root + +``` + +## 搜索算法 +1. 遍历搜索 + - 每个节点都要访问一次 + - 每个节点仅要访问一次 + - 暴力搜索:时间复杂度:O(E)/空间复杂度:O(V) +2. 广度优先搜索(BFS) +``` +def BFS(graph, start, end): + """ + 1. visited -> 记录已被访问的顶点 + 2. queue -> 存储已被访问但相连的顶点还未被访问的顶点 + """ + queue = [] + queue.append([start]) + + visited = set() + visited.add(start) + + while queue: + node = queue.pop() + visited.add(node) + + process(node) + nodes = generate_related_nodes(node) + queue.push(nodes) + + # other processing work + ... +``` +3. 深度优先搜索(DFS) + +``` +def DFS(self, tree): + if tree.root is None: return [] + + visited, stack = [], [] + while stack: + node = stack.pop() + visited.add(node) + + process(node) + nodes = generate_related_nodes(node) + stack.push(nodes) + + # other processing work + ... +``` +``` +def DFS(node, visited): + visited = set() + visited.add(node) + + # process current node here + ... + for next_node in node.children(): + if not next_node in visited: + DFS(next_node, visited) +``` +4. 高级搜索 + - 剪枝 + - 双向搜索 + - 启发式搜索 + +``` +def AstarSearch(graph, start, end): + """ + 1. 启发式函数评价哪些节点最优希望是我们要找的节点的估计成本 + 2. 启发式函数是一种告知搜索方向的方法来猜测会导向的一个目标 + """ + # 优先级 -> 估价函数 + pq = collections.priority_queue() + pq.append([start]) + visited.add(start) + + while pq: + node = pq.pop() + visited.add(node) + + process(node) + + nodes = generate_related_nodes(node) + unvisited = [node for node in nodes if node not in visited] + pq.push(unvisited) +``` diff --git "a/Week07/code/127.\345\215\225\350\257\215\346\216\245\351\276\231.js" "b/Week07/code/127.\345\215\225\350\257\215\346\216\245\351\276\231.js" new file mode 100644 index 000000000..31cd95dc8 --- /dev/null +++ "b/Week07/code/127.\345\215\225\350\257\215\346\216\245\351\276\231.js" @@ -0,0 +1,109 @@ +/* + * @lc app=leetcode.cn id=127 lang=javascript + * + * [127] 单词接龙 + * + * https://leetcode-cn.com/problems/word-ladder/description/ + * + * algorithms + * Medium (42.36%) + * Likes: 365 + * Dislikes: 0 + * Total Accepted: 47.7K + * Total Submissions: 111.7K + * Testcase Example: '"hit"\n"cog"\n["hot","dot","dog","lot","log","cog"]' + * + * 给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord + * 的最短转换序列的长度。转换需遵循如下规则: + * + * + * 每次转换只能改变一个字母。 + * 转换过程中的中间单词必须是字典中的单词。 + * + * + * 说明: + * + * + * 如果不存在这样的转换序列,返回 0。 + * 所有单词具有相同的长度。 + * 所有单词只由小写字母组成。 + * 字典中不存在重复的单词。 + * 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。 + * + * + * 示例 1: + * + * 输入: + * beginWord = "hit", + * endWord = "cog", + * wordList = ["hot","dot","dog","lot","log","cog"] + * + * 输出: 5 + * + * 解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", + * ⁠ 返回它的长度 5。 + * + * + * 示例 2: + * + * 输入: + * beginWord = "hit" + * endWord = "cog" + * wordList = ["hot","dot","dog","lot","log"] + * + * 输出: 0 + * + * 解释: endWord "cog" 不在字典中,所以无法进行转换。 + * + */ + +// @lc code=start +/** + * @param {string} beginWord + * @param {string} endWord + * @param {string[]} wordList + * @return {number} + */ +var ladderLength = function (beginWord, endWord, wordList) { + if (!wordList.includes(endWord)) { + return 0 + } + const wordLength = beginWord.length // 每个单词长度都是相同的 + const comboDict = new Map() + const visited = new Map() + wordList.forEach((word) => { + for (let i = 0; i < wordLength; i++) { + let genericWord = word.substring(0, i) + '*' + word.substring(i + 1) + if (!comboDict.get(genericWord)) { + comboDict.set(genericWord, []) + } + comboDict.get(genericWord).push(word) + } + }) + const queue = [] + queue.push({ [beginWord]: 1 }) + visited.set(beginWord, true) + while (queue.length > 0) { + let node = queue.shift() + let word = Object.keys(node)[0] + let level = node[word] + for (let i = 0; i < wordLength; i++) { + const generic = word.substring(0, i) + '*' + word.substring(i + 1) + const list = comboDict.get(generic) + if (list) { + for (let j = 0; j < list.length; j++) { + if (list[j] === endWord) { + return level + 1 + } + if (!visited.get(list[j])) { + visited.set(list[j], true) + queue.push({ [list[j]]: level + 1 }) + } + } + } + } + } + return 0 +}; +// @lc code=end + diff --git "a/Week07/code/130.\350\242\253\345\233\264\347\273\225\347\232\204\345\214\272\345\237\237.js" "b/Week07/code/130.\350\242\253\345\233\264\347\273\225\347\232\204\345\214\272\345\237\237.js" new file mode 100644 index 000000000..b986328a1 --- /dev/null +++ "b/Week07/code/130.\350\242\253\345\233\264\347\273\225\347\232\204\345\214\272\345\237\237.js" @@ -0,0 +1,82 @@ +/* + * @lc app=leetcode.cn id=130 lang=javascript + * + * [130] 被围绕的区域 + * + * https://leetcode-cn.com/problems/surrounded-regions/description/ + * + * algorithms + * Medium (40.14%) + * Likes: 268 + * Dislikes: 0 + * Total Accepted: 43.8K + * Total Submissions: 108.5K + * Testcase Example: '[["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]' + * + * 给定一个二维的矩阵,包含 'X' 和 'O'(字母 O)。 + * + * 找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。 + * + * 示例: + * + * X X X X + * X O O X + * X X O X + * X O X X + * + * + * 运行你的函数后,矩阵变为: + * + * X X X X + * X X X X + * X X X X + * X O X X + * + * + * 解释: + * + * 被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' + * 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。 + * + */ + +// @lc code=start +/** + * @param {character[][]} board + * @return {void} Do not return anything, modify board in-place instead. + */ +var solve = function (board) { + let m = board.length + if (m == 0) return + let n = board[0].length + let cannot = {} + let dfs = (i, j) => { + // 越界、标示过或者非相连O下return + if (i < 0 || j < 0 || i == m || j == n || board[i][j] != 'O' || cannot[i + '-' + j]) { + return + } + cannot[i + '-' + j] = true + dfs(i - 1, j) + dfs(i + 1, j) + dfs(i, j - 1) + dfs(i, j + 1) + } + + for (let i = 0; i < m; i++) { + for (let j = 0; j < n; j++) { + if ((i == 0 || j == 0 || i == m - 1 || j == n - 1) && board[i][j] == 'O') { + dfs(i, j) + } + } + } + + for (let i = 1; i < m - 1; i++) { + for (let j = 1; j < n - 1; j++) { + if (!cannot[i + '-' + j] && board[i][j] == 'O') { + board[i][j] = 'X' + } + } + } +}; +// @lc code=end + diff --git "a/Week07/code/146.lru\347\274\223\345\255\230\346\234\272\345\210\266.js" "b/Week07/code/146.lru\347\274\223\345\255\230\346\234\272\345\210\266.js" new file mode 100644 index 000000000..97e0d04e1 --- /dev/null +++ "b/Week07/code/146.lru\347\274\223\345\255\230\346\234\272\345\210\266.js" @@ -0,0 +1,107 @@ +/* + * @lc app=leetcode.cn id=146 lang=javascript + * + * [146] LRU缓存机制 + * + * https://leetcode-cn.com/problems/lru-cache/description/ + * + * algorithms + * Medium (49.51%) + * Likes: 771 + * Dislikes: 0 + * Total Accepted: 83.6K + * Total Submissions: 167.2K + * Testcase Example: '["LRUCache","put","put","get","put","get","put","get","get","get"]\n[[2],[1,1],[2,2],[1],[3,3],[2],[4,4],[1],[3],[4]]' + * + * 运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。 + * + * 获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。 + * 写入数据 put(key, value) - + * 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 + * + * + * + * 进阶: + * + * 你是否可以在 O(1) 时间复杂度内完成这两种操作? + * + * + * + * 示例: + * + * LRUCache cache = new LRUCache( 2 /* 缓存容量 */ ); + * + * cache.put(1, 1); + * cache.put(2, 2); + * cache.get(1); // 返回 1 + * cache.put(3, 3); // 该操作会使得关键字 2 作废 + * cache.get(2); // 返回 -1 (未找到) + * cache.put(4, 4); // 该操作会使得关键字 1 作废 + * cache.get(1); // 返回 -1 (未找到) + * cache.get(3); // 返回 3 + * cache.get(4); // 返回 4 + * + * + * / + +// @lc code=start +/** + * @param {number} capacity + */ +var LRUCache = function (capacity) { + this.size = 0 + this.capacity = capacity + this.list = [] + this.last = 0 + this.map = new Map() +}; + +/** + * @param {number} key + * @return {number} + */ +LRUCache.prototype.get = function (key) { + if (this.map.has(key)) { + let pos = this.map.get(key) + let val = this.list[pos] + this.list[pos] = -1 + this.list.push(val) + this.map.set(key, this.list.length - 1) + return val + } + return -1 +}; + +/** + * @param {number} key + * @param {number} value + * @return {void} + */ +LRUCache.prototype.put = function (key, value) { + if (this.get(key) !== -1) { + let pos = this.map.get(key) + this.list[pos] = -1 + this.list.push(value) + this.map.set(key, this.list.length - 1) + return + } + this.list.push(value) + this.map.set(key, this.list.length - 1) + if (this.size < this.capacity) + this.size++ + else { + while (this.list[this.last] === -1) { + this.last++ + } + this.list[this.last] = -1 + } +}; + +/** + * Your LRUCache object will be instantiated and called as such: + * var obj = new LRUCache(capacity) + * var param_1 = obj.get(key) + * obj.put(key,value) + */ +// @lc code=end + diff --git "a/Week07/code/200.\345\262\233\345\261\277\346\225\260\351\207\217.js" "b/Week07/code/200.\345\262\233\345\261\277\346\225\260\351\207\217.js" new file mode 100644 index 000000000..ed870c0ea --- /dev/null +++ "b/Week07/code/200.\345\262\233\345\261\277\346\225\260\351\207\217.js" @@ -0,0 +1,85 @@ +/* + * @lc app=leetcode.cn id=200 lang=javascript + * + * [200] 岛屿数量 + * + * https://leetcode-cn.com/problems/number-of-islands/description/ + * + * algorithms + * Medium (49.56%) + * Likes: 640 + * Dislikes: 0 + * Total Accepted: 124K + * Total Submissions: 249.5K + * Testcase Example: '[["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]]' + * + * 给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。 + * + * 岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。 + * + * 此外,你可以假设该网格的四条边均被水包围。 + * + * + * + * 示例 1: + * + * 输入: + * [ + * ['1','1','1','1','0'], + * ['1','1','0','1','0'], + * ['1','1','0','0','0'], + * ['0','0','0','0','0'] + * ] + * 输出: 1 + * + * + * 示例 2: + * + * 输入: + * [ + * ['1','1','0','0','0'], + * ['1','1','0','0','0'], + * ['0','0','1','0','0'], + * ['0','0','0','1','1'] + * ] + * 输出: 3 + * 解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。 + * + * + */ + +// @lc code=start +/** + * @param {character[][]} grid + * @return {number} + */ +var numIslands = function (grid) { + // dfs + function dfsmarking(grid, i, j) { + if (i < 0 || + j < 0 || + i >= n || + j >= m || + grid[i][j] !== '1') return + grid[i][j] = '0' + dfsmarking(grid, i + 1, j) + dfsmarking(grid, i - 1, j) + dfsmarking(grid, i, j + 1) + dfsmarking(grid, i, j - 1) + } + let sum = 0 + let n = grid.length + if (n === 0) return 0 + m = grid[0].length + for (let i = 0; i < n; i++) { + for (let j = 0; j < m; j++) { + if (grid[i][j] === '1') { + dfsmarking(grid, i, j) + sum++ + } + } + } + return sum +}; +// @lc code=end + diff --git "a/Week07/code/208.\345\256\236\347\216\260-trie-\345\211\215\347\274\200\346\240\221.js" "b/Week07/code/208.\345\256\236\347\216\260-trie-\345\211\215\347\274\200\346\240\221.js" new file mode 100644 index 000000000..1c86780ba --- /dev/null +++ "b/Week07/code/208.\345\256\236\347\216\260-trie-\345\211\215\347\274\200\346\240\221.js" @@ -0,0 +1,113 @@ +/* + * @lc app=leetcode.cn id=208 lang=javascript + * + * [208] 实现 Trie (前缀树) + * + * https://leetcode-cn.com/problems/implement-trie-prefix-tree/description/ + * + * algorithms + * Medium (67.36%) + * Likes: 351 + * Dislikes: 0 + * Total Accepted: 45K + * Total Submissions: 66.3K + * Testcase Example: '["Trie","insert","search","search","startsWith","insert","search"]\n[[],["apple"],["apple"],["app"],["app"],["app"],["app"]]' + * + * 实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。 + * + * 示例: + * + * Trie trie = new Trie(); + * + * trie.insert("apple"); + * trie.search("apple"); // 返回 true + * trie.search("app"); // 返回 false + * trie.startsWith("app"); // 返回 true + * trie.insert("app"); + * trie.search("app"); // 返回 true + * + * 说明: + * + * + * 你可以假设所有的输入都是由小写字母 a-z 构成的。 + * 保证所有输入均为非空字符串。 + * + * + */ + +// @lc code=start +/** + * Initialize your data structure here. + */ +var TrieNode = function () { + this.next = {} + this.isEnd = false +}; + +var Trie = function () { + this.root = new TrieNode() +}; + +/** + * Inserts a word into the trie. + * @param {string} word + * @return {void} + */ +Trie.prototype.insert = function (word) { + if (!word) return false + let node = this.root + for (let i = 0; i < word.length; ++i) { + if (!node.next[word[i]]) { + node.next[word[i]] = new TrieNode() + } + node = node.next[word[i]] + } + node.isEnd = true + return true +}; + +/** + * Returns if the word is in the trie. + * @param {string} word + * @return {boolean} + */ +Trie.prototype.search = function (word) { + if (!word) return false + let node = this.root + for (let i = 0; i < word.length; ++i) { + if (node.next[word[i]]) { + node = node.next[word[i]] + } else { + return false + } + } + return node.isEnd +}; + +/** + * Returns if there is any word in the trie that starts with the given prefix. + * @param {string} prefix + * @return {boolean} + */ +Trie.prototype.startsWith = function (prefix) { + if (!prefix) return true + let node = this.root + for (let i = 0; i < prefix.length; ++i) { + if (node.next[prefix[i]]) { + node = node.next[prefix[i]] + } else { + return false + } + } + return true +}; + +/** + * Your Trie object will be instantiated and called as such: + * var obj = new Trie() + * obj.insert(word) + * var param_2 = obj.search(word) + * var param_3 = obj.startsWith(prefix) + */ +// @lc code=end + diff --git "a/Week07/code/212.\345\215\225\350\257\215\346\220\234\347\264\242-ii.js" "b/Week07/code/212.\345\215\225\350\257\215\346\220\234\347\264\242-ii.js" new file mode 100644 index 000000000..52ea9571d --- /dev/null +++ "b/Week07/code/212.\345\215\225\350\257\215\346\220\234\347\264\242-ii.js" @@ -0,0 +1,117 @@ +/* + * @lc app=leetcode.cn id=212 lang=javascript + * + * [212] 单词搜索 II + * + * https://leetcode-cn.com/problems/word-search-ii/description/ + * + * algorithms + * Hard (40.79%) + * Likes: 196 + * Dislikes: 0 + * Total Accepted: 17.1K + * Total Submissions: 41.2K + * Testcase Example: '[["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]]\n["oath","pea","eat","rain"]' + * + * 给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词。 + * + * + * 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。 + * + * 示例: + * + * 输入: + * words = ["oath","pea","eat","rain"] and board = + * [ + * ⁠ ['o','a','a','n'], + * ⁠ ['e','t','a','e'], + * ⁠ ['i','h','k','r'], + * ⁠ ['i','f','l','v'] + * ] + * + * 输出: ["eat","oath"] + * + * 说明: + * 你可以假设所有输入都由小写字母 a-z 组成。 + * + * 提示: + * + * + * 你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯? + * 如果当前单词不存在于所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? + * 前缀树如何?如果你想学习如何实现一个基本的前缀树,请先查看这个问题: 实现Trie(前缀树)。 + * + * + */ + +// @lc code=start +/** + * @param {character[][]} board + * @param {string[]} words + * @return {string[]} + */ +var findWords = function (board, words) { + // 构建字典树 + class TrieNode { + constructor() { + this.END = false + this.children = {} + } + } + let root = null + let Trie = function () { + root = new TrieNode() + }; + Trie.prototype.insert = function (word) { + let currNode = root + for (let i = 0; i < word.length; i++) { + if (currNode.children[word[i]] == undefined) { + currNode.children[word[i]] = new TrieNode() + } + currNode = currNode.children[word[i]] + } + currNode.END = true + }; + // 初始化变量 + let m = board.length + let n = board[0].length + // 初始化字典树 + let wordsTrie = new Trie() + for (let i = 0; i < words.length; i++) { + wordsTrie.insert(words[i]) + } + // DFS 搜索 + let boardDFS = (i, j, curStr, currNode) => { + // 字典树中找到了 + if (currNode.END) { + result.push(curStr) + currNode.END = false + } + if (i < 0 || j < 0 || i == m || j == n) { + return + } + const restore = board[i][j]; + if (restore == '#' || !currNode.children[restore]) { + return + } + // 前进 + board[i][j] = '#' + curStr += restore + boardDFS(i - 1, j, curStr, currNode.children[restore]) + boardDFS(i + 1, j, curStr, currNode.children[restore]) + boardDFS(i, j - 1, curStr, currNode.children[restore]) + boardDFS(i, j + 1, curStr, currNode.children[restore]) + // 还原(回溯) + board[i][j] = restore + } + // 寻找结果 + let result = [] + for (let i = 0; i < m; i++) { + for (let j = 0; j < n; j++) { + boardDFS(i, j, '', root) + } + } + return result +}; +// @lc code=end + diff --git "a/Week07/code/22.\346\213\254\345\217\267\347\224\237\346\210\220.js" "b/Week07/code/22.\346\213\254\345\217\267\347\224\237\346\210\220.js" new file mode 100644 index 000000000..982ef48d3 --- /dev/null +++ "b/Week07/code/22.\346\213\254\345\217\267\347\224\237\346\210\220.js" @@ -0,0 +1,50 @@ +/* + * @lc app=leetcode.cn id=22 lang=javascript + * + * [22] 括号生成 + * + * https://leetcode-cn.com/problems/generate-parentheses/description/ + * + * algorithms + * Medium (75.55%) + * Likes: 1109 + * Dislikes: 0 + * Total Accepted: 138.8K + * Total Submissions: 183.7K + * Testcase Example: '3' + * + * 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 + * + * + * + * 示例: + * + * 输入:n = 3 + * 输出:[ + * ⁠ "((()))", + * ⁠ "(()())", + * ⁠ "(())()", + * ⁠ "()(())", + * ⁠ "()()()" + * ⁠ ] + * + * + */ + +// @lc code=start +/** + * @param {number} n + * @return {string[]} + */ +var generateParenthesis = function (n) { + let res = [] + let dfs = (s, left, right) => { + if (left == n && right == n) return res.push(s) + if (left < n) dfs(s + '(', left + 1, right) + if (right < left) dfs(s + ')', left, right + 1) + } + dfs('', 0, 0) + return res +}; +// @lc code=end + diff --git "a/Week07/code/242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" "b/Week07/code/242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" new file mode 100644 index 000000000..98735b945 --- /dev/null +++ "b/Week07/code/242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" @@ -0,0 +1,48 @@ +/* + * @lc app=leetcode.cn id=242 lang=javascript + * + * [242] 有效的字母异位词 + * + * https://leetcode-cn.com/problems/valid-anagram/description/ + * + * algorithms + * Easy (60.16%) + * Likes: 203 + * Dislikes: 0 + * Total Accepted: 106.8K + * Total Submissions: 177.5K + * Testcase Example: '"anagram"\n"nagaram"' + * + * 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 + * + * 示例 1: + * + * 输入: s = "anagram", t = "nagaram" + * 输出: true + * + * + * 示例 2: + * + * 输入: s = "rat", t = "car" + * 输出: false + * + * 说明: + * 你可以假设字符串只包含小写字母。 + * + * 进阶: + * 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况? + * + */ + +// @lc code=start +/** + * @param {string} s + * @param {string} t + * @return {boolean} + */ +var isAnagram = function (s, t) { + if(s.length != t.length) return false + return Array.from(s).sort().toString() === Array.from(t).sort().toString() +}; +// @lc code=end + diff --git "a/Week07/code/36.\346\234\211\346\225\210\347\232\204\346\225\260\347\213\254.js" "b/Week07/code/36.\346\234\211\346\225\210\347\232\204\346\225\260\347\213\254.js" new file mode 100644 index 000000000..01a122af9 --- /dev/null +++ "b/Week07/code/36.\346\234\211\346\225\210\347\232\204\346\225\260\347\213\254.js" @@ -0,0 +1,106 @@ +/* + * @lc app=leetcode.cn id=36 lang=javascript + * + * [36] 有效的数独 + * + * https://leetcode-cn.com/problems/valid-sudoku/description/ + * + * algorithms + * Medium (59.74%) + * Likes: 375 + * Dislikes: 0 + * Total Accepted: 82.9K + * Total Submissions: 137.8K + * Testcase Example: '[["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]' + * + * 判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。 + * + * + * 数字 1-9 在每一行只能出现一次。 + * 数字 1-9 在每一列只能出现一次。 + * 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 + * + * + * + * + * 上图是一个部分填充的有效的数独。 + * + * 数独部分空格内已填入了数字,空白格用 '.' 表示。 + * + * 示例 1: + * + * 输入: + * [ + * ⁠ ["5","3",".",".","7",".",".",".","."], + * ⁠ ["6",".",".","1","9","5",".",".","."], + * ⁠ [".","9","8",".",".",".",".","6","."], + * ⁠ ["8",".",".",".","6",".",".",".","3"], + * ⁠ ["4",".",".","8",".","3",".",".","1"], + * ⁠ ["7",".",".",".","2",".",".",".","6"], + * ⁠ [".","6",".",".",".",".","2","8","."], + * ⁠ [".",".",".","4","1","9",".",".","5"], + * ⁠ [".",".",".",".","8",".",".","7","9"] + * ] + * 输出: true + * + * + * 示例 2: + * + * 输入: + * [ + * ["8","3",".",".","7",".",".",".","."], + * ["6",".",".","1","9","5",".",".","."], + * [".","9","8",".",".",".",".","6","."], + * ["8",".",".",".","6",".",".",".","3"], + * ["4",".",".","8",".","3",".",".","1"], + * ["7",".",".",".","2",".",".",".","6"], + * [".","6",".",".",".",".","2","8","."], + * [".",".",".","4","1","9",".",".","5"], + * [".",".",".",".","8",".",".","7","9"] + * ] + * 输出: false + * 解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。 + * ⁠ 但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。 + * + * 说明: + * + * + * 一个有效的数独(部分已被填充)不一定是可解的。 + * 只需要根据以上规则,验证已经填入的数字是否有效即可。 + * 给定数独序列只包含数字 1-9 和字符 '.' 。 + * 给定数独永远是 9x9 形式的。 + * + * + */ + +// @lc code=start +/** + * @param {character[][]} board + * @return {boolean} + */ +var isValidSudoku = function (board) { + // 三个方向判重 + let rows = {} + let columns = {} + let boxes = {} + // 遍历数独 + for (let i = 0; i < 9; i++) { + for (let j = 0; j < 9; j++) { + let num = board[i][j] + if (num != '.') { + // 子数独序号 + let boxIndex = parseInt((i / 3)) * 3 + parseInt(j / 3) + if (rows[i + '-' + num] || columns[j + '-' + num] || boxes[boxIndex + '-' + num]) { + return false + } + // 以各自方向 + 不能出现重复的数字 组成唯一键值,若出现第二次,即为重复 + rows[i + '-' + num] = true + columns[j + '-' + num] = true + boxes[boxIndex + '-' + num] = true + } + } + } + return true +}; +// @lc code=end + diff --git "a/Week07/code/37.\350\247\243\346\225\260\347\213\254.js" "b/Week07/code/37.\350\247\243\346\225\260\347\213\254.js" new file mode 100644 index 000000000..027668485 --- /dev/null +++ "b/Week07/code/37.\350\247\243\346\225\260\347\213\254.js" @@ -0,0 +1,93 @@ +/* + * @lc app=leetcode.cn id=37 lang=javascript + * + * [37] 解数独 + * + * https://leetcode-cn.com/problems/sudoku-solver/description/ + * + * algorithms + * Hard (58.68%) + * Likes: 331 + * Dislikes: 0 + * Total Accepted: 19.8K + * Total Submissions: 33.2K + * Testcase Example: '[["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]' + * + * 编写一个程序,通过已填充的空格来解决数独问题。 + * + * 一个数独的解法需遵循如下规则: + * + * + * 数字 1-9 在每一行只能出现一次。 + * 数字 1-9 在每一列只能出现一次。 + * 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 + * + * + * 空白格用 '.' 表示。 + * + * + * + * 一个数独。 + * + * + * + * 答案被标成红色。 + * + * Note: + * + * + * 给定的数独序列只包含数字 1-9 和字符 '.' 。 + * 你可以假设给定的数独只有唯一解。 + * 给定数独永远是 9x9 形式的。 + * + * + */ + +// @lc code=start +/** + * @param {character[][]} board + * @return {void} Do not return anything, modify board in-place instead. + */ +var solveSudoku = function (board) { + // 遍历 + for (let i = 0; i < 9; i++) { + for (let j = 0; j < 9; j++) { + if (board[i][j] != '.') continue + // 尝试放置数字 1-9 + for (let k = 1; k < 10; k++) { + if (isValid(board, i, j, k.toString())) { + // 能放 + board[i][j] = k.toString() + if (solveSudoku(board)) return true + // 回溯 + board[i][j] = '.' + } + } + return false + } + } + return true +}; + +// 判定数字是否可以放置 +function isValid(board, row, col, k) { + const x = Math.floor(row / 3) * 3 + const y = Math.floor(col / 3) * 3 + for (let i = 0; i < 9; i++) { + // 当前行和列 + if (board[row][i] === k || board[i][col] === k) { + return false + } + } + // 方块内 + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + if (board[x + i][y + j] === k) { + return false + } + } + } + return true +} +// @lc code=end + diff --git "a/Week07/code/433.\346\234\200\345\260\217\345\237\272\345\233\240\345\217\230\345\214\226.js" "b/Week07/code/433.\346\234\200\345\260\217\345\237\272\345\233\240\345\217\230\345\214\226.js" new file mode 100644 index 000000000..bc875e620 --- /dev/null +++ "b/Week07/code/433.\346\234\200\345\260\217\345\237\272\345\233\240\345\217\230\345\214\226.js" @@ -0,0 +1,96 @@ +/* + * @lc app=leetcode.cn id=433 lang=javascript + * + * [433] 最小基因变化 + * + * https://leetcode-cn.com/problems/minimum-genetic-mutation/description/ + * + * algorithms + * Medium (50.78%) + * Likes: 41 + * Dislikes: 0 + * Total Accepted: 5.6K + * Total Submissions: 11K + * Testcase Example: '"AACCGGTT"\n"AACCGGTA"\n["AACCGGTA"]' + * + * 一条基因序列由一个带有8个字符的字符串表示,其中每个字符都属于 "A", "C", "G", "T"中的任意一个。 + * + * 假设我们要调查一个基因序列的变化。一次基因变化意味着这个基因序列中的一个字符发生了变化。 + * + * 例如,基因序列由"AACCGGTT" 变化至 "AACCGGTA" 即发生了一次基因变化。 + * + * 与此同时,每一次基因变化的结果,都需要是一个合法的基因串,即该结果属于一个基因库。 + * + * 现在给定3个参数 — start, end, + * bank,分别代表起始基因序列,目标基因序列及基因库,请找出能够使起始基因序列变化为目标基因序列所需的最少变化次数。如果无法实现目标变化,请返回 + * -1。 + * + * 注意: + * + * + * 起始基因序列默认是合法的,但是它并不一定会出现在基因库中。 + * 所有的目标基因序列必须是合法的。 + * 假定起始基因序列与目标基因序列是不一样的。 + * + * + * 示例 1: + * + * + * start: "AACCGGTT" + * end: "AACCGGTA" + * bank: ["AACCGGTA"] + * + * 返回值: 1 + * + * + * 示例 2: + * + * + * start: "AACCGGTT" + * end: "AAACGGTA" + * bank: ["AACCGGTA", "AACCGCTA", "AAACGGTA"] + * + * 返回值: 2 + * + * + * 示例 3: + * + * + * start: "AAAAACCC" + * end: "AACCCCCC" + * bank: ["AAAACCCC", "AAACCCCC", "AACCCCCC"] + * + * 返回值: 3 + * + * + */ + +// @lc code=start +/** + * @param {string} start + * @param {string} end + * @param {string[]} bank + * @return {number} + */ +var minMutation = function (start, end, bank) { + let bankSet = new Set(bank) + if (!bankSet.has(end)) return -1 + let queue = [[start, 0]] + let dna = ["A", "C", "G", "T"] + while (queue.length) { + let [node, count] = queue.shift() + if (node === end) return count; + for (let i = 0; i < node.length; i++) { + for (let j = 0; j < dna.length; j++) { + let d = node.slice(0, i) + dna[j] + node.slice(i + 1) + if (bankSet.has(d)) { + queue.push([d, count + 1]) + bankSet.delete(d) + } + } + } + } + return -1 +}; +// @lc code=end + diff --git "a/Week07/code/51.n\347\232\207\345\220\216.js" "b/Week07/code/51.n\347\232\207\345\220\216.js" new file mode 100644 index 000000000..1c5e806af --- /dev/null +++ "b/Week07/code/51.n\347\232\207\345\220\216.js" @@ -0,0 +1,99 @@ +/* + * @lc app=leetcode.cn id=51 lang=javascript + * + * [51] N皇后 + * + * https://leetcode-cn.com/problems/n-queens/description/ + * + * algorithms + * Hard (67.42%) + * Likes: 328 + * Dislikes: 0 + * Total Accepted: 25.9K + * Total Submissions: 38K + * Testcase Example: '4' + * + * n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 + * + * + * + * 上图为 8 皇后问题的一种解法。 + * + * 给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。 + * + * 每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 + * + * 示例: + * + * 输入: 4 + * 输出: [ + * ⁠[".Q..", // 解法 1 + * ⁠ "...Q", + * ⁠ "Q...", + * ⁠ "..Q."], + * + * ⁠["..Q.", // 解法 2 + * ⁠ "Q...", + * ⁠ "...Q", + * ⁠ ".Q.."] + * ] + * 解释: 4 皇后问题存在两个不同的解法。 + * + * + */ + +// @lc code=start +/** + * @param {number} n + * @return {string[][]} + */ +var solveNQueens = function (n) { + + // 1. 行不能一样 按照行查找 + + // 2. 列不能一样 + + // 3. 行-列(索引值)不能一样 + + // 4. 行+列(索引值)不能一样 + + // tmp 用于记录之前的路径 + // tmp:当行索引为n时,摆放的棋子列的索引值 + // [1,3,0,2] + // 比如行索引是0时,摆放列索引是1 + // 比如行索引是1时,摆放列索引是3 + let ret = [] + // 查找第0行 + find(0) + return ret + function find(row, tmp = []) { + // 终止条件 + if (row === n) { + // n-1 已经是最后一行了 tmp是所有拜访的位置 + ret.push(tmp.map(c => { + let arr = new Array(n).fill('.') + arr[c] = 'Q' + return arr.join('') + })) + } + // 查找 + for (let col = 0; col < n; col++) { + // 是否无法放置 + let canNotSet = tmp.some((colIndex, rowIndex) => { + // colIndex和rowIndex是之前摆放棋子的行列索引 + // col和row是当前所在位置的索引 + return colIndex === col || + (rowIndex - colIndex) === (row - col) || + (rowIndex + colIndex) === (row + col) + }) + if (canNotSet) { + continue + } + // 如果能放置,直接查找下一行 + find(row + 1, [...tmp, col]) + } + } + +}; +// @lc code=end + diff --git "a/Week07/code/547.\346\234\213\345\217\213\345\234\210.js" "b/Week07/code/547.\346\234\213\345\217\213\345\234\210.js" new file mode 100644 index 000000000..987b626dc --- /dev/null +++ "b/Week07/code/547.\346\234\213\345\217\213\345\234\210.js" @@ -0,0 +1,86 @@ +/* + * @lc app=leetcode.cn id=547 lang=javascript + * + * [547] 朋友圈 + * + * https://leetcode-cn.com/problems/friend-circles/description/ + * + * algorithms + * Medium (57.06%) + * Likes: 268 + * Dislikes: 0 + * Total Accepted: 50K + * Total Submissions: 87.4K + * Testcase Example: '[[1,1,0],[1,1,0],[0,0,1]]' + * + * 班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 + * C 的朋友。所谓的朋友圈,是指所有朋友的集合。 + * + * 给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j + * 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。 + * + * 示例 1: + * + * + * 输入: + * [[1,1,0], + * ⁠[1,1,0], + * ⁠[0,0,1]] + * 输出: 2 + * 说明:已知学生0和学生1互为朋友,他们在一个朋友圈。 + * 第2个学生自己在一个朋友圈。所以返回2。 + * + * + * 示例 2: + * + * + * 输入: + * [[1,1,0], + * ⁠[1,1,1], + * ⁠[0,1,1]] + * 输出: 1 + * 说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。 + * + * + * 注意: + * + * + * N 在[1,200]的范围内。 + * 对于所有学生,有M[i][i] = 1。 + * 如果有M[i][j] = 1,则有M[j][i] = 1。 + * + * + */ + +// @lc code=start +/** + * @param {number[][]} M + * @return {number} + */ +var findCircleNum = function (M) { + let count = M.length + let parnets = Array.from(M).map((item, index) => index) + function find(x) { + if (parnets[x] === x) { + return x + } + return (parnets[x] = find(parnets[x])) + } + + function union(x, y) { + if (find(x) === find(y)) return + parnets[parnets[x]] = parnets[y] + count-- + } + + for (let i = 0; i < M.length; i++) { + for (let j = i + 1; j < M[i].length; j++) { + if (M[i][j]) { + union(i, j) + } + } + } + return count +}; +// @lc code=end + diff --git "a/Week07/code/70.\347\210\254\346\245\274\346\242\257.js" "b/Week07/code/70.\347\210\254\346\245\274\346\242\257.js" new file mode 100644 index 000000000..771944eed --- /dev/null +++ "b/Week07/code/70.\347\210\254\346\245\274\346\242\257.js" @@ -0,0 +1,89 @@ +/* + * @lc app=leetcode.cn id=70 lang=javascript + * + * [70] 爬楼梯 + * + * https://leetcode-cn.com/problems/climbing-stairs/description/ + * + * algorithms + * Easy (49.72%) + * Likes: 1074 + * Dislikes: 0 + * Total Accepted: 221.6K + * Total Submissions: 445.7K + * Testcase Example: '2' + * + * 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 + * + * 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? + * + * 注意:给定 n 是一个正整数。 + * + * 示例 1: + * + * 输入: 2 + * 输出: 2 + * 解释: 有两种方法可以爬到楼顶。 + * 1. 1 阶 + 1 阶 + * 2. 2 阶 + * + * 示例 2: + * + * 输入: 3 + * 输出: 3 + * 解释: 有三种方法可以爬到楼顶。 + * 1. 1 阶 + 1 阶 + 1 阶 + * 2. 1 阶 + 2 阶 + * 3. 2 阶 + 1 阶 + * + * + */ + +// @lc code=start +/** + * @param {number} n + * @return {number} + */ +var climbStairs = function (n) { + // 递推思想,找最近重复子问题 + // 1:1 + // 2:2 + // 3:f(1)+f(2) + // 4:f(2)+f(3) + // n:f(n-2)+f(n-1) + + // // 解法一 + // // 时间复杂度:O(n) + // // 空间复杂度:O(n) + // if (n <= 2) return n + // const a = [1, 2] + // for (let i = 2; i < n; i++) { + // a[i] = a[i - 1] + a[i - 2] + // } + // return a[n - 1] + + // 解法二 + // 思路:循环、只保存最后三个值 + // 时间复杂度:O(n) + // 空间复杂度:O(1) + if (n <= 2) return n + let f1 = 1 + let f2 = 2 + let f3 = 3 + for (let i = 2; i < n; i++) { + f3 = f1 + f2 + f1 = f2 + f2 = f3 + } + return f3 + + // // 解法三 + // // 思路:斐波那契数列公式 + // // 时间复杂度:O(log n) + // // 空间复杂度:O(1) + // const sqrt_5 = Math.sqrt(5); + // const fib_n = Math.pow((1 + sqrt_5) / 2, n + 1) - Math.pow((1 - sqrt_5) / 2, n + 1); + // return Math.round(fib_n / sqrt_5); +}; +// @lc code=end + diff --git a/Week08/NOTE.md b/Week08/NOTE.md deleted file mode 100644 index 50de30414..000000000 --- a/Week08/NOTE.md +++ /dev/null @@ -1 +0,0 @@ -学习笔记 \ No newline at end of file diff --git a/Week08/README.md b/Week08/README.md new file mode 100644 index 000000000..5c37f29d7 --- /dev/null +++ b/Week08/README.md @@ -0,0 +1,85 @@ +# 位运算 + +异或:相同为0,不同为1.也可用"不进位加法"来理解 + +异或操作的一些特点: + +- x^0 = x +- x ^ 1s=~x // 注意 1s=~0 (简称 全1) +- x^(~x)=1s +- x^x=0 +- c = a^b => a^c=b,b^c=a // 交换两个数 +- a^b^c = a^(b^c)=(a^b)^c // + +指定位置的位运算: + +- 将x最右边的n位清零:x & (~0<>n)&1 +- 获取x的第n位的幂值:x&(1< (x&1) == 1 + x%2 == 0 -> (x&1) == 0 +- x >> 1 -> x/2 + 即: x=x/2; -> x = x >>1 + mid = (left+right)/2 -> mid=(left+right) >> 1 +- x=x&(x-1) 清零最低位的1 +- x & -x => 得到最低位1 +- x&~x => 0 + +# 布隆过滤器 + +- 它由一个bit数组和一组Hash算法构成。可用于判断一个元素是否在一个集合中,查询效率很高(1-N,最优能逼近于1) +- 一个很长的 二进制向量和一系列随机映射函数。布隆过滤器用于检索一个元素是否在一个集合中 +- 优点:空间效率和查询时间都远远超过一般算法 +- 缺点:有一定的误识别率和删除困难 + +# LRU CACHE + +- 两个要素:大小、替换 +- Hash Table + Double LinkdList +- O(1)查询 +- O(1)修改。更新 + +# 排序 +1. 比较类排序 + + - 时间复杂度不能突破 O(nlogn) + - 交换排序 + - 插入排序 + - 选择排序 + - 归并排序 +2. 非比较类排序 + + - 对于整型 + - 可以达到 O(n+k) + - 计数排序 + - 桶排序:与计数排序的区别,计数排序是对应的下标是有序的连续的下标的桶排序,桶排序是有一个函数要计算桶的下标,可以理解为计数排序的升级版 +3. 基数排序 + +**初级排序O(n^2)** + +- 选择排序:每次找最小值,然后放到待排序数组的起始位置 +- 插入排序:从前到后逐步构建有序序列;对于未排序数据,在已排序序列中从后向前扫描,找到响应位置并插入 +- 冒泡排序:嵌套循环,每次查看相邻的元素如果逆序,则交换 + + +**高级排序 O(nlogn)** + + - 快速排序:数组取标杆pivot,将小元素放pivot左边,大元素方右侧,然后依次对右边和左边子数组继续排序;以达到整个序列有序 + - 归并排序 --分治 + 1、把长度为n的输入序列分成两个长度为n/2的子序列 + 2、对这两个子序列分别采用归并排序 + 3、将两个排好序的子序列合并成一个最终的排序序列 + - 归并和快排具有相似性,但步骤顺序相反 + 归并:先排序左右子数组,然后合并两个有序子数组 + 快排:想调配出左右子数组,然后对左右子数组进行排序 + - 堆排序: + 堆插入 O(logn),取最大值/小值 O(1) + 1、数组元素依次建立小顶堆 + 2、依次取堆顶元素,并删除 \ No newline at end of file diff --git "a/Week08/code/1122.\346\225\260\347\273\204\347\232\204\347\233\270\345\257\271\346\216\222\345\272\217.js" "b/Week08/code/1122.\346\225\260\347\273\204\347\232\204\347\233\270\345\257\271\346\216\222\345\272\217.js" new file mode 100644 index 000000000..9bf6c7018 --- /dev/null +++ "b/Week08/code/1122.\346\225\260\347\273\204\347\232\204\347\233\270\345\257\271\346\216\222\345\272\217.js" @@ -0,0 +1,69 @@ +/* + * @lc app=leetcode.cn id=1122 lang=javascript + * + * [1122] 数组的相对排序 + * + * https://leetcode-cn.com/problems/relative-sort-array/description/ + * + * algorithms + * Easy (65.99%) + * Likes: 70 + * Dislikes: 0 + * Total Accepted: 20.2K + * Total Submissions: 30.3K + * Testcase Example: '[2,3,1,3,2,4,6,7,9,2,19]\n[2,1,4,3,9,6]' + * + * 给你两个数组,arr1 和 arr2, + * + * + * arr2 中的元素各不相同 + * arr2 中的每个元素都出现在 arr1 中 + * + * + * 对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 + * 的末尾。 + * + * + * + * 示例: + * + * 输入:arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6] + * 输出:[2,2,2,1,4,3,3,9,6,7,19] + * + * + * + * + * 提示: + * + * + * arr1.length, arr2.length <= 1000 + * 0 <= arr1[i], arr2[i] <= 1000 + * arr2 中的元素 arr2[i] 各不相同 + * arr2 中的每个元素 arr2[i] 都出现在 arr1 中 + * + * + */ + +// @lc code=start +/** + * @param {number[]} arr1 + * @param {number[]} arr2 + * @return {number[]} + */ +var relativeSortArray = function (arr1, arr2) { + return arr1.sort((a, b) => { + let ia = arr2.indexOf(a) + let ib = arr2.indexOf(b) + if (ia == -1 && ib == -1) { + return a - b + } else if (ia == -1) { + return 1 + } else if (ib == -1) { + return -1 + } else { + return ia - ib + } + }) +}; +// @lc code=end + diff --git "a/Week08/code/146.lru\347\274\223\345\255\230\346\234\272\345\210\266.js" "b/Week08/code/146.lru\347\274\223\345\255\230\346\234\272\345\210\266.js" new file mode 100644 index 000000000..97e0d04e1 --- /dev/null +++ "b/Week08/code/146.lru\347\274\223\345\255\230\346\234\272\345\210\266.js" @@ -0,0 +1,107 @@ +/* + * @lc app=leetcode.cn id=146 lang=javascript + * + * [146] LRU缓存机制 + * + * https://leetcode-cn.com/problems/lru-cache/description/ + * + * algorithms + * Medium (49.51%) + * Likes: 771 + * Dislikes: 0 + * Total Accepted: 83.6K + * Total Submissions: 167.2K + * Testcase Example: '["LRUCache","put","put","get","put","get","put","get","get","get"]\n[[2],[1,1],[2,2],[1],[3,3],[2],[4,4],[1],[3],[4]]' + * + * 运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。 + * + * 获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。 + * 写入数据 put(key, value) - + * 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 + * + * + * + * 进阶: + * + * 你是否可以在 O(1) 时间复杂度内完成这两种操作? + * + * + * + * 示例: + * + * LRUCache cache = new LRUCache( 2 /* 缓存容量 */ ); + * + * cache.put(1, 1); + * cache.put(2, 2); + * cache.get(1); // 返回 1 + * cache.put(3, 3); // 该操作会使得关键字 2 作废 + * cache.get(2); // 返回 -1 (未找到) + * cache.put(4, 4); // 该操作会使得关键字 1 作废 + * cache.get(1); // 返回 -1 (未找到) + * cache.get(3); // 返回 3 + * cache.get(4); // 返回 4 + * + * + * / + +// @lc code=start +/** + * @param {number} capacity + */ +var LRUCache = function (capacity) { + this.size = 0 + this.capacity = capacity + this.list = [] + this.last = 0 + this.map = new Map() +}; + +/** + * @param {number} key + * @return {number} + */ +LRUCache.prototype.get = function (key) { + if (this.map.has(key)) { + let pos = this.map.get(key) + let val = this.list[pos] + this.list[pos] = -1 + this.list.push(val) + this.map.set(key, this.list.length - 1) + return val + } + return -1 +}; + +/** + * @param {number} key + * @param {number} value + * @return {void} + */ +LRUCache.prototype.put = function (key, value) { + if (this.get(key) !== -1) { + let pos = this.map.get(key) + this.list[pos] = -1 + this.list.push(value) + this.map.set(key, this.list.length - 1) + return + } + this.list.push(value) + this.map.set(key, this.list.length - 1) + if (this.size < this.capacity) + this.size++ + else { + while (this.list[this.last] === -1) { + this.last++ + } + this.list[this.last] = -1 + } +}; + +/** + * Your LRUCache object will be instantiated and called as such: + * var obj = new LRUCache(capacity) + * var param_1 = obj.get(key) + * obj.put(key,value) + */ +// @lc code=end + diff --git "a/Week08/code/190.\351\242\240\345\200\222\344\272\214\350\277\233\345\210\266\344\275\215.js" "b/Week08/code/190.\351\242\240\345\200\222\344\272\214\350\277\233\345\210\266\344\275\215.js" new file mode 100644 index 000000000..6f25a2278 --- /dev/null +++ "b/Week08/code/190.\351\242\240\345\200\222\344\272\214\350\277\233\345\210\266\344\275\215.js" @@ -0,0 +1,66 @@ +/* + * @lc app=leetcode.cn id=190 lang=javascript + * + * [190] 颠倒二进制位 + * + * https://leetcode-cn.com/problems/reverse-bits/description/ + * + * algorithms + * Easy (58.93%) + * Likes: 188 + * Dislikes: 0 + * Total Accepted: 49K + * Total Submissions: 81.3K + * Testcase Example: '00000010100101000001111010011100' + * + * 颠倒给定的 32 位无符号整数的二进制位。 + * + * + * + * 示例 1: + * + * 输入: 00000010100101000001111010011100 + * 输出: 00111001011110000010100101000000 + * 解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596, + * ⁠ 因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。 + * + * 示例 2: + * + * 输入:11111111111111111111111111111101 + * 输出:10111111111111111111111111111111 + * 解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293, + * 因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。 + * + * + * + * 提示: + * + * + * 请注意,在某些语言(如 + * Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。 + * 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 + * -1073741825。 + * + * + * + * + * 进阶: + * 如果多次调用这个函数,你将如何优化你的算法? + * + */ + +// @lc code=start +/** + * @param {number} n - a positive integer + * @return {number} - a positive integer + */ +var reverseBits = function (n) { + let result = 0 + for (let i = 0; i < 32; i++) { + result = (result << 1) + (n & 1) + n >>= 1 + } + return result >>> 0 +}; +// @lc code=end + diff --git "a/Week08/code/191.\344\275\215-1-\347\232\204\344\270\252\346\225\260.js" "b/Week08/code/191.\344\275\215-1-\347\232\204\344\270\252\346\225\260.js" new file mode 100644 index 000000000..20232e5db --- /dev/null +++ "b/Week08/code/191.\344\275\215-1-\347\232\204\344\270\252\346\225\260.js" @@ -0,0 +1,74 @@ +/* + * @lc app=leetcode.cn id=191 lang=javascript + * + * [191] 位1的个数 + * + * https://leetcode-cn.com/problems/number-of-1-bits/description/ + * + * algorithms + * Easy (67.59%) + * Likes: 191 + * Dislikes: 0 + * Total Accepted: 74.9K + * Total Submissions: 108.9K + * Testcase Example: '00000000000000000000000000001011' + * + * 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。 + * + * + * + * 示例 1: + * + * 输入:00000000000000000000000000001011 + * 输出:3 + * 解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 + * + * + * 示例 2: + * + * 输入:00000000000000000000000010000000 + * 输出:1 + * 解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。 + * + * + * 示例 3: + * + * 输入:11111111111111111111111111111101 + * 输出:31 + * 解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。 + * + * + * + * 提示: + * + * + * 请注意,在某些语言(如 + * Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。 + * 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。 + * + * + * + * + * 进阶: + * 如果多次调用这个函数,你将如何优化你的算法? + * + */ + +// @lc code=start +/** + * @param {number} n - a positive integer + * @return {number} + */ +var hammingWeight = function (n) { + let count = 0 + let mask = 1 + for (let i = 0; i < 32; i++) { + if ((n & mask) != 0) { + count++ + } + mask <<= 1 + } + return count +}; +// @lc code=end + diff --git "a/Week08/code/231.2-\347\232\204\345\271\202.js" "b/Week08/code/231.2-\347\232\204\345\271\202.js" new file mode 100644 index 000000000..9a774e275 --- /dev/null +++ "b/Week08/code/231.2-\347\232\204\345\271\202.js" @@ -0,0 +1,46 @@ +/* + * @lc app=leetcode.cn id=231 lang=javascript + * + * [231] 2的幂 + * + * https://leetcode-cn.com/problems/power-of-two/description/ + * + * algorithms + * Easy (48.17%) + * Likes: 225 + * Dislikes: 0 + * Total Accepted: 70.8K + * Total Submissions: 146.4K + * Testcase Example: '1' + * + * 给定一个整数,编写一个函数来判断它是否是 2 的幂次方。 + * + * 示例 1: + * + * 输入: 1 + * 输出: true + * 解释: 2^0 = 1 + * + * 示例 2: + * + * 输入: 16 + * 输出: true + * 解释: 2^4 = 16 + * + * 示例 3: + * + * 输入: 218 + * 输出: false + * + */ + +// @lc code=start +/** + * @param {number} n + * @return {boolean} + */ +var isPowerOfTwo = function (n) { + return n > 0 && (n & (-n)) == n +}; +// @lc code=end + diff --git "a/Week08/code/242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" "b/Week08/code/242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" new file mode 100644 index 000000000..98735b945 --- /dev/null +++ "b/Week08/code/242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" @@ -0,0 +1,48 @@ +/* + * @lc app=leetcode.cn id=242 lang=javascript + * + * [242] 有效的字母异位词 + * + * https://leetcode-cn.com/problems/valid-anagram/description/ + * + * algorithms + * Easy (60.16%) + * Likes: 203 + * Dislikes: 0 + * Total Accepted: 106.8K + * Total Submissions: 177.5K + * Testcase Example: '"anagram"\n"nagaram"' + * + * 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 + * + * 示例 1: + * + * 输入: s = "anagram", t = "nagaram" + * 输出: true + * + * + * 示例 2: + * + * 输入: s = "rat", t = "car" + * 输出: false + * + * 说明: + * 你可以假设字符串只包含小写字母。 + * + * 进阶: + * 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况? + * + */ + +// @lc code=start +/** + * @param {string} s + * @param {string} t + * @return {boolean} + */ +var isAnagram = function (s, t) { + if(s.length != t.length) return false + return Array.from(s).sort().toString() === Array.from(t).sort().toString() +}; +// @lc code=end + diff --git "a/Week08/code/493.\347\277\273\350\275\254\345\257\271.js" "b/Week08/code/493.\347\277\273\350\275\254\345\257\271.js" new file mode 100644 index 000000000..9737f3bb3 --- /dev/null +++ "b/Week08/code/493.\347\277\273\350\275\254\345\257\271.js" @@ -0,0 +1,79 @@ +/* + * @lc app=leetcode.cn id=493 lang=javascript + * + * [493] 翻转对 + * + * https://leetcode-cn.com/problems/reverse-pairs/description/ + * + * algorithms + * Hard (25.68%) + * Likes: 116 + * Dislikes: 0 + * Total Accepted: 6K + * Total Submissions: 21.9K + * Testcase Example: '[1,3,2,3,1]' + * + * 给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。 + * + * 你需要返回给定数组中的重要翻转对的数量。 + * + * 示例 1: + * + * + * 输入: [1,3,2,3,1] + * 输出: 2 + * + * + * 示例 2: + * + * + * 输入: [2,4,3,5,1] + * 输出: 3 + * + * + * 注意: + * + * + * 给定数组的长度不会超过50000。 + * 输入数组中的所有数字都在32位整数的表示范围内。 + * + * + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {number} + */ +var reversePairs = function (nums) { + let count = 0 + let mergeArr = (arr, left, mid, right) => { + let temp = [] + let i = left, j = mid + 1, sortedIndex = 0, p = j + let tmpI = i, tmpJ = j + while (tmpI <= mid) { + while (tmpJ <= right && arr[tmpI] / 2 > arr[tmpJ]) { + tmpJ++ + } + count += tmpJ - (mid + 1) + tmpI++ + } + let sorted = [...arr.slice(left, right + 1)].sort((a, b) => a - b); + for (let r = 0; r < sorted.length; r++) { + arr[left + r] = sorted[r] + } + } + let mergeSort = (arr, left, right) => { + if (left >= right) { + return + } + let mid = (left + right) >> 1 + mergeSort(arr, left, mid) + mergeSort(arr, mid + 1, right) + mergeArr(arr, left, mid, right) + } + mergeSort(nums, 0, nums.length - 1) + return count +}; +// @lc code=end + diff --git "a/Week08/code/51.n\347\232\207\345\220\216.js" "b/Week08/code/51.n\347\232\207\345\220\216.js" new file mode 100644 index 000000000..1c5e806af --- /dev/null +++ "b/Week08/code/51.n\347\232\207\345\220\216.js" @@ -0,0 +1,99 @@ +/* + * @lc app=leetcode.cn id=51 lang=javascript + * + * [51] N皇后 + * + * https://leetcode-cn.com/problems/n-queens/description/ + * + * algorithms + * Hard (67.42%) + * Likes: 328 + * Dislikes: 0 + * Total Accepted: 25.9K + * Total Submissions: 38K + * Testcase Example: '4' + * + * n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 + * + * + * + * 上图为 8 皇后问题的一种解法。 + * + * 给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。 + * + * 每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 + * + * 示例: + * + * 输入: 4 + * 输出: [ + * ⁠[".Q..", // 解法 1 + * ⁠ "...Q", + * ⁠ "Q...", + * ⁠ "..Q."], + * + * ⁠["..Q.", // 解法 2 + * ⁠ "Q...", + * ⁠ "...Q", + * ⁠ ".Q.."] + * ] + * 解释: 4 皇后问题存在两个不同的解法。 + * + * + */ + +// @lc code=start +/** + * @param {number} n + * @return {string[][]} + */ +var solveNQueens = function (n) { + + // 1. 行不能一样 按照行查找 + + // 2. 列不能一样 + + // 3. 行-列(索引值)不能一样 + + // 4. 行+列(索引值)不能一样 + + // tmp 用于记录之前的路径 + // tmp:当行索引为n时,摆放的棋子列的索引值 + // [1,3,0,2] + // 比如行索引是0时,摆放列索引是1 + // 比如行索引是1时,摆放列索引是3 + let ret = [] + // 查找第0行 + find(0) + return ret + function find(row, tmp = []) { + // 终止条件 + if (row === n) { + // n-1 已经是最后一行了 tmp是所有拜访的位置 + ret.push(tmp.map(c => { + let arr = new Array(n).fill('.') + arr[c] = 'Q' + return arr.join('') + })) + } + // 查找 + for (let col = 0; col < n; col++) { + // 是否无法放置 + let canNotSet = tmp.some((colIndex, rowIndex) => { + // colIndex和rowIndex是之前摆放棋子的行列索引 + // col和row是当前所在位置的索引 + return colIndex === col || + (rowIndex - colIndex) === (row - col) || + (rowIndex + colIndex) === (row + col) + }) + if (canNotSet) { + continue + } + // 如果能放置,直接查找下一行 + find(row + 1, [...tmp, col]) + } + } + +}; +// @lc code=end + diff --git "a/Week08/code/52.n\347\232\207\345\220\216-ii.js" "b/Week08/code/52.n\347\232\207\345\220\216-ii.js" new file mode 100644 index 000000000..1e1b41e67 --- /dev/null +++ "b/Week08/code/52.n\347\232\207\345\220\216-ii.js" @@ -0,0 +1,79 @@ +/* + * @lc app=leetcode.cn id=52 lang=javascript + * + * [52] N皇后 II + * + * https://leetcode-cn.com/problems/n-queens-ii/description/ + * + * algorithms + * Hard (78.54%) + * Likes: 137 + * Dislikes: 0 + * Total Accepted: 28.4K + * Total Submissions: 35.9K + * Testcase Example: '4' + * + * n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 + * + * + * + * 上图为 8 皇后问题的一种解法。 + * + * 给定一个整数 n,返回 n 皇后不同的解决方案的数量。 + * + * 示例: + * + * 输入: 4 + * 输出: 2 + * 解释: 4 皇后问题存在如下两个不同的解法。 + * [ + * [".Q..",  // 解法 1 + * "...Q", + * "Q...", + * "..Q."], + * + * ["..Q.",  // 解法 2 + * "Q...", + * "...Q", + * ".Q.."] + * ] + * + * + * + * + * 提示: + * + * + * 皇后,是国际象棋中的棋子,意味着国王的妻子。皇后只做一件事,那就是“吃子”。当她遇见可以吃的棋子时,就迅速冲上去吃掉棋子。当然,她横、竖、斜都可走一或 + * N-1 步,可进可退。(引用自 百度百科 - 皇后 ) + * + * + */ + +// @lc code=start +/** + * @param {number} n + * @return {number} + */ +var totalNQueens = function (n) { + let res = 0 + const dfs = (n, row, cols, pie, na) => { + if (row >= n) { + res++ + return + } + // 得到当前所有的空位 + let bits = (~(cols | pie | na)) & ((1 << n) - 1) + while (bits) { + // 取最低位的1 + let p = bits & -bits + // 把P位置上放入皇后 + bits = bits & (bits - 1) + dfs(n, row + 1, cols | p, (pie | p) << 1, (na | p) >> 1) + } + } + dfs(n, 0, 0, 0, 0) + return res; +}; +// @lc code=end + diff --git "a/Week08/code/56.\345\220\210\345\271\266\345\214\272\351\227\264.js" "b/Week08/code/56.\345\220\210\345\271\266\345\214\272\351\227\264.js" new file mode 100644 index 000000000..70347cbf4 --- /dev/null +++ "b/Week08/code/56.\345\220\210\345\271\266\345\214\272\351\227\264.js" @@ -0,0 +1,57 @@ +/* + * @lc app=leetcode.cn id=56 lang=javascript + * + * [56] 合并区间 + * + * https://leetcode-cn.com/problems/merge-intervals/description/ + * + * algorithms + * Medium (42.68%) + * Likes: 530 + * Dislikes: 0 + * Total Accepted: 124K + * Total Submissions: 290.2K + * Testcase Example: '[[1,3],[2,6],[8,10],[15,18]]' + * + * 给出一个区间的集合,请合并所有重叠的区间。 + * + * 示例 1: + * + * 输入: [[1,3],[2,6],[8,10],[15,18]] + * 输出: [[1,6],[8,10],[15,18]] + * 解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. + * + * + * 示例 2: + * + * 输入: [[1,4],[4,5]] + * 输出: [[1,5]] + * 解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。 + * + */ + +// @lc code=start +/** + * @param {number[][]} intervals + * @return {number[][]} + */ +var merge = function (intervals) { + if (intervals.length == 0) + return [] + var res = [] + intervals.sort(function (a, b) { + return a[0] - b[0] + }) + res.push(intervals[0]) + for (var i = 1; i < intervals.length; i++) { + if (intervals[i][0] > res[res.length - 1][1]) + res.push(intervals[i]) + else + if (intervals[i][1] > res[res.length - 1][1]) + res[res.length - 1][1] = intervals[i][1] + } + return res + +}; +// @lc code=end + diff --git "a/Week08/code/62.\344\270\215\345\220\214\350\267\257\345\276\204.js" "b/Week08/code/62.\344\270\215\345\220\214\350\267\257\345\276\204.js" new file mode 100644 index 000000000..578fa683d --- /dev/null +++ "b/Week08/code/62.\344\270\215\345\220\214\350\267\257\345\276\204.js" @@ -0,0 +1,80 @@ +/* + * @lc app=leetcode.cn id=62 lang=javascript + * + * [62] 不同路径 + * + * https://leetcode-cn.com/problems/unique-paths/description/ + * + * algorithms + * Medium (60.59%) + * Likes: 626 + * Dislikes: 0 + * Total Accepted: 129.2K + * Total Submissions: 210.2K + * Testcase Example: '3\n2' + * + * 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 + * + * 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 + * + * 问总共有多少条不同的路径? + * + * + * + * 例如,上图是一个7 x 3 的网格。有多少可能的路径? + * + * + * + * 示例 1: + * + * 输入: m = 3, n = 2 + * 输出: 3 + * 解释: + * 从左上角开始,总共有 3 条路径可以到达右下角。 + * 1. 向右 -> 向右 -> 向下 + * 2. 向右 -> 向下 -> 向右 + * 3. 向下 -> 向右 -> 向右 + * + * + * 示例 2: + * + * 输入: m = 7, n = 3 + * 输出: 28 + * + * + * + * 提示: + * + * + * 1 <= m, n <= 100 + * 题目数据保证答案小于等于 2 * 10 ^ 9 + * + * + */ + +// @lc code=start +/** + * @param {number} m + * @param {number} n + * @return {number} + */ +var uniquePaths = function (m, n) { + let dp = Array.from({ length: m }, () => Array.from({ length: n }, () => 0)) + // 第一列的走法只有一种 + for (let i = 0; i < m; i++) { + dp[i][0] = 1 + } + // 第一行的走法只有一种 + for (let j = 0; j < n; j++) { + dp[0][j] = 1 + } + for (let i = 1; i < m; i++) { + for (let j = 1; j < n; j++) { + // 当前位置是从左边或上边走过来的 + dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + } + } + return dp[m - 1][n - 1] +}; +// @lc code=end + diff --git "a/Week08/code/64.\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.js" "b/Week08/code/64.\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.js" new file mode 100644 index 000000000..469b76371 --- /dev/null +++ "b/Week08/code/64.\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.js" @@ -0,0 +1,56 @@ +/* + * @lc app=leetcode.cn id=64 lang=javascript + * + * [64] 最小路径和 + * + * https://leetcode-cn.com/problems/minimum-path-sum/description/ + * + * algorithms + * Medium (65.68%) + * Likes: 523 + * Dislikes: 0 + * Total Accepted: 101.5K + * Total Submissions: 153.7K + * Testcase Example: '[[1,3,1],[1,5,1],[4,2,1]]' + * + * 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 + * + * 说明:每次只能向下或者向右移动一步。 + * + * 示例: + * + * 输入: + * [ + * [1,3,1], + * ⁠ [1,5,1], + * ⁠ [4,2,1] + * ] + * 输出: 7 + * 解释: 因为路径 1→3→1→1→1 的总和最小。 + * + * + */ + +// @lc code=start +/** + * @param {number[][]} grid + * @return {number} + */ +var minPathSum = function (grid) { + for (var i = 0; i < grid.length; i++) { + for (var j = 0; j < grid[0].length; j++) { + if (i != 0 && j != 0) { + grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j] + } else if (i == 0 && j != 0) { + grid[i][j] = grid[i][j - 1] + grid[i][j] + } else if (i != 0 && j == 0) { + grid[i][j] = grid[i - 1][j] + grid[i][j] + } else if (i == 0 && j == 0) { + continue + } + } + } + return grid[grid.length - 1][grid[0].length - 1] +}; +// @lc code=end + diff --git a/Week09/NOTE.md b/Week09/NOTE.md deleted file mode 100644 index 50de30414..000000000 --- a/Week09/NOTE.md +++ /dev/null @@ -1 +0,0 @@ -学习笔记 \ No newline at end of file diff --git a/Week09/README.md b/Week09/README.md new file mode 100644 index 000000000..b23cdad13 --- /dev/null +++ b/Week09/README.md @@ -0,0 +1,11 @@ +# 高阶的动态规划方程 + +1. 复杂度来源于状态需要有更多维去表示,当状态数量过多的时候, 还需要对状态进行压缩。 +2. 状态转移方程更加复杂, 更不容易想出。 + +# Rabin-Karp 算法 +在朴素算法中,我们需要挨个比较所有字符,才知道目标字符串中是否包含子串。那么,是否有别的方法可以用来判断目标字符串是否包含子串呢? +答案是肯定的,确实存在一种更快的方法。为了避免挨个字符对目标字符串和子串进行比较,我们可以尝试一次性判断两者是否相等。因此,我们需要一个好的哈希函数(hash function)。 通过哈希函数,我们可以算出子 串的哈希值,然后将它和目标字符串中的子串的哈希值进行比较。 这个新方法在速度上比暴力法有显著提升。 + +# KMP 算法 +KMP算法(Knuth-Morris-Pratt)的思想就是,当子串与目标字符串不匹配时, 其实你已经知道了前面已经匹配成功那 一部分的字符(包括子串与目标字符 串)。以阮一峰的文章为例,当空格与 D 不匹配时,你其实 知道前面六个字符是 “ABCDAB”。KMP 算法的想法是,设法利用这个已知信息,不要把“搜索位置” 移回已经比较过的位置,继续把它向后移,这样就提高了效率。 \ No newline at end of file diff --git "a/Week09/code/115.\344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227.js" "b/Week09/code/115.\344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227.js" new file mode 100644 index 000000000..9459d5d3a --- /dev/null +++ "b/Week09/code/115.\344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227.js" @@ -0,0 +1,43 @@ +/* + * @lc app=leetcode.cn id=115 lang=javascript + * + * [115] 不同的子序列 + */ + +// @lc code=start +/** + * @param {string} s + * @param {string} t + * @return {number} + */ +var numDistinct = function (s, t) { + s = ' ' + s + t = ' ' + t + + const n = s.length, + m = t.length + + const dp = [...new Array(m)].map(() => new Array(n)) + + for (let j = 0; j < n; j++) { + dp[0][j] = 1 + } + + for (let i = 1; i < m; i++) { + dp[i][0] = 0 + } + + for (let i = 1; i < m; i++) { + for (let j = 1; j < n; j++) { + if (s[j] === t[i]) { + dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1] + } else { + dp[i][j] = dp[i][j - 1] + } + } + } + + return dp[m - 1][n - 1] +}; +// @lc code=end + diff --git "a/Week09/code/151.\347\277\273\350\275\254\345\255\227\347\254\246\344\270\262\351\207\214\347\232\204\345\215\225\350\257\215.js" "b/Week09/code/151.\347\277\273\350\275\254\345\255\227\347\254\246\344\270\262\351\207\214\347\232\204\345\215\225\350\257\215.js" new file mode 100644 index 000000000..b5763395e --- /dev/null +++ "b/Week09/code/151.\347\277\273\350\275\254\345\255\227\347\254\246\344\270\262\351\207\214\347\232\204\345\215\225\350\257\215.js" @@ -0,0 +1,16 @@ +/* + * @lc app=leetcode.cn id=151 lang=javascript + * + * [151] 翻转字符串里的单词 + */ + +// @lc code=start +/** + * @param {string} s + * @return {string} + */ +var reverseWords = function (s) { + return s.trim().split(/\s+/).reverse().join(' ') +}; +// @lc code=end + diff --git "a/Week09/code/205.\345\220\214\346\236\204\345\255\227\347\254\246\344\270\262.js" "b/Week09/code/205.\345\220\214\346\236\204\345\255\227\347\254\246\344\270\262.js" new file mode 100644 index 000000000..1eb76b76b --- /dev/null +++ "b/Week09/code/205.\345\220\214\346\236\204\345\255\227\347\254\246\344\270\262.js" @@ -0,0 +1,23 @@ +/* + * @lc app=leetcode.cn id=205 lang=javascript + * + * [205] 同构字符串 + */ + +// @lc code=start +/** + * @param {string} s + * @param {string} t + * @return {boolean} + */ +var isIsomorphic = function (s, t) { + if (s.length != t.length) return false + for (let i = 0; i < s.length; i++) { + if (s.indexOf(s[i]) != t.indexOf(t[i])) { + return false + } + } + return true +}; +// @lc code=end + diff --git "a/Week09/code/300.\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227.js" "b/Week09/code/300.\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227.js" new file mode 100644 index 000000000..61a9e69b4 --- /dev/null +++ "b/Week09/code/300.\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227.js" @@ -0,0 +1,27 @@ +/* + * @lc app=leetcode.cn id=300 lang=javascript + * + * [300] 最长上升子序列 + */ + +// @lc code=start +/** + * @param {number[]} nums + * @return {number} + */ +var lengthOfLIS = function (nums) { + let n = nums.length + if (!n) return 0 + let dp = new Array(n).fill(1) + for (let i = 1; i < n; i++) { + //我们需要找前面比自己小的; + for (let j = 0; j < i; j++) { + if (nums[i] > nums[j]) { + dp[i] = Math.max(dp[i], dp[j] + 1) + } + } + } + return Math.max(...dp) +}; +// @lc code=end + diff --git "a/Week09/code/32.\346\234\200\351\225\277\346\234\211\346\225\210\346\213\254\345\217\267.js" "b/Week09/code/32.\346\234\200\351\225\277\346\234\211\346\225\210\346\213\254\345\217\267.js" new file mode 100644 index 000000000..ea9948311 --- /dev/null +++ "b/Week09/code/32.\346\234\200\351\225\277\346\234\211\346\225\210\346\213\254\345\217\267.js" @@ -0,0 +1,64 @@ +/* + * @lc app=leetcode.cn id=32 lang=javascript + * + * [32] 最长有效括号 + * + * https://leetcode-cn.com/problems/longest-valid-parentheses/description/ + * + * algorithms + * Hard (30.73%) + * Likes: 853 + * Dislikes: 0 + * Total Accepted: 87.3K + * Total Submissions: 262.8K + * Testcase Example: '"(()"' + * + * 给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度。 + * + * 示例 1: + * + * 输入: "(()" + * 输出: 2 + * 解释: 最长有效括号子串为 "()" + * + * + * 示例 2: + * + * 输入: ")()())" + * 输出: 4 + * 解释: 最长有效括号子串为 "()()" + * + * + */ + +// @lc code=start +/** + * @param {string} s + * @return {number} + */ +var longestValidParentheses = function (s) { + let maxLen = 0 + const len = s.length + const dp = new Array(len).fill(0) + for (let i = 1; i < len; i++) { + if (s[i] == ')') { + if (s[i - 1] == '(') { + if (i - 2 >= 0) { + dp[i] = dp[i - 2] + 2 + } else { + dp[i] = 2 + } + } else if (s[i - dp[i - 1] - 1] == '(') { + if (i - dp[i - 1] - 2 >= 0) { + dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2] + } else { + dp[i] = dp[i - 1] + 2 + } + } + } + maxLen = Math.max(maxLen, dp[i]) + } + return maxLen +}; +// @lc code=end + diff --git "a/Week09/code/387.\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\347\254\254\344\270\200\344\270\252\345\224\257\344\270\200\345\255\227\347\254\246.js" "b/Week09/code/387.\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\347\254\254\344\270\200\344\270\252\345\224\257\344\270\200\345\255\227\347\254\246.js" new file mode 100644 index 000000000..47474eecb --- /dev/null +++ "b/Week09/code/387.\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\347\254\254\344\270\200\344\270\252\345\224\257\344\270\200\345\255\227\347\254\246.js" @@ -0,0 +1,21 @@ +/* + * @lc app=leetcode.cn id=387 lang=javascript + * + * [387] 字符串中的第一个唯一字符 + */ + +// @lc code=start +/** + * @param {string} s + * @return {number} + */ +var firstUniqChar = function (s) { + for (let i = 0; i < s.length; i++) { + if (s.indexOf(s[i]) === s.lastIndexOf(s[i])) { + return i + } + } + return -1 +}; +// @lc code=end + diff --git "a/Week09/code/438.\346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" "b/Week09/code/438.\346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" new file mode 100644 index 000000000..538bd6851 --- /dev/null +++ "b/Week09/code/438.\346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.js" @@ -0,0 +1,48 @@ +/* + * @lc app=leetcode.cn id=438 lang=javascript + * + * [438] 找到字符串中所有字母异位词 + */ + +// @lc code=start +/** + * @param {string} s + * @param {string} p + * @return {number[]} + */ +var findAnagrams = function (s, p) { + let res = [] + let left = 0, right = 0 + let needs = {}, windows = {} + let match = 0 + for (let i = 0; i < p.length; i++) { + needs[p[i]] ? needs[p[i]]++ : needs[p[i]] = 1 + } + let needsLen = Object.keys(needs).length + while (right < s.length) { + let c1 = s[right] + if (needs[c1]) { + windows[c1] ? windows[c1]++ : windows[c1] = 1 + if (windows[c1] === needs[c1]) { + match++ + } + } + right++; + while (match === needsLen) { + if (right - left === p.length) { + res.push(left) + } + let c2 = s[left] + if (needs[c2]) { + windows[c2]-- + if (windows[c2] < needs[c2]) { + match-- + } + } + left++ + } + } + return res +}; +// @lc code=end + diff --git "a/Week09/code/44.\351\200\232\351\205\215\347\254\246\345\214\271\351\205\215.js" "b/Week09/code/44.\351\200\232\351\205\215\347\254\246\345\214\271\351\205\215.js" new file mode 100644 index 000000000..1e1cbc907 --- /dev/null +++ "b/Week09/code/44.\351\200\232\351\205\215\347\254\246\345\214\271\351\205\215.js" @@ -0,0 +1,39 @@ +/* + * @lc app=leetcode.cn id=44 lang=javascript + * + * [44] 通配符匹配 + */ + +// @lc code=start +/** + * @param {string} s + * @param {string} p + * @return {boolean} + */ +var isMatch = function (s, p) { + const sLen = s.length + const pLen = p.length + // 初始化(包括了一部分base case) + const dp = new Array(sLen + 1); + for (let i = 0; i < sLen + 1; i++) { + dp[i] = new Array(pLen + 1).fill(false) + } + // base case + dp[0][0] = true; + for (let j = 1; j <= pLen; j++) { + dp[0][j] = p[j - 1] == '*' && dp[0][j - 1] + } + // 迭代 + for (let i = 1; i <= sLen; i++) { + for (let j = 1; j <= pLen; j++) { + if (p[j - 1] == '?' || s[i - 1] == p[j - 1]) + dp[i][j] = dp[i - 1][j - 1] + else if (p[j - 1] == '*' && (dp[i - 1][j] || dp[i][j - 1])) + dp[i][j] = true + } + } + return dp[sLen][pLen] // 整个s串和整个p串是否匹配 + +}; +// @lc code=end + diff --git "a/Week09/code/5.\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.js" "b/Week09/code/5.\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.js" new file mode 100644 index 000000000..8fe59a927 --- /dev/null +++ "b/Week09/code/5.\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.js" @@ -0,0 +1,41 @@ +/* + * @lc app=leetcode.cn id=5 lang=javascript + * + * [5] 最长回文子串 + */ + +// @lc code=start +/** + * @param {string} s + * @return {string} + */ +var longestPalindrome = function (s) { + if (!s || s.length === 0) return "" + let res = s[0] + + const dp = [] + + // 倒着遍历简化操作, 这么做的原因是dp[i][..]依赖于dp[i + 1][..] + for (let i = s.length - 1; i >= 0; i--) { + dp[i] = []; + for (let j = i; j < s.length; j++) { + if (j - i === 0) dp[i][j] = true + // specail case 1 + else if (j - i === 1 && s[i] === s[j]) dp[i][j] = true + // specail case 2 + else if (s[i] === s[j] && dp[i + 1][j - 1]) { + // state transition + dp[i][j] = true + } + + if (dp[i][j] && j - i + 1 > res.length) { + // update res + res = s.slice(i, j + 1) + } + } + } + + return res +}; +// @lc code=end + diff --git "a/Week09/code/541.\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262-ii.js" "b/Week09/code/541.\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262-ii.js" new file mode 100644 index 000000000..71346db97 --- /dev/null +++ "b/Week09/code/541.\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262-ii.js" @@ -0,0 +1,34 @@ +/* + * @lc app=leetcode.cn id=541 lang=javascript + * + * [541] 反转字符串 II + */ + +// @lc code=start +/** + * @param {string} s + * @param {number} k + * @return {string} + */ +var reverseStr = function (s, k) { + let strArr = s.split('') + + let reverse = (start, end) => { + let temp = null + while (start < end) { + temp = strArr[start] + strArr[start] = strArr[end] + strArr[end] = temp + start++ + end-- + } + } + + for (let i = 0; i < s.length; i += 2 * k) { + reverse(i, i + k - 1) + } + + return strArr.join('') +}; +// @lc code=end + diff --git "a/Week09/code/557.\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215-iii.js" "b/Week09/code/557.\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215-iii.js" new file mode 100644 index 000000000..5c81b6c55 --- /dev/null +++ "b/Week09/code/557.\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215-iii.js" @@ -0,0 +1,16 @@ +/* + * @lc app=leetcode.cn id=557 lang=javascript + * + * [557] 反转字符串中的单词 III + */ + +// @lc code=start +/** + * @param {string} s + * @return {string} + */ +var reverseWords = function (s) { + return s.split(' ').map(val => val.split('').reverse().join('')).join(' ') +}; +// @lc code=end + diff --git "a/Week09/code/63.\344\270\215\345\220\214\350\267\257\345\276\204-ii.js" "b/Week09/code/63.\344\270\215\345\220\214\350\267\257\345\276\204-ii.js" new file mode 100644 index 000000000..cb9b5a598 --- /dev/null +++ "b/Week09/code/63.\344\270\215\345\220\214\350\267\257\345\276\204-ii.js" @@ -0,0 +1,33 @@ +/* + * @lc app=leetcode.cn id=63 lang=javascript + * + * [63] 不同路径 II + */ + +// @lc code=start +/** + * @param {number[][]} obstacleGrid + * @return {number} + */ +var uniquePathsWithObstacles = function (obstacleGrid) { + let m = obstacleGrid.length + if (!m || obstacleGrid[0][0] === 1) return 0 // 空数组 或 开始方块就是障碍的情况 + if (m === 1) return +!obstacleGrid[0].includes(1) // 当只有一行的情况 + + let n = obstacleGrid[0].length + let dp = Array.apply(null, Array(m + 1)).map(() => Array(n + 1).fill(0)) // 生成 (m + 1) * (n + 1) 的数组 + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + if (i === 1 && j === 1) { // 使dp[1][1] = 1 , dp[1][1] 对应 obstacleGrid[0][0] + dp[i][j] = 1 + } else if (obstacleGrid[i - 1][j - 1] != 1) { // 当不是障碍物时 dp[当前] = dp[上] + dp[左] + dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + } else { // 当碰到障碍物 说明此路不通 dp[i][j] = 0 + dp[i][j] = 0 + } + } + } + return dp[m][n] +}; +// @lc code=end + diff --git "a/Week09/code/680.\351\252\214\350\257\201\345\233\236\346\226\207\345\255\227\347\254\246\344\270\262-\342\205\261.js" "b/Week09/code/680.\351\252\214\350\257\201\345\233\236\346\226\207\345\255\227\347\254\246\344\270\262-\342\205\261.js" new file mode 100644 index 000000000..8a5119d62 --- /dev/null +++ "b/Week09/code/680.\351\252\214\350\257\201\345\233\236\346\226\207\345\255\227\347\254\246\344\270\262-\342\205\261.js" @@ -0,0 +1,31 @@ +/* + * @lc app=leetcode.cn id=680 lang=javascript + * + * [680] 验证回文字符串 Ⅱ + */ + +// @lc code=start +/** + * @param {string} s + * @return {boolean} + */ +var validPalindrome = function (s) { + let left = 0 + let right = s.length - 1 + + while (left < right) { + if (s[left] !== s[right]) { + let str = s.split("") + let strRight = s.split("") + str.splice(left, 1) + strRight.splice(right, 1) + + return str.join("") === str.reverse().join("") || strRight.join("") === strRight.reverse().join("") + } + left++ + right-- + } + return true +}; +// @lc code=end + diff --git "a/Week09/code/8.\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\225\264\346\225\260-atoi.js" "b/Week09/code/8.\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\225\264\346\225\260-atoi.js" new file mode 100644 index 000000000..fb3dac9b1 --- /dev/null +++ "b/Week09/code/8.\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\225\264\346\225\260-atoi.js" @@ -0,0 +1,104 @@ +/* + * @lc app=leetcode.cn id=8 lang=javascript + * + * [8] 字符串转换整数 (atoi) + */ + +// @lc code=start +/** + * @param {string} str + * @return {number} + */ +var myAtoi = function (str) { + // 自动机类 + class Automaton { + constructor() { + // 执行阶段,默认处于开始执行阶段 + this.state = 'start' + // 正负符号,默认是正数 + this.sign = 1 + // 数值,默认是0 + this.answer = 0 + /* + 关键点: + 状态和执行阶段的对应表 + 含义如下: + [执行阶段, [空格, 正负, 数值, 其他]] + */ + this.map = new Map([ + ['start', ['start', 'signed', 'in_number', 'end']], + ['signed', ['end', 'end', 'in_number', 'end']], + ['in_number', ['end', 'end', 'in_number', 'end']], + ['end', ['end', 'end', 'end', 'end']] + ]) + } + + // 获取状态的索引 + getIndex(char) { + if (char === ' ') { + // 空格判断 + return 0 + } else if (char === '-' || char === '+') { + // 正负判断 + return 1 + } else if (typeof Number(char) === 'number' && !isNaN(char)) { + // 数值判断 + return 2 + } else { + // 其他情况 + return 3 + } + } + + /* + 关键点: + 字符转换执行函数 + */ + get(char) { + /* + 易错点: + 每次传入字符时,都要变更自动机的执行阶段 + */ + this.state = this.map.get(this.state)[this.getIndex(char)] + + if (this.state === 'in_number') { + /* + 小技巧: + 在JS中,对字符串类型进行减法操作,可以将得到一个数值型(Number)的值 + + 易错点: + 本处需要利用括号来提高四则运算的优先级 + */ + this.answer = this.answer * 10 + (char - 0) + + /* + 易错点: + 在进行负数比较时,需要将INT_MIN变为正数 + */ + this.answer = this.sign === 1 ? Math.min(this.answer, Math.pow(2, 31) - 1) : Math.min(this.answer, -Math.pow(-2, 31)) + } else if (this.state === 'signed') { + /* + 优化点: + 对于一个整数来说,非正即负, + 所以正负号的判断,只需要一次。 + 故,可以降低其判断的优先级 + */ + this.sign = char === '+' ? 1 : -1 + } + } + } + + // 生成自动机实例 + let automaton = new Automaton() + + // 遍历每个字符 + for (let char of str) { + // 依次进行转换 + automaton.get(char) + } + + // 返回值,整数 = 正负 * 数值 + return automaton.sign * automaton.answer +}; +// @lc code=end + diff --git "a/Week09/code/818.\350\265\233\350\275\246.js" "b/Week09/code/818.\350\265\233\350\275\246.js" new file mode 100644 index 000000000..75a2773ac --- /dev/null +++ "b/Week09/code/818.\350\265\233\350\275\246.js" @@ -0,0 +1,47 @@ +/* + * @lc app=leetcode.cn id=818 lang=javascript + * + * [818] 赛车 + */ + +// @lc code=start +/** + * @param {number} target + * @return {number} + */ +var racecar = function (target) { + let queue = [[0, 1]] + let visited = new Set(['0,1']) + let res = 0 + + while (queue.length) { + let tmp = []; + for (let i = 0; i < queue.length; i++) { + let [posi, speed] = queue[i] + if (posi == target) return res + + // A 加速 + let newP = posi + speed, newS = speed * 2 + addQueue(newP, newS, tmp) + + // R 开始变换方向 + newP = posi, newS = speed > 0 ? -1 : 1 + addQueue(newP, newS, tmp) + } + + res++ + queue = tmp + } + + return -1 + + function addQueue(posi, speed, tmp) { + let key = `${posi},${speed}` + if (!visited.has(key) && posi > 0 && posi < 2 * target) { + visited.add(key) + tmp.push([posi, speed]) + } + } +}; +// @lc code=end + diff --git "a/Week09/code/91.\350\247\243\347\240\201\346\226\271\346\263\225.js" "b/Week09/code/91.\350\247\243\347\240\201\346\226\271\346\263\225.js" new file mode 100644 index 000000000..bc22e7f06 --- /dev/null +++ "b/Week09/code/91.\350\247\243\347\240\201\346\226\271\346\263\225.js" @@ -0,0 +1,64 @@ +/* + * @lc app=leetcode.cn id=91 lang=javascript + * + * [91] 解码方法 + * + * https://leetcode-cn.com/problems/decode-ways/description/ + * + * algorithms + * Medium (23.72%) + * Likes: 441 + * Dislikes: 0 + * Total Accepted: 56.6K + * Total Submissions: 235.8K + * Testcase Example: '"12"' + * + * 一条包含字母 A-Z 的消息通过以下方式进行了编码: + * + * 'A' -> 1 + * 'B' -> 2 + * ... + * 'Z' -> 26 + * + * + * 给定一个只包含数字的非空字符串,请计算解码方法的总数。 + * + * 示例 1: + * + * 输入: "12" + * 输出: 2 + * 解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。 + * + * + * 示例 2: + * + * 输入: "226" + * 输出: 3 + * 解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 + * + * + */ + +// @lc code=start +/** + * @param {string} s + * @return {number} + */ +var numDecodings = function (s) { + if (!s) return 0 + let len = s.length + let dp = Array(len + 1).fill(0) + dp[0] = 1 + dp[1] = s[0] === '0' ? 0 : 1 + for (let i = 2; i <= len; i++) { + if (s[i - 1] !== '0') { + dp[i] += dp[i - 1] + } + if (s[i - 2] === '1' || (s[i - 2] === '2' && s[i - 1] >= 0 && s[i - 1] <= 6)) { + dp[i] += dp[i - 2] + } + } + return dp[len] +}; +// @lc code=end + diff --git "a/Week09/code/917.\344\273\205\344\273\205\345\217\215\350\275\254\345\255\227\346\257\215.js" "b/Week09/code/917.\344\273\205\344\273\205\345\217\215\350\275\254\345\255\227\346\257\215.js" new file mode 100644 index 000000000..e756e3264 --- /dev/null +++ "b/Week09/code/917.\344\273\205\344\273\205\345\217\215\350\275\254\345\255\227\346\257\215.js" @@ -0,0 +1,18 @@ +/* + * @lc app=leetcode.cn id=917 lang=javascript + * + * [917] 仅仅反转字母 + */ + +// @lc code=start +/** + * @param {string} S + * @return {string} + */ +var reverseOnlyLetters = function (S) { + var arr = S.match(/[a-zA-Z]/g) + if (arr === null) return S + return S.replace(/[a-zA-Z]/g, () => arr.pop()) +}; +// @lc code=end + diff --git a/Week10/README.md b/Week10/README.md new file mode 100644 index 000000000..5b3dafad0 --- /dev/null +++ b/Week10/README.md @@ -0,0 +1,248 @@ +### 总结: +两个月过得很快,由一个算法小白到如今稍稍入门,收益匪浅。作为一个前端工程师,实际工作中用到算法的地方很少,所以容易遗忘,后续还需要刷第二遍第三遍第N遍来加深记忆,直到融会贯通,让算法成为编码中融会贯通的内功。 +#### Week1 +时间复杂度 + - 1)二叉树遍历:前中后o(n) + - 2)图的遍历:o(n) + - 3)dfs,bfs:o(n) + - 4)二分查找:o(logn) +array + - 1)增加元素,中间插入要移动后面的o(n) + - 2)删除元素,中间删除要把后面的前移o(n) +linked list + - 1)复杂度: + - Prepend o(1) Append o(1) insert o(1) delete o(1) Lookup o(n) +skip list : 升维,空间换时间 + - 1)只能用于链表里的元素有序的情况 + - 2)对标替代平衡树和二分查找 + - 3)插入 删除 搜索 都是log n + - 4)空间复杂度o(n) + + +ADT: + + - Stack:先入后出;添加,删除皆为o(1) + - Queue:先入先出;添加,删除皆为o(1) + - Deque: 两端都可以进出的queue;插入删除都是o(1)查询都为o(n) + - PriorityQueue:插入o(1)(斐波那契堆,二叉堆为ologn),取出o(logn) + - 按照元素的优先级取出 + - 底层实现多样+复杂:heap,bst,treap + +#### Week2 +- hash table + - 根据关键码值(key value)而直接进行访问的数据结构 + - 通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度 + - 映射函数叫hash function散列函数,存放记录的数组叫做哈希表 + - 类比:电话号码表,用户信息表 + - 一种collision解决方案:将键值相同的元素用链表连接 + - 作业:看懂hashmap的get,put,写个总结 +- tree,binary tree,bst + - 二叉树的遍历: + - 二叉搜索树 查询 插入:logn复杂度 + - 二叉搜索树 删除:找到第一个大于该节点的数来替换要删除的节点(右子树最小值) + - 二叉搜索树 查找worst case:当树从根节点开始只有右子树,此时树变成了链表,查找为o(n) + - 思考:二叉树解法为何都是递归的? + +- 堆heap,二叉堆binary heap + - 可以迅速找到一堆数中的最大或者最小值的数据结构 + - time: + - find-max: o(1) + - delete-max: o(logn) + - insert(create): o(logn) or o(1) (fibo heap) + - 二叉堆只是堆的一种实现,比较简单,但time复杂度不是最好,还有很多其他更好的实现,比如fibonacci堆 + + - 二叉堆(大顶)性质: + - 通过完全二叉树来实现 + - 树中任意节点的值总是大于等于其子节点的值 + - 二叉堆实现细节: + - 通过数组实现 + - 数组中各索引i所对应的节点: + - 索引i的左孩子:2*i + 1 + - 索引i的右孩子:2*i + 2 + - 索引i的父节点是floor((i-1)/2) + - 二叉堆insert插入操作 + - 新元素一律先插入到堆的底部 + - 依次向上调整整个堆的结构(一直到根即可)heapify-up + - delete max删除堆顶操作 + - 为了保证堆一直保持一个完全二叉树的形态,此时需要将堆尾元素替换到顶部(等价于堆顶元素被替代删除掉) + - 依次从顶部向下调整整个堆的结构(一直到堆尾即可)heapify-down + - 堆的实现代码: + - [https://shimo.im/docs/Lw86vJzOGOMpWZz2/read] + - heap sort + - [https://www.geeksforgeeks.org/heap-sort/] +#### Week3 +- 递归 + - 模版 + - 注意 + - 利用数学归纳法 + - 不要人肉递归 + - 找最近最简单方法, 将其拆解成可重复解决的问题(重复子问题) +- 分治回溯 + - 分治:大问题由细的子问题组成 + - 模版: + - Base case + - 处理数据 + 准备subproblem + - 做计算/处理subproblem + - 返回结果 + - 回溯:试错思想 + - 注意:熟悉牛顿迭代法 + - [http://www.voidcn.com/article/p-eudisdmk-zm.html] + +#### Week4 + +- DFS + - 遍历顺序:一冲到底,深度优先 + - 树 + - 图 + - 递归写法: + - 迭代写法:自主维护一个stack来模拟系统的stack + +- BFS + - 遍历顺序:层层展开 queue + - 树 +- 贪心算法 + - 局限:每一步选取结果只按一种策略,无法保证最优 + - 定义: 贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(最有利)的选择,从而希望导致结果是全局最好或最优的算法 + - 与动态规划区别:贪心对子问题的解决方案(解决子问题的方案一旦设定,则每次都会运用相同的方案)不能回退;动规则会保存以前的运算结果,并可以根据以前结果对当前进行选择,有回退功能 + - 主要解决最优化问题 + - 适用场景:问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解;这种子问题最优解称为最优子结构; + +- 二分查找 + - 前提 + - 1)目标函数单调性 + - 2)存在上下界 + - 3)能够通过索引访问 + - 代码模版: + - 牛顿迭代法 + [http://www.matrix67.com/blog/archives/361] + +#### Week6 +- 牢记递归模版 + - 终止条件 + - 处理当前层逻辑 + - 下探到下一层 + - 恢复到下一层 + +- 分治代码模版 + + - 一般思路:找到最近最简方法,将问题拆解成可重复解决的问题 + - 数学归纳法 + - 寻找重复性-〉计算机指令集(if else,for,recursion) +- **dp** = 分治 + 最优子结构 + - 分治过程中,每一步不需要保存所有状态,只需要保存最优的状态 + - 动态规划 和 递归或者分治没有根本上的区别(关键看有无最优子结构) + - 共性:找到重复子问题 + - 差异性:最优子结构,中途可以淘汰次优解 + - 自底向上写循环: + - dp关键点: + - 最优子结构 opt[n] = best_of( opt[n-1], opt[n-2], …) + - 储存中间状态:opt[i] + - 递推公式(状态转移方程) + - Fib: opt[i] = opt[i-2] + opt[i-2] + - 二维路径:opt[i,j] = opt[i+1][j] + opt[i][j+1] (且判断a[i,j]是否空地) + +#### Week7 +- 字典树 + - [https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/shi-xian-trie-qian-zhui-shu-by-leetcode/] + - 性质: + - 1. 节点本身不存单词,只存要去到的路径上的这个路径所代表的字符 + - 2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串 + - 3. 每个节点的所有子节点路径代表的字符都不相同 + - 节点可以存储额外信息(例如该节点被访问过的频次) + - 核心思想: 空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的 +- 并查集, union find + - 组团+配对, 用于快速判断某两个个体是否在同一个集合中 + - group or not? a 和 b是不是朋友 + - 实现过程: + - 一开始各自节点各自为一个集合 + - 随着各集合节点的增加,不断地扩充集合树 + - find: 找该集合的领头元素,不断的往上跳跃直到parent【i】=i + - 路径压缩: 让集合中的各个节点直接跟老大连接,方便find + +- 高级搜索 + - 数形结合,状态树 + - 剪枝 + - 回溯 + - 递归+分治+试错 + - 回溯法采用试错的思想,它尝试分步的去解决一个问题,在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答时,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案 + - 双向bfs (单词接龙1,2) + - 启发式搜索(a*)heuristic search + - 智能搜索基于bfs + - 根据某一项条件,我们来不断优化搜索的方向 + - 一边搜索,一边思考 + - 通过优先级不断找要找的点,先用优先级高的拿出来搜索即可 + - 利用启发函数h(n),(估价函数)来找到我们需要找的点,h(n)会返回一个非负实数,也可以认为是从节点n到目标节点路径的估计成本 +- 红黑树和avl树 + - 复习树的遍历: + - preorder:根左右 + - inorder:左根右 + - postorder:左右根 + - binary search tree + - 二叉搜索树的中序遍历都是升序排列的 + - 增加,查找,o(logn),worst case o(n)(链表) + - 删除 + - bst保证性能的关键是 + - 保证二维维度 + - balanced + - 2,3 tree, avl tree, b-tree, red-black tree, splay tree + - avl + - 通过旋转来使得平衡因子保持在1范围内 + - 右右子树 = 左旋转 + - 左左子树 = 右旋转 + - 左右子树 = 左右旋转 + - 右左子树 = 右左旋转 + - avl总结: + - 1. 平衡二叉搜索树 + - 2. 每个结点存balance factor = 【-1,0,1】 + - 3. 四种旋转操作 + - 4. 不足:结点需要存储额外信息,且调整次数频繁,维护高 + - red black tree(近似平衡二叉树,不需要常维护) + + - 关键性质:从根到叶子的最长的可能路径不多于最短的可能路径的两倍长(高度差小于两倍) +#### Week8 +- 位运算 +- 布隆过滤器,bloom filter + - 一个很长的二进制向量和一系列随机映射函数 + - 一开始二进制位数组全为零 + - 映射函数决定了进来的元素要分成几个二进制位,每映射一次就将对应的二进制位变成1 + - 用于检索一个元素是否在一个集合中 + - 而hash table不但可以检索元素是否在集合,还可以存元素本身的各种信息 + - 优点:时间空间效率,查询时间都超过一般算法 + - 缺点:一定的误识别率和删除困难 + - 布隆过滤器能查到(A, B),说明元素可能在集合中,也可能不在;布隆过滤器查不到(C)的就一定不在 + - 布隆过滤器只是挡在机器前面快速查询的缓存,真正要确定元素一定存在就必须访问机器的存储器(数据库) +- LRU – Cache + +- 排序算法 + - 重点看nlogn排序:堆排序,快速排序,归并排序 + - 初级排序n^2: + - 选择排序:每次找最小值,然后放到待排序数组的起始位置 + - 插入排序:从前到后逐步构建有序序列;对未排序数据,在已排序序列中从后向 前扫描,找到相应位置并插入 + - 冒泡排序:每一轮两两交换都把最大的元素送到到最后(冒泡出来),与选择排序相反 + - 高级排序nlogn + - 快速排序quick sort + - 数组取标杆pivot,将小元素放在pivot左边,大元素放在pivot右边,然后再依次对右边和左边的子数组继续快排,直到整个序列有序 + - 归并排序merge sort + - 把长度为n的输入序列分成两个长度为n/2的子序列 + - 对这两个子序列分别采用归并排序 + - 将两个排序好的子序列合并成一个最终的排序序列 + - 堆排序 heap sort + - [https://courses.cs.washington.edu/courses/cse373/16wi/Hashing/visualization/HeapSort.html] + - 堆插入logn,取最大最小o(1) + - 特殊排序 + +#### Week9 + +- 高级动态规划 + - 递归 + - 分治 + - 分治典型:归并排序 + - 要点是找到重复子结构,最近重复性 + - 动态规划: + - 分治(找重复子问题) + 最优子结构,中途淘汰次优解 + - dp最后一步的状态 = 前一步子问题的最优解 + - 多维dp,每个维度代表了问题的一个状态,把问题抽象化 + - 高阶动态规划 + - 编辑距离:增加和删除的情况是相通的 +- 字符串 +