diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..a1879fcd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +[*.{js,jsx,ts,tsx,vue}] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7160133c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +note.md +jshandwrite +LCCP +BST diff --git a/README.md b/README.md index 4d40aca4..4018551a 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,3 @@ -# 极客大学「算法训练营-第24期」作业提交仓库 +# 算法训练 - - -## 仓库目录结构说明 - -1. `week01/` 代表第一周作业提交目录,以此类推。 -2. 请在对应周的目录下新建或修改自己的代码作业。 -2. 每周均有一个 `README.md` 文档,你可以将自己当周的学习心得以及做题过程中的思考记录在该文档中。 - -## 作业提交规则 - -1. 先将本仓库 Fork 到自己 GitHub 账号下。 -2. 将 Fork 后的仓库 Clone 到本地,然后在本地仓库中对应周的目录下新建或修改自己的代码作业,当周的学习总结写在对应周的README.md文件里。 -3. 在本地仓库完成作业后,push 到自己的 GitHub 远程仓库。 -4. 最后将远程仓库中当周的作业链接,按格式贴到班级仓库对应学习周的issue下面。 -5. 提交issue请务必按照规定格式进行提交,否则作业统计工具将抓取不到你的作业提交记录。 - -详细的作业提交流程可以查阅:https://shimo.im/docs/m5rtM8K8rNsjw5jk/ - - -## 注意事项 - - 如果对 Git 和 GitHub 不太了解,请参考 [Git 官方文档](https://git-scm.com/book/zh/v2) 或者极客时间的[《玩转 Git 三剑客》](https://time.geekbang.org/course/intro/145)视频课程。 +[刷题路线](./Week_10%20%E6%AF%95%E4%B8%9A%E6%80%BB%E7%BB%93/road.md) diff --git a/Week_01/541reverseStr.js b/Week_01/541reverseStr.js new file mode 100644 index 00000000..8fda28ff --- /dev/null +++ b/Week_01/541reverseStr.js @@ -0,0 +1,29 @@ +/** + * https://leetcode-cn.com/problems/reverse-string-ii/ + */ + +function swap(arr, i, j) { + if (i !== j) { + const tmp = arr[i] + arr[i] = arr[j] + arr[j] = tmp + } +} + +function reverseStr(s, k) { + const arr = s.split('') + for (let i = 0; i < s.length; i += 2 * k) { + let start = i + let end = Math.min(i + k - 1, arr.length - 1) + while (start < end) { + swap(arr, start++, end--) + } + } + return arr.join('') +} + +// test case +console.log(reverseStr('abcdefg', 2)) +console.log(reverseStr("abcdefg", 8)) + + diff --git a/Week_01/641MyCircularDeque.js b/Week_01/641MyCircularDeque.js new file mode 100644 index 00000000..d30cde42 --- /dev/null +++ b/Week_01/641MyCircularDeque.js @@ -0,0 +1,58 @@ +/** + * https://leetcode-cn.com/problems/design-circular-deque/?utm_source=LCUS&utm_medium=ip_redirect&utm_campaign=transfer2china + * 10. 设计循环双端队列(Facebook 在 1 年内面试中考过) + * medium | leetcode-641 | deque + * 思路: js的array完全是超集,约束下 length 可以很简单实现 + */ + +var MyCircularDeque = function(k) { + this.queue = new Array() + this.length = k +}; + +MyCircularDeque.prototype.insertFront = function(value) { + if (this.queue.length < this.length) { + this.queue.unshift(value) + return true + } + return false +}; +MyCircularDeque.prototype.insertLast = function(value) { + if (this.queue.length < this.length) { + this.queue.push(value) + return true + } + return false +}; +MyCircularDeque.prototype.deleteFront = function() { + if (this.queue.length > 0) { + this.queue.shift() + return true + } + return false +}; +MyCircularDeque.prototype.deleteLast = function() { + if (this.queue.length > 0) { + this.queue.pop() + return true + } + return false +}; +MyCircularDeque.prototype.getFront = function() { + if (this.queue.length === 0) { + return -1 + } + return this.queue[0] +}; +MyCircularDeque.prototype.getRear = function() { + if (this.queue.length === 0) { + return -1 + } + return this.queue[this.queue.length - 1] +}; +MyCircularDeque.prototype.isEmpty = function() { + return this.queue.length === 0 +}; +MyCircularDeque.prototype.isFull = function() { + return this.queue.length === this.length +}; diff --git a/Week_01/README.md b/Week_01/README.md index 50de3041..cb1d4145 100644 --- a/Week_01/README.md +++ b/Week_01/README.md @@ -1 +1,28 @@ -学习笔记 \ No newline at end of file +# 学习笔记 + +## 第3课:数组、链表、跳表 + +> [Java.util.ArrayList](http://developer.classpath.org/doc/java/util/ArrayList-source.html) +> [Java.util.LinkedList](http://developer.classpath.org/doc/java/util/LinkedList-source.html) + +**手撕代码** + ++ [JavaScript版实现单向链表](../linkedlist/LinkedList.js) + +**时间复杂度** + +备注:skiplist 是有序的,实现简单,维护成本较高,空间复杂度O(n)。 + +array | linkedlist | skiplist +prepend O(1) O(1) O(1) +append O(1) O(1) O(1) +lookup O(1) O(n) O(logn) +insert O(n) O(1) O(logn) +delete O(n) O(1) O(logn) + +## 第4课:栈、队列、优先队列、双端队列 + ++ stack FILO ++ queue FIFO ++ priorityQueue (heap) ++ deque diff --git a/Week_01/array/001twoSum.js b/Week_01/array/001twoSum.js new file mode 100644 index 00000000..f8063b7a --- /dev/null +++ b/Week_01/array/001twoSum.js @@ -0,0 +1,18 @@ +/** + * https://leetcode-cn.com/problems/two-sum/ + * 两数之和(亚马逊、字节跳动、谷歌、Facebook、苹果、微软在半年内面试中高频常考) + * + */ + +const twoSum = function(nums, target) { + if (Object.prototype.toString.call(nums) !== '[object Array]' || nums.length < 2) return [] + const map = new Map() + for(let i = 0; i < nums.length; ++i) { + if (!map.has(nums[i])) { + map.set(target - nums[i], i) + } else { + return [map.get(nums[i]), i] + } + } + return [] +} diff --git a/Week_01/array/011maxArea.js b/Week_01/array/011maxArea.js new file mode 100644 index 00000000..1deb5f12 --- /dev/null +++ b/Week_01/array/011maxArea.js @@ -0,0 +1,47 @@ +/** + * https://leetcode-cn.com/problems/container-with-most-water/ + * 011 盛水最多的容器 medium + * + * 思路: + * 1. 暴力枚举 O(n^2) + * 2. 双指针,矮的挪位置 + */ + +// 解法一: O(n^2) +const maxArea1 = function(a) { + if (Object.prototype.toString.apply(a) !== '[object Array]' + || a.length < 2) return 0 + const _getArea = function(a, i, j) { + return (j - i) * Math.min(a[i], a[j]) + } + let max = 0 + for (let i = 0; i < a.length - 1; ++i) { + for (let j = i + 1; j < a.length; ++j) { + const area = _getArea(a, i, j) + max = Math.max(max, area) + } + } + return max +} + +console.log(maxArea1([1,8,6,2,5,4,8,3,7])) +console.log(maxArea1([1,1])) +console.log(maxArea1([4,3,2,1,4])) +console.log(maxArea1([1,2,1])) + +// 解法二: O(n) 核心:移动短板,area可能会变大;移动长板,area只会不变或变小。因此,双指针,每次移动短板即可。 +const maxArea = function(A) { + if (!Array.isArray(A) || A.length < 2) return 0 + let max = 0, l = 0, r = A.length - 1 + while (l < r) { + const minHeight = A[l] < A[r] ? A[l++] : A[r--] + const area = minHeight * (r - l + 1) + max = Math.max(max, area) + } + return max +} + +console.log(maxArea([1,8,6,2,5,4,8,3,7])) +console.log(maxArea([1,1])) +console.log(maxArea([4,3,2,1,4])) +console.log(maxArea([1,2,1])) diff --git a/Week_01/array/015threeSum.js b/Week_01/array/015threeSum.js new file mode 100644 index 00000000..d4943476 --- /dev/null +++ b/Week_01/array/015threeSum.js @@ -0,0 +1,69 @@ +/** + * https://leetcode-cn.com/problems/3sum/ + * 015 三数之和 medium + * + * 思路: + * 1. 暴力,三重循环(O(n^3)超时) + * 2. hash,两重暴力 + hash + * 3. 夹逼,因为不需要给下标,先排序后夹逼 + */ + +// 解法2 O(n^2) +// trick: 用set 和 JSON.stringify 去重 +var threeSum2 = function(nums) { + if (!Array.isArray(nums) || nums.length < 3) return [] + nums.sort((x, y) => x - y) + const resSet = new Set(), res = [] + for (let k = 0; k < nums.length - 2 && nums[k] <= 0; ++k) { + const target = -nums[k], map = new Map() + for (let i = k + 1; i < nums.length; ++i) { + if (!map.has(nums[i])) { + map.set(target - nums[i], i) + } else { + const resItem = [k, map.get(nums[i]), i].map(x => nums[x]) + const strItem = resItem.join(',') + if (!resSet.has(strItem)) { + resSet.add(strItem) + res.push(resItem) + } + } + } + } + return res +}; + +console.log('solution 1: ', threeSum2([-1, 0, 1, 2, -1, -4])) + +/** + * 解法3 O(n^2) + * 思路: + * 1. 先排序 + * 2. 外层循环k,左右双指针夹逼。挪动去重 + */ + +function threeSum(nums) { + if (!Array.isArray(nums) || nums.length < 3) return [] + + nums.sort((x, y) => x - y) + const res = [] + for (let k = 0; k < nums.length - 2 && nums[k] <= 0; ++k) { + if (k > 0 && nums[k] === nums[k - 1]) continue // 与上一轮 k 相同,不用在试 + for (let l = k + 1, r = nums.length - 1; l < r; ) { + const sum = nums[k] + nums[l] + nums[r] + if (sum === 0) { + // console.log(k, l, r) + res.push([k, l, r].map(x => nums[x])) + while(l < r && nums[l] === nums[++l]) {} + while(l < r && nums[r] === nums[--r]) {} + } else if (sum < 0) { + while(l < r && nums[l] === nums[++l]) {} + } else { + while(l < r && nums[r] === nums[--r]) {} + } + } + } + return res +} + +console.log('solution 2: ', threeSum([-1, 0, 1, 2, -1])) +console.log('solution 2: ', threeSum([-2, 0, 0, 2, 2])) diff --git a/Week_01/array/018fourSum.js b/Week_01/array/018fourSum.js new file mode 100644 index 00000000..437a7d11 --- /dev/null +++ b/Week_01/array/018fourSum.js @@ -0,0 +1,72 @@ +/** + * https://leetcode-cn.com/problems/4sum/ + * 18. 四数之和 | medium + */ + +// O(n^3) +// const fourSum = function (nums, target) { +// if (!Array.isArray(nums) || nums.length < 4) return [] +// nums.sort((x, y) => x - y) +// const res = [] +// for (let i = 0; i < nums.length - 3; ++i) { +// if (i > 0 && nums[i] === nums[i - 1]) continue // unique +// for (let j = i + 1; j < nums.length - 2; ++j) { +// if (j > i + 1 && nums[j] === nums[j - 1]) continue //unique +// let l = j + 1, r = nums.length - 1 +// while (l < r) { +// const sum = nums[i] + nums[j] + nums[l] + nums[r] +// if (sum < target) { +// while (l < r && nums[l] === nums[++l]) { } +// } else if (sum > target) { +// while (l < r && nums[r] === nums[--r]) { } +// } else { +// res.push([nums[i], nums[j], nums[l], nums[r]]) +// while (l < r && nums[l] === nums[++l]) { } +// while (l < r && nums[r] === nums[--r]) { } +// } +// } +// } +// } +// return res +// } + +/** + * @param {number[]} nums + * @param {number} target + * @return {number[][]} + */ +var fourSum = function(nums, target) { + if (!Array.isArray(nums) || nums.length < 4) return []; + nums.sort((x, y) => x - y); + const res = []; + for (let i = 0; i < nums.length - 3 && nums[i] <= 0; ++i) { + if (i > 0 && nums[i] === nums[i - 1]) continue; + console.log('i>>>', i) + for (let j = i + 1; j < nums.length - 2; ++j) { + if (j > i + 1 && nums[j] === nums[j - 1]) continue; + console.log('j>>>', j) + for (let l = j + 1, r = nums.length - 1; l < r;) { + const sum = nums[i] + nums[j] + nums[l] + nums[r]; + console.log(i, j, l, r, sum) + if (sum === target) { + res.push([i, j, l, r].map(x => nums[x])); + while(l < r && nums[l] === nums[++l]) {}; + while(l < r && nums[r] === nums[--r]) {}; + } else if (sum < target) { + while(l < r && nums[l] === nums[++l]) {}; + } else { + while(l < r && nums[r] === nums[--r]) {}; + } + } + } + } + return res; +}; + +// test case +console.time('fourSum') +console.log(fourSum([], 0)) +console.log(fourSum([1,0,-1,0,-2,2], 0)) +console.log(fourSum([1,0,-1,0,-2,2], 2)) +console.log(fourSum([1,-2,-5,-4,-3,3,3,5], -11)) +console.timeEnd('fourSum') diff --git a/Week_01/array/020isValid.js b/Week_01/array/020isValid.js new file mode 100644 index 00000000..7c401fb0 --- /dev/null +++ b/Week_01/array/020isValid.js @@ -0,0 +1,30 @@ +/** + * https://leetcode-cn.com/problems/valid-parentheses/ + * 【stack】 + * + */ + +/** + * @param {string} s + * @return {boolean} + */ +var isValid = function(s) { + const m = new Map([ + ['(', ')'], + ['[', ']'], + ['{', '}'], + ]); + const stack = []; + let isMatch = true; + for (let i = 0; i < s.length; ++i) { + const ch = s[i]; + if (m.has(ch)) { + stack.push(m.get(ch)); + } else if (stack.length && stack[stack.length - 1] === ch) { + stack.pop(); + } else { + isMatch = false; + } + } + return isMatch && stack.length === 0; +}; diff --git a/Week_01/array/026removeDuplicates.js b/Week_01/array/026removeDuplicates.js new file mode 100644 index 00000000..cb469433 --- /dev/null +++ b/Week_01/array/026removeDuplicates.js @@ -0,0 +1,20 @@ +/** + * https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/ + * 删除排序数组中的重复项(Facebook、字节跳动、微软在半年内面试中考过) + * + * 思路:利用数组是有序的特性,重复元素必相邻。遍历一轮,用cnt记录重复项数量,用第i个元素覆盖i-cnt个元素 + * + */ + +const removeDuplicates = function(nums) { + if (Object.prototype.toString.call(nums) !== '[object Array]' || nums.length < 1) return 0 + let cnt = 0 + for(let i = 1; i < nums.length; ++i) { + if (nums[i] === nums[i - 1]) { + ++cnt + } else { + nums[i - cnt] = nums[i] + } + } + return nums.length - cnt +} diff --git a/Week_01/array/042trap.js b/Week_01/array/042trap.js new file mode 100644 index 00000000..bae9b860 --- /dev/null +++ b/Week_01/array/042trap.js @@ -0,0 +1,38 @@ +/** + * https://leetcode.com/problems/trapping-rain-water/ + * 11. 接雨水(亚马逊、字节跳动、高盛集团、Facebook 在半年内面试常考) + * hard | leetcode-042 | array + * + * 思路: maxLo、maxHi 记录遍历过的柱子中,左右最高柱子 + */ + +/** + * @param {number[]} height + * @return {number} + */ +var trap = function(A) { + let res = 0; + for (let maxR = maxL = l = 0, r = A.length - 1; l < r;) { + if (A[l] < A[r]) { + if (maxL < A[l]) { + maxL = A[l]; + } else { + res += maxL - A[l]; + } + ++l; + } else { + if (maxR < A[r]) { + maxR = A[r]; + } else { + res += maxR - A[r]; + } + --r; + } + console.log('aaa', l, r, maxL, maxR, res) + } + return res; +}; + +// ---- test case ---- +console.log(trap([0,1,0,2,1,0,1,3,2,1,2,1])) +console.log(trap([4,2,0,3,2,5])) diff --git a/Week_01/array/066plusOne.js b/Week_01/array/066plusOne.js new file mode 100644 index 00000000..d281d81c --- /dev/null +++ b/Week_01/array/066plusOne.js @@ -0,0 +1,33 @@ +/** + * https://leetcode-cn.com/problems/plus-one/ + * 9. 加一(谷歌、字节跳动、Facebook 在半年内面试中考过) + * easy | leetcode-066 | array + */ + +// 思路:从右往左遍历,遇9进位 +/** + * @param {number[]} digits + * @return {number[]} + */ +var plusOne = function(digits) { + if (!Array.isArray(digits) || !digits.length) return [1]; + + for (let i = digits.length - 1; i >= 0; --i) { + if (digits[i] < 9) { + digits[i] += 1; + break; + } else { + digits[i] = 0; + } + } + if (digits[0] === 0) { + digits.unshift(1); + } + return digits; +}; + +console.time('plusOne') +console.log(plusOne([1, 2, 3])) +console.log(plusOne([8, 9, 9])) +console.log(plusOne([9, 9, 9])) +console.timeEnd('plusOne') diff --git a/Week_01/array/070climbStairs.js b/Week_01/array/070climbStairs.js new file mode 100644 index 00000000..556e64c5 --- /dev/null +++ b/Week_01/array/070climbStairs.js @@ -0,0 +1,25 @@ +/** + * https://leetcode-cn.com/problems/climbing-stairs/ + * 70. 爬楼梯 | easy + * dp + * f(n) = f(n - 1) + f(n - 2) + */ + +// 思路:fibnacci数列 +/** + * @param {number} n + * @return {number} + */ +var climbStairs = function(n) { + if (typeof n !== 'number') return 0; + + if (n < 3) return n; + + let f1 = 1, f2 = 2, f3; + for (let i = 3; i <= n; ++i) { + f3 = f1 + f2; + f1 = f2; + f2 = f3; + } + return f3; +}; diff --git a/Week_01/array/075sortColors.js b/Week_01/array/075sortColors.js new file mode 100644 index 00000000..528df518 --- /dev/null +++ b/Week_01/array/075sortColors.js @@ -0,0 +1,23 @@ +/** + * https://leetcode-cn.com/problems/sort-colors/ + * medium + * + */ + +// 思路:双指针夹逼,维护l指针左边全是0,r指针右边全是2 +const sortColors = function(nums) { + if (Object.prototype.toString.call(nums) !== '[object Array]') return + + const swap = (nums, l, r) => { + if (l !== r) { + const tmp = nums[l] + nums[l] = nums[r] + nums[r] = tmp + } + } + + for (let i = 0, l = 0, r = nums.length - 1; i < nums.length; ++i) { + while(i < r && nums[i] === 2) swap(nums, i, r--) + while(i > l && nums[i] === 0) swap(nums, i, l++) + } +} diff --git a/Week_01/array/088merge.js b/Week_01/array/088merge.js new file mode 100644 index 00000000..4d495f8c --- /dev/null +++ b/Week_01/array/088merge.js @@ -0,0 +1,26 @@ +/** + * https://leetcode-cn.com/problems/merge-sorted-array/ + * 6. 合并两个有序数组(Facebook 在半年内面试常考) + * easy | leetcode-088 | array + * + * 思路:O(n), 尾部比较,nums1[k--] = xxx + */ + +/** + * @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) { + if (!Array.isArray(nums1) || !Array.isArray(nums1)) return; + + let i = m - 1, j = n - 1, k = m + n - 1; + while (i >= 0 && j >= 0) { + nums1[k--] = nums1[i] > nums2[j] ? nums1[i--] : nums2[j--]; + } + while(j >= 0) { + nums1[k--] = nums2[j--]; + } +}; diff --git a/Week_01/array/155MinStack.js b/Week_01/array/155MinStack.js new file mode 100644 index 00000000..83a0a9bd --- /dev/null +++ b/Week_01/array/155MinStack.js @@ -0,0 +1,69 @@ +/** + * https://leetcode-cn.com/problems/min-stack/ + * + * 思路: + * 多维护一个辅助栈 min_stack,记录每个元素对应的 min + */ +var MinStack = function() { + this._stack = [] + this._min_stack = [] +}; + +/** + * @param {number} x + * @return {void} + */ +MinStack.prototype.push = function(x) { + this._stack.push(x) + const n = this._min_stack.length + if (n > 0) { + const top = this._min_stack[n - 1] + this._min_stack.push(Math.min(top, x)) + } else { + this._min_stack.push(x) + } +}; + +/** + * @return {void} + */ +MinStack.prototype.pop = function() { + this._min_stack.pop() + return this._stack.pop() +}; + +/** + * @return {number} + */ +MinStack.prototype.top = function() { + const n = this._stack.length + return this._stack[n - 1] +}; + +/** + * @return {number} + */ +MinStack.prototype.getMin = function() { + const n = this._min_stack.length + return this._min_stack[n - 1] +}; + +/** + * Your MinStack object will be instantiated and called as such: + * var obj = new MinStack() + * obj.push(x) + * obj.pop() + * var param_3 = obj.top() + * var param_4 = obj.getMin() + */ + + +// ---- test case ---- +var ms = new MinStack() +console.log(ms.push(-2)) +console.log(ms.push(0)) +console.log(ms.push(-3)) +console.log(ms.getMin()) +console.log(ms.pop()) +console.log(ms.top()) +console.log(ms.getMin()) diff --git a/Week_01/array/189rotate.js b/Week_01/array/189rotate.js new file mode 100644 index 00000000..15449493 --- /dev/null +++ b/Week_01/array/189rotate.js @@ -0,0 +1,37 @@ +/** + * https://leetcode-cn.com/problems/rotate-array/ + * 4. 旋转数组(微软、亚马逊、PayPal 在半年内面试中考过) + * medium | leetcode-189 | array + * 思路:反转三次,时间复杂度O(n), 空间复杂度O(1) + */ + +// O(n) 时间复杂度,O(1)空间复杂度解法: 反转3次 +const rotate = function(nums, k) { + if (!Array.isArray(nums) || nums.length < 2) return; + + const reverse = (start, end) => { + while (start < end) { + const tmp = nums[start]; + nums[start++] = nums[end]; + nums[end--] = tmp; + } + } + + k = k % nums.length; + reverse(0, nums.length - 1); + reverse(0, k - 1); + reverse(k, nums.length - 1) +} + +// 方法二:splice 剪切法~ 空间复杂度 O(k) +const rotate2 = function (nums, k) { + k = k % nums.length; + nums.splice(0, 0, ...nums.splice(-k)); +} + +// ---- test ---- +var arr1 = [1,2,3,4,5,6,7], arr2 = Array.from(arr1), k = 3; +console.log('rotate:', arr1, arr2) +rotate(arr1, k); +rotate2(arr2, k); +console.log('rotate:', arr1, arr2) diff --git a/Week_01/array/283moveZeros.js b/Week_01/array/283moveZeros.js new file mode 100644 index 00000000..73842dae --- /dev/null +++ b/Week_01/array/283moveZeros.js @@ -0,0 +1,44 @@ +/* + * https://leetcode-cn.com/problems/move-zeroes/ + * 思路: 用一个标记记录非0的位置 + */ + +const moveZeroes1 = function(nums) { + if (Object.prototype.toString.call(nums) !== '[object Array]' || nums.length <= 0) return nums + + const swap = function(nums, i, j) { + if (i !== j) { + const tmp = nums[i] + nums[i] = nums[j] + nums[j] = tmp + } + } + + for(let i = 0, j = 0; i < nums.length; ++i) { + if (nums[i] !== 0) { + swap(nums, i, j++) + } + } +} + +/** + * @param {number[]} nums + * @return {void} Do not return anything, modify nums in-place instead. + * 解法二:nums[pos++] = nums[i] + */ +const moveZeroes = function(nums) { + if (!Array.isArray(nums) || nums.length < 1) return + let insertPos = 0 + for (let i = 0; i < nums.length; ++i) { + if (nums[i] !== 0) { + nums[insertPos++] = nums[i] + } + } + while (insertPos < nums.length) { + nums[insertPos++] = 0 + } +} +// ---- test case ---- +var nums = [0,1,0,3,12] +moveZeroes(nums) +console.log(nums) diff --git a/Week_01/array/hard/084largestRectangleArea.js b/Week_01/array/hard/084largestRectangleArea.js new file mode 100644 index 00000000..f954fd85 --- /dev/null +++ b/Week_01/array/hard/084largestRectangleArea.js @@ -0,0 +1,28 @@ +/** + * https://leetcode-cn.com/problems/largest-rectangle-in-histogram/ + * 84. 柱状图中最大的矩形 | hard + * + * 思路:单调栈存idx,直到遇到右边界才出栈 + */ + +// stack O(n) +var largestRectangleArea = function(heights) { + let max = 0; + const stack = [-1]; + const A = [...heights, 0]; + for (let i = 0; i < A.length; ++i) { + while (stack.length > 1 && A[stack[stack.length - 1]] > A[i]) { + const curIdx = stack.pop(); + const left = stack[stack.length - 1] + 1; + const right = i - 1; + const curArea = A[curIdx] * (right - left + 1); + max = Math.max(max, curArea); + } + stack.push(i); + } + return max; +}; + +// ---- test case ---- +console.log(largestRectangleArea([2,1,5,6,2,3])) +console.log(largestRectangleArea([2,4])) diff --git a/Week_01/demo.js b/Week_01/demo.js new file mode 100644 index 00000000..4a2ecbc2 --- /dev/null +++ b/Week_01/demo.js @@ -0,0 +1,24 @@ +let nums = [1,1,2,3,4] +let i = 0, j = nums.length - 1 +while (i < j && nums[i] === nums[++i]) { + console.log('[circle 1]:', i, j) +} + +i = 0, j = nums.length - 1 +while (i < j && nums[i] === nums[i++]) { + console.log('[circle 2]:', i, j) +} + +i = 0, j = nums.length - 1 +while (i < j && nums[++i] === nums[i]) { + console.log('[circle 3]:', i, j) +} + +i = 0, j = nums.length - 1 +while (i < j && nums[i++] === nums[i]) { + console.log('[circle 4]:', i, j) +} + +// ++i 一定要放到后面!!!,不然先运算 ++i,再判断,起不到效果! +// 3sum 总结 +// https://stackoverflow.com/questions/24853/c-what-is-the-difference-between-i-and-i diff --git a/Week_01/homework.md b/Week_01/homework.md new file mode 100644 index 00000000..1d386de7 --- /dev/null +++ b/Week_01/homework.md @@ -0,0 +1,55 @@ +# Week01作业 + +## 一、数组 +### 删除排序数组中的重复项(Facebook、字节跳动、微软在半年内面试中考过) + +[实现代码](./026removeDuplicates.js) + +### 旋转数组(微软、亚马逊、PayPal 在半年内面试中考过) + +[实现代码](./189rotate.js) + +### 合并两个有序数组(Facebook 在半年内面试常考) + +[实现代码](./088merge.js) + +### 两数之和(亚马逊、字节跳动、谷歌、Facebook、苹果、微软在半年内面试中高频常考) + +[实现代码](./001twoSum.js) + +### 移动零(Facebook、亚马逊、苹果在半年内面试中考过) + +[实现代码](./283moveZeros.js) + +### 加一(谷歌、字节跳动、Facebook 在半年内面试中考过) + +[实现代码](./066plusOne.js) + +### 接雨水(亚马逊、字节跳动、高盛集团、Facebook 在半年内面试常考) + +[实现代码](./042trap.js) + + + + +## 二、链表 + +### 合并两个有序链表(亚马逊、字节跳动在半年内面试常考) + +[实现代码](./021mergeTwoLists.js) + + + + +## 三、栈、队列、双端队列 + +### 用 add first 或 add last 这套新的 API 改写 Deque 的代码 + +### 分析 Queue 和 Priority Queue 的源码 + +> [队列](http://fuseyism.com/classpath/doc/java/util/Queue-source.html) +> [优先队列](https://docs.oracle.com/javase/10/docs/api/java/util/PriorityQueue.html) + +## 10. 设计循环双端队列(Facebook 在 1 年内面试中考过) + +[实现代码](./641MyCircularDeque.js) diff --git a/Week_01/linklist/021mergeTwoLists.js b/Week_01/linklist/021mergeTwoLists.js new file mode 100644 index 00000000..4f82b2c2 --- /dev/null +++ b/Week_01/linklist/021mergeTwoLists.js @@ -0,0 +1,26 @@ +/** + * https://leetcode-cn.com/problems/merge-two-sorted-lists/ + * easy | leetcode-021 | link-list + */ + +/** + * 21. 合并两个有序链表 + * 思路: 类似归并排序的归并过程 + * 思路: 声明一个空指针头,遍历l1 和 l2,谁的值比较小就把谁拼接在后面 + */ +var mergeTwoLists = function(l1, l2) { + const dummyHead = new ListNode(-1); + let p = dummyHead; + while (l1 && l2) { + if (l1.val <= l2.val) { + p.next = l1; + l1 = l1.next; + } else { + p.next = l2; + l2 = l2.next; + } + p = p.next; + } + p.next = l1 || l2; + return dummyHead.next; +}; diff --git a/Week_01/linklist/024swapPairs.js b/Week_01/linklist/024swapPairs.js new file mode 100644 index 00000000..46c7193a --- /dev/null +++ b/Week_01/linklist/024swapPairs.js @@ -0,0 +1,32 @@ +/** + * https://leetcode-cn.com/problems/swap-nodes-in-pairs/ + * 24. 两两交换链表中的节点 + * + */ + +// 思路1: 递归 +const swapPairs1 = function(head) { + // terminator + if (!head || !head.next) return head + // process & drill down + let hn = head.next, hnn = hn.next + head.next = swapPairs(hnn) + hn.next = head + // reverse status + return hn +} + +// 思路2: 非递归(空指针头) +var swapPairs2 = function(head) { + const dummy = new ListNode(-1, head); + let prev = dummy; + + while (prev.next && prev.next.next) { + const curr = prev.next, then = curr.next; + curr.next = then.next; + then.next = curr; + prev.next = then; + prev = curr; + } + return dummy.next; +}; diff --git a/Week_01/linklist/025reverseKGroup.js b/Week_01/linklist/025reverseKGroup.js new file mode 100644 index 00000000..0ae9be73 --- /dev/null +++ b/Week_01/linklist/025reverseKGroup.js @@ -0,0 +1,38 @@ +/** + * https://leetcode.cn/problems/reverse-nodes-in-k-group + * + * k 个一组翻转链表 + */ + +var reverseKGroup = function(head, k) { + const dummy = new ListNode(-1, head); + let pre = end = dummy; + + while (end.next) { + for (let i = 0; i < k && end; ++i) { + end = end.next; + } + if (!end) break; + const start = pre.next; + const next = end.next; + + end.next = null; + pre.next = reverse(start); + start.next = next; + + pre = start; + end = pre; + } + return dummy.next; +}; + +function reverse(head) { + let prev = null; + while (head) { + const curr = head; + head = head.next; + curr.next = prev; + prev = curr; + } + return prev; +} diff --git a/Week_01/linklist/092reverseBetween.js b/Week_01/linklist/092reverseBetween.js new file mode 100644 index 00000000..2cfb13dd --- /dev/null +++ b/Week_01/linklist/092reverseBetween.js @@ -0,0 +1,48 @@ +/** +反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。 + +说明: +1 ≤ m ≤ n ≤ 链表长度。 + +示例: + +输入: 1->2->3->4->5->NULL, m = 2, n = 4 +输出: 1->4->3->2->5->NULL + +来源:力扣(LeetCode) +链接:https://leetcode-cn.com/problems/reverse-linked-list-ii +著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + +思路: 4点: dummy,pre,start,then( 每次只挪动then ) + */ + +const reverseBetween = function(head, m, n) { + if (head == null || m === n) return head + const dummy = new ListNode(-1, head) + let prev = dummy + for (let i = 0; i < m - 1; ++i) prev = prev.next + let start = prev.next, then = start.next + /* 1 -> 2 -> 3 ->4->5->NULL, m = 2, n = 4 + prev -> start -> then + + _________________ + | ! + 1 2 <- 3 4 -> 5 + |_________________^ + prev start then + + + __________________________ + | ! + 1 2 <- 3 <- 4 5 + |__________________________^ + prev start then + */ + for(let i = 0; i < n - m; ++i) { + start.next = then.next + then.next = prev.next + prev.next = then + then = start.next // then 右移 + } + return dummy.next +} diff --git a/Week_01/linklist/141hasCycle.js b/Week_01/linklist/141hasCycle.js new file mode 100644 index 00000000..1f03569a --- /dev/null +++ b/Week_01/linklist/141hasCycle.js @@ -0,0 +1,15 @@ +/** + * https://leetcode-cn.com/problems/linked-list-cycle/ + */ + +const hasCycle = function(head) { + let fast = slow = head + while(fast && fast.next && slow) { + fast = fast.next.next + slow = slow.next + if (fast === slow) { + return true + } + } + return false +} diff --git a/Week_01/linklist/142detectCycle.js b/Week_01/linklist/142detectCycle.js new file mode 100644 index 00000000..66565dca --- /dev/null +++ b/Week_01/linklist/142detectCycle.js @@ -0,0 +1,24 @@ +/** + * https://leetcode-cn.com/problems/linked-list-cycle-ii/ + */ + +const detectCycle = function(head) { + let fast = slow = head, hasCycle = false + while (fast && fast.next && slow) { + fast = fast.next.next + slow = slow.next + if (fast === slow) { + hasCycle = true + break + } + } + if (hasCycle) { + fast = head + while (fast && slow && fast !== slow) { + fast = fast.next + slow = slow.next + } + return fast + } + return null +} diff --git a/Week_01/linklist/206reverseList.js b/Week_01/linklist/206reverseList.js new file mode 100644 index 00000000..0100eddb --- /dev/null +++ b/Week_01/linklist/206reverseList.js @@ -0,0 +1,19 @@ +/** + * https://leetcode-cn.com/problems/reverse-linked-list/ + * https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/submissions/ + * 思路:维护好 prev,cur + * + * @param {ListNode} head + * @return {ListNode} + */ + +const reverseList = function(head) { + let prev = null; + while (head) { + const curr = head; + head = head.next; + curr.next = prev; + prev = curr; + } + return prev; +} diff --git a/Week_01/m17.09getKthMagicNumber.js b/Week_01/m17.09getKthMagicNumber.js new file mode 100644 index 00000000..e1015fe6 --- /dev/null +++ b/Week_01/m17.09getKthMagicNumber.js @@ -0,0 +1,29 @@ +/** + * https://leetcode-cn.com/problems/get-kth-magic-number-lcci/ + * + * 思路:三指针法 + * + * 1, 3, 5, 7, 9, 15, 21, 35, 49, + * + * 推导公式:f(n) = min(3f(p3), 5f(p5), 7f(p7)) + * + */ +const getKthMagicNumber = function(k) { + if (k < 2) return k + let p3 = p5 = p7 = 0, nums = [1] + for(let i = 1; i < k; ++i) { + const x = 3 * nums[p3], y = 5 * nums[p5], z = 7 * nums[p7] + nums[i] = Math.min(x, y, z) + if (nums[i] === x) ++p3 + if (nums[i] === y) ++p5 + if (nums[i] === z) ++p7 + } + + console.log("🚀🚀🚀🚀", p3, p5, p7) + return nums[k - 1] +} + +// ---- test case ---- +new Array(20).fill(0).forEach((item, idx) => { + console.log(`${idx + 1}: ` + getKthMagicNumber(idx + 1)) +}) diff --git a/Week_02/049groupAnagram.js b/Week_02/049groupAnagram.js new file mode 100644 index 00000000..dd0456ef --- /dev/null +++ b/Week_02/049groupAnagram.js @@ -0,0 +1,34 @@ +/** + * https://leetcode-cn.com/problems/group-anagrams/ + * 字母异位词分组 + * + * 【hashmap】 + * + */ + +/** + * @param {string[]} strs + * @return {string[][]} + */ +var groupAnagrams = function(strs) { + if (!Array.isArray(strs) || strs.length < 1) return [] + + const m = new Map(), startCp = 'a'.codePointAt(0); + strs.forEach(str => { + const cntArr = Array(26).fill(0); + for (let i = 0; i < str.length; ++i) { + cntArr[str.codePointAt(i) - startCp] += 1; + } + const key = cntArr.toString(); + if (!m.has(key)) { + m.set(key, [str]) + } else { + m.set(key, [...m.get(key), str]) + } + }) + return Array.from(m.values()); +}; + + +// ---- test case ---- +console.log(groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"])) diff --git a/Week_02/094inorderTraversal.js b/Week_02/094inorderTraversal.js new file mode 100644 index 00000000..a5f217b8 --- /dev/null +++ b/Week_02/094inorderTraversal.js @@ -0,0 +1,32 @@ +/** + * https://leetcode-cn.com/problems/binary-tree-inorder-traversal/ + */ + +// 1. 递归DFS +var inorderTraversal = function(root) { + if (!root) return []; + const res = []; + const dfs = (p) => { + if (p.left) dfs(p.left); + res.push(p.val); + if (p.right) dfs(p.right); + } + dfs(root); + return res; +}; + +// 2. 非递归,手动维护 stack +var inorderTraversal = function(root) { + if (root == null) return []; + const res = [], stack = []; + while (root || stack.length) { + while (root) { // 往左找到底 + stack.push(root); + root = root.left; + } + root = stack.pop(); // 根 + res.push(root.val); + root = root.right; // 右 + } + return res; +}; diff --git a/Week_02/1021.js b/Week_02/1021.js new file mode 100644 index 00000000..a6bd0f70 --- /dev/null +++ b/Week_02/1021.js @@ -0,0 +1,15 @@ +/** + * @param {string} S + * @return {string} + * https://leetcode-cn.com/problems/remove-outermost-parentheses/ + * 思路: 一轮遍历,把非最外层的括号放入 res + */ +var removeOuterParentheses = function(S) { + var res = [], cnt = 0 + for(var i = 0; i < S.length; ++i) { + S[i] === ')' && --cnt + cnt > 0 && res.push(S[i]) + S[i] === '(' && ++cnt + } + return res.join('') +}; diff --git a/Week_02/104maxDepth.js b/Week_02/104maxDepth.js new file mode 100644 index 00000000..cadfd71a --- /dev/null +++ b/Week_02/104maxDepth.js @@ -0,0 +1,25 @@ +/** + * https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/submissions/ + * 思路:递归遍历左右子树 + */ +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ + +/** + * @param {TreeNode} root + * @return {number} + * // 1 递归 + */ +var maxDepth = function(root) { + if (!root) return 0; + const left = maxDepth(root.left); + const right = maxDepth(root.right); + return Math.max(left, right) + 1; +}; + diff --git a/Week_02/1201nthUglyNumber.js b/Week_02/1201nthUglyNumber.js new file mode 100644 index 00000000..7edbda07 --- /dev/null +++ b/Week_02/1201nthUglyNumber.js @@ -0,0 +1,83 @@ +/** + * https://leetcode-cn.com/problems/ugly-number-iii/ + * + * 解法: + * 1. 二分 + * 2. 容斥原理,计算个数 + */ + +// 最大公约数(greatest common divisor) +const gcd = (x, y) => x % y ? gcd(y, x % y) : y +// 最小公倍数(least common multiple) +const lcm = (x, y) => x * y / gcd(x, y) + +// 解法一 +var nthUglyNumber = function(n, a, b, c) { + const ab = lcm(a, b), + ac = lcm(a, c), + bc = lcm(b, c), + abc = lcm(ab, c); + let left = 0, right = n * Math.min(a, b, c); + while (left <= right) { + const mid = left + ((right - left) >> 1); + const num = Math.floor(mid / a) + + Math.floor(mid / b) + + Math.floor(mid / c) + - Math.floor(mid / ab) + - Math.floor(mid / ac) + - Math.floor(mid / bc) + + Math.floor(mid / abc); + if (num >= n) { + right = mid - 1; + } else { + left = mid + 1; + } + } + return left; +}; + +// 解法二:【优化】位运算提速(但需要用bigInt类型,防止越界) +const nthUglyNumber = function(n, a, b, c) { + let left = 0n, + right = BigInt(n) * BigInt(Math.min(a, b, c)) + a = BigInt(a) + b = BigInt(b) + c = BigInt(c) + const ab = lcm(a, b), + ac = lcm(a, c), + bc = lcm(b, c), + abc = lcm(ab, c) + while(left <= right) { + const mid = (left + right) >> 1n + const cnt = ((mid / a ) >> 0n) + + ((mid / b ) >> 0n) + + ((mid / c ) >> 0n) + - ((mid / ab ) >> 0n) + - ((mid / ac ) >> 0n) + - ((mid / bc ) >> 0n) + + ((mid / abc) >> 0n) + + if (cnt >= n) { + right = mid - 1n + } else { + left = mid + 1n + } + } + return left +} + +// ---- test case ---- +console.log(nthUglyNumber1(3, 2, 3, 5)) +console.log(nthUglyNumber1(4, 2, 3, 4)) +console.log(nthUglyNumber1(5, 2, 11, 13)) +console.log(nthUglyNumber1(5, 2, 3, 3)) +console.log(nthUglyNumber1(1000000000, 2, 217983653, 336916467)) + +console.log(nthUglyNumber(3, 2, 3, 5)) +console.log(nthUglyNumber(4, 2, 3, 4)) +console.log(nthUglyNumber(5, 2, 11, 13)) +console.log(nthUglyNumber(5, 2, 3, 3)) +console.log(nthUglyNumber(1000000000, 2, 217983653, 336916467)) + + + diff --git a/Week_02/144preorderTraversal.js b/Week_02/144preorderTraversal.js new file mode 100644 index 00000000..1104ec94 --- /dev/null +++ b/Week_02/144preorderTraversal.js @@ -0,0 +1,30 @@ +/** + * https://leetcode-cn.com/problems/binary-tree-preorder-traversal/ + * 【dfs】 + */ + +// 1. 递归解法 dfs +var preorderTraversal = function(root) { + if (!root) return []; + const res = []; + const dfs = (root) => { + res.push(root.val); + if (root.left) dfs(root.left); + if (root.right) dfs(root.right); + } + dfs(root); + return res; +}; + +// 2. 非递归解法 stack dfs +var preorderTraversal = function(root) { + if (!root) return []; + const res = [], stack = [root]; + while (stack.length) { + const p = stack.pop(); + res.push(p.val) + if (p.right) stack.push(p.right); + if (p.left) stack.push(p.left); + } + return res; +}; diff --git a/Week_02/239maxSlidingWindow.js b/Week_02/239maxSlidingWindow.js new file mode 100644 index 00000000..5465be72 --- /dev/null +++ b/Week_02/239maxSlidingWindow.js @@ -0,0 +1,35 @@ +/** + * https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/ + * https://leetcode-cn.com/problems/sliding-window-maximum/ + * 239. 滑动窗口最大值 + * + * 用 window 记录窗口下标值 + * + */ + +// 思路,win 必须存下标,每次遍历,从右往左清理掉又老又小的数 +var maxSlidingWindow = function(nums, k) { + const res = [], win = []; + for (let i = 0; i < nums.length; ++i) { + if (win.length > 0 && i - win[0] >= k) { // 删除队首过期元素 + win.shift(); + } + while (win.length > 0 && nums[i] >= nums[win[win.length - 1]]) { // 删除队尾所有又旧又小的元素 + win.pop(); + } + + win.push(i); // 入队 + if (i >= k - 1) { + res.push(nums[win[0]]); // 队首元素 + } + } + return res; +}; + +// ---- test case ---- +// console.log(maxSlidingWindow([1,3,-1,-3,5,3,6,7], 3)) +// console.log(maxSlidingWindow([1], 1)) +// console.log(maxSlidingWindow([1,-1], 1)) +// console.log(maxSlidingWindow([9,11], 2)) +// console.log(maxSlidingWindow([4,-2], 2)) +console.log(maxSlidingWindow([1,3,1,2,0,5], 3)) diff --git a/Week_02/242isAnagram.js b/Week_02/242isAnagram.js new file mode 100644 index 00000000..23bf8403 --- /dev/null +++ b/Week_02/242isAnagram.js @@ -0,0 +1,23 @@ +/** + * https://leetcode-cn.com/problems/valid-anagram/ + * 思路:map统计 + * + */ + +const isAnagram = function(s, t) { + if ( typeof s != 'string' + || typeof t != 'string' + || s.length !== t.length + ) return false + const arr = new Array(26).fill(0), + startCodePoint = 'a'.codePointAt(0) + for(let i = 0; i < s.length; ++i) { + const idx = s[i].codePointAt(0) - startCodePoint + ++arr[idx] + } + for(let i = 0; i < t.length; ++i) { + const idx = t[i].codePointAt(0) - startCodePoint + --arr[idx] + } + return arr.filter(item => item != 0).length === 0 +} diff --git a/Week_02/258.js b/Week_02/258.js new file mode 100644 index 00000000..15556749 --- /dev/null +++ b/Week_02/258.js @@ -0,0 +1,32 @@ +/** + * @param {number} num + * @return {number} + * https://leetcode-cn.com/problems/add-digits/ + * 思路1: 强制转换 + */ +var addDigits = function(num) { + while(num > 9) { + var sum = 0 + num = String(num).split('').forEach(item => sum += Number(item)) + num = sum + } + return num +}; + + +/** + * @param {number} num + * @return {number} + * 思路2: while + */ +var addDigits = function(num) { + while(num > 9) { + var sum = 0 + while(num > 9) { + sum += num % 10 + num /= 10 + } + num = sum + } + return num +}; diff --git a/Week_02/263isUgly.js b/Week_02/263isUgly.js new file mode 100644 index 00000000..ea07555c --- /dev/null +++ b/Week_02/263isUgly.js @@ -0,0 +1,16 @@ +/** + * https://leetcode-cn.com/problems/ugly-number/ + * 思路: 判断不断整除2、3、5后是否等于1 + */ + +const isUgly = function(num) { + for(let factor of [2, 3, 5]) { + while(num && num % factor === 0) num /= factor + } + return num === 1 +} + +// ---- test case ---- +void [6, 8, 14].forEach(num => { + console.log(isUgly(num)) +}) diff --git a/Week_02/264nthUglyNumber.js b/Week_02/264nthUglyNumber.js new file mode 100644 index 00000000..13f31f01 --- /dev/null +++ b/Week_02/264nthUglyNumber.js @@ -0,0 +1,28 @@ +/** + * https://leetcode-cn.com/problems/ugly-number-ii/ + * + * 相同题目: + * 【剑指 Offer 49. 丑数】 https://leetcode-cn.com/problems/chou-shu-lcof/ + * 【面试题 17.09. 第 k 个数】 https://leetcode-cn.com/problems/get-kth-magic-number-lcci/ + */ + +// 解法: 三指针法递推 +var nthUglyNumber = function(n) { + if (n < 7) return n; + let p2 = p3 = p5 = 0, nums = [1]; + for (let i = 1; i < n; ++i) { + const x = 2 * nums[p2]; + const y = 3 * nums[p3]; + const z = 5 * nums[p5]; + nums[i] = Math.min(x, y, z); + if (nums[i] === x) ++p2; + if (nums[i] === y) ++p3; + if (nums[i] === z) ++p5; + } + return nums[n - 1]; +}; + +// ---- test case ---- +for(let i = 1; i <= 100; ++i) { + console.log(`${i}: ${nthUglyNumber(i)}`) +} diff --git a/Week_02/283.js b/Week_02/283.js new file mode 100644 index 00000000..94abc592 --- /dev/null +++ b/Week_02/283.js @@ -0,0 +1,16 @@ +/** + * @param {number[]} nums + * @return {void} Do not return anything, modify nums in-place instead. + * 思路:保证insertPos指针之前的元素是全非零 + */ +var moveZeroes = function (nums) { + var insertPos = 0 + for (var i = 0; i < nums.length; ++i) { + if (nums[i] !== 0) { + nums[insertPos++] = nums[i] + } + } + while (insertPos < nums.length) { + nums[insertPos++] = 0 + } +}; diff --git a/Week_02/313nthSuperUglyNumber.js b/Week_02/313nthSuperUglyNumber.js new file mode 100644 index 00000000..03825bd0 --- /dev/null +++ b/Week_02/313nthSuperUglyNumber.js @@ -0,0 +1,29 @@ +/** + * https://leetcode-cn.com/problems/super-ugly-number/ + * + */ + +// 思路:此题采用丑数的定义一,用递推法求解 +const nthSuperUglyNumber = function(n, primes) { + const size = primes.length, + idxs = new Array(size).fill(0), + arr = [1] + + for(let i = 1; i < n; ++i) { + const nexts = [] + for(let j = 0; j < size; ++j) { + nexts[j] = primes[j] * arr[idxs[j]] + } + arr[i] = Math.min(...nexts) + nexts.forEach((next, idx) => { + if (arr[i] === next) { + ++idxs[idx] + } + }) + // console.log("🚀", i, primes, idxs, nexts) + } + return arr[n - 1] +} + +// ---- test case ---- +console.log(nthSuperUglyNumber(12, [2,7,13,19])) diff --git a/Week_02/347topKFrequent.js b/Week_02/347topKFrequent.js new file mode 100644 index 00000000..c8f45500 --- /dev/null +++ b/Week_02/347topKFrequent.js @@ -0,0 +1,122 @@ +/** + * https://leetcode-cn.com/problems/top-k-frequent-elements/ + * + */ + +// 解法一:用map统计,再排序,取前k项 O(nlogn) +const topKFrequent1 = function(nums, k) { + const m = {} + for(let i = 0; i < nums.length; ++i) { + if (m[nums[i]] !== void(0)) { + ++m[nums[i]] + } else { + m[nums[i]] = 1 + } + } + const sortedM = Object.entries(m).sort(([_k1, v1], [_k2, v2]) => v2 - v1) // 按 value 逆序 + return sortedM.map(m => Number(m[0])).slice(0, k) +} + + +const top = 0 +const parent = i => ((i+1) >>> 1) - 1 +const left = i => (i << 1) + 1 +const right = i => (i + 1) << 1 + +class PriorityQueue { + constructor(comparator = (a, b) => a > b) { + this._heap = []; + this._comparator = comparator; + } + size() { + return this._heap.length; + } + isEmpty() { + return this.size() == 0; + } + peek() { + return this._heap[top]; + } + push(...values) { + values.forEach(value => { + this._heap.push(value); + this._siftUp(); + }); + return this.size(); + } + pop() { + const poppedValue = this.peek(); + const bottom = this.size() - 1; + if (bottom > top) { + this._swap(top, bottom); + } + this._heap.pop(); + this._siftDown(); + return poppedValue; + } + replace(value) { + const replacedValue = this.peek(); + this._heap[top] = value; + this._siftDown(); + return replacedValue; + } + _greater(i, j) { + return this._comparator(this._heap[i], this._heap[j]); + } + _swap(i, j) { + [this._heap[i], this._heap[j]] = [this._heap[j], this._heap[i]]; + } + _siftUp() { + let node = this.size() - 1; + while (node > top && this._greater(node, parent(node))) { + this._swap(node, parent(node)); + node = parent(node); + } + } + _siftDown() { + let node = top; + while ( + (left(node) < this.size() && this._greater(left(node), node)) || + (right(node) < this.size() && this._greater(right(node), node)) + ) { + let maxChild = (right(node) < this.size() && this._greater(right(node), left(node))) ? right(node) : left(node); + this._swap(node, maxChild); + node = maxChild; + } + } +} + +// 解法二:利用heap O(nlogk) +const topKFrequent = function(nums, k) { + // 1. 统计 + const m = {}, res = [] + for(let i = 0; i < nums.length; ++i) { + if (m[nums[i]] !== void(0)) { + ++m[nums[i]] + } else { + m[nums[i]] = 1 + } + } + // 2. 建堆,比较函数 v1 - v2 > 0(按次数降序) + const pq = new PriorityQueue( + ([_k1, v1], [_k2, v2]) => {return v1 - v2 > 0} + ) + // 3. 入堆 + Object.entries(m).forEach(entry => { + pq.push(entry) + }) + // 4. 取堆顶 k 次 + for(let i = 0; i < k; ++i) { + res.push(pq.pop()) + } + // 5. 取 key 构造结果 + return res.map(m => Number(m[0])) +} + +// TODO 解法三:快排思想 + +// ---- test case ---- +console.log(topKFrequent1([2,2,1,1,1,2,2,3], 2)) +console.log(topKFrequent([2,2,1,1,1,2,2,3], 2)) + + diff --git a/Week_02/350.js b/Week_02/350.js new file mode 100644 index 00000000..f37d5d9f --- /dev/null +++ b/Week_02/350.js @@ -0,0 +1,27 @@ +/** + * @param {number[]} nums1 + * @param {number[]} nums2 + * @return {number[]} + * https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/submissions/ + * easy | 350. 两个数组的交集 II | hashmap + */ +var intersect = function(nums1, nums2) { + if(nums1.length > nums2.length) { + return intersect(nums2, nums1) + } + let cntMap = {}, res = [] + for(var i = 0; i < nums1.length; ++i) { + if (cntMap[nums1[i]] === void(0)) { + cntMap[nums1[i]] = 1 + } else { + ++cntMap[nums1[i]] + } + } + for(var i = 0; i < nums2.length; ++i) { + if (cntMap[nums2[i]] > 0) { + --cntMap[nums2[i]] + res.push(nums2[i]) + } + } + return res +}; diff --git a/Week_02/412.js b/Week_02/412.js new file mode 100644 index 00000000..fdf3ad77 --- /dev/null +++ b/Week_02/412.js @@ -0,0 +1,39 @@ +/** + * @param {number} n + * @return {string[]} + * https://leetcode-cn.com/problems/fizz-buzz/submissions/ + * 思路1: 暴力 + */ +var fizzBuzz = function(n) { + var res = [] + for(var i = 1; i <= n; ++i) { + if (i % 3 === 0 && i % 5 === 0) { + res.push('FizzBuzz') + } else if (i % 3 === 0) { + res.push('Fizz') + } else if (i % 5 === 0) { + res.push('Buzz') + } else { + res.push(String(i)) + } + } + return res +}; + + +/** + * @param {number} n + * @return {string[]} + * 思路2: 对照表 + */ +var fizzBuzz = function(n) { + var arr = ['FizzBuzz', '', '', 'Fizz', '', 'Buzz', 'Fizz', '', '', 'Fizz', 'Buzz', '', 'Fizz', '', ''], res = [] + for(var i = 1; i <= n; ++i) { + if (!arr[i % 15]) { + res.push(String(i)) + } else { + res.push(arr[i % 15]) + } + } + return res +}; diff --git a/Week_02/429levelOrder.js b/Week_02/429levelOrder.js new file mode 100644 index 00000000..363b020d --- /dev/null +++ b/Week_02/429levelOrder.js @@ -0,0 +1,22 @@ +/** + * https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/ + * 429. N 叉树的层序遍历 + * BFS + */ + +var levelOrder = function(root) { + if (!root) return []; + const res = [], queue = [root]; + while (queue.length) { + const line = [], n = queue.length; + for (let i = 0; i < n; ++i) { + const node = queue.pop(); + if (node.children.length) { + node.children.forEach(child => queue.unshift(child)); + } + line.push(node.val); + } + res.push(line); + } + return res; +}; diff --git a/Week_02/589preorder.js b/Week_02/589preorder.js new file mode 100644 index 00000000..4278b807 --- /dev/null +++ b/Week_02/589preorder.js @@ -0,0 +1,32 @@ +/** + * https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal/ + * + */ + +// 1. 递归版 dfs +const preorder1 = function(root) { + if (root == null) return [] + const res = [] + const dfs = (p) => { + res.push(p.val) + p.children && p.children.forEach(child => { + if (child) dfs(child) + }) + } + dfs(root) + return res +} + +// !! 2. 非递归版 +const preorder = function(root) { + if (root == null) return [] + const res = [], stack = [root] + while (stack.length > 0) { + const p = stack.pop() + if (p.children.length > 0) + for(let i = p.children.length - 1; i >= 0; --i) + stack.push(p.children[i]) + res.push(p.val) + } + return res +} diff --git a/Week_02/590postorder.js b/Week_02/590postorder.js new file mode 100644 index 00000000..2b01ff6b --- /dev/null +++ b/Week_02/590postorder.js @@ -0,0 +1,32 @@ +/** + * https://leetcode-cn.com/problems/n-ary-tree-postorder-traversal/ + * + */ + +// 1. 递归版 dfs +var preorderTraversal = function(root) { + if (!root) return []; + const res = [], stack = [root]; + while (stack.length) { + const p = stack.pop(); + res.push(p.val) + if (p.right) stack.push(p.right); + if (p.left) stack.push(p.left); + } + return res; +}; + +// !! 2. 非递归版 【反转】 +// trick 孩子从左往右压栈,出来则是从右往左遍历。最后再翻转 +var postorder = function(root) { + if (root == null) return [] + const res = [], stack = [root] + while (stack.length) { + root = stack.pop() + if (root) res.push(root.val) + root.children.forEach(child => { + stack.push(child) + }) + } + return res.reverse() +} diff --git a/Week_02/README.md b/Week_02/README.md index 50de3041..e33257ed 100644 --- a/Week_02/README.md +++ b/Week_02/README.md @@ -1 +1,67 @@ -学习笔记 \ No newline at end of file +# 学习笔记 + +## 第5课:哈希表、映射、集合 + +### 哈希表总结 + +1. 写一个关于HashMap的小总结 + +> 资料:https://time.geekbang.org/column/article/64233 + ++ 衡量散列表负载:装载因子 = 已载入元素 / 散列表容量 ++ 动态扩容/缩容:(根据 load factor,重新计算,迁移) ++ 优化:一次性扩容 -> 均摊扩容 + +**解决散列冲突:** +1. 开放寻址法(线性探测) + - eg: Java 的 ThreadLocalMap + - 线性探测 + - 二次探测 + - 双重散列 + - (优点:1、因为数据都存储在数组中,可以有效利用CPU cache加快查询速度;2、序列化更简单) + - (缺点:1、删除需要标记;2、对load factor更敏感、内存利用率更低;) + - (适合:数据量较小、load factor小) +2. 拉链法 + - eg: Java 的 LinkedHashMap + - (优点:1、对load factor容忍度更高、内存利用率更高;2、更灵活地支持优化策略,例如红黑树代替链表) + - (适合:大对象、大数据量) + +> 一种攻击手段:散列碰撞攻击 + +### [JS中,Set、Map、WeakSet 和 WeakMap 的区别](https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/6) + +**Set** + +1. 成员不能重复 +2. 只有健值,没有健名,有点类似数组 +3. 可以遍历,方法有add, delete, has + +**weakSet** + +1. 成员都是对象 +2. 成员都是弱引用,随时可以消失。 可以用来保存DOM节点,不容易造成内存泄漏 +3. 不能遍历,方法有add, delete, has + +**Map** + +1. 本质上是健值对的集合,类似集合 +2. 可以遍历,方法很多,可以干跟各种数据格式转换 + +**weakMap** + +1. 直接受对象作为健名(null除外),不接受其他类型的值作为健名 +2. 健名所指向的对象,不计入垃圾回收机制 +3. 不能遍历,方法同 get, set, delete, has + ++ [JavaScript 实现 PriorityQueue](./priorityQueue.js) + +## 第6课:树、二叉树、二叉搜索树 + +## 第6课:堆和二叉堆、图 + +**[heap](https://www.geeksforgeeks.org/heap-sort/)** + ++ 时间复杂度 O(nlogn), 稳定的原地排序 ++ https://time.geekbang.org/column/article/69913 ++ Step1: 建堆 ++ Step2: 排序 diff --git a/Week_02/homework.md b/Week_02/homework.md new file mode 100644 index 00000000..35dfd76a --- /dev/null +++ b/Week_02/homework.md @@ -0,0 +1,72 @@ +# 作业 + +## 第5课:哈希表、映射、集合 + +### 1. 有效的字母异位词(亚马逊、Facebook、谷歌在半年内面试中考过) + ++ map ++ [实现代码](./242isAnagram.js) + +### 2. 字母异位词分组(亚马逊在半年内面试中常考) + ++ map ++ [实现代码](./049groupAnagram.js) + +### 3. 两数之和(近半年内,亚马逊考查此题达到 216 次、字节跳动 147 次、谷歌 104 次,Facebook、苹果、微软、腾讯也在近半年内面试常考) + ++ map ++ [实现代码](../Week_01/001twoSum.js) + + + + + +## 第6课:树、二叉树、二叉搜索树 + +### 4. 二叉树的前序遍历(字节跳动、谷歌、腾讯在半年内面试中考过) + ++ tree ++ [实现代码](./144preorderTraversal.js) + +### 5. 二叉树的中序遍历(亚马逊、字节跳动、微软在半年内面试中考过) + ++ tree ++ [实现代码](./094inorderTraversal.js) + +### 6. N 叉树的前序遍历(亚马逊在半年内面试中考过) + ++ tree ++ [实现代码](./589preorder.js) + +### 7. N 叉树的后序遍历 + ++ tree ++ [实现代码](./590postorder.js) + +### 8. N 叉树的层序遍历(亚马逊在半年内面试中考过) + ++ tree ++ [实现代码](./429levelOrder.js) + + + + + +## 第6课:堆和二叉堆、图 + +### 9. 丑数(字节跳动在半年内面试中考过) + ++ [丑数](./263isUgly.js) ++ [丑数II](./264nthUglyNumber.js) ++ [丑数III](./1201nthUglyNumber.js) ++ [超级丑数](./313nthSuperUglyNumber.js) + +**丑数有两种定义** + +1. 定义一:质因数只包含 a,b,c,... -> 多指针递推法 +2. 定义二:能被 a,b,c,... 整除的 -> 容斥原理 + 二分法 + +### 10. 前 K 个高频元素(亚马逊在半年内面试中常考) + ++ heap ++ [实现代码](./347topKFrequent.js) diff --git a/Week_02/priorityQueue.js b/Week_02/priorityQueue.js new file mode 100644 index 00000000..21c1ec49 --- /dev/null +++ b/Week_02/priorityQueue.js @@ -0,0 +1,92 @@ +const top = 0 +const parent = i => ((i+1) >>> 1) - 1 +const left = i => (i << 1) + 1 +const right = i => (i + 1) << 1 + +class PriorityQueue { + constructor(comparator = (a, b) => a > b) { + this._heap = []; + this._comparator = comparator; + } + size() { + return this._heap.length; + } + isEmpty() { + return this.size() == 0; + } + peek() { + return this._heap[top]; + } + push(...values) { + values.forEach(value => { + this._heap.push(value); + this._siftUp(); + }); + return this.size(); + } + pop() { + const poppedValue = this.peek(); + const bottom = this.size() - 1; + if (bottom > top) { + this._swap(top, bottom); + } + this._heap.pop(); + this._siftDown(); + return poppedValue; + } + replace(value) { + const replacedValue = this.peek(); + this._heap[top] = value; + this._siftDown(); + return replacedValue; + } + _greater(i, j) { + return this._comparator(this._heap[i], this._heap[j]); + } + _swap(i, j) { + [this._heap[i], this._heap[j]] = [this._heap[j], this._heap[i]]; + } + _siftUp() { + let node = this.size() - 1; + while (node > top && this._greater(node, parent(node))) { + this._swap(node, parent(node)); + node = parent(node); + } + } + _siftDown() { + let node = top; + while ( + (left(node) < this.size() && this._greater(left(node), node)) || + (right(node) < this.size() && this._greater(right(node), node)) + ) { + let maxChild = (right(node) < this.size() && this._greater(right(node), left(node))) ? right(node) : left(node); + this._swap(node, maxChild); + node = maxChild; + } + } +} + +// test case +var s = new PriorityQueue() +console.log(s.size()) +s.push(56) +s.push(156) +s.push(536) +s.push(456) +s.push(564) +s.push(561) +console.log(s.size(), s.peek()) +console.log('pop:', s.pop()) +console.log(s.size(), s.peek()) +console.log('pop:', s.pop()) +console.log(s.size(), s.peek()) +console.log('pop:', s.pop()) +console.log(s.size(), s.peek()) +console.log('pop:', s.pop()) +console.log(s.size(), s.peek()) +console.log('pop:', s.pop()) +console.log(s.size(), s.peek()) +console.log('pop:', s.pop()) +console.log(s.size(), s.peek()) +console.log('pop:', s.pop()) + diff --git a/Week_03/017letterCombinations.js b/Week_03/017letterCombinations.js new file mode 100644 index 00000000..2ad47fd5 --- /dev/null +++ b/Week_03/017letterCombinations.js @@ -0,0 +1,56 @@ +/** + * https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/ + * 思路:分治 + * 1. 画递归树,构造辅助函数 + * + * @param {string} digits + * @return {string[]} + */ +var letterCombinations = function(digits) { + if (!digits.length) return []; + const map = new Map([ + ['2', 'abc'], ['3', 'def'], ['4', 'ghi'], + ['5', 'jkl'], ['6', 'mno'], ['7', 'pqrs'], + ['8', 'tuv'], ['9', 'wxyz'] + ]); + + const res = []; + const helper = (level, path) => { + // terminator + if (level === digits.length) { + res.push(path.join('')); + return; + } + // process + const selectors = map.get(digits[level]); + for (let i = 0; i < selectors.length; ++i) { + path.push(selectors[i]); + // drill down + helper(level + 1, path); + // revert status + path.pop(); + } + } + + helper(0, []); + return res; +}; + +console.log(letterCombinations('23')) +// console.log(letterCombinations('995')) +console.log(letterCombinations('')) + +/* +Line 41 in solution.js + throw new TypeError(__serialize__(ret) + " is not valid value for the expected return type list"); +: "" is not valid value for the expected return type list + Line 41: Char 20 in solution.js (Object.) + Line 16: Char 8 in runner.js (Object.runner) + Line 27: Char 26 in solution.js (Object.) + Line 1251: Char 30 in loader.js (Module._compile) + Line 1272: Char 10 in loader.js (Object.Module._extensions..js) + Line 1100: Char 32 in loader.js (Module.load) + Line 962: Char 14 in loader.js (Function.Module._load) + at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12) + Line 17: Char 47 in run_main_module.js +*/ diff --git a/Week_03/022generateParenthesis.js b/Week_03/022generateParenthesis.js new file mode 100644 index 00000000..a06019b6 --- /dev/null +++ b/Week_03/022generateParenthesis.js @@ -0,0 +1,25 @@ +/** + * medium + * https://leetcode-cn.com/problems/generate-parentheses/ + * + * 思路:递归、辅助函数、剪枝 + */ + +// 解法一:递归 +const generateParenthesis = function(n) { + const _generate = (cntL, cntR, curStr) => { + if (cntL === n && cntR === n) { + res.push(curStr) + return + } + if (cntL < n) _generate(cntL + 1, cntR, curStr + '(') + if (cntR < cntL) _generate(cntL, cntR + 1, curStr + ')') + } + + const res = [] + _generate(0, 0, '') + return res +} + +// ---- test case ---- +console.log(generateParenthesis(3)) diff --git a/Week_03/046permute.js b/Week_03/046permute.js new file mode 100644 index 00000000..cbcc85cf --- /dev/null +++ b/Week_03/046permute.js @@ -0,0 +1,33 @@ +/** + * https://leetcode-cn.com/problems/permutations/ + * 思路:分治 + 回溯 + * + * @param {number[]} nums + * @return {number[][]} + */ +function dfs (path, remain, res) { + // terminator + if (remain.length === 0) { + res.push(path.slice()) + return + } + for (let i = 0; i < remain.length; ++i) { + // process + const [val] = remain.splice(i, 1) + path.push(val) + // drill down + dfs(path, remain, res) + // revert status + path.pop() + remain.splice(i, 0, val) + } +} + +function permute(arr) { + const res = [] + dfs([], arr, res) + return res +} + +// ---- test case ---- +console.log(permute([1,2,3])) diff --git a/Week_03/047permuteUnique.js b/Week_03/047permuteUnique.js new file mode 100644 index 00000000..72729d9c --- /dev/null +++ b/Week_03/047permuteUnique.js @@ -0,0 +1,33 @@ +/** + * https://leetcode-cn.com/problems/permutations-ii/ + * + * 47. 全排列 II + */ + +const permuteUnique = function(nums) { + const res = [] + + const helper = (path, part) => { + if (path.length >= nums.length) { + res.push(path.slice()) + return + } + for(let i = 0; i < part.length; ++i) { + if (i > 0 && part[i] === part[i - 1]) continue // 去重 + const val = part[i] + path.push(val) + part.splice(i, 1) + helper(path, part) + path.pop() + part.splice(i, 0, val) + } + } + + helper([], nums.sort((x, y) => x - y).slice()) + return res +} + +// ---- test case ---- +console.log(permuteUnique([1,2,3])) +console.log(permuteUnique([1,1,2])) +console.log(permuteUnique([1,2,1])) diff --git a/Week_03/050myPow.js b/Week_03/050myPow.js new file mode 100644 index 00000000..9ae9236b --- /dev/null +++ b/Week_03/050myPow.js @@ -0,0 +1,37 @@ +/** + * medium + * https://leetcode-cn.com/problems/powx-n/ + */ + +// 思路1:分治(分解为子问题,再合并结果) +const myPow1 = function(x, n) { + const fastPow = (x, n) => { + if (n === 0) return 1.0 + const half = fastPow(x, Math.floor(n / 2)) + return n % 2 === 0 ? half * half : half * half * x + } + return n >= 0 ? fastPow(x, n) : 1.0 / fastPow(x, -n) +} + +// 思路2:【快速幂】 +// 自底向上迭代,遇到奇数扩大为 x * x ^ 2, 遇到偶数扩大为 x ^ 2 +const myPow = function(x, n) { + if (n < 0) return 1.0 / myPow(x, -n) + let res = 1.0 + for(let i = n; i != 0; i = Math.floor(i / 2)) { + if (i % 2 !== 0) { + res *= x + } + x *= x + } + return res +} + +// ---- test case ---- +console.log(myPow1(2.0, 10)) +console.log(myPow1(2.1, 3)) +console.log(myPow1(2.0, -2)) + +console.log(myPow(2.0, 10)) +console.log(myPow(2.1, 3)) +console.log(myPow(2.0, -2)) diff --git a/Week_03/051solveNQueens.js b/Week_03/051solveNQueens.js new file mode 100644 index 00000000..26ef02d5 --- /dev/null +++ b/Week_03/051solveNQueens.js @@ -0,0 +1,88 @@ +/** + * https://leetcode-cn.com/problems/n-queens/ + * @param {number} n + * @return {string[][]} + */ + +// 解法一:【DFS 递归搜索 + 剪枝】 +const solveNQueens = function(n) { + if (n < 1) return [[]] + + const result = [], + cols = new Set(), + pie = new Set(), // 撇 + na = new Set() // 捺 + + const dfs = (i, curState) => { + // terminator + if (i >= n) { + result.push(curState) + return + } + // process + for(let j = 0; j < n; ++j) { + if (cols.has(j) || pie.has(i + j) || na.has(i - j)) + continue + // drill down + cols.add(j) + pie.add(i + j) + na.add(i - j) + dfs(i + 1, curState.concat([j])) + // revert states + cols.delete(j) + pie.delete(i + j) + na.delete(i - j) + } + } + + // 把 result 转换成题目要求的格式 + const generateResult = (result) => { + const board = [] + result.forEach(res => { + const matrix = [] + res.forEach(i => { + const line = '.'.repeat(i) + 'Q' + '.'.repeat(n - i - 1) + matrix.push(line) + }) + board.push(matrix) + }) + return board + } + + dfs(0, []) + return generateResult(result) +} + +// TODO 解法二:位运算 + +// ---- test case ---- +new Array(10).fill(0).forEach((item, idx) => { + const g = solveNQueens(idx) + console.log(`---- ${idx}, 共${g.length}种解法 ----`) + // console.log(g) +}) + +/* +输入: n = 4 +中间结果result: +[ + [1, 3, 0, 2], + [2, 0, 3, 1], +] + +输出结果: +[ + [ + '.Q..', + '...Q', + 'Q...', + '..Q.', + ], + [ + '..Q.', + 'Q...', + '...Q', + '.Q..', + ], +] +*/ diff --git a/Week_03/077combine.js b/Week_03/077combine.js new file mode 100644 index 00000000..53e8c9ea --- /dev/null +++ b/Week_03/077combine.js @@ -0,0 +1,37 @@ +/** + * https://leetcode-cn.com/problems/combinations/ + * 思路:回溯 + 剪枝 + * + * + * @param {number} n + * @param {number} k + * @return {number[][]} + */ +const combine = function(n, k) { + const res = [] + /** + * 辅助函数 + * @param {*} start 枚举起点 + * @param {*} path “部分解” + */ + const helper = (start, path) => { + if (path.length === k) { + res.push(path.slice()) + return + } + for(let i = start; i <= n; ++i) { + path.push(i) // 选择 + helper(i + 1, path) + path.pop() // 撤销选择 + } + } + + helper(1, []) + return res +} + +// ---- test case ---- +console.log(combine(4, 2)) +console.log(combine(4, 3)) +console.log(combine(5, 3)) + diff --git a/Week_03/078subsets.js b/Week_03/078subsets.js new file mode 100644 index 00000000..621f55c4 --- /dev/null +++ b/Week_03/078subsets.js @@ -0,0 +1,66 @@ +/** + * https://leetcode-cn.com/problems/subsets/ + * 思路1:【分治 + 回溯】 + * 分治,求子问题。每一步有两种情况,选or不选 + * + * @param {number[]} nums + * @return {number[][]} + */ +const subsets = function(nums) { + if (Object.prototype.toString.call(nums) !== '[object Array]' || nums.length < 1) return [[]] + + const res = [] + const dfs = (level, path) => { + // terminator + if (level === nums.length) { + res.push(path.slice()) + return + } + // process1: not pick + dfs(level + 1, path) + // process2: pick + path.push(nums[level]) + dfs(level + 1, path) + // revert states + path.pop() + } + + dfs(0, []) + return res +} + + +// 思路2: 【迭代】 +const subsets2 = function(nums) { + if (Object.prototype.toString.call(nums) !== '[object Array]' || nums.length < 1) return [[]] + + const res = [[]] + nums.forEach(num => { + res.forEach(r => { + res.push(r.concat(num)) + }) + }) + return res +} + + + +// ---- test case --- +// console.log(subsets([1])) +// console.log(subsets([1,2])) +// console.log(subsets([1,2,3])) +// console.log(subsets([1,2,3,4])) +console.log(subsets2([1,2,3])) +console.log(JSON.stringify(subsets2([1,2,3]))) + + +/* +[0] +[[], [0]] + +[0, 1] +[[], [0], [1], [0,1]] + +[0, 1, 2] +[[], [0], [1], [2], [0, 1], [0, 2], [1, 2], [1,2,3]] +*/ diff --git a/Week_03/098isValidBST.js b/Week_03/098isValidBST.js new file mode 100644 index 00000000..b51fb444 --- /dev/null +++ b/Week_03/098isValidBST.js @@ -0,0 +1,34 @@ +/** + * https://leetcode-cn.com/problems/validate-binary-search-tree/ + * 思路: + * 分治法,左子树全小于根结点,右子树全大于根结点 + * 构造辅助函数,扩展参数 lower(下界) 和 upper(上界) + */ + +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {boolean} + */ + +// 递归版 +const isValidBST = function(root) { + function helper(root, lower, upper) { + // terminator + if (root == null) return true + // process + if (root.val <= lower || root.val >= upper) return false + // drill down + return helper(root.left, lower, root.val) && helper(root.right, root.val, upper) + // revert states + } + + return helper(root, -Infinity, Infinity) +} diff --git a/Week_03/104maxDepth.js b/Week_03/104maxDepth.js new file mode 100644 index 00000000..14ad4e15 --- /dev/null +++ b/Week_03/104maxDepth.js @@ -0,0 +1,51 @@ +/** + * https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/ + * https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/ + * + * 剑指 Offer 55 - I. 二叉树的深度 + */ + +// 1. 递归 +function maxDepth(root) { + if (root == null) return 0 + return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1 +} + +// 2. 非递归, BFS +function maxDepth(root) { + if (root == null) return 0 + const queue = [root] + let cnt = 0 + while (queue.length > 0) { + const size = queue.length + for (let i = 0; i < size; ++i) { + const node = queue.pop() + if (node.left) queue.unshift(node.left) + if (node.right) queue.unshift(node.right) + } + ++cnt + } + return cnt +} + +// 3. 非递归, DFS +function maxDepth(root) { + if (root == null) return 0 + const stack = [root] + const level = [1] + let max = 0 + while (stack.length > 0) { + const node = stack.pop() + const l = level.pop() + max = Math.max(max, l) + if (node.right) { + stack.push(node.right) + level.push(l + 1) + } + if (node.left) { + stack.push(node.left) + level.push(l + 1) + } + } + return max +} diff --git a/Week_03/105buildTree.js b/Week_03/105buildTree.js new file mode 100644 index 00000000..367c115d --- /dev/null +++ b/Week_03/105buildTree.js @@ -0,0 +1,44 @@ +/** + * https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ + * 思路: + * 1. 建立辅助函数,扩充4个下标。pStart,pEnd,iStart,iEnd + * 2. 递归调用辅助函数 + * + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {number[]} preorder + * @param {number[]} inorder + * @return {TreeNode} + */ + +const buildTree = function(preorder, inorder) { + const helper = (preorder, pStart, pEnd, inorder, iStart, iEnd) => { + // terminator + if (pStart === pEnd) { + return null + } + // process [rootValue -> iRootIdx -> leftSize] + const rootValue = preorder[pStart], root = new TreeNode(rootValue) + let iRootIdx = 0 + for(let i = 0; i < inorder.length; ++i) { + if (inorder[i] === rootValue) { + iRootIdx = i + break + } + } + const leftSize = iRootIdx - iStart + // drill down + root.left = helper(preorder, pStart + 1, pStart + leftSize + 1, inorder, iStart, iRootIdx) + root.right = helper(preorder, pStart + leftSize + 1, pEnd, inorder, iRootIdx + 1, iEnd) + // revert states + return root + } + + return helper(preorder, 0, preorder.length, inorder, 0, inorder.length) +} diff --git a/Week_03/111minDepth.js b/Week_03/111minDepth.js new file mode 100644 index 00000000..96166218 --- /dev/null +++ b/Week_03/111minDepth.js @@ -0,0 +1,32 @@ +/** + * https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/ + * 思路: 递归 + * + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {number} + */ +const minDepth = function(root) { + // terminator + if(root == null) return 0 + // process + if (root.left == null && root.right == null) return 1 + // drill down + const dLeft = minDepth(root.left) + const dRight = minDepth(root.right) + // revert states + if (root.left == null) { + return dRight + 1 + } else if (root.right == null) { + return dLeft + 1 + } else { + return Math.min(dLeft, dRight) + 1 + } +} diff --git a/Week_03/169majorityElement.js b/Week_03/169majorityElement.js new file mode 100644 index 00000000..829a11b5 --- /dev/null +++ b/Week_03/169majorityElement.js @@ -0,0 +1,37 @@ +/** + * https://leetcode-cn.com/problems/majority-element/ + * 169. 多数元素 (easy) + * 摩尔投票法 O(n) 【Majority Voting Algorithm】 + * + * 难度升级 -> [./229majorityElement.js] + */ + +const majorityElement = function(nums) { + if (Object.prototype.toString.call(nums) !== '[object Array]') return null + + let res = null, cnt = 0 + for(let i = 0; i < nums.length; ++i) { + // 写法一 + // if (cnt > 0 && nums[i] !== res) { + // --cnt + // } else { + // res = nums[i] + // ++cnt + // } + // 写法二:更好理解一些 + if (nums[i] === res) { + ++cnt + } else { + --cnt + if (cnt < 0) { + res = nums[i] + cnt = 1 + } + } + } + return res +} + +// ---- test case ---- +console.log(majorityElement([3, 2, 3])) +console.log(majorityElement([3, 2, 2])) diff --git a/Week_03/226invertTree.js b/Week_03/226invertTree.js new file mode 100644 index 00000000..f2a2f26e --- /dev/null +++ b/Week_03/226invertTree.js @@ -0,0 +1,49 @@ +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + * https://leetcode-cn.com/problems/invert-binary-tree/submissions/ + * 1. terminator + * 2. process + * 3. drill down + * 4. reverse states + */ +/** + * @param {TreeNode} root + * @return {TreeNode} + */ +const invertTree = function(root) { + // terminator + if (root == null) return root + // process + const tmp = root.left + root.left = root.right + root.right = tmp + // drill down + invertTree(root.left) + invertTree(root.right) + // revert states + return root +} + +// queue + bfs +var invertTree2 = function(root) { + if (!root) return root; + let queue = [root]; + while (queue.length) { + const node = queue.pop(); + const tmp = node.left; + node.left = node.right; + node.right = tmp; + if (node.left) { + queue.unshift(node.left); + } + if (node.right) { + queue.unshift(node.right); + } + } + return root; +}; diff --git a/Week_03/229majorityElement.js b/Week_03/229majorityElement.js new file mode 100644 index 00000000..58fdb930 --- /dev/null +++ b/Week_03/229majorityElement.js @@ -0,0 +1,78 @@ +/** + * https://leetcode-cn.com/problems/majority-element-ii/ + * + * 229. 求众数 II + * medium + * + * + */ + +// 解法一:扩展摩尔投票法 O(n) 推荐! +const majorityElement = function(nums) { + if (Object.prototype.toString.call(nums) !== '[object Array]' || nums.length < 2) return nums + + const cnt = new Array(2).fill(0), + res = new Array(2).fill(null) + for(const num of nums) { + if (num === res[0]) { + ++cnt[0] + } else if (num === res[1]) { + ++cnt[1] + } else if (cnt[0] === 0) { + res[0] = num + ++cnt[0] + } else if (cnt[1] === 0) { + res[1] = num + ++cnt[1] + } else { + --cnt[0] + --cnt[1] + } + } + + const minCnt = Math.floor(nums.length / 3) + const realCnt = new Array(2).fill(0) + nums.forEach(num => { + if (num === res[0]) { + ++realCnt[0] + } else if (num === res[1]) { + ++realCnt[1] + } + }) + + return res.filter((_, idx) => realCnt[idx] > minCnt) +} + +// 解法二:哈希表统计 O(n) 但是空间复杂度不符合题目要求!! +const majorityElement2 = function(nums) { + if (Object.prototype.toString.call(nums) !== '[object Array]' || nums.length < 2) return nums + + const map = new Map(), + minCnt = Math.floor(nums.length / 3), + res = [] + for(const num of nums) { + if (map.has(num)) { + map.set(num, map.get(num) + 1) + } else { + map.set(num, 1) + } + } + for(let [key, val] of map) { + if (val > minCnt) { + res.push(key) + } + } + return res +} + + +// ---- test case ---- +// console.log(majorityElement([3])) +// console.log(majorityElement([1, 2, 3])) +// console.log(majorityElement([3, 2, 3])) +// console.log(majorityElement([1, 1, 1, 3, 3, 2, 2, 2])) + +// console.log(majorityElement2([3])) +// console.log(majorityElement2([1, 2, 3])) +console.log(majorityElement2([3, 2, 3])) +console.log(majorityElement2([1, 1, 1, 3, 3, 2, 2, 2])) diff --git a/Week_03/236lowestCommonAncestor.js b/Week_03/236lowestCommonAncestor.js new file mode 100644 index 00000000..bce8113e --- /dev/null +++ b/Week_03/236lowestCommonAncestor.js @@ -0,0 +1,32 @@ +/** + * medium + * https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/ + * 思路:递归 + */ + +/** + * 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} + */ + +const lowestCommonAncestor = function(root, p, q) { + // terminator + if (root == null || root == p || root == q) return root + // process + // drill down + const findL = lowestCommonAncestor(root.left, p, q) + const findR = lowestCommonAncestor(root.right, p, q) + if (findL == null) return findR + if (findR == null) return findL + // revert states + return root +} diff --git a/Week_03/297serialize.js b/Week_03/297serialize.js new file mode 100644 index 00000000..a905ba8c --- /dev/null +++ b/Week_03/297serialize.js @@ -0,0 +1,39 @@ +/** + * https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/ + * hard + * + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ + + // 解法一:DFS (递归写法) +const serialize = function(root) { + if (root == null) { + return 'X'; + } + const left = serialize(root.left) + const right = serialize(root.right) + return root.val + ',' + left + ','+ right +} + +const deserialize = function(data) { + const treeVals = data.split(',') + + const buildTree = (list) => { + const rootVal = list.shift() + if (rootVal === 'X') return null + const root = new TreeNode(rootVal) + root.left = buildTree(list) + root.right = buildTree(list) + return root + } + + return buildTree(treeVals) +} + + +// 1,2,X,X,3,4,X,X,5,X,X + diff --git a/Week_03/README.md b/Week_03/README.md index 50de3041..8ca4691b 100644 --- a/Week_03/README.md +++ b/Week_03/README.md @@ -1 +1,45 @@ -学习笔记 \ No newline at end of file +# 学习笔记 + +## 第7课:泛型递归、树的递归 + +**递归模版** + +1. 递归终结条件 recursion terminator +2. 处理当前层逻辑 process logic in current level +3. 下探到下一层 drill down +4. 清理当前层 revere the current level status if needed + +## 第8课:分治、回溯 + +**分治模版** + +1. recursion terminator +2. process logic in current level +3. drill down +4. merge results +5. revert the current level status if needed + +```js +const divide_conquer = (problem, params) => { + // terminator + if (problem == null) { + process_result + return + } + // process current problem + subproblems = split_problem(problem, data) + subresult1 = divide_conquer(subproblem[0], p1) + subresult2 = divide_conquer(subproblem[1], p1) + ... + subresultn = divide_conquer(subproblem[2], p1) + // merge + result = process_result(subresult1, rubresult2, ..., subresultn) + // revert the current level status +} +``` + +**回溯(backtraking)** + +1. 走一步 +2. 递归调用 +3. 恢复 diff --git a/Week_03/homework.md b/Week_03/homework.md new file mode 100644 index 00000000..76a474b9 --- /dev/null +++ b/Week_03/homework.md @@ -0,0 +1,26 @@ +# 作业 + +## 1. 二叉树的最近公共祖先(Facebook 在半年内面试常考) + ++ recursion ++ [实现代码](./236lowestCommonAncestor.js) + +## 2. 从前序与中序遍历序列构造二叉树(字节跳动、亚马逊、微软在半年内面试中考过) + ++ recursion ++ [实现代码](./105buildTree.js) + +## 3. 组合(微软、亚马逊、谷歌在半年内面试中考过) + ++ backtrack ++ [实现代码](./077combine.js) + +## 4. 全排列(字节跳动在半年内面试常考) + ++ backtrack ++ [实现代码](./046permute.js) + +## 5. 全排列 II (亚马逊、字节跳动、Facebook 在半年内面试中考过) + ++ backtrack ++ [实现代码](./047permuteUnique.js) diff --git a/Week_03/offer-006.js b/Week_03/offer-006.js new file mode 100644 index 00000000..4c8e5542 --- /dev/null +++ b/Week_03/offer-006.js @@ -0,0 +1,25 @@ +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + * easy | https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/ + * 思路:利用stack + */ +/** + * @param {ListNode} head + * @return {number[]} + */ +var reversePrint = function(head) { + var p = head, stack = [] + while(p) { + stack.push(p.val) + p = p.next + } + var res = [] + while(stack.length) { + res.push(stack.pop()) + } + return res +}; diff --git a/Week_03/offer-068lowestCommonAncestor.js b/Week_03/offer-068lowestCommonAncestor.js new file mode 100644 index 00000000..a1944d87 --- /dev/null +++ b/Week_03/offer-068lowestCommonAncestor.js @@ -0,0 +1,27 @@ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + * https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/ + * 思路:递归 + */ +/** + * @param {TreeNode} root + * @param {TreeNode} p + * @param {TreeNode} q + * @return {TreeNode} + */ +const lowestCommonAncestor = function(root, p, q) { + // terminator + if (root == null || root == p || root == q) return root + // process + // drill down + const findL = lowestCommonAncestor(root.left, p, q) + const findR = lowestCommonAncestor(root.right, p, q) + if (findL == null) return findR + if (findR == null) return findL + // revert states + return root +} diff --git a/Week_04/022generateParenthesis.js b/Week_04/022generateParenthesis.js new file mode 100644 index 00000000..ac9c26aa --- /dev/null +++ b/Week_04/022generateParenthesis.js @@ -0,0 +1,37 @@ +/** + * https://leetcode-cn.com/problems/generate-parentheses/ + * 22. 括号生成 + * medium + */ + +// 解法一:递归 + 剪枝 +const generateParenthesis1 = function(n) { + const _generate = (cntL, cntR, curStr) => { + if (cntL === n && cntR === n) { + res.push(curStr) + return + } + if (cntL < n) _generate(cntL + 1, cntR, curStr + '(') + if (cntR < cntL) _generate(cntL, cntR + 1, curStr + ')') + } + + const res = [] + _generate(0, 0, '') + return res +} + +// 解法二:非递归(自己维护栈) + 剪枝 +const generateParenthesis2 = function(n) { + const res = [], stack = [['(', 1, 0]] + while (stack.length) { + const [curStr, cntL, cntR] = stack.pop() + if (cntL === n && cntR === n) res.push(curStr) + if (cntR < cntL) stack.push([curStr + ')', cntL, cntR + 1]) + if (cntL < n) stack.push([curStr + '(', cntL + 1, cntR]) + } + return res +} + + +console.log(generateParenthesis1(3)) +console.log(generateParenthesis2(3)) diff --git a/Week_04/033search.js b/Week_04/033search.js new file mode 100644 index 00000000..3ab06741 --- /dev/null +++ b/Week_04/033search.js @@ -0,0 +1,34 @@ +/** + * https://leetcode-cn.com/problems/search-in-rotated-sorted-array/ + * + * 33. 搜索旋转排序数组 + * + * binarySearch + */ +const search = function(nums, target) { + let l = 0, r = nums.length - 1 + while (l <= r) { + const mid = l + ((r - l) >> 1) + if (nums[mid] === target) return mid + if (nums[l] <= nums[mid]) { // 逆序在右边 + if (nums[l] <= target && target < nums[mid]) { + r = mid - 1 + } else { + l = mid + 1 + } + } else { // 逆序在左边 + if (nums[mid] < target && target <= nums[r]) { + l = mid + 1 + } else { + r = mid - 1 + } + } + } + return -1 +} + + +// ---- test case ---- +console.log(search([4, 5, 6, 7, 0, 1, 2], 0)) +console.log(search([4, 5, 6, 7, 0, 1, 2], 3)) +console.log(search([1], 0)) diff --git a/Week_04/045jump.js b/Week_04/045jump.js new file mode 100644 index 00000000..3775616a --- /dev/null +++ b/Week_04/045jump.js @@ -0,0 +1,34 @@ +/** + * https://leetcode-cn.com/problems/jump-game-ii/ + * + * 45. 跳跃游戏 II + * hard + */ + +/* + [2, 3, 1, 1, 4] + l 0 2 + r 2 4 +*/ + +const jump = function(nums) { + if (!Array.isArray(nums) || nums.length <= 1) return 0 + let l = 0, r = nums[0], times = 1 + while (r < nums.length - 1) { + ++times + let next = -Infinity + for(let i = l; i <= r; ++i) { + next = Math.max(next, nums[i] + i) + } + if (next <= r) return -1 // 没增加最远距离,则无法更远 + l = r + r = next + } + return times +} + +// ---- test case ---- +// console.log(jump([])) +console.log(jump([2,3,1,1,4])) // 2 +// console.log(jump([2,1,1,1,1,4])) +// console.log(jump([2,1,1,0,1,4])) diff --git a/Week_04/055canJump.js b/Week_04/055canJump.js new file mode 100644 index 00000000..d0381603 --- /dev/null +++ b/Week_04/055canJump.js @@ -0,0 +1,20 @@ +/** + * https://leetcode-cn.com/problems/jump-game/ + * + * 55. 跳跃游戏 + * + * 思路: 从后往前贪心,依次判断第i个元素是否能到标记节点,能则记录更新 endReachable + */ + +const canJump = function(nums) { + if (!Array.isArray(nums)) return false + let endReachable = nums.length - 1 + for (let i = nums.length - 2; i >= 0; --i) { + if (nums[i] + i >= endReachable) endReachable = i + } + return endReachable === 0 +} + +// ---- test case ---- +console.log(canJump([])) +console.log(canJump([0])) diff --git a/Week_04/069sqrtx.js b/Week_04/069sqrtx.js new file mode 100644 index 00000000..bd59854f --- /dev/null +++ b/Week_04/069sqrtx.js @@ -0,0 +1,49 @@ +/** + * https://leetcode-cn.com/problems/sqrtx/ + * + * 069 x的平方根 + * + */ + +// 方法1. 二分查找 +// y = x ^ 2, (x > 0): 是单调递增的抛物线 +const mySqrt = function(x) { + if (x === 0 || x === 1) return x + let l = 1, r = x + while (l <= r) { + const mid = l + ((r - l) >> 1) + if (mid * mid > x) { + r = mid - 1 + } else { + l = mid + 1 + } + } + return r +} + +// 方法2: 牛顿迭代法 +// r = (r + x / r) / 2 +const mySqrt2 = function(x) { + let r = x + while (r * r > x) { + r = Math.floor(x / r + (r - x / r) / 2) + } + return r +} + + +// ---- test case ---- +console.log(mySqrt(0)) +console.log(mySqrt(1)) +console.log(mySqrt(4)) +console.log(mySqrt(8)) +console.log(mySqrt(9)) +console.log(mySqrt(2147483647)) +console.log(mySqrt(2147395600)) + + +console.log(mySqrt2(4)) +console.log(mySqrt2(8)) +console.log(mySqrt2(9)) +console.log(mySqrt2(2147483647)) +console.log(mySqrt2(2147395600)) diff --git a/Week_04/074searchMatrix.js b/Week_04/074searchMatrix.js new file mode 100644 index 00000000..68b2f82b --- /dev/null +++ b/Week_04/074searchMatrix.js @@ -0,0 +1,46 @@ +/** + * https://leetcode-cn.com/problems/search-a-2d-matrix/ + * + * 74. 搜索二维矩阵 + * + * 0 1 2 3 4 5 6 7 8 9 10 11 + * 00 01 02 03, 10 11 12 13, 20 21 22 23 + * m = 3, n = 4 + * + * x -> i, j + * + * i = Math.floor(x / n), j = x % n + * + * 复杂度 log(mn) = log(m) + log(n) < logm * logn < O(m + n) < O(mn) + */ + +const searchMatrix = function(matrix, target) { + const _getVal = (idx) => { + const x = Math.floor(idx / n) + const y = idx % n + return matrix[x][y] + } + const m = matrix.length, n = matrix[0].length + let lo = 0, hi = m * n - 1 + while (lo <= hi) { + const mid = lo + ((hi - lo) >> 1) + const val = _getVal(mid) + if (val === target) { + return true + } else if (val > target){ + hi = mid - 1 + } else { + lo = mid + 1 + } + } + return false +} + +// ---- test case ---- +const matrix = [ + [ 1, 3, 5, 7], + [10, 11, 16, 20], + [23, 30, 34, 60], +] +console.log(searchMatrix(matrix, 3)) +console.log(searchMatrix(matrix, 13)) diff --git a/Week_04/102levelOrder.js b/Week_04/102levelOrder.js new file mode 100644 index 00000000..108e05d1 --- /dev/null +++ b/Week_04/102levelOrder.js @@ -0,0 +1,24 @@ +/** + * https://leetcode-cn.com/problems/binary-tree-level-order-traversal/ + * 二叉树的层序遍历 + * + * 思路:【BFS】 + * 手动维护queue (push + unshift) + * 内层维护一个循环,循环当前queue的size,为一层(null不要加入queue) + */ + +var levelOrder = function(root) { + if (!root) return []; + const queue = [root], res = []; + while (queue.length) { + const size = queue.length, row = []; + for (let i = 0; i < size; ++i) { + const node = queue.pop(); + if (node.left) queue.unshift(node.left); + if (node.right) queue.unshift(node.right); + row.push(node.val); + } + res.push(row); + } + return res; +}; diff --git a/Week_04/122maxProfitII.js b/Week_04/122maxProfitII.js new file mode 100644 index 00000000..37549852 --- /dev/null +++ b/Week_04/122maxProfitII.js @@ -0,0 +1,18 @@ +/** + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/ + * + * 122. 买卖股票的最佳时机 II + * + * 买入卖出无限制,可以用贪心法 + */ + +const maxProfit = function(prices) { + let res = 0 + for(let i = 0; i < prices.length - 1; ++i) { + const profit = prices[i + 1] - prices[i] + if (profit > 0) { + res += profit + } + } + return res +} diff --git a/Week_04/126findLadders.js b/Week_04/126findLadders.js new file mode 100644 index 00000000..8c16d26b --- /dev/null +++ b/Week_04/126findLadders.js @@ -0,0 +1,124 @@ +/** + * https://leetcode-cn.com/problems/word-ladder-ii + * hard + * 126. 单词接龙 II + * + * @param {string} beginWord + * @param {string} endWord + * @param {string[]} wordList + * @return {string[][]} + */ + +// 解法一:BFS (要搜寻的情况太多,会超时!!!) +// 难点在于要找到 “所有的最短路径” +// 无法通过第 23 条测试用例,开 set 太多了,JS引擎垃圾回收跑崩了!!! +const findLadders1 = function (beginWord, endWord, wordList) { + const res = [], + queue = [[1, [beginWord], new Set(wordList)]], + alphs = [...'abcdefghijklmnopqrstuvwxyz'] + let minLevel = Infinity + while (queue.length) { + const [level, path, wordSet] = queue.pop() + const word = path[path.length - 1] + + if (word === endWord && (minLevel === Infinity || level === minLevel)) { + res.push(path.slice()) + minLevel = level // 记录最短路径,用于后续剪枝 + } else { + if (level >= minLevel) continue // 剪枝 + for (let i = 0; i < word.length; ++i) { + for (const ch of alphs) { + const nextWord = word.slice(0, i) + ch + word.slice(i + 1) + if (wordSet.has(nextWord)) { + wordSet.delete(nextWord) + queue.unshift([level + 1, path.concat([nextWord]), new Set(wordSet)]) + } + } + } + } + } + return res +} + + + + + +// 解法二:构建图 +var findLadders = function (beginWord, endWord, wordList) { + if (wordList.indexOf(endWord) < 0) return []; + if (wordList.indexOf(beginWord) == -1) wordList.push(beginWord); + + let allCombDict = new Map(); + let wordLevel = new Map(); + let wordConnection = new Map(); + let L = beginWord.length; + // 建图 + for (let word of wordList) { + for (let i = 0; i < L; ++i) { + let key = word.slice(0, i) + "*" + word.slice(i + 1); + if (allCombDict.has(key)) allCombDict.get(key).push(word); + else allCombDict.set(key, [word]); + } + } + let queue = [beginWord]; + let wordUsed = new Set(); + let step = 1; + let flag = 1; + //帮助我们判断是否能从beginWord到endWord,如果可以则转为0,这可以帮助我们提前结束循环,并且如果不能到达endWord,则不需要再进行DFS 直接返回[]; + //BFS + while (queue.length && flag) { + let len = queue.length; + for (let t = 0; t < len; ++t) { + let word = queue.shift(); + if (!wordUsed.has(word)) { + wordUsed.add(word); + wordLevel.set(word, step); + if (word == endWord) flag = 0; + for (let i = 0; i < L; ++i) { + let key = word.slice(0, i) + "*" + word.slice(i + 1); + if (allCombDict.has(key)) { + let connected = allCombDict.get(key).filter((d) => d != word);//这里要去除自身,两个原因:1.connected里面要保存的是该节点的邻居节点,自身不属于;2.如果将自身这个节点加进去会产生重复; + if (wordConnection.has(word)) + wordConnection.get(word).push(...connected); + else wordConnection.set(word, [...connected]); + queue.push(...connected); + } + } + } + } + step++; + } + if (flag) return []; + let res = []; + //DFS + function dfs(list, word, connection, level) { + let lev = level.get(word); + if (lev == 1) { + res.push([...list]); + return; + } + for (let node of connection.get(word)) { + if (level.get(node) == lev - 1) { + list.unshift(node); + dfs(list, node, connection, level); + list.shift(); + } + } + } + dfs([endWord], endWord, wordConnection, wordLevel); + return res; +}; + + + +// ---- test case ---- +// console.log(findLadders("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"])) +// console.log(findLadders("hit", "cog", ["hot", "dot", "dog", "lot", "log"])) +console.time('findLadders1') +console.log(findLadders1("qa", "sq", ["si", "go", "se", "cm", "so", "ph", "mt", "db", "mb", "sb", "kr", "ln", "tm", "le", "av", "sm", "ar", "ci", "ca", "br", "ti", "ba", "to", "ra", "fa", "yo", "ow", "sn", "ya", "cr", "po", "fe", "ho", "ma", "re", "or", "rn", "au", "ur", "rh", "sr", "tc", "lt", "lo", "as", "fr", "nb", "yb", "if", "pb", "ge", "th", "pm", "rb", "sh", "co", "ga", "li", "ha", "hz", "no", "bi", "di", "hi", "qa", "pi", "os", "uh", "wm", "an", "me", "mo", "na", "la", "st", "er", "sc", "ne", "mn", "mi", "am", "ex", "pt", "io", "be", "fm", "ta", "tb", "ni", "mr", "pa", "he", "lr", "sq", "ye"])) +console.timeEnd('findLadders1') + +console.time('findLadders') +console.log(findLadders("cet", "ism", ["kid","tag","pup","ail","tun","woo","erg","luz","brr","gay","sip","kay","per","val","mes","ohs","now","boa","cet","pal","bar","die","war","hay","eco","pub","lob","rue","fry","lit","rex","jan","cot","bid","ali","pay","col","gum","ger","row","won","dan","rum","fad","tut","sag","yip","sui","ark","has","zip","fez","own","ump","dis","ads","max","jaw","out","btu","ana","gap","cry","led","abe","box","ore","pig","fie","toy","fat","cal","lie","noh","sew","ono","tam","flu","mgm","ply","awe","pry","tit","tie","yet","too","tax","jim","san","pan","map","ski","ova","wed","non","wac","nut","why","bye","lye","oct","old","fin","feb","chi","sap","owl","log","tod","dot","bow","fob","for","joe","ivy","fan","age","fax","hip","jib","mel","hus","sob","ifs","tab","ara","dab","jag","jar","arm","lot","tom","sax","tex","yum","pei","wen","wry","ire","irk","far","mew","wit","doe","gas","rte","ian","pot","ask","wag","hag","amy","nag","ron","soy","gin","don","tug","fay","vic","boo","nam","ave","buy","sop","but","orb","fen","paw","his","sub","bob","yea","oft","inn","rod","yam","pew","web","hod","hun","gyp","wei","wis","rob","gad","pie","mon","dog","bib","rub","ere","dig","era","cat","fox","bee","mod","day","apr","vie","nev","jam","pam","new","aye","ani","and","ibm","yap","can","pyx","tar","kin","fog","hum","pip","cup","dye","lyx","jog","nun","par","wan","fey","bus","oak","bad","ats","set","qom","vat","eat","pus","rev","axe","ion","six","ila","lao","mom","mas","pro","few","opt","poe","art","ash","oar","cap","lop","may","shy","rid","bat","sum","rim","fee","bmw","sky","maj","hue","thy","ava","rap","den","fla","auk","cox","ibo","hey","saw","vim","sec","ltd","you","its","tat","dew","eva","tog","ram","let","see","zit","maw","nix","ate","gig","rep","owe","ind","hog","eve","sam","zoo","any","dow","cod","bed","vet","ham","sis","hex","via","fir","nod","mao","aug","mum","hoe","bah","hal","keg","hew","zed","tow","gog","ass","dem","who","bet","gos","son","ear","spy","kit","boy","due","sen","oaf","mix","hep","fur","ada","bin","nil","mia","ewe","hit","fix","sad","rib","eye","hop","haw","wax","mid","tad","ken","wad","rye","pap","bog","gut","ito","woe","our","ado","sin","mad","ray","hon","roy","dip","hen","iva","lug","asp","hui","yak","bay","poi","yep","bun","try","lad","elm","nat","wyo","gym","dug","toe","dee","wig","sly","rip","geo","cog","pas","zen","odd","nan","lay","pod","fit","hem","joy","bum","rio","yon","dec","leg","put","sue","dim","pet","yaw","nub","bit","bur","sid","sun","oil","red","doc","moe","caw","eel","dix","cub","end","gem","off","yew","hug","pop","tub","sgt","lid","pun","ton","sol","din","yup","jab","pea","bug","gag","mil","jig","hub","low","did","tin","get","gte","sox","lei","mig","fig","lon","use","ban","flo","nov","jut","bag","mir","sty","lap","two","ins","con","ant","net","tux","ode","stu","mug","cad","nap","gun","fop","tot","sow","sal","sic","ted","wot","del","imp","cob","way","ann","tan","mci","job","wet","ism","err","him","all","pad","hah","hie","aim","ike","jed","ego","mac","baa","min","com","ill","was","cab","ago","ina","big","ilk","gal","tap","duh","ola","ran","lab","top","gob","hot","ora","tia","kip","han","met","hut","she","sac","fed","goo","tee","ell","not","act","gil","rut","ala","ape","rig","cid","god","duo","lin","aid","gel","awl","lag","elf","liz","ref","aha","fib","oho","tho","her","nor","ace","adz","fun","ned","coo","win","tao","coy","van","man","pit","guy","foe","hid","mai","sup","jay","hob","mow","jot","are","pol","arc","lax","aft","alb","len","air","pug","pox","vow","got","meg","zoe","amp","ale","bud","gee","pin","dun","pat","ten","mob"])) +console.timeEnd('findLadders') diff --git a/Week_04/127ladderLength.js b/Week_04/127ladderLength.js new file mode 100644 index 00000000..098c7767 --- /dev/null +++ b/Week_04/127ladderLength.js @@ -0,0 +1,62 @@ +/** + * https://leetcode-cn.com/problems/word-ladder/description/ + * hard + * 127. 单词接龙 + */ + +// BFS, 搜索所有可能的单词是否在词库中 +// 复杂度:O(单词个数 * 单词长度 * 26) +const ladderLength = function (beginWord, endWord, wordList) { + const s = new Set(wordList), + queue = [[1, beginWord]], + alphs = [...'abcdefghijklmnopqrstuvwxyz'] + while (queue.length) { + const [level, word] = queue.pop() + // console.log("🚀", level, word, s) + if (word === endWord) return level + for (let i = 0; i < word.length; ++i) { + for (const ch of alphs) { + const nextWord = word.slice(0, i) + ch + word.slice(i + 1) + if (s.has(nextWord)) { + s.delete(nextWord) + queue.unshift([level + 1, nextWord]) + } + } + } + } + return 0 +} + +// 拓展:返回其中一条最短路径 +const findOnePath = function (beginWord, endWord, wordList) { + const s = new Set(wordList), + queue = [[1, [beginWord]]] + alphs = [...'abcdefghijklmnopqrstuvwxyz'] + while (queue.length) { + const [level, path] = queue.pop() + const word = path[path.length - 1] + if (word === endWord) return path + for (let i = 0; i < word.length; ++i) { + for (const ch of alphs) { + const nextWord = word.slice(0, i) + ch + word.slice(i + 1) + if (s.has(nextWord)) { + s.delete(nextWord) + queue.unshift([level + 1, path.concat([nextWord])]) + } + } + } + } + return [] +} + +// ---- test case ---- +// console.log(ladderLength('abc', 'def', ['dbc', 'bbc', 'dec', 'def', 'log', 'cog'])) +// console.log(ladderLength('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log', 'cog'])) +// console.log(ladderLength('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log'])) +// console.log(ladderLength("qa","sq",["si","go","se","cm","so","ph","mt","db","mb","sb","kr","ln","tm","le","av","sm","ar","ci","ca","br","ti","ba","to","ra","fa","yo","ow","sn","ya","cr","po","fe","ho","ma","re","or","rn","au","ur","rh","sr","tc","lt","lo","as","fr","nb","yb","if","pb","ge","th","pm","rb","sh","co","ga","li","ha","hz","no","bi","di","hi","qa","pi","os","uh","wm","an","me","mo","na","la","st","er","sc","ne","mn","mi","am","ex","pt","io","be","fm","ta","tb","ni","mr","pa","he","lr","sq","ye"])) + + +console.log(findOnePath('abc', 'def', ['dbc', 'bbc', 'dec', 'def', 'log', 'cog'])) +console.log(findOnePath('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log', 'cog'])) +console.log(findOnePath('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log'])) +console.log(findOnePath("qa","sq",["si","go","se","cm","so","ph","mt","db","mb","sb","kr","ln","tm","le","av","sm","ar","ci","ca","br","ti","ba","to","ra","fa","yo","ow","sn","ya","cr","po","fe","ho","ma","re","or","rn","au","ur","rh","sr","tc","lt","lo","as","fr","nb","yb","if","pb","ge","th","pm","rb","sh","co","ga","li","ha","hz","no","bi","di","hi","qa","pi","os","uh","wm","an","me","mo","na","la","st","er","sc","ne","mn","mi","am","ex","pt","io","be","fm","ta","tb","ni","mr","pa","he","lr","sq","ye"])) diff --git a/Week_04/153findMin.js b/Week_04/153findMin.js new file mode 100644 index 00000000..0d5ac85b --- /dev/null +++ b/Week_04/153findMin.js @@ -0,0 +1,28 @@ +/** + * https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/ + * + * 153. 寻找旋转排序数组中的最小值 + * binarySearch + */ + +const findMin = function(nums) { + if (Object.prototype.toString.call(nums) !== '[object Array]' || nums.length < 1) return null + + let l = 0, r = nums.length - 1 + while (l < r) { + if (nums[l] < nums[r]) return nums[l] + const mid = l + ((r - l) >> 1) + if (nums[mid] >= nums[l]) { + l = mid + 1 + } else { + r = mid + } + } + + return nums[l] +} + +// ---- test case ---- +console.log(findMin([3, 4, 5, 1, 2])) +console.log(findMin([4, 5, 6, 7, 0, 1, 2])) +console.log(findMin([1])) diff --git a/Week_04/200numIslands.js b/Week_04/200numIslands.js new file mode 100644 index 00000000..4caa1106 --- /dev/null +++ b/Week_04/200numIslands.js @@ -0,0 +1,51 @@ +/** + * https://leetcode-cn.com/problems/number-of-islands/ + * 200. 岛屿数量 + * medium + */ + +// dfs +var numIslands = function(A) { + if (!Array.isArray(A) || A.length < 1 || !Array.isArray(A[0]) || A[0].length < 1) return 0; + + const m = A.length, n = A[0].length; + let cnt = 0; + + const dfsMarking = (i, j) => { + if (i >= 0 && i < m && j >= 0 && j < n && A[i][j] === '1') { + A[i][j] = '0'; + dfsMarking(i + 1, j); + dfsMarking(i - 1, j); + dfsMarking(i, j + 1); + dfsMarking(i, j - 1); + } + } + + for (let i = 0; i < m; ++i) { + for (let j = 0; j < n; ++j) { + if (A[i][j] === '1') { + ++cnt; + dfsMarking(i, j); + } + } + } + return cnt; +}; + + +// ---- test case ---- +const grid1 = [ + ['1', '1', '1', '1', '0'], + ['1', '1', '0', '1', '0'], + ['1', '1', '0', '0', '0'], + ['0', '0', '0', '0', '0'], +] +const grid2 = [ + ['1', '1', '0', '0', '0'], + ['1', '1', '0', '0', '0'], + ['0', '0', '1', '0', '0'], + ['0', '0', '0', '1', '1'], +] +console.log(numIslands(grid1)) +console.log(numIslands(grid2)) +console.log(numIslands([[]])) diff --git a/Week_04/240searchMatrix.js b/Week_04/240searchMatrix.js new file mode 100644 index 00000000..8a33d495 --- /dev/null +++ b/Week_04/240searchMatrix.js @@ -0,0 +1,37 @@ +/** + * https://leetcode.cn/problems/search-a-2d-matrix-ii + * + * 搜索二维矩阵 II + * + */ + +const searchMatrix = function(matrix, target) { + const _getVal = (idx) => { + const x = Math.floor(idx / n) + const y = idx % n + return matrix[x][y] + } + const m = matrix.length, n = matrix[0].length + let lo = 0, hi = m * n - 1 + while (lo <= hi) { + const mid = lo + ((hi - lo) >> 1) + const val = _getVal(mid) + if (val === target) { + return true + } else if (val > target){ + hi = mid - 1 + } else { + lo = mid + 1 + } + } + return false + } + + // ---- test case ---- + const matrix = [ + [ 1, 3, 5, 7], + [10, 11, 16, 20], + [23, 30, 34, 60], + ] + console.log(searchMatrix(matrix, 3)) + console.log(searchMatrix(matrix, 13)) diff --git a/Week_04/367isPerfectSquare.js b/Week_04/367isPerfectSquare.js new file mode 100644 index 00000000..af648b6f --- /dev/null +++ b/Week_04/367isPerfectSquare.js @@ -0,0 +1,50 @@ + +/** + * https://leetcode-cn.csom/problems/valid-perfect-square/ + * + * 367. 有效的完全平方数 + */ + +// 解法一:binarySearch O(logn) 效果优于解法二 +const isPerfectSquare = function(n) { + let l = 0, r = n + while (l <= r) { + const mid = l + ((r - l) >> 1), val = mid * mid + if (val === n) { + return true + } else if (val > n) { + r = mid - 1 + } else { + l = mid + 1 + } + } + return false +} + +// 解法二: 找规律,循环减去一个奇数 O(sqrt(n)) +/** + * 1 + * 4 = 1 + 3 + * 9 = 1 + 3 + 5 + * 16 = 1 + 3 + 5 + 7 + * 25 = 1 + 3 + 5 + 7 + 9 + * ... + */ +const isPerfectSquare2 = function(n) { + let x = 1 + while (n > 0) { + n -= x + x += 2 + } + return n === 0 +} + +// ---- test case ---- +console.log(isPerfectSquare(0)) +console.log(isPerfectSquare(1)) +console.log(isPerfectSquare(14)) +console.log(isPerfectSquare(16)) +console.log(isPerfectSquare2(0)) +console.log(isPerfectSquare2(1)) +console.log(isPerfectSquare2(14)) +console.log(isPerfectSquare2(16)) diff --git a/Week_04/433minMutation.js b/Week_04/433minMutation.js new file mode 100644 index 00000000..77c5aef4 --- /dev/null +++ b/Week_04/433minMutation.js @@ -0,0 +1,35 @@ +/** + * https://leetcode-cn.com/problems/minimum-genetic-mutation/ + * + * 433. 最小基因变化 + * medium + * + */ + +// 思路:BFS暴力搜索 +// lastStep记录上一步的基因🧬和步数,bankSet用于可遍历的基因库 +// 复杂度 O(单条基因长度 * 基因元素数 * 基因库包含基因数) +const minMutation = function(start, end, bank) { + const lastStep = [[start, 0]], bankSet = new Set(bank) + while(lastStep.length) { + const [curr, step] = lastStep.pop() + if (curr === end) return step + for(let i = 0; i < curr.length; ++i) { + for(const ch of ['A', 'C', 'G', 'T']) { + // 突变后的基因(暴力穷举) + const mutation = curr.slice(0, i) + ch + curr.slice(i+1) + // console.log(mutation, lastStep, bankSet) + if (bankSet.has(mutation)) { + bankSet.delete(mutation) + lastStep.unshift([mutation, step + 1]) + } + } + } + } + return -1 +} + +// ---- test case ---- +// console.log(minMutation('AACCGGTT', 'AACCGGTA', ['AACCGGTA'])) +// console.log(minMutation('AACCGGTT', 'AAACGGTA', ['AACCGGTA', 'AACCGCTA', 'AAACGGTA'])) +console.log(minMutation('AAAAACCC', 'AACCCCCC', ['AAAACCCC', 'AAACCCCC', 'AACCCCCC'])) diff --git a/Week_04/455findContentChildren.js b/Week_04/455findContentChildren.js new file mode 100644 index 00000000..4eaa965a --- /dev/null +++ b/Week_04/455findContentChildren.js @@ -0,0 +1,29 @@ +/** + * https://leetcode-cn.com/problems/assign-cookies/ + * + * greedy + * 1. 排序 + * 2. 循环,小胃口👬匹配小饼干🍪 + */ + +const findContentChildren = function(g, s) { + const toStr = Object.prototype.toString, + ARR_TYPE = '[object Array]' + if (toStr.call(g) !== ARR_TYPE + || toStr.call(s) !== ARR_TYPE + || g.length < 1 + || s.length < 1 + ) return 0 + g.sort((x, y) => x - y) + s.sort((x, y) => x - y) + + let i = 0, j = 0, cnt = 0 + while (i < g.length && j < s.length) { + g[i++] <= s[j++] ? ++cnt : --i + } + return cnt +} + +// ---- test case ---- +console.log(findContentChildren([1, 2, 3], [1, 1])) +console.log(findContentChildren([1, 2], [1, 2, 3])) diff --git a/Week_04/515largestValues.js b/Week_04/515largestValues.js new file mode 100644 index 00000000..7a4d6dec --- /dev/null +++ b/Week_04/515largestValues.js @@ -0,0 +1,35 @@ +/** + * https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/ + * + * 515. 在每个树行中找最大值 + * medium + * + */ + +/* + 1 + / \ + 3 2 + / \ \ +5 3 9 + +[1, 3, 9] +*/ + +// 二叉树的层序遍历 +const largestValues = function(root) { + if (root == null) return [] + const res = [], queue = [root] + while (queue.length) { + const n = queue.length + let max = -Infinity + for(let i = 0; i < n; ++i) { + const p = queue.pop() + max = Math.max(max, p.val) + if (p.left != null) queue.unshift(p.left) + if (p.right != null) queue.unshift(p.right) + } + res.push(max) + } + return res +} diff --git a/Week_04/529updateBoard.js b/Week_04/529updateBoard.js new file mode 100644 index 00000000..08bd3893 --- /dev/null +++ b/Week_04/529updateBoard.js @@ -0,0 +1,117 @@ +/** + * https://leetcode-cn.com/problems/minesweeper/ + * 529. 扫雷游戏 + * medium + */ + +/* +1. 玩家未知区域 + + M 地雷 + + E 未点击的空地 +2. 玩家已知区域 + + B 此次和周围都无地雷 + + X 此处为地雷 + + Digit 此处无地雷,且周围有地雷 + +分析 +1. 踩中地雷(M): M -> X, 停止 +2. 未踩中地雷(E): + 1. 周围有地雷: E -> Digit, 停止搜索 (等待玩家点击) + 2. 周围无地雷: E -> B,搜索周围8个点,遇到 E,递归搜索 +*/ + +// 解法一:DFS (递归) +const updateBoard1 = function(board, click) { + const m = board.length, n = board[0].length + const [x, y] = click + if (board[x][y] === 'M') { + board[x][y] = 'X' + } else { + // cnt 统计周围8个点的地雷数 + let cnt = 0 + for (let i = -1; i <= 1; ++i) { + for(let j = -1; j <= 1; ++j) { + if (i === 0 && j === 0) continue + const r = x + i, c = y + j + if (r < 0 || r >= m || c < 0 || c >= n) continue + if (board[r][c] === 'M' || board[r][c] === 'X') ++cnt + } + } + if (cnt > 0) { + board[x][y] = String(cnt) + } else { + board[x][y] = 'B' + for(let i = -1; i <= 1; ++i) { + for(let j = -1; j <= 1; ++j) { + if (i === 0 && j === 0) continue + const r = x + i, c = y + j + if (r < 0 || r >= m || c < 0 || c >= n) continue + if (board[r][c] === 'E') updateBoard1(board, [r, c]) + } + } + } + } + + return board +} + +// 解法二:BFS (非递归,手动维护队列) +const updateBoard2 = function(board, click) { + const m = board.length, + n = board[0].length, + queue = [click] + + while (queue.length) { + const [x, y] = queue.pop() + if (board[x][y] === 'M') { + board[x][y] = 'X' + } else { + let cnt = 0 + for (let i = -1; i <= 1; ++i) { + for (let j = -1; j <= 1; ++j) { + if (i === 0 && j === 0) continue + const r = x + i, c = y + j + if (r < 0 || r >= m || c < 0 || c >= n) continue + if (board[r][c] === 'M' || board[r][c] === 'X') ++cnt + } + } + if (cnt > 0) { + board[x][y] = String(cnt) + } else { + board[x][y] = 'B' + for (let i = -1; i <= 1; ++i) { + for (let j = -1; j <= 1; ++j) { + if (i === 0 && j === 0) continue + const r = x + i, c = y + j + if (r < 0 || r >= m || c < 0 || c >= n) continue + if (board[r][c] === 'E') { + queue.unshift([r, c]) + board[r][c] = 'B' // 避免被重复加入queue + } + } + } + } + } + } + return board +} + +// ---- test cases ---- +const board1 = [ + ['E', 'E', 'E', 'E', 'E'], + ['E', 'E', 'M', 'E', 'E'], + ['E', 'E', 'E', 'E', 'E'], + ['E', 'E', 'E', 'E', 'E'], +] +const board2 = [ + ['B', '1', 'E', '1', 'B'], + ['B', '1', 'M', '1', 'B'], + ['B', '1', '1', '1', 'B'], + ['B', 'B', 'B', 'B', 'B'], +] + +// console.log(updateBoard1(board1, [3, 0])) +// console.log(updateBoard1(board2, [1, 2])) + +console.log(updateBoard2(board1, [3, 0])) +console.log(updateBoard2(board2, [1, 2])) diff --git a/Week_04/860lemonadeChange.js b/Week_04/860lemonadeChange.js new file mode 100644 index 00000000..a21ec0d9 --- /dev/null +++ b/Week_04/860lemonadeChange.js @@ -0,0 +1,32 @@ +/** + * https://leetcode-cn.com/problems/lemonade-change/ + * + * 860. 柠檬水找零 + * greedy algorithm + */ + +const lemonadeChange = function(bills) { + if (Object.prototype.toString.call(bills) !== '[object Array]' || bills.length < 1) return true + let five = 0, ten = 0 + for (const bill of bills) { + if (bill === 5) { + ++five + } else if (bill === 10) { + if (five === 0) { + return false + } + --five + ++ten + } else { + if (five > 0 && ten > 0) { + --five + --ten + } else if (five >= 3) { + five -= 3 + } else { + return false + } + } + } + return true +} diff --git a/Week_04/874robotSim.js b/Week_04/874robotSim.js new file mode 100644 index 00000000..4b9ffc3b --- /dev/null +++ b/Week_04/874robotSim.js @@ -0,0 +1,42 @@ +/** + * https://leetcode-cn.com/problems/walking-robot-simulation/ + * + * 874. 模拟行走机器人 + * easy + * 注意审题,求到过的最远距离 😓 + */ + +// 思路:用 set 存储障碍坐标,每次走一步,遇到障碍则停下来 +// direct: 0 top | 1 right | 2 down | 3 left +const robotSim = function(commands, obstacles) { + let x = 0, y = 0, direct = 0, maxDist = 0 + const obsSet = new Set(obstacles.map(pos => pos.join())), + dx = [0, 1, 0, -1], + dy = [1, 0, -1, 0] + for(const command of commands) { + if (command === -2) { + direct = (direct + 3) % 4 + } else if (command === -1) { + direct = (direct + 1) % 4 + } else if (command >= 1 && command <= 9) { + for (let i = 0; i < command; ++i) { + const px = x + dx[direct] + const py = y + dy[direct] + if (!obsSet.has(`${px},${py}`)) { + x = px + y = py + } else { + break + } + } + maxDist = Math.max(maxDist, x * x + y * y) + } + } + return maxDist +} + +console.time('robotSim') +console.log(robotSim([4, -1, 3], [])) +console.log(robotSim([4, -1, 4, -2, 4], [[2,4]])) +console.log(robotSim([1,2,-2,5,-1,-2,-1,8,3,-1,9,4,-2,3,2,4,3,9,2,-1,-1,-2,1,3,-2,4,1,4,-1,1,9,-1,-2,5,-1,5,5,-2,6,6,7,7,2,8,9,-1,7,4,6,9,9,9,-1,5,1,3,3,-1,5,9,7,4,8,-1,-2,1,3,2,9,3,-1,-2,8,8,7,5,-2,6,8,4,6,2,7,2,-1,7,-2,3,3,2,-2,6,9,8,1,-2,-1,1,4,7], [[-57,-58],[-72,91],[-55,35],[-20,29],[51,70],[-61,88],[-62,99],[52,17],[-75,-32],[91,-22],[54,33],[-45,-59],[47,-48],[53,-98],[-91,83],[81,12],[-34,-90],[-79,-82],[-15,-86],[-24,66],[-35,35],[3,31],[87,93],[2,-19],[87,-93],[24,-10],[84,-53],[86,87],[-88,-18],[-51,89],[96,66],[-77,-94],[-39,-1],[89,51],[-23,-72],[27,24],[53,-80],[52,-33],[32,4],[78,-55],[-25,18],[-23,47],[79,-5],[-23,-22],[14,-25],[-11,69],[63,36],[35,-99],[-24,82],[-29,-98],[-50,-70],[72,95],[80,80],[-68,-40],[65,70],[-92,78],[-45,-63],[1,34],[81,50],[14,91],[-77,-54],[13,-88],[24,37],[-12,59],[-48,-62],[57,-22],[-8,85],[48,71],[12,1],[-20,36],[-32,-14],[39,46],[-41,75],[13,-23],[98,10],[-88,64],[50,37],[-95,-32],[46,-91],[10,79],[-11,43],[-94,98],[79,42],[51,71],[4,-30],[2,74],[4,10],[61,98],[57,98],[46,43],[-16,72],[53,-69],[54,-96],[22,0],[-7,92],[-69,80],[68,-73],[-24,-92],[-21,82],[32,-1],[-6,16],[15,-29],[70,-66],[-85,80],[50,-3],[6,13],[-30,-98],[-30,59],[-67,40],[17,72],[79,82],[89,-100],[2,79],[-95,-46],[17,68],[-46,81],[-5,-57],[7,58],[-42,68],[19,-95],[-17,-76],[81,-86],[79,78],[-82,-67],[6,0],[35,-16],[98,83],[-81,100],[-11,46],[-21,-38],[-30,-41],[86,18],[-68,6],[80,75],[-96,-44],[-19,66],[21,84],[-56,-64],[39,-15],[0,45],[-81,-54],[-66,-93],[-4,2],[-42,-67],[-15,-33],[1,-32],[-74,-24],[7,18],[-62,84],[19,61],[39,79],[60,-98],[-76,45],[58,-98],[33,26],[-74,-95],[22,30],[-68,-62],[-59,4],[-62,35],[-78,80],[-82,54],[-42,81],[56,-15],[32,-19],[34,93],[57,-100],[-1,-87],[68,-26],[18,86],[-55,-19],[-68,-99],[-9,47],[24,94],[92,97],[5,67],[97,-71],[63,-57],[-52,-14],[-86,-78],[-17,92],[-61,-83],[-84,-10],[20,13],[-68,-47],[7,28],[66,89],[-41,-17],[-14,-46],[-72,-91],[4,52],[-17,-59],[-85,-46],[-94,-23],[-48,-3],[-64,-37],[2,26],[76,88],[-8,-46],[-19,-68]])) +console.timeEnd('robotSim') diff --git a/Week_04/README.md b/Week_04/README.md index 50de3041..7a6e7b3e 100644 --- a/Week_04/README.md +++ b/Week_04/README.md @@ -1 +1,88 @@ -学习笔记 \ No newline at end of file +# 学习笔记 + +## 第9课:深度优先搜索和广度优先搜索 + +**DFS(递归版)** + +```js +const dfs_wrap = (root) => { + const dfs_recur = (root) => { + if (root == null) return + res.push(root.val) + dfs_recur(root.left) + dfs_recur(root.right) + } + const res = [] + dfs_recur(root) + return res +} +``` + +**DFS(非递归版)** + ++ 手动维护stack + +```js +/** + * 非递归的深搜,手动维护stack (push / pop) + * 因为stack的 FILO 特点,要注意先入右子,再入左子 + * + */ +const dfs = (root) => { + if (root == null) return [] + const res = [], stack = [root] + while(stack.length > 0) { + const p = stack.pop() + if(p.right != null) stack.push(p.right) + if(p.left != null) stack.push(p.left) + res.push(p.val) + } + return res +} +``` + +**BFS** + ++ 手动维护queue + +```js +const bfs = (root) => { + if (root == null) return [] + const res = [], queue = [root] + while(queue.length > 0) { + const p = queue.pop() + if(p.left != null) queue.unshift(p.left) + if(p.right != null) queue.unshift(p.right) + res.push(p.val) + } + return res +} +``` + +## 第10课:贪心算法 + +适用场景:问题能分解成子问题,子问题的最优解能递推到最终问题的最优解。【最优子结构】 + +## 第11课:二分查找 + +**前提** +1. 单调性 +2. 存在上下界 +3. 能通过索引访问 + +```js +const binarySearch = function (arr, target) { + let l = 0, r = arr.length - 1 + while (l <= r) { + const mid = (r - l) >> 1 + l + if (arr[mid] === target) { + return mid + } else if (arr[mid] < target) { + l = mid + 1 + } else { + r = mid - 1 + } + } + return -1 +} +``` diff --git a/Week_04/demotree.js b/Week_04/demotree.js new file mode 100644 index 00000000..24e4d39c --- /dev/null +++ b/Week_04/demotree.js @@ -0,0 +1,82 @@ +const dfs_wrap = (root) => { + const dfs_recur = (root) => { + if (root == null) return + res.push(root.val) + dfs_recur(root.left) + dfs_recur(root.right) + } + const res = [] + dfs_recur(root) + return res +} + +/** + * 非递归的深搜,手动维护stack (push / pop) + * 因为stack的 FILO 特点,要注意先入右子,再入左子 + * + */ +const dfs = (root) => { + if (root == null) return [] + const res = [], stack = [root] + while(stack.length > 0) { + const p = stack.pop() + if(p.right != null) stack.push(p.right) + if(p.left != null) stack.push(p.left) + res.push(p.val) + } + return res +} + +const bfs = (root) => { + if (root == null) return [] + const res = [], queue = [root] + while(queue.length > 0) { + const p = queue.pop() + if(p.left != null) queue.unshift(p.left) + if(p.right != null) queue.unshift(p.right) + res.push(p.val) + } + return res +} + + +// ---- test case ---- +function TreeNode(val) { + this.val = val + this.left = null + this.right = null +} + +const deserialize = function(data) { + const treeVals = data.split(',') + + const buildTree = (list) => { + const rootVal = list.shift() + if (rootVal === 'X') return null + const root = new TreeNode(rootVal) + root.left = buildTree(list) + root.right = buildTree(list) + return root + } + return buildTree(treeVals) +} + +/* + 1 + / \ + 2 5 + / \ / +3 4 6 + / \ + 8 9 +*/ +const t1 = deserialize('1,2,3,X,X,4,8,X,X,9,X,X,5,6,X,X,X') +console.log(JSON.stringify(t1, null, 2)) +console.log(dfs_wrap(t1)) +console.log(dfs(t1)) +console.log(bfs(t1)) + +const t2 = null +console.log(dfs_wrap(t2)) +console.log(dfs(t2)) +console.log(bfs(t2)) diff --git a/Week_04/homework.md b/Week_04/homework.md new file mode 100644 index 00000000..0a7567bc --- /dev/null +++ b/Week_04/homework.md @@ -0,0 +1,79 @@ +# 作业 +## 第9课:深度优先搜索和广度优先搜索 +### 1. 单词接龙(亚马逊在半年内面试常考) + ++ dfs & bfs ++ [实现代码](./127ladderLength.js) + +### TODO 2. 单词接龙 II (微软、亚马逊、Facebook 在半年内面试中考过) + ++ dfs & bfs ++ [实现代码](./126findLadders.js) ++ 有点难,自己写的BFS解法无法通过全部测试用例,会超时 ++ 网上图论的方法不是很懂 + +### 3. 岛屿数量(近半年内,亚马逊在面试中考查此题达到 350 次) + ++ dfs & bfs ++ [实现代码](./200numIslands.js) + +### 4. 扫雷游戏(亚马逊、Facebook 在半年内面试中考过) + ++ dfs & bfs ++ [实现代码](./529updateBoard.js) + + + + +## 第10课:贪心算法 + +### 6. 柠檬水找零(亚马逊在半年内面试中考过) + ++ greedy ++ [实现代码](./860lemonadeChange.js) + +### 7. 买卖股票的最佳时机 II (亚马逊、字节跳动、微软在半年内面试中考过) + ++ greedy ++ [实现代码](./122maxProfitII.js) + +### 8. 分发饼干(亚马逊在半年内面试中考过) + ++ greedy ++ [实现代码](./455findContentChildren.js) + +### 9. 模拟行走机器人 + ++ greedy ++ [实现代码](./874robotSim.js) + +### 10. 跳跃游戏 (亚马逊、华为、Facebook 在半年内面试中考过) + ++ greedy ++ [实现代码](./055canJump.js) + +### 11. 跳跃游戏 II (亚马逊、华为、字节跳动在半年内面试中考过) + ++ greedy ++ [实现代码](./045jump.js) + + + + + + +## 第11课:二分查找 +### 12. 搜索旋转排序数组(Facebook、字节跳动、亚马逊在半年内面试常考) + ++ binarySearch ++ [实现代码](./033search.js) + +### 13. 搜索二维矩阵(亚马逊、微软、Facebook 在半年内面试中考过) + ++ binarySearch ++ [实现代码](./074searchMatrix.js) + +### 14. 寻找旋转排序数组中的最小值(亚马逊、微软、字节跳动在半年内面试中考过) + ++ binarySearch ++ [实现代码](./153findMin.js) diff --git a/Week_05/README.md b/Week_05/README.md index 50de3041..93c95ca1 100644 --- a/Week_05/README.md +++ b/Week_05/README.md @@ -1 +1,3 @@ -学习笔记 \ No newline at end of file +# 学习笔记 + +期中考试 diff --git a/Week_06/032longestValidParentheses.js b/Week_06/032longestValidParentheses.js new file mode 100644 index 00000000..4048371d --- /dev/null +++ b/Week_06/032longestValidParentheses.js @@ -0,0 +1,4 @@ +/** + * https://leetcode-cn.com/problems/longest-valid-parentheses/ + * 32. 最长有效括号 | hard + */ diff --git a/Week_06/053maxSubArray.js b/Week_06/053maxSubArray.js new file mode 100644 index 00000000..c10a2b89 --- /dev/null +++ b/Week_06/053maxSubArray.js @@ -0,0 +1,26 @@ +/** + * https://leetcode-cn.com/problems/maximum-subarray/ + * 53. 最大子序和 | easy + * + * a. 分治(子问题) maxSum(i) = max(maxSum(i-1), 0) + A[i] + * b. 状态数组定义 f[i] + * c. DP方程 f[i] = max(f(i-1), 0) + A[i] + */ + +// dp 空间可以压缩到单个变量 +// O(n) O(1) +const maxSubArray = function(A) { + if (!Array.isArray(A) || A.length < 1) return + let max = dp = A[0] + for (let i = 1; i < A.length; ++i) { + dp = Math.max(0, dp) + A[i] + max = Math.max(max, dp) + } + return max +} + +// ---- test case ---- +console.log(maxSubArray([-2,1,-3,4,-1,2,1,-5,4])) +console.log(maxSubArray([1])) +console.log(maxSubArray([0])) +console.log(maxSubArray([-1000])) diff --git a/Week_06/062uniquePaths.js b/Week_06/062uniquePaths.js new file mode 100644 index 00000000..a991fc51 --- /dev/null +++ b/Week_06/062uniquePaths.js @@ -0,0 +1,59 @@ +/** + * https://leetcode-cn.com/problems/unique-paths/ + * 62. 不同路径 | medium + * + * dp + * a. 重复性 problem(i, j) = problem(i + 1, j) + problem(i, j + 1) + * b. 定义状态数组 + * c. DP方程 + */ + +// dp1: O(mn) O(mn) +const uniquePaths = function(m, n) { + const dp = new Array(m).fill(new Array(n).fill(1)) + for (let i = m - 2; i >= 0; --i) { + for (let j = n - 2; j >= 0; --j) { + dp[i][j] = dp[i + 1][j] + dp[i][j + 1] + } + } + return dp[0][0] +} + +// dp2 优化空间复杂度 +// O(mn) O(n) +const uniquePaths2 = function(m, n) { + const dp = new Array(n).fill(1) + for (let i = m - 2; i >= 0; --i) { + for (let j = n - 2; j >= 0; --j) { + dp[j] = dp[j] + dp[j + 1] + } + } + return dp[0] +} + +// 变成正向计算 +// O(mn) O(n) +const uniquePaths = function(m, n) { + const dp = Array(n).fill(1) + for (let i = 1; i < m; ++i) { + for (let j = 1; j < n; ++j) { + dp[j] += dp[j - 1] + } + } + return dp[n - 1] +} + +// ---- test case ---- +console.time('uniquePaths') +console.log(uniquePaths(3,7)) +console.log(uniquePaths(3,2)) +console.log(uniquePaths(7,3)) +console.log(uniquePaths(3,3)) +console.timeEnd('uniquePaths') + +console.time('uniquePaths2') +console.log(uniquePaths2(3,7)) +console.log(uniquePaths2(3,2)) +console.log(uniquePaths2(7,3)) +console.log(uniquePaths2(3,3)) +console.timeEnd('uniquePaths2') diff --git a/Week_06/063uniquePathsWithObstacles.js b/Week_06/063uniquePathsWithObstacles.js new file mode 100644 index 00000000..cf5f6f0e --- /dev/null +++ b/Week_06/063uniquePathsWithObstacles.js @@ -0,0 +1,41 @@ +/** + * https://leetcode-cn.com/problems/unique-paths-ii/ + * 63. 不同路径 II | medium + * + * dp[i][j] = dp[i + 1][j] + dp[i][j + 1] + * + */ + +// 障碍物处 dp[j] = 0 +const uniquePathsWithObstacles = function(A) { + if (!Array.isArray(A) || A.length < 1 || + !Array.isArray(A[0]) || A[0].length < 1) return 0 + + const m = A.length + const n = A[0].length + const lastObsIdx = A[m - 1].lastIndexOf(1) + const dp = Array(lastObsIdx + 1).fill(0) + .concat(Array(n - lastObsIdx - 1).fill(1)) // 初始化最下行(最后一个障碍物前全是零,后面是1) + + for (let i = m - 2; i >= 0; --i) { + for (let j = n - 1; j >= 0; --j) { + if (A[i][j] === 1) { // 障碍物 + dp[j] = 0 + } else if (j === n - 1) { // 最右列(如果该列下面有障碍物,则为零) + dp[j] = dp[j] === 0 ? 0 : 1 + } else { + dp[j] += dp[j + 1] + } + } + } + return dp[0] +} + + +// ---- test case ---- +console.time('uniquePathsWithObstacles') +console.log(uniquePathsWithObstacles([[0,0,0],[0,1,0],[0,0,0]])) +console.log(uniquePathsWithObstacles([[0,1],[0,0]])) +console.log(uniquePathsWithObstacles([[0,0],[0,1]])) +console.log(uniquePathsWithObstacles([[0,0],[1,1],[0,0]])) +console.timeEnd('uniquePathsWithObstacles') diff --git a/Week_06/064minPathSum.js b/Week_06/064minPathSum.js new file mode 100644 index 00000000..f932a069 --- /dev/null +++ b/Week_06/064minPathSum.js @@ -0,0 +1,31 @@ +/** + * https://leetcode-cn.com/problems/minimum-path-sum/ + * 64. 最小路径和 | medium + * + * f(i, j) = A[i][j] + min(f(i-1, j) + f(i, j-1)) + */ + +// O(mn) O(n) +const minPathSum = function(A) { + if (!Array.isArray(A) || A.length < 1 || + !Array.isArray(A[0]) || A[0].length < 1) return 0 + + const m = A.length, n = A[0].length + const dp = A[0].slice() + for (let j = 1; j < n; ++j) { + dp[j] += dp[j - 1] // 首行(从左到右累加) + } + for (let i = 1; i < m; ++i) { + dp[0] += A[i][0] // 最左列 + for (let j = 1; j < n; ++j) { + dp[j] = A[i][j] + Math.min(dp[j], dp[j - 1]) + } + } + return dp[n - 1] +} + +// ---- test case ---- +console.time('minPathSum') +console.log(minPathSum([[1,3,1],[1,5,1],[4,2,1]])) +console.log(minPathSum([[1,2,3],[4,5,6]])) +console.timeEnd('minPathSum') diff --git a/Week_06/072minDistance.js b/Week_06/072minDistance.js new file mode 100644 index 00000000..3dc03178 --- /dev/null +++ b/Week_06/072minDistance.js @@ -0,0 +1,4 @@ +/** + * https://leetcode-cn.com/problems/edit-distance/ + * 72. 编辑距离 | hard + */ diff --git a/Week_06/076minWindow.js b/Week_06/076minWindow.js new file mode 100644 index 00000000..f82789db --- /dev/null +++ b/Week_06/076minWindow.js @@ -0,0 +1,57 @@ +/** + * https://leetcode-cn.com/problems/minimum-window-substring/ + * 76. 最小覆盖子串 + */ + +var minWindow = function(s, t) { + if (!s || !t) return ''; + + // 用 need 统计字符串 t + const need = new Map(); + for (const ch of t) { + const cnt = (need.get(ch) || 0) + 1; + need.set(ch, cnt); + } + + let start = 0, minSize = Infinity, needCnt = t.length; + for (let i = j = 0; j < s.length; ++j) { + // Step1: 右边界右移,窗口增加元素 + const jChar = s[j]; + if (need.has(jChar)) { + if (need.get(jChar) > 0) { // !! 这个 if 很关键,减到零之后,就不管了 + --needCnt; + } + need.set(jChar, need.get(jChar) - 1); // 需求被满足了,减少1 + } + + // Step2: 如果需求全部满足了,左边界右移,窗口减少元素,尝试寻找最短 str + if (needCnt === 0) { + let iChar = s[i]; + while (!need.has(iChar) || need.get(iChar) < 0) { // 有富余,尽管移 + if (need.has(iChar)) { + need.set(iChar, need.get(iChar) + 1); + // ++needCnt; + } + ++i; + iChar = s[i]; + } + // console.log(i, j, start, minSize, s.slice(start, start + minSize), need, needCnt) + if (j - i + 1 < minSize) { // 找到了满足条件的 + minSize = j - i + 1; + start = i; + } + // Step3: 左边界再多右移 1 位,让窗口不满足条件 + need.set(iChar, need.get(iChar) + 1); + ++needCnt; + ++i; + } + } + return minSize === Infinity ? '' : s.slice(start, start + minSize); +}; + +// ----- test ----- +// console.log(minWindow('ADOBECODEBANC', 'ABC')); // BANC +// console.log(minWindow('DOABECODEBANC', 'ABC')); // BANC +// console.log(minWindow('a', 'a')); // a +// console.log(minWindow('a', 'aa')); // '' +console.log(minWindow('bba', 'ab')); // 'ba' diff --git a/Week_06/085maximRectangle.js b/Week_06/085maximRectangle.js new file mode 100644 index 00000000..8c18993f --- /dev/null +++ b/Week_06/085maximRectangle.js @@ -0,0 +1,24 @@ +/** + * https://leetcode-cn.com/problems/maximal-rectangle/ + * 85. 最大矩形 | hard + * + */ + +// +const maximalRectangle = function(A) { + +} + +// ---- test case ---- +console.log(maximalRectangle( + [ + ["1","0","1","0","0"], + ["1","0","1","1","1"], + ["1","1","1","1","1"], + ["1","0","0","1","0"] + ] +)) +console.log(maximalRectangle([])) +console.log(maximalRectangle([["0"]])) +console.log(maximalRectangle([["1"]])) +console.log(maximalRectangle([["0","0"]])) diff --git a/Week_06/091numDecodings.js b/Week_06/091numDecodings.js new file mode 100644 index 00000000..a637aba0 --- /dev/null +++ b/Week_06/091numDecodings.js @@ -0,0 +1,32 @@ +/** + * https://leetcode-cn.com/problems/decode-ways/ + * 91. 解码方法 | medium + * + */ + +// O(n) 向前回头看一步 & 回头看两步 +const numDecodings = function(s) { + if (typeof s !== 'string' || !/^\d*$/.test(s)) return 0 + const n = s.length, dp = Array(n + 1).fill(0) + dp[0] = 1 + dp[1] = s[0] === '0' ? 0 : 1 + for (let i = 2; i <= n; ++i) { + const first = Number(s[i - 1]) + const second = Number(s[i - 2] + s[i - 1]) + if (first >= 1 && first <= 9) { + dp[i] += dp[i - 1] + } + if (second >= 10 && second <= 26) { + dp[i] += dp[i - 2] + } + } + return dp[n] +} + +// ---- test case ---- +console.log(numDecodings('12')) +console.log(numDecodings('226')) +console.log(numDecodings('0')) +console.log(numDecodings('06')) +console.log(numDecodings('11111')) +console.log(numDecodings('10011')) diff --git a/Week_06/1143longestCommonSubsequence.js b/Week_06/1143longestCommonSubsequence.js new file mode 100644 index 00000000..fce2cf05 --- /dev/null +++ b/Week_06/1143longestCommonSubsequence.js @@ -0,0 +1,38 @@ +/** + * https://leetcode-cn.com/problems/longest-common-subsequence/ + * 1143. 最长公共子序列 | medium + */ + +/* +情况1: s1[-1] == s2[-1] +-> f(i, j) = f(i - 1, j - 1) + 1 +情况2: s1[-1] != s2[-1] +-> f(i, j) = max(f(i - 1, j), f(i, j - 1)) +*/ + +// !!! 因为要用到dp[i-1][j-1]的值,dp须开二维数组 +const longestCommonSubsequence = function(s1, s2) { + if (typeof s1 !== 'string' || typeof s2 !== 'string' || !s1.length || !s2.length) return 0 + const m = s1.length, n = s2.length + const dp = Array(m + 1).fill(0).map( + _ => Array(n + 1).fill(0)) + + for (let i = 1; i <= m; ++i) { + for (let j = 1; j <= n; ++j) { + if (s1[i - 1] === s2[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] +} + +// ---- test case ---- +console.time('longestCommonSubsequence') +console.log(longestCommonSubsequence('abcde', 'ace')) +console.log(longestCommonSubsequence('abc', 'abc')) +console.log(longestCommonSubsequence('abc', 'def')) +console.log(longestCommonSubsequence("abcba", "abcbcba")) +console.timeEnd('longestCommonSubsequence') diff --git a/Week_06/120minimumTotal.js b/Week_06/120minimumTotal.js new file mode 100644 index 00000000..b7163ded --- /dev/null +++ b/Week_06/120minimumTotal.js @@ -0,0 +1,41 @@ +/** + * https://leetcode-cn.com/problems/triangle/ + * 120. 三角形最小路径和 | medium + * f(i, j) = min(f(i+1, j), f(i+1, j+1)) + A[i, j] + */ + +// O(mn) O(n) +function minimumTotal(A) { + const m = A.length, n = A[m - 1].length + const dp = A[m - 1].slice() + for (let i = m - 2; i >= 0; --i) { + for (let j = 0; j <= i; ++j) { + dp[j] = A[i][j] + Math.min(dp[j], dp[j + 1]) + } + } + return dp[0] +} + +// ~等边三角形 +function minimumTotal(triangle) { + const n = triangle.length + const dp = triangle[n - 1].slice() + for(let i = n - 2; i >= 0; --i) { + for (let j = 0; j <= i; ++j) { + dp[j] = triangle[i][j] + Math.min(dp[j], dp[j + 1]) + } + } + return dp[0] +} + +// ---- test case ---- +console.log(minimumTotal([ + [ 2 ], + [ 3, 4 ], + [ 6, 5, 7 ], + [4, 1, 8, 3] +])) + +console.log(minimumTotal([ + [-10] +])) diff --git a/Week_06/152maxProduct.js b/Week_06/152maxProduct.js new file mode 100644 index 00000000..b44d0973 --- /dev/null +++ b/Week_06/152maxProduct.js @@ -0,0 +1,24 @@ +/** + * https://leetcode-cn.com/problems/maximum-product-subarray/ + * 152. 乘积最大子数组 | medium + * + */ + +// 保持最大值和最小值 +const maxProduct = function(A) { + if (!Array.isArray(A) || A.length < 1) return + const n = A.length + let min = max = res = A[0] + for (let i = 1; i < n; ++i) { + if (A[i] < 0) [min, max] = [max, min] // 交换 + max = Math.max(max * A[i], A[i]) + min = Math.min(min * A[i], A[i]) + res = Math.max(res, max) + } + return res +} + +// ---- test case ---- +console.log(maxProduct([2 , 3, -2, 4])) +console.log(maxProduct([2 , 3, -2, 4, -4])) +console.log(maxProduct([-2, 0, -1])) diff --git a/Week_06/198rob.js b/Week_06/198rob.js new file mode 100644 index 00000000..0fff87fa --- /dev/null +++ b/Week_06/198rob.js @@ -0,0 +1,33 @@ +/** + * https://leetcode-cn.com/problems/house-robber/ + * 198. 打家劫舍 | medium + * + * f(i) = max(f(i-1), f(i-2) + A[i]) + */ + +const rob1 = function(A) { + if (!Array.isArray(A) || A.length < 1) return 0 + if (A.length < 3) return Math.max(...A) + const n = A.length, dp = new Array(n).fill(0) + dp[0] = A[0], dp[1] = Math.max(A[0], A[1]) + for (let i = 2; i < n; ++i) { + dp[i] = Math.max(dp[i - 1], dp[i - 2] + A[i]) + } + return dp[n - 1] +} + +// 优化空间复杂度为 O(1) +const rob = function(A) { + if (!Array.isArray(A) || A.length < 1) return 0 + let pre = 0, now = 0 + for (const a of A) { + ;[pre, now] = [now, Math.max(pre + a, now)] + } + return now +} + +// ---- test case ---- +console.log(rob1([1,2,3,1])) +console.log(rob1([2,7,9,3,1])) +console.log(rob([1,2,3,1])) +console.log(rob([2,7,9,3,1])) diff --git a/Week_06/213rob.js b/Week_06/213rob.js new file mode 100644 index 00000000..fc8289ed --- /dev/null +++ b/Week_06/213rob.js @@ -0,0 +1,34 @@ +/** + * https://leetcode-cn.com/problems/house-robber-ii/ + * 213. 打家劫舍 II | medium + * + * 环形~ + * + * 1. 不偷第一个 + * 2. 偷第一个 -> 不能偷最后一个 + * 3. 取max + */ + +// O(n) +const rob = function(A) { + if (!Array.isArray(A) || A.length < 1) return 0 + if (A.length < 4) return Math.max(...A) + + const myRob = (A) => { + const n = A.length + const dp = new Array(n).fill(0) + dp[0] = A[0] + dp[1] = Math.max(A[0], A[1]) + for (let i = 2; i < n; ++i) { + dp[i] = Math.max(dp[i - 1], dp[i - 2] + A[i]) + } + return dp[n - 1] + } + + return Math.max(myRob(A.slice(0, A.length - 1)), myRob(A.slice(1))) +} + +// ---- test case ---- +console.log(rob([2,3,2])) +console.log(rob([1,2,3,1])) +console.log(rob([0])) diff --git a/Week_06/221maximalSquare.js b/Week_06/221maximalSquare.js new file mode 100644 index 00000000..d3226592 --- /dev/null +++ b/Week_06/221maximalSquare.js @@ -0,0 +1,42 @@ +/** + * https://leetcode-cn.com/problems/maximal-square/ + * 221. 最大正方形 | medium + * + * f(i, j) 表示当前节点可往左上角扩散的正方形边长 + * f(i, j) = min{ f(i-1, j - 1), f(i, j -1), f(i - 1, j)} + 1 + */ + +const maximalSquare = function(A) { + if (!Array.isArray(A) || A.length < 1 || + !Array.isArray(A[0]) || A[0].length < 1) return 0 + + const m = A.length, n = A[0].length + const dp = Array(m + 1).fill(0) + .map(_ => Array(n + 1).fill(0)) + let max = 0 + for (let i = 1; i <= m; ++i) { + for (let j = 1; j <= n; ++j) { + if (A[i - 1][j - 1] != 0) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1 + max = Math.max(dp[i][j], max) + } + } + } + return max * max +} + +// ---- test case ---- +const m1 = [ + ["1","0","1","0","0"], + ["1","0","1","1","1"], + ["1","1","1","1","1"], + ["1","0","0","1","0"] +] +console.log(maximalSquare(m1)) +const m2 = [ + ["0","1"], + ["1","0"] +] +console.log(maximalSquare(m2)) +const m3 = [["0"]] +console.log(maximalSquare(m3)) diff --git a/Week_06/312maxCoins.js b/Week_06/312maxCoins.js new file mode 100644 index 00000000..adfc94d8 --- /dev/null +++ b/Week_06/312maxCoins.js @@ -0,0 +1,4 @@ +/** + * https://leetcode-cn.com/problems/burst-balloons/ + * 312. 戳气球 | hard + */ diff --git a/Week_06/322coinChange.js b/Week_06/322coinChange.js new file mode 100644 index 00000000..9dd500bf --- /dev/null +++ b/Week_06/322coinChange.js @@ -0,0 +1,35 @@ +/** + * https://leetcode-cn.com/problems/coin-change/ + * 322. 零钱兑换 | medium + * + * f(amount) = min((f(amount - coin) + 1) for coin in coins) + * O(mn) O(n) + * + * 11 -> [1, 2, 5] + * amount 0 1 2 3 4 5 6 7 8 9 10 11 + * 0 1 1 2 2 1 2 2 3 3 2 2 + */ + +const coinChange = function(coins, amount) { + if (!Array.isArray(coins) || coins.length < 1 || amount < 0) return -1 + if (amount === 0) return 0 + const UPBOUND = amount + 1 + const dp = Array(amount + 1).fill(UPBOUND) + dp[0] = 0 + for (let i = 1; i <= amount; ++i) { + for (const coin of coins) { + if (i - coin >= 0) { + dp[i] = Math.min(dp[i], dp[i - coin] + 1) + } + } + } + return dp[amount] === UPBOUND ? -1 : dp[amount] +} + +// ---- test case ---- +console.log(coinChange([1,2,5], 11)) +console.log(coinChange([2], 3)) +console.log(coinChange([1], 0)) +console.log(coinChange([1], 1)) +console.log(coinChange([1], 2)) +console.log(coinChange([186,419,83,408], 6249)) diff --git a/Week_06/363maxSumSubmatrix.js b/Week_06/363maxSumSubmatrix.js new file mode 100644 index 00000000..b6c64ee2 --- /dev/null +++ b/Week_06/363maxSumSubmatrix.js @@ -0,0 +1,44 @@ +/** + * https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/ + * 363. 矩形区域不超过 K 的最大数值和 | hard + * + */ + +const maxSumSubmatrix = function (matrix, k) { + const row = matrix.length, + col = matrix[0].length; + let result = -Infinity; + for (let i = 0; i < col; i++) { + let rowSum = Array(row).fill(0); + for (let j = i; j < col; j++) { + let sum = 0, + max = -Infinity; + for (let r = 0; r < row; r++) { + rowSum[r] += matrix[r][j]; + if (sum < 0) sum = 0; + sum += rowSum[r]; + max = Math.max(max, sum) + } + if (max <= k) result = Math.max(result, max) + else { + max = -Infinity; + for (let m = 0; m < row; m++) { + sum = 0; + for (let n = m; n < row; n++) { + sum += rowSum[n]; + if (sum <= k) max = Math.max(max, sum) + } + } + result = Math.max(result, max) + } + if (result === k) return k; + } + } + return result; +} + +// ---- test case ---- +console.log([ + [1, 0, 1], + [0, -2, 3] +], 2) diff --git a/Week_06/403canCross.js b/Week_06/403canCross.js new file mode 100644 index 00000000..b26c8e35 --- /dev/null +++ b/Week_06/403canCross.js @@ -0,0 +1,4 @@ +/** + * https://leetcode-cn.com/problems/frog-jump/ + * 403. 青蛙过河 | hard + */ diff --git a/Week_06/410splitArray.js b/Week_06/410splitArray.js new file mode 100644 index 00000000..6f4e04e0 --- /dev/null +++ b/Week_06/410splitArray.js @@ -0,0 +1,4 @@ +/** + * https://leetcode-cn.com/problems/split-array-largest-sum/ + * 410. 分割数组的最大值 | hard + */ diff --git a/Week_06/552checkRecord.js b/Week_06/552checkRecord.js new file mode 100644 index 00000000..23ffe595 --- /dev/null +++ b/Week_06/552checkRecord.js @@ -0,0 +1,4 @@ +/** + * https://leetcode-cn.com/problems/student-attendance-record-ii/ + * 552. 学生出勤记录 II | hard + */ diff --git a/Week_06/621leastInterval.js b/Week_06/621leastInterval.js new file mode 100644 index 00000000..9e62e437 --- /dev/null +++ b/Week_06/621leastInterval.js @@ -0,0 +1,34 @@ +/** + * https://leetcode-cn.com/problems/task-scheduler/ + * 621. 任务调度器 | medium + * + */ + +// 后面的计算方法有点烧脑。。。 +const leastInterval = function(tasks, n) { + const startCodePoint = 'A'.codePointAt(0) + let counter = Array(26).fill(0) + let maxCnt = max = 0 + for (const task of tasks) { + const idx = task.codePointAt(0) - startCodePoint + counter[idx]++ + if (counter[idx] == max) maxCnt++ + else if (counter[idx] > max) { + max = counter[idx] + maxCnt = 1 + } + } + const emptySlots = (max - 1) * (n - (maxCnt - 1)) + const availTasks = tasks.length - max * maxCnt + const idles = Math.max(0, emptySlots - availTasks) + return tasks.length + idles; +} + +// ---- test case ---- +console.log(leastInterval( + ["A","A","A","B","B","B"], 2)) // A B x A B x A B x +console.log(leastInterval( + ["A","A","A","B","B","B"], 0)) +console.log(leastInterval( + ["A","A","A","A","A","A","B","C","D","E","F","G"], 2)) +// A B diff --git a/Week_06/647countSubstrings.js b/Week_06/647countSubstrings.js new file mode 100644 index 00000000..91d326e4 --- /dev/null +++ b/Week_06/647countSubstrings.js @@ -0,0 +1,26 @@ +/** + * https://leetcode-cn.com/problems/palindromic-substrings/ + * 647. 回文子串 | medium + * + */ + +const countSubstrings = function(s) { + if (!s.length) return 0 + let cnt = 0, n = s.length + + const help = (l, r) => { + while (l >= 0 && r < n && s[l--] === s[r++]) { + ++cnt + } + } + + for (let i = 0; i < n; ++i) { + help(i, i) + help(i, i + 1) + } + return cnt +} + +// ---- test case ---- +console.log(countSubstrings('abc')) +console.log(countSubstrings('aaa')) diff --git a/Week_06/887superEggDrop.js b/Week_06/887superEggDrop.js new file mode 100644 index 00000000..9ba23b58 --- /dev/null +++ b/Week_06/887superEggDrop.js @@ -0,0 +1,19 @@ +/** + * https://leetcode-cn.com/problems/super-egg-drop/ + * 887. 鸡蛋掉落 | hard + * + * M(k, n) = max(M(k - 1, part), M(k, n - part)) + 1 + * for part in range(1...n) + * + * TODO x + */ + +const superEggDrop = function(k, n) { + +} + +// ---- test case ---- +console.log(superEggDrop(1, 2)) +console.log(superEggDrop(2, 6)) +console.log(superEggDrop(3, 14)) + diff --git a/Week_06/README.md b/Week_06/README.md index 50de3041..70054192 100644 --- a/Week_06/README.md +++ b/Week_06/README.md @@ -1 +1,17 @@ -学习笔记 \ No newline at end of file +# 学习笔记 + +## 第12课:动态规划 + +**dp的关键点** + +1. 最优子结构 opt[n] = bestOf(opt[n-1], opt[n-2],...) +2. 储存中间状态 opt[i] +3. 递推公式(状态转移方程、dp方程) + + 🌰(Fib):f(i) = f(i-1) + f(i-2) + + 🌰(二维路径):f(i, j) = f(i+1, j) + f(i, j+1) + +**切题步骤** + +a. 重复性(分治) +b. 定义状态数组 +c. DP方程 diff --git a/Week_06/homework.md b/Week_06/homework.md new file mode 100644 index 00000000..282942c6 --- /dev/null +++ b/Week_06/homework.md @@ -0,0 +1,66 @@ +# 动态规划作业 + +## 1. 最小路径和(亚马逊、高盛集团、谷歌在半年内面试中考过) + ++ dp ++ [实现代码](./064minPathSum.js) + +## 2. 解码方法(亚马逊、Facebook、字节跳动在半年内面试中考过) + ++ dp ++ [实现代码](./091numDecodings.js) + +## 3. 最大正方形(华为、谷歌、字节跳动在半年内面试中考过) + ++ dp ++ [实现代码](./221maximalSquare.js) + +## 4. 任务调度器(Facebook 在半年内面试中常考) + ++ dp ++ [实现代码](./621leastInterval.js) + +## 5. 回文子串(Facebook、苹果、字节跳动在半年内面试中考过) + ++ dp ++ [实现代码](./647countSubstrings.js) + +## TODO 6. 最长有效括号(字节跳动、亚马逊、微软在半年内面试中考过) + ++ dp ++ [实现代码](./032longestValidParentheses.js) + +## TODO 7. 编辑距离(字节跳动、亚马逊、谷歌在半年内面试中考过) + ++ dp ++ [实现代码](./072minDistance.js) + +## TODO 8. 矩形区域不超过 K 的最大数值和(谷歌在半年内面试中考过) + ++ binarySearch + dp ++ [实现代码](./363maxSumSubmatrix.js) + +## TODO 9. 青蛙过河(亚马逊、苹果、字节跳动在半年内面试中考过) + ++ dp ++ [实现代码](./403canCross.js) + +## TODO 10. 分割数组的最大值(谷歌、亚马逊、Facebook 在半年内面试中考过) + ++ dp ++ [实现代码](./410splitArray.js) + +## TODO 11. 学生出勤记录 II (谷歌在半年内面试中考过) + ++ dp ++ [实现代码](./552checkRecord.js) + +## TODO 12. 最小覆盖子串(Facebook 在半年内面试中常考) + ++ dp ++ [实现代码](./076minWindow.js) + +## TODO 13. 戳气球(亚马逊在半年内面试中考过) + ++ dp ++ [实现代码](./312maxCoins.js) diff --git a/Week_07/022generateParenthesis.js b/Week_07/022generateParenthesis.js new file mode 100644 index 00000000..9a394637 --- /dev/null +++ b/Week_07/022generateParenthesis.js @@ -0,0 +1,25 @@ +/** + * https://leetcode-cn.com/problems/generate-parentheses/ + * 22. 括号生成 | medium + * + */ + +// DFS + prun +const generateParenthesis = function(n) { + const res = [] + const stack = [['(', 1, 0]] + while (stack.length > 0) { + const [curStr, cntL, cntR] = stack.pop() + if (cntL === n && cntR === n) res.push(curStr) + if (cntR < cntL) stack.push([curStr + ')', cntL, cntR + 1]) + if (cntL < n) stack.push([curStr + '(', cntL + 1, cntR]) + } + return res +} + +// ---- test case ---- +new Array(5) + .fill(0) + .map((_, idx) => { + console.log(idx, generateParenthesis(idx)) + }) diff --git a/Week_07/036isValidSudoku.js b/Week_07/036isValidSudoku.js new file mode 100644 index 00000000..47d58fd5 --- /dev/null +++ b/Week_07/036isValidSudoku.js @@ -0,0 +1,46 @@ +/** + * https://leetcode-cn.com/problems/valid-sudoku/ + * 36. 有效的数独 | medium + */ + +// O(1) +const isValidSudoku = function(A) { + for (let i = 0; i < 9; ++i) { + const row = new Set() + const col = new Set() + const box = new Set() + + for (let j = 0; j < 9; ++j) { + const rItem = A[i][j] + const cItem = A[j][i] + const bItem = A[3 * Math.floor(i / 3) + Math.floor(j / 3)][3 * (i % 3) + (j % 3)] + + if (rItem !== '.') { + if (row.has(rItem)) return false + row.add(rItem) + } + if (cItem !== '.') { + if (col.has(cItem)) return false + col.add(cItem) + } + if (bItem !== '.') { + if (box.has(bItem)) return false + box.add(bItem) + } + } + } + return true +} + +// ---- test case ---- +console.log(isValidSudoku([ + ["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 diff --git a/Week_07/037solveSudoku.js b/Week_07/037solveSudoku.js new file mode 100644 index 00000000..e0c7ecdd --- /dev/null +++ b/Week_07/037solveSudoku.js @@ -0,0 +1,79 @@ +/** + * https://leetcode-cn.com/problems/sudoku-solver/ + * 37. 解数独 | hard + */ + +// set 交集 +function intersection(set1, set2) { + return new Set([...set1].filter(x => set2.has(x))) +} + +function solveSudoku(A) { + const init = (n) => + Array(n).fill(0) + .map( + _ => + new Set( + Array(n).fill(0) + .map( + (_, i) => i + 1 + ) + ) + ) + const row = init(9) // 行剩余可用数字 + const col = init(9) // 列剩余可用数字 + const box = init(9) // 块剩余可用数字 + const empty = [] // 收集需填数位置 + for (let i = 0; i < 9; ++i) { + for (let j = 0; j < 9; ++j) { + if (A[i][j] === '.') { + empty.push([i, j]) + } else { + const val = Number(A[i][j]) + row[i].delete(val) + col[j].delete(val) + box[Math.floor(i / 3) * 3 + Math.floor(j / 3)].delete(val) + } + } + } + // console.log(row, col, box, empty) + backtrack(A, row, col, box, empty, 0) +} + +// 对于每一个 empty 坐标,试探所有 set 交集中的数值,没有路了就回溯 +function backtrack(A, row, col, box, empty, iter) { + if (iter === empty.length) return true + const [i, j] = empty[iter] + const b = Math.floor(i / 3) * 3 + Math.floor(j / 3) + const validSet = intersection(intersection(row[i], col[j]), box[b]) + // console.log("🚀validSet", i, j, validSet) + for (const val of validSet) { + // console.log("🚀", iter, ': ', i, j, val) + row[i].delete(val) + col[j].delete(val) + box[b].delete(val) + A[i][j] = String(val) + if (backtrack(A, row, col, box, empty, iter + 1)) { + return true + } + row[i].add(val) + col[j].add(val) + box[b].add(val) + } + return false +} + +// ---- test case ---- +const board = [ + ["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"], +] +solveSudoku(board) +console.log(board) diff --git a/Week_07/051solveNQueens.js b/Week_07/051solveNQueens.js new file mode 100644 index 00000000..eb8c430b --- /dev/null +++ b/Week_07/051solveNQueens.js @@ -0,0 +1,59 @@ +/** + * https://leetcode-cn.com/problems/n-queens/ + * 51. N 皇后 | hard + */ + +/** + * 构建结果表示形式 + * @param {Array} results 二维数组,每一项是一个可行解 + * @param {Number} n N皇后问题的N + * @returns {Array} 结果 + */ +function buildResultsBoard(results, n) { + const board = [] + results.forEach(res => { + const matrix = [] + res.forEach(i => { + const line = '.'.repeat(i) + 'Q' + '.'.repeat(n - i - 1) + matrix.push(line) + }) + board.push(matrix) + }) + return board +} + +// DFS +function solveNQueens(n) { + if (n < 1) return [[]] + const results = [] + const cols = new Set() // 竖 + const pies = new Set() // 撇 + const nas = new Set() // 捺 + + const dfs = (i, curState) => { + if (i >= n) { + results.push(curState) + return + } + for (let j = 0; j < n; ++j) { + if (cols.has(j) || pies.has(i + j) || nas.has(i - j)) continue + cols.add(j) + pies.add(i + j) + nas.add(i - j) + dfs(i + 1, curState.concat([j])) + cols.delete(j) + pies.delete(i + j) + nas.delete(i - j) + } + } + + dfs(0, []) + return buildResultsBoard(results, n) +} + +// ---- test case ---- +// console.log(buildResultsBoard([ +// [1, 3, 0, 2], +// [2, 0, 3, 1], +// ], 4)) +console.log(solveNQueens(4)) diff --git a/Week_07/070climbStairs.js b/Week_07/070climbStairs.js new file mode 100644 index 00000000..5e3b545c --- /dev/null +++ b/Week_07/070climbStairs.js @@ -0,0 +1,23 @@ +/** + * https://leetcode-cn.com/problems/climbing-stairs/ + * 70. 爬楼梯 + */ + +// DP +const climbStairs = function(n) { + if (n < 4) return n + let f1 = 2, f2 = 3, f3 + for(let i = 4; i <= n; ++i) { + f3 = f1 + f2 + f1 = f2 + f2 = f3 + } + return f3 +} + +// ---- test case ---- +new Array(20) + .fill(0) + .map((_, idx) => { + console.log(climbStairs(idx + 1)) + }) diff --git a/Week_07/1091shortestPathBinaryMatrix.js b/Week_07/1091shortestPathBinaryMatrix.js new file mode 100644 index 00000000..fac2159a --- /dev/null +++ b/Week_07/1091shortestPathBinaryMatrix.js @@ -0,0 +1,53 @@ +/** + * https://leetcode-cn.com/problems/shortest-path-in-binary-matrix/ + * 1091. 二进制矩阵中的最短路径 + * + * TODO A*解法更快 + */ + +// BFS, 有副作用 +const shortestPathBinaryMatrix = function(A) { + const n = A.length + if (A[0][0] === 1 || A[n - 1][n - 1] === 1) { + return -1 + } else if (n <= 2) { + return n + } + const dx = [0, 0, 1, -1, 1, -1, 1, -1] + const dy = [1, -1, 0, 0, 1, 1, -1, -1] + const queue = [[0, 0, 2]] + for (let idx = 0; idx < queue.length; ++idx) { + const [i, j, level] = queue[idx] + for (let dir = 0; dir < 8; ++dir) { + const x = i + dx[dir] + const y = j + dy[dir] + if (x >= 0 && x < n && + y >= 0 && y < n && + A[x][y] === 0) { + if (x === n - 1 && y === n - 1) return level + queue.push([x, y, level + 1]) + A[x][y] = 1 + } + } + } + return -1 +} + +// ---- test case ---- +const grid1 = [ + [0, 1], + [1, 0], +] +const grid2 = [ + [0, 0, 0], + [1, 1, 0], + [1, 1, 0], +] +const grid3 = [ + [1, 0, 0], + [1, 1, 0], + [1, 1, 0], +] +console.log(shortestPathBinaryMatrix(grid1)) // 2 +console.log(shortestPathBinaryMatrix(grid2)) // 4 +console.log(shortestPathBinaryMatrix(grid3)) // -1 diff --git a/Week_07/127ladderLength.js b/Week_07/127ladderLength.js new file mode 100644 index 00000000..9f1327df --- /dev/null +++ b/Week_07/127ladderLength.js @@ -0,0 +1,71 @@ +/** + * https://leetcode-cn.com/problems/word-ladder/ + * 127. 单词接龙 | hard + */ + +// 解法一:BFS + 回溯 + 剪枝 +const ladderLength1 = function(beginWord, endWord, wordList) { + if (wordList.indexOf(endWord) === -1) return 0 + const wordSet = new Set(wordList) + const queue = [[beginWord, 1]] + const alphas = [...'abcdefghijklmnopqrstuvwxyz'] + while (queue.length > 0) { + const [curWord, level] = queue.pop() + if (curWord === endWord) return level + for (let i = 0; i < curWord.length; ++i) { + for (const ch of alphas) { + if (ch === curWord[i]) continue // prun + const nextWord = curWord.slice(0, i) + ch + curWord.slice(i + 1) + if (wordSet.has(nextWord)) { + wordSet.delete(nextWord) + queue.unshift([nextWord, level + 1]) + } + } + } + } + return 0 +} + +// 解法二:双向BFS +const ladderLength = function(beginWord, endWord, wordList) { + if (wordList.indexOf(endWord) === -1) return 0 + const alphas = [...'abcdefghijklmnopqrstuvwxyz'] + const n = beginWord.length + const wordSet = new Set(wordList) + let frontSet = new Set([beginWord]) + let endSet = new Set([endWord]) + let level = 1 + while (frontSet.size > 0) { + ++level + const nextFrontSet = new Set() + for (curWord of frontSet) { + for (let i = 0; i < n; ++i) { + for (const ch of alphas) { + if (ch !== curWord[i]) { + nextWord = curWord.slice(0, i) + ch + curWord.slice(i + 1) + if (endSet.has(nextWord)) { + return level + } + if (wordSet.has(nextWord)) { + nextFrontSet.add(nextWord) + wordSet.delete(nextWord) + } + } + } + } + } + frontSet = nextFrontSet + if (frontSet.size > endSet.size) { // swap + const tmp = frontSet + frontSet = endSet + endSet = tmp + } + } + return 0 +} + +// ---- test case ---- +console.log(ladderLength('abc', 'def', ['dbc', 'bbc', 'dec', 'def', 'log', 'cog'])) // 4 +console.log(ladderLength('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log', 'cog'])) // 5 +console.log(ladderLength('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log'])) // 0 +console.log(ladderLength("qa","sq",["si","go","se","cm","so","ph","mt","db","mb","sb","kr","ln","tm","le","av","sm","ar","ci","ca","br","ti","ba","to","ra","fa","yo","ow","sn","ya","cr","po","fe","ho","ma","re","or","rn","au","ur","rh","sr","tc","lt","lo","as","fr","nb","yb","if","pb","ge","th","pm","rb","sh","co","ga","li","ha","hz","no","bi","di","hi","qa","pi","os","uh","wm","an","me","mo","na","la","st","er","sc","ne","mn","mi","am","ex","pt","io","be","fm","ta","tb","ni","mr","pa","he","lr","sq","ye"])) // 5 diff --git a/Week_07/433minMutation.js b/Week_07/433minMutation.js new file mode 100644 index 00000000..9aaef8c3 --- /dev/null +++ b/Week_07/433minMutation.js @@ -0,0 +1,70 @@ +/** + * https://leetcode-cn.com/problems/minimum-genetic-mutation/ + * 433. 最小基因变化 | medium + * + */ + +// 解法一:BFS + 回溯 + 剪枝 +const minMutation1 = function(start, end, bank) { + const bankSet = new Set(bank) + const genes = [...'ACGT'] + const queue = [[start, 0]] + while (queue.length > 0) { + const [curStr, level] = queue.pop() + if (curStr === end) return level + for (let i = 0; i < curStr.length; ++i) { + for (const ch of genes) { + if (ch === curStr[i]) continue + const nextStr = curStr.slice(0, i) + ch + curStr.slice(i + 1) + if (bankSet.has(nextStr)) { + bankSet.delete(nextStr) + queue.unshift([nextStr, level + 1]) + } + } + } + } + return -1 +} + +// 解法二:双向BFS +const minMutation = function(beginWord, endWord, wordList) { + if (wordList.indexOf(endWord) === -1) return -1 + const alphas = [...'ACGT'] + const n = beginWord.length + const wordSet = new Set(wordList) + let frontSet = new Set([beginWord]) + let endSet = new Set([endWord]) + let level = 0 + while (frontSet.size > 0) { + ++level + const nextFrontSet = new Set() + for (curWord of frontSet) { + for (let i = 0; i < n; ++i) { + for (const ch of alphas) { + if (ch !== curWord[i]) { + nextWord = curWord.slice(0, i) + ch + curWord.slice(i + 1) + if (endSet.has(nextWord)) { + return level + } + if (wordSet.has(nextWord)) { + nextFrontSet.add(nextWord) + wordSet.delete(nextWord) + } + } + } + } + } + frontSet = nextFrontSet + if (frontSet.size > endSet.size) { // swap + const tmp = frontSet + frontSet = endSet + endSet = tmp + } + } + return -1 +} + +// ---- test case ---- +console.log(minMutation('AACCGGTT', 'AACCGGTA', ['AACCGGTA'])) // 1 +console.log(minMutation('AACCGGTT', 'AAACGGTA', ['AACCGGTA', 'AACCGCTA', 'AAACGGTA'])) // 2 +console.log(minMutation('AAAAACCC', 'AACCCCCC', ['AAAACCCC', 'AAACCCCC', 'AACCCCCC'])) // 3 diff --git a/Week_07/773slidingPuzzle.js b/Week_07/773slidingPuzzle.js new file mode 100644 index 00000000..59b11872 --- /dev/null +++ b/Week_07/773slidingPuzzle.js @@ -0,0 +1,70 @@ +/** + * https://leetcode-cn.com/problems/sliding-puzzle/ + * 773. 滑动谜题 | hard + * + * 0, 1, 2 + * 3, 4, 5 + */ + +function swapArr(arr, i, j) { + if (i !== j && + i >= 0 && i < arr.length && + j >= 0 && j < arr.length) { + const tmp = arr[i] + arr[i] = arr[j] + arr[j] = tmp + } +} + +// BFS [[str, idxOf0]...] +const slidingPuzzle = function(A) { + const moves = [ + [1, 3], + [0, 2, 4], + [1, 5], + [0, 4], + [1, 3, 5], + [2, 4], + ] + const used = new Set() + const str = A[0].join('') + A[1].join('') + let queue = [[str, str.indexOf('0')]] + let cnt = 0 + while (queue.length > 0) { + const nextQueue = [] + for (let idx = 0; idx < queue.length; ++idx) { + const [s, i] = queue[idx] + used.add(s) + if (s === '123450') return cnt + const curArr = s.split('') + for (const move of moves[i]) { + const nextArr = [].concat(curArr) + swapArr(nextArr, move, i) + const nextStr = nextArr.join('') + if (!used.has(nextStr)) { + nextQueue.push([nextStr, move]) + } + } + } + ++cnt + queue = nextQueue + } + return -1 +} + +// ---- test case ---- +const A1 = [ + [1, 2, 3], + [4, 0, 5], +] +const A2 = [ + [1, 2, 3], + [5, 4, 0], +] +const A3 = [ + [4, 1, 2], + [5, 0, 3], +] +console.log(slidingPuzzle(A1)) // 1 +console.log(slidingPuzzle(A2)) // -1 +console.log(slidingPuzzle(A3)) // 5 diff --git a/Week_07/README.md b/Week_07/README.md index 50de3041..662fb9e4 100644 --- a/Week_07/README.md +++ b/Week_07/README.md @@ -1 +1,139 @@ -学习笔记 \ No newline at end of file +# 学习笔记 + +```js +// BFS 手动维护队列 +const bfs = (root) => { + const result = [], queue = [root] + while (queue.length > 0) [ + const line = [], n = queue.length + for (let i = 0; i < n; ++i) { + const node = queue.pop() + line.push(node.val) + if (node.left) queue.unshift(node.left) + if (node.right) queue.unshift(node.right) + } + result.push(line) + ] + return result +} +// DFS 手动维护栈 +const dfs = (root) => { + const result = [], stack = [root] + while (stack.length > 0) { + const node = stack.pop() + res.push(node.val) + if (node.right) stack.push(node.right) + if (node.left) stack.push(node.left) + } + return res +} +``` + +## 第13课:高级搜索 + +### 初级搜索 + +1. 朴素搜索 +2. 优化方式:不重复(figonacci),剪枝(生成括号问题) +3. 搜索方向:DFS、BFS、双向搜索、启发式搜索 + +### 剪枝 + +尽早删除无效分支,加速搜索 + +### 双向BFS + ++ two ended search + +### 启发式搜索 A* + +```python +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) +``` + +```python +def AstarSearch(graph, start, end): + 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) +``` + +**估价函数** + +启发式函数:h(n),它用来评价哪些节点最有希望的是一个我们要找的节点,h(n)会返回一个非负实数,也可以认为是从节点n的目标节点路径的估计成本。 + +启发式函数是一种告知搜索节点方向的方法。它提供了一种明智的方法来猜测哪个邻居节点会导向一个目标。 + + +```js +class SortedArray { + constructor(data, compare) { + this.data = data + this.compare = compare + } + // 每次取最小值 + take() { + let minIdx = 0, min = this.data[0] + for(let i = 0; i < this.data.length; ++i) { + if (this.compare(min, this.data[i]) > 0) { + min = this.data[i] + minIdx = i + } + } + this.data[minIdx] = this.data[this.data.length - 1] + this.data.push() + return min + } + insert(value) { + this.data.push(value) + } + get length() { + return this.data.length + } +} + +function aStarSearch (graph, start, end) { + // graph 使用二维数组来存储数据 + let collection = new SortedArray([start], (p1, p2) => distance(p1) - distance(p2)) + while (collection.length > 0) { + let [x, y] = collection.take() + if (x === end[0] && y === end[1]) { + return true + } + insert([x - 1, y]) + insert([x + 1, y]) + insert([x, y - 1]) + insert([x, y + 1]) + } + return false + + function distance([x, y]) { + return (x - end[0]) ** 2 - (y - end[1]) ** 2 + } + + function insert([x, y]) { + if (graph[x][y] !== 0) return + if (x < 0 || x >= graph[0].length || + y < 0 || y >= graph.length) return + graph[x][y] = 2 + collection.insert([x, y]) + } +} +``` diff --git a/Week_07/homework.md b/Week_07/homework.md new file mode 100644 index 00000000..97517a2f --- /dev/null +++ b/Week_07/homework.md @@ -0,0 +1,50 @@ +# 作业 + +## 一、剪枝 + +### 爬楼梯(阿里巴巴、腾讯、字节跳动在半年内面试常考) + ++ [代码](./070climbStairs.js) + +### 括号生成(亚马逊、Facebook、字节跳动在半年内面试中考过) + ++ [代码](./022generateParenthesis.js) + +### N 皇后(亚马逊、苹果、字节跳动在半年内面试中考过) + ++ DFS ++ [代码](./051solveNQueens.js) + +TODO 位运算 + +### 有效的数独(亚马逊、苹果、微软在半年内面试中考过) + ++ [代码](./036isValidSudoku.js) + +### 解数独(亚马逊、华为、微软在半年内面试中考过) + ++ [代码](./037solveSudoku.js) + +## 二、双向BFS + +### 单词接龙(亚马逊、Facebook、谷歌在半年内面试中考过) + ++ [代码](./127ladderLength.js) + +### 最小基因变化(谷歌、Twitter、腾讯在半年内面试中考过) + ++ [代码](./433minMutation.js) + +## 三、启发式搜索 +### 1091. 二进制矩阵中的最短路径 + ++ 解法一:DP ++ 解法二:BFS ++ 解法三:A* ++ [代码](./1091shortestPathBinaryMatrix.js) + +### 773. 滑动谜题 + ++ 解法一:BFS ++ 解法二:A* ++ [代码](./773slidingPuzzle.js) diff --git a/Week_08/051solveNQueens.js b/Week_08/051solveNQueens.js new file mode 100644 index 00000000..96999bb2 --- /dev/null +++ b/Week_08/051solveNQueens.js @@ -0,0 +1,60 @@ +/** + * https://leetcode-cn.com/problems/n-queens/ + * 51. N 皇后 | hard + * + */ + +function buildResult (res, n) { + const graphes = [] + res.forEach(state => { + const graph = [] + state.forEach(bit => { + let lineStr = '' + for (let i = 0; i < n; ++i) { + const lastBit = bit & 1 + bit = bit >> 1 + lineStr = (lastBit ? 'Q' : '.') + lineStr + } + graph.push(lineStr) + }) + graphes.push(graph) + }) + return graphes +} + +function solveNQueens(n) { + if (n < 1) return [] + + /** + * DFS + * @param {number} row 当前遍历到第几行 + * @param {number} col 不能用的列 + * @param {number} pie 不能用的撇 + * @param {number} na 不能用的捺 + * 副作用:修改 res + */ + const dfs = (row, col, pie, na, state) => { + if (row >= n) { + res.push(state) + return + } + let bits = (~(col | pie | na)) & ((1 << n) - 1) // 取得所有得空位 + while (bits) { + const pos = bits & (-bits) // 最低可用位 + // console.log("🚀", + // bits.toString(2).padStart(4, '0'), + // pos.toString(2).padStart(4, '0') + // ) + bits = bits & (bits - 1) + dfs(row + 1, col | pos, (pie | pos) << 1, (na | pos) >> 1, state.concat([pos])) + } + } + + let res = [] + dfs(0, 0, 0, 0, []) + // console.log("🚀🚀🚀", res) + return buildResult(res, n) +} + +// ---- test case ---- +console.log(solveNQueens(4)) diff --git a/Week_08/052totalNQueens.js b/Week_08/052totalNQueens.js new file mode 100644 index 00000000..a5dc1bf4 --- /dev/null +++ b/Week_08/052totalNQueens.js @@ -0,0 +1,43 @@ +/** + * https://leetcode-cn.com/problems/n-queens-ii/ + * 52. N皇后 II | hard + * + */ + +// 位运算 +function totalNQueens(n) { + if (n < 1) return 0 + + /** + * DFS + * @param {number} row 当前遍历到第几行 + * @param {number} col 不能用的列 + * @param {number} pie 不能用的撇 + * @param {number} na 不能用的捺 + * 副作用:修改 cnt 值 + */ + const dfs = (row, col, pie, na) => { + if (row >= n) { + ++cnt + console.log("🚀🚀🚀", col, pie, na) + return + } + let bits = (~(col | pie | na)) & ((1 << n) - 1) // 取得所有得空位 + while (bits) { + const pos = bits & (-bits) // 最低可用位 + console.log("🚀", + bits.toString(2).padStart(4, '0'), + pos.toString(2).padStart(4, '0') + ) + bits = bits & (bits - 1) + dfs(row + 1, col | pos, (pie | pos) << 1, (na | pos) >> 1) + } + } + + let cnt = 0 + dfs(0, 0, 0, 0) + return cnt +} + +// ---- test case ---- +console.log(totalNQueens(4)) diff --git a/Week_08/130solve.js b/Week_08/130solve.js new file mode 100644 index 00000000..c9704036 --- /dev/null +++ b/Week_08/130solve.js @@ -0,0 +1,11 @@ +/** + * https://leetcode-cn.com/problems/surrounded-regions/ + * 130. 被围绕的区域 | medium + * + */ + +function solve(A) { + +} + +// ---- test case ---- diff --git a/Week_08/190reverseBits.js b/Week_08/190reverseBits.js new file mode 100644 index 00000000..ea28bde9 --- /dev/null +++ b/Week_08/190reverseBits.js @@ -0,0 +1,27 @@ +/** + * https://leetcode-cn.com/problems/reverse-bits/ + * 190. 颠倒二进制位 + */ + +// O(logn), 每次把最低位挪给 res +function reverseBits(n) { + let res = 0, loops = 32 + while (--loops >= 0) { + res = (res << 1) + (n & 1) + n = n >> 1 + } + return res >>> 0 // 无符号右移 +} + +// ---- test case ---- +let str1 = '00000010100101000001111010011100' +let str2 = '11111111111111111111111111111101' +let ret1 = reverseBits(parseInt(str1, 2)) +let ret2 = reverseBits(parseInt(str2, 2)) + +console.log(str1) +console.log(ret1.toString(2).padStart(32, '0')) + + +console.log(str2) +console.log(ret2.toString(2)) diff --git a/Week_08/191hammingWeight.js b/Week_08/191hammingWeight.js new file mode 100644 index 00000000..7cceb402 --- /dev/null +++ b/Week_08/191hammingWeight.js @@ -0,0 +1,27 @@ +/** + * https://leetcode-cn.com/problems/number-of-1-bits/ + * 191. 位1的个数 + * + */ + +// 清除末位零 n & (n - 1) +function hammingWeight(n) { + let cnt = 0 + while (n !== 0) { + // console.log("🚀", (n >>> 0).toString(2)) + n = n & (n - 1) + ++cnt + } + return cnt +} + +// ---- test case ---- +console.log(hammingWeight( + parseInt('00000000000000000000000000001011', 2) +)) // 3 +console.log(hammingWeight( + parseInt('00000000000000000000000010000000', 2) +)) // 1 +console.log(hammingWeight( + parseInt('11111111111111111111111111111101', 2) +)) // 31 diff --git a/Week_08/200numIslands.js b/Week_08/200numIslands.js new file mode 100644 index 00000000..62344a66 --- /dev/null +++ b/Week_08/200numIslands.js @@ -0,0 +1,27 @@ +/** + * https://leetcode-cn.com/problems/number-of-islands/ + * 200. 岛屿数量 | medium + * + */ + +function numIslands(A) { + +} + + +// ---- test case ---- +const grid1 = [ + ['1', '1', '1', '1', '0'], + ['1', '1', '0', '1', '0'], + ['1', '1', '0', '0', '0'], + ['0', '0', '0', '0', '0'], +] +const grid2 = [ + ['1', '1', '0', '0', '0'], + ['1', '1', '0', '0', '0'], + ['0', '0', '1', '0', '0'], + ['0', '0', '0', '1', '1'], +] +console.log(numIslands(grid1)) +console.log(numIslands(grid2)) +console.log(numIslands([[]])) diff --git a/Week_08/208trie.js b/Week_08/208trie.js new file mode 100644 index 00000000..11af5c33 --- /dev/null +++ b/Week_08/208trie.js @@ -0,0 +1,50 @@ +/** + * https://leetcode-cn.com/problems/implement-trie-prefix-tree/ + * 208. 实现 Trie (前缀树) | medium + * + */ + +class Trie { + constructor() { + this.root = {} + } + + insert(word) { + let node = this.root + for (const ch of word) { + if (node[ch] == null) node[ch] = {} + node = node[ch] + } + node.isWord = true + } + + _traverse (word) { + let node = this.root + for (const ch of word) { + node = node[ch] + if (node == null) return null + } + return node + } + + search (word) { + const node = this._traverse(word) + return node != null && node.isWord === true + } + + startsWith (prefix) { + const node = this._traverse(prefix) + return !!node + } +} + +// ---- test case ---- +var trie = new Trie() +trie.insert("apple") +console.log(trie.search("bbb")) +console.log(trie.search("apple")) // 返回 true +console.log(trie.search("app")) // 返回 false +console.log(trie.startsWith("app")) // 返回 true +console.log(trie.startsWith("apbbb")) // 返回 false +trie.insert("app") +console.log(trie.search("app")) // 返回 true diff --git a/Week_08/212findWords.js b/Week_08/212findWords.js new file mode 100644 index 00000000..207e7672 --- /dev/null +++ b/Week_08/212findWords.js @@ -0,0 +1,90 @@ +/** + * https://leetcode-cn.com/problems/word-search-ii/ + * 212. 单词搜索 II | hard + * + * trie树 + */ + +const findWords = function(A, words) { + if (!Array.isArray(words) || words.length < 1 || + !Array.isArray(A) || A.length < 1 || + !Array.isArray(A[0]) || A[0].length < 1) return [] + // 1. 构建 trie 树 + const trie = new Trie() + for (const word of words) { + trie.insert(word) + } + const dfs = (i, j, prefix, trieNode) => { + prefix += A[i][j] + trieNode = trieNode[A[i][j]] + if (trieNode && trieNode.isWord) res.add(prefix) + const tmp = A[i][j] + A[i][j] = TAG // 标记已访问过的节点 + for (let direct = 0; direct <= 3; ++direct) { + const x = i + dx[direct] + const y = j + dy[direct] + if (x >= 0 && x < m && + y >= 0 && y < n && + A[x][y] != TAG && + trieNode[A[x][y]]) { + dfs(x, y, prefix, trieNode) + } + } + A[i][j] = tmp // 去除标记 + } + // 2. 启动DFS + const res = new Set(), m = A.length, n = A[0].length + const TAG = '@' + const dx = [1, 0, -1, 0] + const dy = [0, 1, 0, -1] + for (let i = 0; i < m; ++i) { + for (let j = 0; j < n; ++j) { + if (trie.startsWith(A[i][j])) { + dfs(i, j, '', trie.root) + } + } + } + return [...res] +} +class Trie { + constructor() { + this.root = {} + } + + insert(word) { + let node = this.root + for (const ch of word) { + if (node[ch] == null) node[ch] = {} + node = node[ch] + } + node.isWord = true + } + + _traverse (word) { + let node = this.root + for (const ch of word) { + node = node[ch] + if (node == null) return null + } + return node + } + + search (word) { + const node = this._traverse(word) + return node != null && node.isWord === true + } + + startsWith (prefix) { + const node = this._traverse(prefix) + return !!node + } +} + +// ---- test words ---- +console.log(findWords([ + ["o","a","a","n"], + ["e","t","a","e"], + ["i","h","k","r"], + ["i","f","l","v"]], + ["oath","pea","eat","rain"] +)) diff --git a/Week_08/231isPowerOfTwo.js b/Week_08/231isPowerOfTwo.js new file mode 100644 index 00000000..51c332c9 --- /dev/null +++ b/Week_08/231isPowerOfTwo.js @@ -0,0 +1,16 @@ +/** + * https://leetcode-cn.com/problems/power-of-two/ + * 231. 2的幂 + * + */ + +// 清除末位零 n & (n - 1) +function isPowerOfTwo(n) { + return n > 0 && (n & (n - 1)) === 0 +} + +// ---- test case ---- +console.log(isPowerOfTwo(1)) +console.log(isPowerOfTwo(16)) +console.log(isPowerOfTwo(1024)) +console.log(isPowerOfTwo(294)) diff --git a/Week_08/338countBits.js b/Week_08/338countBits.js new file mode 100644 index 00000000..360ecd97 --- /dev/null +++ b/Week_08/338countBits.js @@ -0,0 +1,16 @@ +/** + * https://leetcode-cn.com/problems/counting-bits/ + * 338. 比特位计数 | medium + */ + +function countBits(n) { + const bits = Array(n + 1).fill(0) + for (let i = 1; i <= n; ++i) { + bits[i] = bits[i >> 1] + (i & 1) + } + return bits +} + +// ---- test case ---- +console.log(countBits(2)) +console.log(countBits(5)) diff --git a/Week_08/547findCircleNum.js b/Week_08/547findCircleNum.js new file mode 100644 index 00000000..de3ab8b2 --- /dev/null +++ b/Week_08/547findCircleNum.js @@ -0,0 +1,9 @@ +/** + * https://leetcode-cn.com/problems/number-of-provinces/ + * 547. 省份数量 | medium + * + */ + +function findCircleNum(isConnected) { + +} diff --git a/Week_08/README.md b/Week_08/README.md index 50de3041..2b880609 100644 --- a/Week_08/README.md +++ b/Week_08/README.md @@ -1 +1,37 @@ -学习笔记 \ No newline at end of file +# 学习笔记 + +## 第 14 课 | 字典树和并查集 + +**并查集** + ++ makeSet(s) 建立一个新的并查集 ++ unionSet(x, y) 把元素 x 和元素 y 所在的集合合并,要求x和y所在的集合不相交,如果相交则不合并 ++ find(x) 找到x所在的集合的代表。该操作也用来判断两个元素是否位于同一个集合 + +## 第 15 课 | 红黑树和AVL树 + +**平衡二叉搜索树** + +**AVL树** + +1. 发明人:G.M.Adelson-Velsky 和 Evgenii Landis +2. 平衡因子 Balance Factor + - 左子树的高度减去右子树的高度(有时相反) + - balance factor = {-1, 0, 1} +3. 通过旋转获得平衡 + +## 第 16 课 | 位运算 + ++ | 按位或 ++ & 按位与 ++ ~ 按位取反 ++ ^ 按位异或 + +**trick** + +1. 把 x 最右边的 n 位清零:x & (~0 << n) +2. 获取 x 的第 n 位值:(x >> n) & 1 +3. 获取 x 的第 n 位幂值:x & (1 << n) +4. 把 x 的第 n 位置 1:x | (1 << n) +5. 把 x 的第 n 位置 0:x & (~(1 << n)) +6. 把 x 的最高位到第n位(含)清零:x & ((1 << n) - 1) diff --git a/Week_08/homework.md b/Week_08/homework.md new file mode 100644 index 00000000..691dd7b0 --- /dev/null +++ b/Week_08/homework.md @@ -0,0 +1,52 @@ +# 作业 + +## 第 14 课 | 字典树和并查集 + +### 实现 Trie (前缀树) (亚马逊、微软、谷歌在半年内面试中考过) + ++ trie树 ++ [代码](./208trie.js) + +### 单词搜索 II (亚马逊、微软、苹果在半年内面试中考过) + ++ trie树 ++ [代码](./212findWords.js) + +### TODO 省份数量(亚马逊、Facebook、字节跳动在半年内面试中考过) + ++ UnionFind ++ [代码](./547findCircleNum.js) + +### TODO 岛屿数量(近半年内,亚马逊在面试中考查此题达到 361 次) + ++ UnionFind ++ [代码](./200numIslands.js) + +### TODO 被围绕的区域(亚马逊、eBay、谷歌在半年内面试中考过) + ++ UnionFind ++ [代码](./130solve.js) + + + +## 第 16 课 | 位运算 + +### 位 1 的个数(Facebook、苹果在半年内面试中考过) + ++ [代码](./191hammingWeight.js) + +### 2 的幂(谷歌、亚马逊、苹果在半年内面试中考过) + ++ [代码](./231isPowerOfTwo.js) + +### 颠倒二进制位(苹果在半年内面试中考过) + ++ [代码](./190reverseBits.js) + +### N 皇后(字节跳动、亚马逊、百度在半年内面试中考过) + ++ [代码](./051solveNQueens.js) + +### N 皇后 II (亚马逊在半年内面试中考过) + ++ [代码](./052totalNQueens.js) diff --git a/Week_09/032longestValidParentheses.js b/Week_09/032longestValidParentheses.js new file mode 100644 index 00000000..d5dffb52 --- /dev/null +++ b/Week_09/032longestValidParentheses.js @@ -0,0 +1,62 @@ +/** + * https://leetcode-cn.com/problems/longest-valid-parentheses/ + * 32. 最长有效括号 + * + * !!! 格式正确且连续 + */ + +// 解法一:用栈记录下标 O(n) O(n) +function longestValidParentheses(s) { + if (s.length < 2) return 0 + const stack = [-1] // 栈最左边位置用来存最后一个无法匹配的')'的下标,无则存 -1 + let res = 0 + for (let i = 0; i < s.length; ++i) { + if (s[i] === '(') { // 左括号先入栈等待匹配 + stack.push(i) + } else { + stack.pop() + if (stack.length > 0) { + res = Math.max(res, i - stack[stack.length - 1]) + } else { + stack.push(i) // 更新最左元素 + } + } + // console.log(stack, res, i) + } + return res +} + +/** + * i 0 1 2 3 4 5 + * s ( ) ( ( ) ) + *dp 0 2 0 0 2 6 + */ +// DP O(n) O(n) +function longestValidParentheses(s) { + const n = s.length + if (n < 2) return 0 + const dp = Array(n).fill(0) + let leftCnt = 0 + for (let i = 0; i < n; ++i) { + if (s[i] === '(') { + ++leftCnt + } else if (leftCnt > 0) { + --leftCnt + dp[i] = dp[i - 1] + 2 + // if (i - dp[i] >= 0) { + // const prev = dp[i - dp[i]] // 之前有效的部分 + // dp[i] += prev + // } + dp[i] += i - dp[i] >= 0 ? dp[i - dp[i]] : 0 + } + } + // console.log(dp) + return Math.max(...dp) +} + +// ---- test case ---- +console.log(longestValidParentheses('(()')) // 2 +console.log(longestValidParentheses('))))()())')) // 4 +console.log(longestValidParentheses('')) // 0 +console.log(longestValidParentheses('()()')) // 4 +console.log(longestValidParentheses('()(()')) // 2 diff --git a/Week_09/056merge.js b/Week_09/056merge.js new file mode 100644 index 00000000..0b531095 --- /dev/null +++ b/Week_09/056merge.js @@ -0,0 +1,36 @@ +/** + * https://leetcode-cn.com/problems/merge-intervals/ + * 56. 合并区间 | medium + */ + +// O(nlogn) ✅ +var merge = function(A) { + if (!Array.isArray(A) || A.length < 2) return A; + + A.sort(([s1, d1], [s2, d2]) => s1 === s2 ? d1 - d2 : s1 - s2); + let prev = A[0]; + const res = [prev]; + for (let i = 1; i < A.length; ++i) { + const curr = A[i]; + if (curr[0] <= prev[1]) { // 可以合并 + prev[1] = Math.max(prev[1], curr[1]); + } else { + res.push(curr); + prev = curr + } + } + return res; +}; + +// // O(n) +// var merge2 = function (A) { +// return A.reduce(([prevStart, prevEnd], [currStart, currEnd]) => { +// if +// }, []) +// } + +// ---- test case ---- +console.log(merge([[1,3],[2,6],[8,10],[15,18]])) +// [[1,6],[8,10],[15,18]] +console.log(merge([[1,4],[4,5]])) +// [[1,5]] diff --git a/Week_09/062uniquePaths.js b/Week_09/062uniquePaths.js new file mode 100644 index 00000000..071aec1f --- /dev/null +++ b/Week_09/062uniquePaths.js @@ -0,0 +1,20 @@ +/** + * https://leetcode-cn.com/problems/unique-paths/ + * 62. 不同路径 | medium + */ + +// dp f(i, j) = f(i - 1, j) + f(i, j-1) + +// O(mn) O(n) +function uniquePaths(m, n) { + const dp = Array(n).fill(1) + for (let i = 1; i < m; ++i) { + for (let j = 1; j < n; ++j) { + dp[j] += dp[j - 1] + } + } + return dp[n - 1] +} + +// ---- test case ---- +console.log(uniquePaths(3, 7)) diff --git a/Week_09/063uniquePathsWithObstacles.js b/Week_09/063uniquePathsWithObstacles.js new file mode 100644 index 00000000..c748a964 --- /dev/null +++ b/Week_09/063uniquePathsWithObstacles.js @@ -0,0 +1,38 @@ +/** + * https://leetcode-cn.com/problems/unique-paths-ii/ + * 63. 不同路径 II + */ + +/* +if 障碍物处: + f(i, j) = 0 +else + f(i, j) = f(i+1, j) + f(i, j+1) +*/ +function uniquePathsWithObstacles(A) { + const m = A.length, n = A[0].length + if (m < 1 || n < 1 || A[m-1][n-1] === 1) return 0 + const lastObsIdx = A[m - 1].lastIndexOf(1) + const dp = Array(lastObsIdx + 1).fill(0) + .concat(Array(n - lastObsIdx - 1).fill(1)) + for (let i = m - 2; i >= 0; --i) { + for (let j = n - 1; j >= 0; --j) { + if (A[i][j] === 1) { // 障碍物 + dp[j] = 0 + } else if (j === n - 1) { // 最右边一列 + dp[j] = dp[j] === 0 ? 0 : 1 + } else { + dp[j] += dp[j + 1] + } + } + } + return dp[0] +} + +// ---- test case ---- +console.time('uniquePathsWithObstacles') +console.log(uniquePathsWithObstacles([[0,0,0],[0,1,0],[0,0,0]])) +console.log(uniquePathsWithObstacles([[0,1],[0,0]])) +console.log(uniquePathsWithObstacles([[0,0],[0,1]])) +console.log(uniquePathsWithObstacles([[0,0],[1,1],[0,0]])) +console.timeEnd('uniquePathsWithObstacles') diff --git a/Week_09/072minDistance.js b/Week_09/072minDistance.js new file mode 100644 index 00000000..eabba773 --- /dev/null +++ b/Week_09/072minDistance.js @@ -0,0 +1,42 @@ +/** + * https://leetcode-cn.com/problems/edit-distance/ + * 72. 编辑距离 | hard + */ + +/* +定义:f(i, j) + +if (w1,w2末尾字符相同) +---> f(i, j) = f(i-1, j-1) +else +---> f(i, j) = min( + f(i-1, j-1) + 1, // 修改w1,把最后一个x修改为y,然后相同末尾可以去掉 + f(i-1, j) + 1, // 删除w1末尾字符 + f(i, j-1) + 1, // 删除w2末尾字符 + ) +*/ +function minDistance(word1, word2) { + const m = word1.length, n = word2.length + if (m === 0 || n === 0) return Math.max(m, n) + const dp = Array.from(Array(m + 1)).map(_ => Array(n + 1).fill(0)) + for (let i = 1; i <= m; ++i) dp[i][0] = i + for (let j = 1; 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]) { // tip: 注意下标,是 wordl1[i-1] 与 word2[j-1] + dp[i][j] = dp[i-1][j-1] + } else { + dp[i][j] = Math.min( + dp[i-1][j-1] + 1, // 修改word1的末尾字符 + dp[i-1][j] + 1, // 删word1 + dp[i][j-1] + 1, // 删word2 + ) + } + } + } + return dp[m][n] +} + +// ---- test case ---- +console.log(minDistance('horse', 'ros')) // 3 +console.log(minDistance('intention', 'execution')) // 5 diff --git a/Week_09/085maximalRectangle.js b/Week_09/085maximalRectangle.js new file mode 100644 index 00000000..22cb550d --- /dev/null +++ b/Week_09/085maximalRectangle.js @@ -0,0 +1,20 @@ +/** + * https://leetcode-cn.com/problems/maximal-rectangle/ + * 85. 最大矩形 + */ + +function maximalRectangle(matrix) { + +} + +// ---- test case ---- +console.log(maximalRectangle([ + ['1', '0', '1', '0', '0'], + ['1', '0', '1', '1', '1'], + ['1', '1', '1', '1', '1'], + ['1', '0', '0', '1', '0'], +])) // 6 +console.log(maximalRectangle([])) // 0 +console.log(maximalRectangle([['0']])) // 0 +console.log(maximalRectangle([['1']])) // 1 +console.log(maximalRectangle([['0', '0']])) // 0 diff --git a/Week_09/091numDecodings.js b/Week_09/091numDecodings.js new file mode 100644 index 00000000..f53f744e --- /dev/null +++ b/Week_09/091numDecodings.js @@ -0,0 +1,34 @@ +/** + * https://leetcode-cn.com/problems/decode-ways/ + * 91. 解码方法 + */ + +/* +转移方程 +if 1 <= A[i-1] <= 9 + f(i) += f(i-1) +if 10 <= A[i-2..i-1] <= 26 + f(i) += f(i-2) + +复杂度 O(n) +*/ +function numDecodings(s) { + const n = s.length + const dp = Array(n + 1).fill(0) + dp[0] = 1 + dp[1] = s[0] === '0' ? 0 : 1 + for (let i = 2; i <= n; ++i) { + const curr = +s[i - 1] + const prev = +s.substr(i - 2, 2) + if (curr >= 1 && curr <= 9 ) dp[i] += dp[i - 1] // 第一种方式:当前位自成一个字符 + if (prev >= 10 && prev <= 26) dp[i] += dp[i - 2] // 第二种方式:与前一个字符组成一个字符 + } + return dp[n] +} + +// ---- test case ---- +// console.log(numDecodings('0')) // 0 +// console.log(numDecodings('1')) // 1 A +// console.log(numDecodings('12')) // 2 AB L +console.log(numDecodings('226')) // 3 BZ VF BBF +console.log(numDecodings('222312')) // 3 BZ VF BBF diff --git a/Week_09/1112relativeSortArray.js b/Week_09/1112relativeSortArray.js new file mode 100644 index 00000000..cef678d7 --- /dev/null +++ b/Week_09/1112relativeSortArray.js @@ -0,0 +1,51 @@ +/** + * https://leetcode-cn.com/problems/relative-sort-array/ + * 1122. 数组的相对排序 + * + */ + +// 方法一:O(nlogn) +// Step1: 使用 hashmap 记录相对次序 O(n) +// Step2: 按照相对顺序进行排序 O(nlogn) +function relativeSortArray(arr1, arr2) { + const rank = new Map() + const N = arr2.length + arr2.forEach((item, idx) => { + rank.set(item, idx) + }) + return arr1.sort((x, y) => { + const idx1 = rank.has(x) ? rank.get(x) : N + x + const idx2 = rank.has(y) ? rank.get(y) : N + y + return idx1 - idx2 + }) +} + +// 方法二:计数排序 O(n) +// 注意题眼:1. 数据范围 [1, 1000] 2. arr2元素各不相同 +// Step1: 确定arr1上界,优化空间复杂度 +// Step2: 统计所有元素出现的频率 cnt +// Step3: 按照 arr2 的顺序,填充在arr2中出现过的元素 +// Step4: 填充 cnt 中剩余的元素 +function relativeSortArray(arr1, arr2) { + const upper = Math.max(...arr1) // O(n) + const cnt = Array(upper + 1).fill(0) + arr1.forEach(x => ++cnt[x]) // O(n) + let idx = 0 + arr2.forEach(n => { + while (cnt[n]-- > 0) { + arr1[idx++] = n + } + }) + for (let n = 0; n < cnt.length; ++n) { + while (cnt[n]-- > 0) { + arr1[idx++] = n + } + } + return arr1 +} + +// ---- test case ---- +var arr1 = [2,3,1,3,2,4,6,7,9,2,19] +var arr2 = [2,1,4,3,9,6] +console.log(relativeSortArray(arr1, arr2)) +console.log(arr1) diff --git a/Week_09/115numDistinct.js b/Week_09/115numDistinct.js new file mode 100644 index 00000000..ee32ef3a --- /dev/null +++ b/Week_09/115numDistinct.js @@ -0,0 +1,41 @@ +/** + * https://leetcode-cn.com/problems/distinct-subsequences/ + * 115. 不同的子序列 | hard + * + * dp + * if s[i] === t[j] + * f(i, j) = f(i-1, j) + f(i-1, j-1) // s去掉末尾 + s、t都去掉末尾 + * else + * f(i, j) = f(i-1, j) // s去掉末尾 + * + * ' b a g + * b 1 1 0 0 + * a 1 1 1 0 + * b 1 2 1 0 + * g 1 2 1 1 + * b 1 3 1 1 + * a 1 3 4 1 + * g 1 3 4 5 + * + */ + +// O(mn) O(n) +function numDistinct (s, t) { + const m = s.length, n = t.length + if (m < n) return 0 + const dp = new Array(n + 1).fill(0) + dp[0] = 1 + for(let i = 0; i < m; ++i) { + for(let j = n; j > 0; --j) { + if(s[i] === t[j - 1]) { + dp[j] += dp[j - 1] + } + } + console.log(i, dp) + } + return dp[n] +} + +// ---- test case ---- +console.log(numDistinct('rabbbit', 'rabbit')) // 3 +console.log(numDistinct('babgbag', 'bag')) // 5 diff --git a/Week_09/146LRUCache.js b/Week_09/146LRUCache.js new file mode 100644 index 00000000..33f39df8 --- /dev/null +++ b/Week_09/146LRUCache.js @@ -0,0 +1,101 @@ +/** + * https://leetcode-cn.com/problems/lru-cache/ + * 146. LRU 缓存机制 | medium + */ + +// 解法一:利用 Map 本身是 orderedDict 的特性 +class LRUCache { + constructor(capacity) { + this.cache = new Map() + this.capacity = capacity + } + get(key) { + if (!this.cache.has(key)) return -1 + const v = this.cache.get(key) + this.cache.delete(key) + this.cache.set(key, v) + return this.cache.get(key) + } + put(key, value) { + if (this.cache.has(key)) { + this.cache.delete(key) + } + this.cache.set(key, value) + if (this.cache.size > this.capacity) { + const iter = this.cache.keys() + // console.log("🚀", iter) + this.cache.delete(iter.next().value) + } + } +} + +// 解法二:HashMap + DoubleLinkedList +class ListNode { + constructor(key, value) { + this.key = key; // 用于存放key,以便于后面在cache中删除 + this.value = value; + this.prev = null; + this.next = null; + } +} +class LRUCache1 { + constructor(capacity) { + this.capacity = capacity; + this.cache = new Map(); + // 空头节点和空尾节点方便操作 + this.dummyHead = new ListNode(-1, -1); + this.dummyTail = new ListNode(-1, -1); + this.dummyHead.next = this.dummyTail; + this.dummyTail.prev = this.dummyHead; + } + get(key) { + if (!this.cache.has(key)) { + return -1; + } + const node = this.cache.get(key); + this._move2head(node); // 移到头部 + return node.value; + } + put(key, value) { + if (this.cache.has(key)) { // 存在 + const node = this.cache.get(key); + node.value = value; // 更新值 + this._move2head(node); + } else { // 不存在 + if (this.cache.size === this.capacity) { // 满了 + const removedNode = this.dummyTail.prev; // 移除最后一个 + this._removeNode(removedNode); + this.cache.delete(removedNode.key); + } + const newNode = new ListNode(key, value); + this.cache.set(key, newNode); + this._addHead(newNode); + } + } + _addHead(node) { + node.next = this.dummyHead.next; + this.dummyHead.next.prev = node; + this.dummyHead.next = node; + node.prev = this.dummyHead; + } + _removeNode(node) { + node.prev.next = node.next; + node.next.prev = node.prev; + } + _move2head(node) { + this._removeNode(node); + this._addHead(node); + } +} + +// ---- test case ---- +const cache = new LRUCache(2) +cache.put(1, 1) +cache.put(2, 2) +console.log(cache.get(1)) // 1 +cache.put(3, 3) +console.log(cache.get(2)) // -1 +cache.put(4, 4) +console.log(cache.get(1)) // -1 +console.log(cache.get(3)) // 3 +console.log(cache.get(4)) // 4 diff --git a/Week_09/242isAnagram.js b/Week_09/242isAnagram.js new file mode 100644 index 00000000..673ae07a --- /dev/null +++ b/Week_09/242isAnagram.js @@ -0,0 +1,27 @@ +/** + * https://leetcode-cn.com/problems/valid-anagram/ + * 242. 有效的字母异位词 + */ + +// 解法一:词频统计 O(n) O(n) +// 思想即计数排序 +function isAnagram(s, t) { + const m = s.length, n = t.length + if (m !== n) return false + const startIdx = 'a'.codePointAt(0) + const cnt = Array(26).fill(0) + for (let i = 0; i < m; ++i) { + const idx = s.codePointAt(i) - startIdx + ++cnt[idx] + } + for (let i = 0; i < n; ++i) { + const idx = t.codePointAt(i) - startIdx + --cnt[idx] + } + // return cnt.join('') === '0'.repeat(26) + return cnt.filter(item => item != 0).length === 0 +} + +// ---- test case ---- +console.log(isAnagram('anagram', 'nagaram')) +console.log(isAnagram('rat', 'car')) diff --git a/Week_09/300lengthOfLIS.js b/Week_09/300lengthOfLIS.js new file mode 100644 index 00000000..2e68a18c --- /dev/null +++ b/Week_09/300lengthOfLIS.js @@ -0,0 +1,64 @@ +/** + * https://leetcode-cn.com/problems/longest-increasing-subsequence/ + * 300. 最长递增子序列 + */ + +/* +[解法一] 动态规划 +[定义f(i)] 以 arr[i] 结尾的最长递增子序列长度,(arr[i]必选) +[状态方程] f(i) = max(f(j) + 1) + for (j at [0,i-1] && arr[j] < arr[i]) +[复杂度] O(n^2) O(n) +*/ +function lengthOfLIS(arr) { + const n = arr.length + if (n < 2) return n + const dp = Array(n).fill(1) + for (let i = 1; i < n; ++i) { + for (let j = 0; j < i; ++j) { + if (arr[j] < arr[i]) { + dp[i] = Math.max(dp[i], dp[j] + 1) + } + } + } + return Math.max(...dp) +} + + +// 解法二:贪心 + 二分查找 O(nlogn) O(n) +function lengthOfLIS(nums) { + const n = nums.length + if (n < 2) return n + + const search = (dp, target, hi) => { + let lo = 0 + while (lo <= hi) { + let mid = lo + ((hi - lo) >> 1) + if (target === dp[mid]) { + return mid + } else if (target < dp[mid]) { + hi = mid - 1 + } else { + lo = mid + 1 + } + } + return lo + } + + const UPBOUND = Math.max(...nums) + 1 // 上界 + const dp = Array(n).fill(UPBOUND) + for (let i = 0; i < n; i++) { + let pos = search(dp, nums[i], i) // 找到位置 + dp[pos] = nums[i] // 取而代之 + // console.log(dp) + } + for (let i = dp.length - 1; i >= 0; i--) { + if (dp[i] !== UPBOUND) return i + 1 + } + return 0 +} + +// ---- test case ---- +console.log(lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18])) // 4 +console.log(lengthOfLIS([0, 1, 0, 3, 2, 3])) // 4 +console.log(lengthOfLIS([7, 7, 7, 7, 7, 7, 7])) // 1 diff --git a/Week_09/493reversePairs.js b/Week_09/493reversePairs.js new file mode 100644 index 00000000..3e575c20 --- /dev/null +++ b/Week_09/493reversePairs.js @@ -0,0 +1,59 @@ +/** + * https://leetcode-cn.com/problems/reverse-pairs/ + * 493. 翻转对 | hard + */ + +// 解法一:暴力 O(n^2) +function reversePairs(arr) { + const n = arr.length + if (n < 2) return 0 + let cnt = 0 + for (let i = 0; i < n - 1; ++i) { + for (let j = i + 1; j < n; ++j) { + if (arr[i] > 2 * arr[j]) ++cnt + } + } + return cnt +} + +// 解法二:归并排序 O(nlogn) +function reversePairs(arr, left = 0, right = arr.length - 1) { + if (left >= right) return 0 + const mid = left + ((right - left) >> 1) + const cnt1 = reversePairs(arr, left, mid) + const cnt2 = reversePairs(arr, mid + 1, right) + return merge(arr, left, mid, right, cnt1 + cnt2) +} + +function merge(arr, left, mid, right, cnt) { + // 统计逆序对的逻辑 + let i = left, j = mid + 1 + while (i <= mid && j <= right) { + if (arr[i] > 2 * arr[j]) { + cnt += mid - i + 1 // 左边数组的i之后的元素与j都形成翻转对 + ++j + } else { + ++i + } + } + // 归并排序逻辑 + const tmp = Array(right - left + 1) + let k = 0 + i = left + j = mid + 1 + while (i <= mid && j <= right) { + tmp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++] + } + while (i <= mid) tmp[k++] = arr[i++] + while (j <= right) tmp[k++] = arr[j++] + for (let idx = 0; idx < tmp.length; ++idx) { + arr[left + idx] = tmp[idx] + } + return cnt +} + +// TODO 解法三:树状数组 O(nlogn) + +// ---- test case ---- +console.log(reversePairs([1, 3, 2, 3, 1])) // 2 +console.log(reversePairs([2, 4, 3, 5, 1])) // 3 diff --git a/Week_09/718findLength.js b/Week_09/718findLength.js new file mode 100644 index 00000000..ff1a6a7d --- /dev/null +++ b/Week_09/718findLength.js @@ -0,0 +1,38 @@ +/** + * https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/ + * 718. 最长重复子数组 | easy + * + * dp + * if (A[i] === B[j]) { + * f(i, j) = f(i-1, j-1) + 1 + * } else { + * f(i, j) = 0 + * } + */ + +function findLength(A, B) { + if (A.length < 1 || B.length < 1) return 0 + const m = A.length, n = B.length + const dp = Array(m).fill(0).map( + _ => Array(n).fill(0) + ) + let max = 0 + + for (let i = 0; i < m; ++i) { + for (let j = 0; j < n; ++j) { + if (A[i] === B[j]) { + if (i === 0 || j === 0) { + dp[i][j] = 1 + } else { + dp[i][j] = dp[i - 1][j - 1] + 1 + } + max = Math.max(max, dp[i][j]) + } + } + } + // console.log(dp) + return max +} + +// ---- test case ---- +console.log(findLength([1,2,3,2,1], [3,2,1,4,7])) diff --git a/Week_09/818racecar.js b/Week_09/818racecar.js new file mode 100644 index 00000000..d47f5f1a --- /dev/null +++ b/Week_09/818racecar.js @@ -0,0 +1,12 @@ +/** + * https://leetcode-cn.com/problems/race-car/ + * 818. 赛车 + */ + +function racecar (target) { + +} + +// ---- test case ---- +console.log(racecar(3)) // 2 AA +console.log(racecar(6)) // 5 AAARA diff --git a/Week_09/BloomFilter.js b/Week_09/BloomFilter.js new file mode 100644 index 00000000..d6bd9743 --- /dev/null +++ b/Week_09/BloomFilter.js @@ -0,0 +1,116 @@ +// 布隆过滤器 +class BloomFilter { + constructor(maxKeys, errorRate) { + this.bitMap = [] + this.maxKeys = maxKeys + this.errorRate = errorRate + // 位图变量的长度,需要根据 maxKeys 和 errorRate 来计算 + this.bitSize = Math.ceil(maxKeys * (-Math.log(errorRate) / (Math.log(2) * Math.log(2)))) + // 哈希数量 + this.hashCount = Math.ceil(Math.log(2) * (this.bitSize / maxKeys)) + // 已加入元素数量 + this.keyCount = 0 + } + + bitSet(bit) { + let numArr = Math.floor(bit / 31) + let numBit = Math.floor(bit % 31) + this.bitMap[numArr] |= 1 << numBit + } + + bitGet(bit) { + let numArr = Math.floor(bit / 31) + let numBit = Math.floor(bit % 31) + return (this.bitMap[numArr] &= 1 << numBit) + } + + add(key) { + if (this.contain(key)) { + return -1 + } + let hash1 = MurmurHash(key, 0, 0), + hash2 = MurmurHash(key, 0, hash1) + for (let i = 0; i < this.hashCount; i++) { + this.bitSet(Math.abs(Math.floor((hash1 + i * hash2) % this.bitSize))) + } + this.keyCount++ + } + + contain(key) { + let hash1 = MurmurHash(key, 0, 0) + let hash2 = MurmurHash(key, 0, hash1) + for (let i = 0; i < this.hashCount; i++) { + if (!this.bitGet(Math.abs(Math.floor((hash1 + i * hash2) % this.bitSize)))) { + return false + } + } + return true + } +} + +/** + * MurmurHash + * + * 参考 http://murmurhash.googlepages.com/ + * + * data:待哈希的值 + * offset: + * seed:种子集 + * + */ +function MurmurHash(data, offset, seed) { + let len = data.length, + m = 0x5bd1e995, + r = 24, + h = seed ^ len, + len_4 = len >> 2; + + for (let i = 0; i < len_4; i++) { + let i_4 = (i << 2) + offset, + k = data[i_4 + 3]; + + k = k << 8; + k = k | (data[i_4 + 2] & 0xff); + k = k << 8; + k = k | (data[i_4 + 1] & 0xff); + k = k << 8; + k = k | (data[i_4 + 0] & 0xff); + k *= m; + k ^= k >>> r; + k *= m; + h *= m; + h ^= k; + } + + // avoid calculating modulo + let len_m = len_4 << 2, + left = len - len_m, + i_m = len_m + offset; + + if (left != 0) { + if (left >= 3) { + h ^= data[i_m + 2] << 16; + } + if (left >= 2) { + h ^= data[i_m + 1] << 8; + } + if (left >= 1) { + h ^= data[i_m]; + } + + h *= m; + } + + h ^= h >>> 13; + h *= m; + h ^= h >>> 15; + + return h; +} + + +let bloomFilter = new BloomFilter(10000, 0.01); + +bloomFilter.add("abcdefgh"); +console.log(bloomFilter.contain("abcdefgh")); +console.log(bloomFilter.contain("abcdefghi")); diff --git a/Week_09/README.md b/Week_09/README.md index 50de3041..981cfb7d 100644 --- a/Week_09/README.md +++ b/Week_09/README.md @@ -1 +1,75 @@ -学习笔记 \ No newline at end of file +# 学习笔记 + +## 第 17 课 | 布隆过滤器 & LRU 缓存 + +### Bloom Filter + +案例 + +1. 比特币网络 +2. 分布式系统(Map-Reduce)—— Hadoop,search engine +3. Redis缓存 +4. 垃圾邮件、评论等的过滤 + +> https://www.cnblogs.com/cpselvis/p/6265825.html +> https://blog.csdn.net/tianyaleixiaowu/article/details/74721877 + +### LRU cache + ++ Least recently used -> 最近最少使用 ++ 实现:HashMap + 双向链表 ++ O(1) 查询 ++ O(1) 修改、更新 + +## 第 18 课 | 排序算法 + +**比较类排序** + ++ 交换排序 + - [冒泡排序](./sort-basic.js) + - [快速排序](./sort-quick.js) ++ 插入排序 + - [简单插入排序](./sort-basic.js) + - 希尔排序 ++ 选择排序 + - [简单选择排序](./sort-basic.js) + - [堆排序](./sort-heap.js) ++ 归并排序 + - [二路归并排序](./sort-merge.js) + - 多路归并排序 + +![](./sort.png) + +**非比较类排序** + ++ O(n) ++ 只能排整型数据 ++ [计数排序] Counting Sort ++ [桶排序] Bucket Sort ++ [基数排序] Radix Sort + +## 第 19 课 | 高级动态规划 + +1. 划分子问题 +2. 分治 + 最优子结构 +3. 顺推公式:动态递推 + +**常见问题** + + +1. 爬楼梯问题 f(n) = f(n-1) + f(n-2) +2. 不同路径 f(i, j) = f(i-1, j) + f(i, j-1) +3. 打家劫舍 + + 方法一:f(i) = max(f(i-2) + A[i], f(i-1)) + + 方法二:f(i, 0) = max(f(i-1, 0), f(i-1, 1)), f(i, 1) = f(i-1, 0) + A[i] +4. 最小路径和 f(i, j) = min(f(i-1, j), f(i, j-1)) + A[i][j] +5. 股票买卖 第i天,交易了k次,是否持有股票 + + f(i, k, 0) = max(f(i-1, k, 0), f(i-1, k, 1) + A[i]) + + f(i, k, 1) = max(f(i-1, k, 1), f(i-1, k-1, 0) - A[i]) + +**高级DP** + +复杂度来源 + +1. 状态拥有更多维度(太多需要考虑压缩) +2. 状态方程更加复杂 diff --git a/Week_09/homework.md b/Week_09/homework.md new file mode 100644 index 00000000..c3b50f29 --- /dev/null +++ b/Week_09/homework.md @@ -0,0 +1,79 @@ +# 作业 +## 一、BloomFilter 和 LRU Cache + +### LRU 缓存机制(亚马逊、字节跳动、Facebook、微软在半年内面试中常考) + ++ [代码](./146LRUCache.js) + + + + +## 二、排序 + +### 用自己熟悉的编程语言,手写各种初级排序代码,提交到学习总结中。 + +1. [冒泡排序、插入排序、选择排序](./sort-basic.js) +2. [快速排序](./sort-quick.js) +3. [归并排序](./sort-merge.js) +4. [堆排序](./sort-heap.js) + +### 数组的相对排序(谷歌在半年内面试中考过) + ++ [代码](./1112relativeSortArray.js) + +### 有效的字母异位词(Facebook、亚马逊、谷歌在半年内面试中考过) + ++ [代码](./242isAnagram.js) + +### 合并区间(Facebook、字节跳动、亚马逊在半年内面试中常考) + ++ [代码](./056merge.js) + +### 翻转对(字节跳动在半年内面试中考过) + ++ [代码](./493reversePairs.js) + + + + + +## 三、高级动态规划 + +### 不同路径 2 道题目的状态转移方程 + +```js +// 不同路径 I +f(i, j) = f(i-1, j) + f(i, j-1) +// 不同路径 II +if 障碍物处: + f(i, j) = 0 +else + f(i, j) = f(i+1, j) + f(i, j+1) +``` + ++ [不同路径 I](./062uniquePaths.js) ++ [不同路径 II](./063uniquePathsWithObstacles.js) + +### 最长上升子序列(字节跳动、亚马逊、微软在半年内面试中考过) + ++ [代码](./300lengthOfLIS.js) + +### 解码方法(字节跳动、亚马逊、Facebook 在半年内面试中考过) + ++ [代码](./091numDecodings.js) + +### 最长有效括号(亚马逊、字节跳动、华为在半年内面试中考过) + ++ [代码](./032longestValidParentheses.js) + +TODO ### 最大矩形(谷歌、微软、字节跳动在半年内面试中考过) + ++ [代码](./085maximalRectangle.js) + +### 不同的子序列(MathWorks 在半年内面试中考过) + ++ [代码](./115numDistinct.js) + +TODO ### 赛车(谷歌在半年内面试中考过) + ++ [代码](./818racecar.js) diff --git a/Week_09/sort-basic.js b/Week_09/sort-basic.js new file mode 100644 index 00000000..d8c0831f --- /dev/null +++ b/Week_09/sort-basic.js @@ -0,0 +1,54 @@ +// O(n^2) O(1) +function swap(arr, i, j) { + if (i !== j) { + const tmp = arr[i] + arr[i] = arr[j] + arr[j] = tmp + } +} +// 不稳定 +function selectionSort(arr) { + const n = arr.length + for (let i = 0; i < n - 1; ++i) { + let j, minIdx = i + for (j = i + 1; j < n; ++j) { + if (arr[j] < arr[minIdx]) { + minIdx = j + } + } + swap(arr, i, minIdx) + } + return arr +} +// 稳定 +function insertionSort(arr) { + const n = arr.length + let preIdx, curr + for (let i = 1; i < n; ++i) { + preIdx = i - 1 + curr = arr[i] + while (preIdx >= 0 && arr[preIdx] > curr) { + arr[preIdx + 1] = arr[preIdx] + --preIdx + } + arr[preIdx + 1] = curr + } + return arr +} +// 稳定 +function bubbleSort(arr) { + const n = arr.length + for (let i = 0; i < n - 1; ++i) { + for (let j = 0; j < n - 1 - i; ++j) { + if (arr[j] > arr[j + 1]) { + swap(arr, j, j + 1) + } + } + } + return arr +} + +// ---- test case ---- +console.log(selectionSort([3,2,32,45,767,234,66,32])) +console.log(insertionSort([3,2,32,45,767,234,66,32])) +console.log(bubbleSort([3,2,32,45,767,234,66,32])) diff --git a/Week_09/sort-heap.js b/Week_09/sort-heap.js new file mode 100644 index 00000000..cf16a302 --- /dev/null +++ b/Week_09/sort-heap.js @@ -0,0 +1,48 @@ +// 堆排序 不稳定 O(nlogn) +/** + * [array heap] + * element : i + * left child : 2i + 1 + * right child: 2i + 2 + */ + +function swap(arr, i, j) { + if (i !== j) { + const tmp = arr[i] + arr[i] = arr[j] + arr[j] = tmp + } +} + +function heapSort(arr) { + if (!arr.length) return + const n = arr.length + for (let i = (n >> 1) - 1; i >= 0; --i) { + heapify(arr, n, i) + } + for (let i = n - 1; i >= 0; --i) { + swap(arr, 0, i) + heapify(arr, i, 0) + } +} + +function heapify(arr, len, i) { + const left = 2 * i + 1, right = 2 * i + 2 + let largest = i + if (left < len && arr[left] > arr[largest]) { + largest = left + } + if (right < len && arr[right] > arr[largest]) { + largest = right + } + if (largest !== i) { + swap(arr, i, largest) + heapify(arr, len, largest) + } +} + + +// ---- test case ---- +const arr = [3, 2, 32, 45, 767, 234, 66, 32] +heapSort(arr) +console.log(arr) diff --git a/Week_09/sort-merge.js b/Week_09/sort-merge.js new file mode 100644 index 00000000..cd0a8eef --- /dev/null +++ b/Week_09/sort-merge.js @@ -0,0 +1,27 @@ +// 归并排序 稳定 O(nlogn) +// 缺点:需要额外空间 +function mergeSort(arr, left = 0, right = arr.length - 1) { + if (left >= right) return + const mid = left + ((right - left) >> 1) + mergeSort(arr, left, mid) + mergeSort(arr, mid + 1, right) + merge(arr, left, mid, right) +} + +function merge(arr, left, mid, right) { + const tmp = new Array(right - left + 1) + let i = left, j = mid + 1, k = 0 + while (i <= mid && j <= right) { + tmp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++] + } + while (i <= mid) tmp[k++] = arr[i++] + while (j <= right) tmp[k++] = arr[j++] + for (let p = 0; p < tmp.length; ++p) { + arr[left + p] = tmp[p] + } +} + +// ---- test case ---- +const arr = [3, 2, 32, 45, 767, 234, 66, 32] +mergeSort(arr) +console.log(arr) diff --git a/Week_09/sort-quick.js b/Week_09/sort-quick.js new file mode 100644 index 00000000..b327da24 --- /dev/null +++ b/Week_09/sort-quick.js @@ -0,0 +1,62 @@ +// 快速排序 不稳定 O(nlogn) +function quickSort(arr, left = 0, right = arr.length - 1) { + if (left >= right) return + const pivotIdx = partition(arr, left, right) + quickSort(arr, left, pivotIdx - 1) + quickSort(arr, pivotIdx + 1, right) +} + +function partition(arr, left, right) { +// const pivot = arr[left] + +const mid = left + ((right - left) >> 1); +const pivot = arr[mid]; +arr[mid] = arr[left]; +// arr[left] = pivot; + while (left < right) { + while (left < right && arr[right] >= pivot) --right + if (left < right) arr[left] = arr[right] + while (left < right && arr[left] <= pivot) ++left + if (left < right) arr[right] = arr[left] + } + arr[left] = pivot + return left +} + +// ---- test case ---- +const arr1 = [5,2,3,1] +quickSort(arr1) +console.log(arr1) + +const arr2 = [5,1,1,2,0,0] +quickSort(arr2) +console.log(arr2) + + + +// var quickSort = function(nums) { +// function quickSortHelper(nums, start, end) { +// if (start >= end) return nums + +// var pivotValue = nums[start] +// var smaller = start +// for (var i = start + 1; i <= end; i++) { +// var bigger = i +// if (nums[bigger] < pivotValue) { +// smaller++ +// var smallerValue = nums[bigger] +// nums[bigger] = nums[smaller] +// nums[smaller] = smallerValue +// } +// } +// var smallerCache = nums[smaller] +// nums[smaller] = nums[start] +// nums[start] = smallerCache + +// quickSortHelper(nums, start, smaller - 1) +// quickSortHelper(nums, smaller + 1, end) +// return nums +// } + +// return quickSortHelper(nums, 0, nums.length - 1) +// }; diff --git a/Week_09/sort.png b/Week_09/sort.png new file mode 100644 index 00000000..1027e763 Binary files /dev/null and b/Week_09/sort.png differ diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/005longestPalindrome.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/005longestPalindrome.js" new file mode 100644 index 00000000..d7e2615f --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/005longestPalindrome.js" @@ -0,0 +1,37 @@ +/** + * https://leetcode-cn.com/problems/longest-palindromic-substring/ + * 5. 最长回文子串 | medium + */ + +// 中心扩散法,共 2n - 1 个中心 +function longestPalindrome (s) { + const n = s.length + if (n < 1) return '' + let start = 0, end = 0 + for (let i = 0; i < n; ++i) { + const len1 = expand(s, i, i) // 奇数长度 + const len2 = expand(s, i, i + 1) // 偶数长度 + const len = Math.max(len1, len2) + if (len > end - start + 1) { // 找到了更长的 + console.log(len1, len2, i) + start = i - ((len - 1) >> 1) + end = i + ( len >> 1) + } + } + return s.slice(start, end + 1) +} + +function expand(s, lo, hi) { + while (lo >= 0 && hi < s.length && s.charAt(lo) === s.charAt(hi)) { + --lo + ++hi + } + return hi - lo - 1 +} + +// ---- test case ---- +console.log(longestPalindrome('babad')) // bab +console.log(longestPalindrome('cbbd')) // bb +console.log(longestPalindrome('a')) // a +console.log(longestPalindrome('ac')) // a +console.log(longestPalindrome('cbbd')) // bb diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/008myAtoi.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/008myAtoi.js" new file mode 100644 index 00000000..44f01fb1 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/008myAtoi.js" @@ -0,0 +1,51 @@ +/** + * https://leetcode-cn.com/problems/string-to-integer-atoi/ + * 8. 字符串转换整数 (atoi) | medium + */ + +// 解法一:使用API +function myAtoi(str) { + return Math.max(Math.min(parseInt(str) || 0, 2147483647), -2147483648) +} + +// 解法二:手写一趟遍历 O(n) +function myAtoi(str) { + const n = str.length + if (n < 1) return 0 + + let idx = 0, sign = 1, total = 0 + + // 去左空格 + while (str.charAt(idx) === ' ' && idx < n) ++idx + + // 处理正负号 + const ch = str.charAt(idx) + if (ch === '+' || ch === '-') { + sign = ch === '-' ? -1 : 1 + ++idx + } + + // 检索数字,计算total + const startIdx = '0'.codePointAt(0) + for (; idx < n; ++idx) { + const digit = str.codePointAt(idx) - startIdx + if (digit < 0 || digit > 9) { // 遇到非数字,结束循环 + break + } + total = 10 * total + digit + } + + // 处理 int(32bit) 边界 + const MIN = 1 << 31, MAX = -1 * (MIN + 1) + let ret = total * sign + ret = ret > MAX ? MAX : ret + ret = ret < MIN ? MIN : ret + return ret +} + +// ---- test case ---- +console.log(myAtoi('42')) // 42 +console.log(myAtoi(' -42')) // -42 +console.log(myAtoi('4139 with words'))// 4139 +console.log(myAtoi('words and 987')) // 0 +console.log(myAtoi('-91283472332')) // -2147483648 diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/010isMatch.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/010isMatch.js" new file mode 100644 index 00000000..2450e343 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/010isMatch.js" @@ -0,0 +1,15 @@ +/** + * https://leetcode-cn.com/problems/regular-expression-matching/ + * 10. 正则表达式匹配 | hard + */ + +function isMatch(s, p) { + +} + +// ---- test case ---- +console.log(isMatch('aa', 'a')) // false +console.log(isMatch('aa', 'a*')) // true +console.log(isMatch('ab', '.*')) // true +console.log(isMatch('aab', 'c*a*b')) // true +console.log(isMatch('mississippi', 'mis*is*p*.'))// false diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/014longestCommonPrefix.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/014longestCommonPrefix.js" new file mode 100644 index 00000000..100e366f --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/014longestCommonPrefix.js" @@ -0,0 +1,26 @@ +/** + * https://leetcode-cn.com/problems/longest-common-prefix/ + * 14. 最长公共前缀 + */ + +// O(n * minStrLen) +function longestCommonPrefix(strs) { + const minStrLen = Math.min(...strs.map(str => str.length)) + const n = strs.length + if (minStrLen < 1 || n < 1) return '' + if (n < 2) return strs[0] + + for (let i = 0; i < minStrLen; ++i) { + for (let j = 1; j < n; ++j) { + if (strs[j][i] !== strs[0][i]) { + return strs[j].slice(0, i) + } + } + } + return strs[0].slice(0, minStrLen) +} + +// ---- test case ---- +console.log(longestCommonPrefix(["flower","flow","flight"])) +console.log(longestCommonPrefix(["dog","racecar","car"])) +console.log(longestCommonPrefix(["ab","a"])) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/044isMatch.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/044isMatch.js" new file mode 100644 index 00000000..0e52962c --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/044isMatch.js" @@ -0,0 +1,15 @@ +/** + * https://leetcode-cn.com/problems/wildcard-matching/ + * 44. 通配符匹配 | hard + */ + +function isMatch(s, p) { + +} + +// ---- test case ---- +console.log(isMatch('aa', 'a')) // false +console.log(isMatch('aa', '*')) // true +console.log(isMatch('cb', '?a')) // false +console.log(isMatch('adceb', '*a*b')) // true +console.log(isMatch('acdcb', 'a*c?b'))// false diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/049groupAnagrams.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/049groupAnagrams.js" new file mode 100644 index 00000000..1a0277c0 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/049groupAnagrams.js" @@ -0,0 +1,28 @@ +/** + * https://leetcode-cn.com/problems/group-anagrams/ + * 49. 字母异位词分组 | medium + */ + +// 哈希表统计 +function groupAnagrams(strs) { + const startIdx = 'a'.codePointAt(0) + const map = {} + for(const str of strs) { + const cntArr = Array(26).fill(0) + for (let i = 0; i < str.length; ++i) { + cntArr[str.codePointAt(i) - startIdx]++ + } + const key = cntArr.toString() + if (map[key] === void(0)) { + map[key] = [str] + } else { + map[key].push(str) + } + } + return Object.values(map) +} + +// ---- test case ---- +console.log(groupAnagrams( + ["eat", "tea", "tan", "ate", "nat", "bat"] +)) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/058lengthOfLastWord.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/058lengthOfLastWord.js" new file mode 100644 index 00000000..70eaea5f --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/058lengthOfLastWord.js" @@ -0,0 +1,22 @@ +/** + * https://leetcode-cn.com/problems/length-of-last-word/ + * 58. 最后一个单词的长度 + */ + +// 从后往前遍历 +function lengthOfLastWord(str) { + let cnt = 0, i = str.length - 1 + while (str.charAt(i) === ' ') --i // 去掉末尾空格 + for (; i >= 0; --i, ++cnt) { + if (str.charAt(i) === ' ') { + break + } + } + return cnt +} + +// ---- test case ---- +console.log(lengthOfLastWord('Hello World')) +console.log(lengthOfLastWord('a')) +console.log(lengthOfLastWord('a ')) +console.log(lengthOfLastWord('')) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/10\346\257\225\344\270\232\346\200\273\347\273\223.textClipping" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/10\346\257\225\344\270\232\346\200\273\347\273\223.textClipping" deleted file mode 100644 index 6e67767c..00000000 Binary files "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/10\346\257\225\344\270\232\346\200\273\347\273\223.textClipping" and /dev/null differ diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/1143longestCommonSubsequence.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/1143longestCommonSubsequence.js" new file mode 100644 index 00000000..313f4c4f --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/1143longestCommonSubsequence.js" @@ -0,0 +1,29 @@ +/** + * https://leetcode-cn.com/problems/longest-common-subsequence/ + * 1143. 最长公共子序列 | medium + */ + +// 因为要用到dp[i-1][j-1]的值,所以dp开二维数组 +const longestCommonSubsequence = function(s1, s2) { + if (typeof s1 !== 'string' || typeof s2 !== 'string' || !s1.length || !s2.length) return 0 + const m = s1.length, n = s2.length + const dp = Array(m + 1).fill(0).map( + _ => Array(n + 1).fill(0)) + + for (let i = 1; i <= m; ++i) { + for (let j = 1; j <= n; ++j) { + if (s1[i - 1] === s2[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] +} + +// ---- test case ---- +console.log(longestCommonSubsequence('abcde', 'ace')) +console.log(longestCommonSubsequence('abc', 'abc')) +console.log(longestCommonSubsequence('abc', 'def')) +console.log(longestCommonSubsequence("abcba", "abcbcba")) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/115numDistinct.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/115numDistinct.js" new file mode 100644 index 00000000..2dc0131f --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/115numDistinct.js" @@ -0,0 +1,19 @@ +/** + * https://leetcode-cn.com/problems/distinct-subsequences/ + * 115. 不同的子序列 | hard + * + * dp + * if s[i] === t[j] + * f(i, j) = f(i-1, j) + f(i-1, j-1) // s去掉末尾 + s、t都去掉末尾 + * else + * f(i, j) = f(i-1, j) // s去掉末尾 + */ + +// O(mn) O(n) +function numDistinct (s, t) { + +} + +// ---- test case ---- +console.log(numDistinct('rabbbit', 'rabbit')) // 3 +console.log(numDistinct('babgbag', 'bag')) // 5 diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/125isPalindrome.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/125isPalindrome.js" new file mode 100644 index 00000000..310810ac --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/125isPalindrome.js" @@ -0,0 +1,78 @@ +/** + * https://leetcode-cn.com/problems/valid-palindrome/ + * 125. 验证回文串 + */ + +// 解法一:先用正则处理 O(n), O(n) +function isPalindrome(s) { + const t = s.replace(/[^\da-zA-Z]/g, '').toLowerCase() + const n = t.length + // console.log(t, n) + if (n < 2) return true + let lo = 0, hi = n - 1 + while (lo < hi && t[lo] === t[hi]) { + ++lo + --hi + } + return t[lo] === t[hi] +} + +// 解法二: 码点 + 双指针收缩 O(n) O(1) +function isPalindrome(s) { + const n = s.length + if (n < 2) return true + const s1 = 'A'.codePointAt(0) + const d1 = 'Z'.codePointAt(0) + const s2 = 'a'.codePointAt(0) + const d2 = 'z'.codePointAt(0) + const s3 = '0'.codePointAt(0) + const d3 = '9'.codePointAt(0) + const isAlpha = (ch) => { + const cp = ch.codePointAt(0) + return (cp >= s1 && cp <= d1 || cp >= s2 && cp <= d2) + } + const isDigital = (ch) => { + const cp = ch.codePointAt(0) + return (cp >= s3 && cp <= d3) + } + const isSameChar = (ch1, ch2) => { + const cp1 = ch1.codePointAt(0) + const cp2 = ch2.codePointAt(0) + return cp1 === cp2 + } + const isSameAlpha = (ch1, ch2) => { + if (!isAlpha(ch1) || !isAlpha(ch2)) return false // 要先保证两个字符都是字母 + const cp1 = ch1.codePointAt(0) + const cp2 = ch2.codePointAt(0) + const gap = Math.abs(cp1 - cp2) + return gap === s2 - s1 + } + + let lo = 0, hi = n - 1 + while (lo < hi) { + // filter + while (lo < hi && !isAlpha(s.charAt(lo)) && !isDigital(s.charAt(lo))) { ++lo } + while (lo < hi && !isAlpha(s.charAt(hi)) && !isDigital(s.charAt(hi))) { --hi } + // console.log(lo, hi, s[lo], s[hi]) + if (lo >= hi) { + break + } else if ( + isSameChar( s.charAt(lo), s.charAt(hi)) || + isSameAlpha(s.charAt(lo), s.charAt(hi)) + ) { + ++lo + --hi + } else { + return false + } + } + return true +} + +// ---- test case ---- +console.log(isPalindrome('A man, a plan, a canal: Panama')) +console.log(isPalindrome('race a car')) +console.log(isPalindrome('.,')) +console.log(isPalindrome('OP')) +console.log(isPalindrome('0P')) +console.log(isPalindrome('ab_a')) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/146toLowerCase.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/146toLowerCase.js" new file mode 100644 index 00000000..dbfe9535 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/146toLowerCase.js" @@ -0,0 +1,26 @@ +/** + * https://leetcode-cn.com/problems/to-lower-case/ + * 709. 转换成小写字母 + * + * 扩展知识:ascii码点 + * + 数字零为 48 + * + 大写字母A 为 65 + * + 小写字母a 为 97 + */ + +// 解法一、调用语言内置API +function toLowerCase(str) { + return str.toLowerCase() +} + +// 解法二、利用正则匹配 + 码点API +function toLowerCase(str) { + return str.replace(/[A-Z]/g, ch => { + return String.fromCharCode(ch.charCodeAt() + 32) + }) +} + +// ---- test case ---- +console.log(toLowerCase('Hello')) +console.log(toLowerCase('here')) +console.log(toLowerCase('LOVELY')) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/151reverseWords.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/151reverseWords.js" new file mode 100644 index 00000000..d5e0ab77 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/151reverseWords.js" @@ -0,0 +1,19 @@ +/** + * https://leetcode-cn.com/problems/reverse-words-in-a-string/ + * 151. 翻转字符串里的单词 + */ + +// 解法一:调用api +function reverseWords(s) { + return s.split(' ') + .filter(str => !!str) + .reverse() + .join(' ') +} + +// ---- test case ---- +console.log(reverseWords('the sky is blue')) +console.log(reverseWords(' hello world! ')) +console.log(reverseWords('a good example')) +console.log(reverseWords(' Bob Loves Alice ')) +console.log(reverseWords('Alice does not even like bob')) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/242isAnagram.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/242isAnagram.js" new file mode 100644 index 00000000..c104b6e3 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/242isAnagram.js" @@ -0,0 +1,22 @@ +/** + * https://leetcode-cn.com/problems/valid-anagram/ + * 242. 有效的字母异位词 + */ + +function isAnagram (s, t) { + const m = s.length, n = t.length + if (m !== n) return false + const startIdx = 'a'.codePointAt(0) + const cnt = Array(26).fill(0) + for (let i = 0; i < n; ++i) { + const idxS = s.codePointAt(i) - startIdx + const idxT = t.codePointAt(i) - startIdx + cnt[idxS] = cnt[idxS] + 1 + cnt[idxT] = cnt[idxT] - 1 + } + return cnt.filter(item => item !== 0).length === 0 +} + +// ---- test case ---- +console.log(isAnagram('anagram', 'nagaram')) // true +console.log(isAnagram('rat', 'car')) // false diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/344reverseString.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/344reverseString.js" new file mode 100644 index 00000000..c7119190 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/344reverseString.js" @@ -0,0 +1,18 @@ +/** + * https://leetcode-cn.com/problems/reverse-string/ + * 344. 反转字符串 + */ + +function reverseString(s) { + const loops = s.length >> 1 + for (let i = 0; i < loops; ++i) { + const tmp = s[i] + s[i] = s[s.length - i - 1] + s[s.length - i - 1] = tmp + } +} + +// ---- test case ---- +const arr = ["h","e","l","l","o"] +reverseString(arr) +console.log(arr) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/387firstUniqChar.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/387firstUniqChar.js" new file mode 100644 index 00000000..5d4451d7 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/387firstUniqChar.js" @@ -0,0 +1,39 @@ +/** + * https://leetcode-cn.com/problems/first-unique-character-in-a-string/ + * 387. 字符串中的第一个唯一字符 + */ + +// O(n^2) O(1) +function firstUniqChar1(s) { + for (let i = 0; i < s.length; ++i) { + if (s.lastIndexOf(s[i]) === s.indexOf(s[i])) { + return i + } + } + return -1 +} + +function firstUniqChar(s) { + const UPPER = s.length + const positions = Array(26).fill(UPPER) // 默认为 empty + const pos0 = 'a'.codePointAt(0) + for (let i = 0; i < s.length; ++i) { + const chIdx = s.codePointAt(i) - pos0 + if (positions[chIdx] < UPPER) { // 已经出现过 & 未标记过 + positions[chIdx] = UPPER + 1 + } else if (positions[chIdx] === UPPER) { // 第一次出现 + positions[chIdx] = i + } else { // 已经出现过 & 已标记过 + continue + } + } + const minPos = Math.min(...positions) + return minPos >= UPPER ? -1 : minPos +} + + +// ---- test case ---- +console.log(firstUniqChar('aadadaad')) // -1 +console.log(firstUniqChar('aabb')) // -1 +console.log(firstUniqChar('leetcode')) // 0 +console.log(firstUniqChar('loveleetcode'))// 2 diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/438findAnagrams.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/438findAnagrams.js" new file mode 100644 index 00000000..55bc9c43 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/438findAnagrams.js" @@ -0,0 +1,82 @@ +/** + * https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/ + * 438. 找到字符串中所有字母异位词 | medium + */ + +// 滑动窗口 + 数组统计 O(n) +function findAnagrams1 (s, p) { + const m = s.length, n = p.length + if (m < n || n < 1) return [] + const startIdx = 'a'.codePointAt(0) + // 按 s、p 前n个字符,初始化统计数组 cntArr + const cntArr = Array(26).fill(0) + for (let i = 0; i < n; ++i) { + const idxP = p.codePointAt(i) - startIdx + const idxS = s.codePointAt(i) - startIdx + ++cntArr[idxP] + --cntArr[idxS] + } + // 初始化 win 窗口,存储当前窗口中的字母 + const res = [] + const win = [...s.substr(0, n)] + if (cntArr.filter(cnt => cnt !== 0).length === 0) res.push(0) + for (let i = n; i < m; ++i) { + // 更新窗口 win + const newCh = s.charAt(i) + win.push(newCh) + const oldCh = win.shift() + // 更新 cntArr + const newIdx = newCh.codePointAt(0) - startIdx + const oldIdx = oldCh.codePointAt(0) - startIdx + --cntArr[newIdx] + ++cntArr[oldIdx] + // console.log(newCh, oldCh, cntArr, res) + if (cntArr.filter(cnt => cnt !== 0).length === 0) res.push(i - n + 1) + } + return res +} + +// 滑动窗口套路 +var findAnagrams = function(s, t) { + const need = new Map(), wind = new Map(); + for (const ch of t) { + need.set(ch, (need.get(ch) || 0) + 1); + } + + const res = [], size = t.length; + let count = 0; + for (let i = 0, j = 0; j < s.length; ++j) { + // Step1: 扩 1 + const jChar = s[j]; + if (need.has(jChar)) { + wind.set(jChar, (wind.get(jChar) || 0) + 1); + if (wind.get(jChar) === need.get(jChar)) { // !! + ++count; + } + } else { // 遇到不符合的元素,可以直接挪左指针到 j+1 + wind.clear(); + count = 0; + i = j + 1; + continue; + } + // Step2: 看看结果ok不 + if (j - i + 1 === size) { + if (count === need.size) { + res.push(i); + } + // Step3: 缩 1 + const iChar = s[i]; + ++i; + if (wind.get(iChar) === need.get(iChar)) { // !! + --count; + } + wind.set(iChar, wind.get(iChar) - 1); + } + } + return res; +}; + +// ---- test case ---- +console.log(findAnagrams('cbaebabacd', 'abc')) // [0, 6] +console.log(findAnagrams('abab', 'ab')) // [0, 1, 2] +// console.log(findAnagrams('baa', 'aa')) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/541reverseStr.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/541reverseStr.js" new file mode 100644 index 00000000..3c4d1419 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/541reverseStr.js" @@ -0,0 +1,28 @@ +/** + * https://leetcode-cn.com/problems/reverse-string-ii/ + * 541. 反转字符串 II + */ + +function swap(arr, i, j) { + if (i !== j) { + const tmp = arr[i] + arr[i] = arr[j] + arr[j] = tmp + } +} + +// 双指针 +function reverseStr(s, k) { + const arr = s.split('') + for (let i = 0; i < s.length; i += 2 * k) { + let start = i + let end = Math.min(i + k - 1, arr.length - 1) + while (start < end) { + swap(arr, start++, end--) + } + } + return arr.join('') +} + +// ---- test case ---- +console.log(reverseStr('abcdefg', 2)) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/557reverseWords.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/557reverseWords.js" new file mode 100644 index 00000000..2bb3d133 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/557reverseWords.js" @@ -0,0 +1,14 @@ +/** + * https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/ + * 557. 反转字符串中的单词 III + */ + +// 调api +function reverseWords(s) { + return s.split(' ').map( + str => str.split('').reverse().join('') + ).join(' ') +} + +// ---- test case ---- +console.log(reverseWords("Let's take LeetCode contest")) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/680validPalindrome.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/680validPalindrome.js" new file mode 100644 index 00000000..e6ef3078 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/680validPalindrome.js" @@ -0,0 +1,30 @@ +/** + * https://leetcode-cn.com/problems/valid-palindrome-ii/ + * 680. 验证回文字符串 Ⅱ + */ + +// 分治,判断首尾是否相同来缩短问题规模 +function validPalindrome(s) { + if (s.length < 3) return true + const n = s.length + if (s[0] === s[n - 1]) { + return validPalindrome(s.slice(1, n - 1)) + } + return isPalinDrome(s.slice(1)) || isPalinDrome(s.slice(0, n - 1)) +} + +function isPalinDrome(s) { + if (s.length < 2) return true + const n = s.length + const halfLen = s.length >> 1 + for (let i = 0; i < halfLen; ++i) { + if (s.charAt(i) !== s.charAt(n - i - 1)) { + return false + } + } + return true +} + +// ---- test case ---- +console.log(validPalindrome('aba')) +console.log(validPalindrome('abca')) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/771numJewelsInStones.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/771numJewelsInStones.js" new file mode 100644 index 00000000..942fd2f1 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/771numJewelsInStones.js" @@ -0,0 +1,19 @@ +/** + * https://leetcode-cn.com/problems/jewels-and-stones/ + * 771. 宝石与石头 + */ + +// 集合, O(m + n) +function numJewelsInStones(jewels, stones) { + const jewelSet = new Set([...jewels]) + let cnt = 0 + for (const ch of stones) { + if (jewelSet.has(ch)) ++cnt + } + return cnt +} + +// ---- test case ---- +console.log(numJewelsInStones('aA', 'aAAbbbb')) // 3 +console.log(numJewelsInStones('z', 'ZZ')) // 0 +console.log() diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/917reverseOnlyLetters.js" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/917reverseOnlyLetters.js" new file mode 100644 index 00000000..fdd607d8 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/917reverseOnlyLetters.js" @@ -0,0 +1,41 @@ +/** + * https://leetcode-cn.com/problems/reverse-only-letters/ + * 917. 仅仅反转字母 + */ + +function swap(arr, i, j) { + if (i !== j) { + const tmp = arr[i] + arr[i] = arr[j] + arr[j] = tmp + } +} + +function isStr(ch, A, Z, a, z) { + if (ch >= A && ch <= Z || ch >= a && ch <= z) { + return true + } + return false +} + +function reverseOnlyLetters(S) { + const A = 'A'.codePointAt(0) + const Z = 'Z'.codePointAt(0) + const a = 'a'.codePointAt(0) + const z = 'z'.codePointAt(0) + + const res = S.split('') + let lo = 0, hi = res.length - 1 + while (lo < hi) { + while(lo < hi && !isStr(res[lo].codePointAt(0), A, Z, a, z)) { ++lo } + while(lo < hi && !isStr(res[hi].codePointAt(0), A, Z, a, z)) { --hi } + swap(res, lo++, hi--) + } + return res.join('') +} + +// ---- test case ---- +console.log(reverseOnlyLetters('ab-cd')) +console.log(reverseOnlyLetters('a-bC-dEf-ghIj')) +console.log(reverseOnlyLetters('Test1ng-Leet=code-Q!')) +console.log(reverseOnlyLetters("7_28]")) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/README.md" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/README.md" new file mode 100644 index 00000000..f140d216 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/README.md" @@ -0,0 +1,22 @@ +# 学习笔记 + +## 第 20 课 | 字符串算法 + +string immutable + +**字符串访问** +1. [String.prototype.charAt()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/charAt) +2. StringName[index] +3. [区别](https://stackoverflow.com/questions/5943726/string-charatx-or-stringx) + - charAt 兼容性更好 + +**字符串拷贝** +1. [String.prototype.slice()] +2. [String.prototype.substr()] +3. [String.prototype.substring()] + +**字符串DP** +1. 最长回文串 + 1. 暴力 O(n^3) + 2. 中间向两边扩张 O(n^2) + 3. 动态规划 O(n^2) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/homework.md" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/homework.md" new file mode 100644 index 00000000..32f6bde7 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/homework.md" @@ -0,0 +1,140 @@ +# 作业 + +## 一、字符串基础 + +### 709 转换成小写字母(谷歌在半年内面试中考过) + ++ https://leetcode-cn.com/problems/to-lower-case/ ++ [代码](./146toLowerCase.js) + +### 58 最后一个单词的长度 + ++ https://leetcode-cn.com/problems/length-of-last-word/ ++ [代码](./058lengthOfLastWord.js) + +### 771 宝石与石头 + ++ https://leetcode-cn.com/problems/jewels-and-stones/ ++ [代码](./771numJewelsInStones.js) + +### 387 字符串中的第一个唯一字符 + ++ https://leetcode-cn.com/problems/first-unique-character-in-a-string/ ++ [代码](./387firstUniqChar.js) + +### 8 字符串转换整数 (atoi) + ++ https://leetcode-cn.com/problems/string-to-integer-atoi/ ++ [代码](./008myAtoi.js) + + + + + +## 二、字符串操作 + +### 14 最长公共前缀 + ++ https://leetcode-cn.com/problems/longest-common-prefix/ ++ [代码](./014longestCommonPrefix.js) + +### 344 反转字符串 + ++ https://leetcode-cn.com/problems/reverse-string/ ++ [代码](./344reverseString.js) + +### 541 反转字符串 II + ++ https://leetcode-cn.com/problems/reverse-string-ii/ ++ [代码](./541reverseStr.js) + +### 557 反转字符串中的单词 III + ++ https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/ ++ [代码](./557reverseWords.js) + +### 151 翻转字符串里的单词 + ++ https://leetcode-cn.com/problems/reverse-words-in-a-string/ ++ [代码](./151reverseWords.js) + +### 917 仅仅反转字母 + ++ https://leetcode-cn.com/problems/reverse-only-letters/ ++ [代码](./917reverseOnlyLetters.js) + + + + + +## 三、异位词问题 + + +### 242 有效的字母异位词 + ++ https://leetcode-cn.com/problems/valid-anagram/ ++ [代码](./242isAnagram.js) + +### 49 字母异位词分组 + ++ https://leetcode-cn.com/problems/group-anagrams/ ++ [代码](./049groupAnagrams.js) + +### 438 找到字符串中所有字母异位词 + ++ https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/ ++ [代码](./438findAnagrams.js) + + + + + + +## 四、回文串问题 + +### 125 验证回文串 + ++ https://leetcode-cn.com/problems/valid-palindrome/ ++ [代码](./125isPalindrome.js) + +### 680 验证回文字符串 Ⅱ + ++ https://leetcode-cn.com/problems/valid-palindrome-ii/ ++ [代码](./680validPalindrome.js) + +### 5 最长回文子串 + ++ https://leetcode-cn.com/problems/longest-palindromic-substring/ ++ [代码](./005longestPalindrome.js) + + + + +## 五、最长子串子序列问题 + +### 1143 最长公共子序列 + ++ https://leetcode-cn.com/problems/longest-common-subsequence/ ++ [代码](./1143longestCommonSubsequence.js) + + + +## 六、字符串 + DP问题 + +### 10 正则表达式匹配 + ++ https://leetcode-cn.com/problems/regular-expression-matching/ ++ [代码](./010isMatch.js) + +### 44 通配符匹配 + ++ https://leetcode-cn.com/problems/wildcard-matching ++ [代码](./044isMatch.js) + +### 115 不同的子序列 + ++ https://leetcode-cn.com/problems/distinct-subsequences/ ++ [代码](./115numDistinct.js) + + + diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/road.md" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/road.md" new file mode 100644 index 00000000..4b7f5943 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/road.md" @@ -0,0 +1,123 @@ +# 毕业刷题路线 + +```js +function getQuestionsArr () { + const list = document.querySelectorAll('.article-typo li') + return [...list].map((li, idx) => { + const href = li.childNodes[0].href // 题目地址 + const title = li.childNodes[0].innerText // 题目 + const level = li?.childNodes[1]?.nodeValue?.split(/[()()]/g)[1] || '' + console.log(idx, ':', href, title, level) // 难度级别 + return { + level, + title, + href, + } + }) +} +function getPrintTxt(quesArr) { + const map = { '简单':'😊', '中等':'🤔', '困难':'🤯' } + var txt = '' + for (let i = 0; i < quesArr.length; ++i) { + txt += `+ ${map[quesArr[i].level]} [${quesArr[i].title}](${quesArr[i].href})\n` + } + return txt +} +var quesArr = getQuestionsArr() +var txt = getPrintTxt(quesArr) +console.log(txt) +``` + +## 77题(“左耳朵耗子”陈皓老师和超哥分享的毕业刷题路线) + +### 一、 基础 + ++ 😊 [两数之和](http://leetcode-cn.com/problems/two-sum) ++ 😊 [有效的括号](http://leetcode-cn.com/problems/valid-parentheses/) ++ 🤔 [字符串解码](http://leetcode-cn.com/problems/decode-string/) ++ 🤯 [ LRU 缓存机制](http://leetcode-cn.com/problems/lru-cache/submissions/) ++ 🤔 [实现 Trie(前缀树)](http://leetcode-cn.com/problems/implement-trie-prefix-tree/) ++ 🤔 [添加与搜索单词 - 数据结构设计](http://leetcode-cn.com/problems/add-and-search-word-data-structure-design/) ++ 🤯 [单词搜索 II ](http://leetcode-cn.com/problems/word-search-ii/) ++ 😊 [找不同](http://leetcode-cn.com/problems/find-the-difference/) ++ 😊 [单词规律](http://leetcode-cn.com/problems/word-pattern/) ++ 😊 [字符串中的第一个唯一字符](http://leetcode-cn.com/problems/first-unique-character-in-a-string) ++ 🤔 [无重复字符的最长子串](http://leetcode-cn.com/problems/longest-substring-without-repeating-characters) ++ 🤯 [最小覆盖子串](http://leetcode-cn.com/problems/minimum-window-substring/) ++ 😊 [合并两个有序链表](http://leetcode-cn.com/problems/merge-two-sorted-lists) ++ 😊 [环形链表](http://leetcode-cn.com/problems/linked-list-cycle) ++ 🤔 [环形链表 II ](http://leetcode-cn.com/problems/linked-list-cycle-ii) ++ 😊 [反转链表](http://leetcode-cn.com/problems/reverse-linked-list) ++ 🤔 [反转链表 II ](http://leetcode-cn.com/problems/reverse-linked-list-ii) ++ 🤔 [旋转链表](http://leetcode-cn.com/problems/rotate-list) ++ 🤔 [排序链表](http://leetcode-cn.com/problems/sort-list/) ++ 😊 [链表中倒数第 k 个节点](http://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) ++ 🤔 [两两交换链表中的节点](http://leetcode-cn.com/problems/swap-nodes-in-pairs) ++ 😊 [按奇偶排序数组](http://leetcode-cn.com/problems/sort-array-by-parity/) ++ 😊 [按奇偶排序数组 II ](http://leetcode-cn.com/problems/sort-array-by-parity-ii/) ++ 😊 [有序数组的平方](http://leetcode-cn.com/problems/squares-of-a-sorted-array/) ++ 😊 [山脉数组的峰顶索引](http://leetcode-cn.com/problems/peak-index-in-a-mountain-array) ++ 🤯 [搜索旋转排序数组](http://leetcode-cn.com/problems/search-in-rotated-sorted-array) ++ 🤔 [搜索旋转排序数组 II ](http://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) ++ 🤔 [寻找旋转排序数组中的最小值](http://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) ++ 🤯 [寻找旋转排序数组中的最小值 II ](http://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) ++ 🤔 [搜索二维矩阵](http://leetcode-cn.com/problems/search-a-2d-matrix) ++ 🤔 [等式方程的可满足性](http://leetcode-cn.com/problems/satisfiability-of-equality-equations/) ++ 🤔 [朋友圈](http://leetcode-cn.com/problems/friend-circles/) ++ 🤔 [账户合并](http://leetcode-cn.com/problems/accounts-merge/) + +### 二、DFS + ++ 😊 [二叉树的最大深度](http://leetcode-cn.com/problems/maximum-depth-of-binary-tree) ++ 😊 [路径总和](http://leetcode-cn.com/problems/path-sum/) ++ 🤔 [路径总和 II ](http://leetcode-cn.com/problems/path-sum-ii/) ++ 🤔 [被围绕的区域](http://leetcode-cn.com/problems/surrounded-regions/) ++ 🤔 [岛屿数量](http://leetcode-cn.com/problems/number-of-islands/) ++ 🤔 [岛屿的最大面积](http://leetcode-cn.com/problems/max-area-of-island/) ++ 🤔 [在二叉树中分配硬币](http://leetcode-cn.com/problems/distribute-coins-in-binary-tree/) + +### 三、回溯 + ++ 🤔 [括号生成](http://leetcode-cn.com/problems/generate-parentheses/) ++ 🤯 [ N 皇后](http://leetcode-cn.com/problems/n-queens/) ++ 🤯 [ N 皇后 II ](http://leetcode-cn.com/problems/n-queens-ii/) ++ 🤔 [解数独](http://leetcode-cn.com/problems/sudoku-solver/) ++ 🤯 [不同路径 III ](http://leetcode-cn.com/problems/unique-paths-iii/) ++ 🤔 [单词搜索](http://leetcode-cn.com/problems/word-search/) + +### 四、分治 + ++ 🤔 [搜索二维矩阵 II ](http://leetcode-cn.com/problems/search-a-2d-matrix-ii/) ++ 🤔 [合并 K 个排序链表](http://leetcode-cn.com/problems/merge-k-sorted-lists) ++ 🤔 [为运算表达式设计优先级](http://leetcode-cn.com/problems/different-ways-to-add-parentheses) ++ 🤯 [给表达式添加运算符](http://leetcode-cn.com/problems/expression-add-operators) ++ 🤔 [数组中的第 K 个最大元素](http://leetcode-cn.com/problems/kth-largest-element-in-an-array) ++ 🤔 [最接近原点的 K 个点](http://leetcode-cn.com/problems/k-closest-points-to-origin/) ++ 🤯 [鸡蛋掉落](http://leetcode-cn.com/problems/super-egg-drop/) + +### 五、动态规划 + ++ 😊 [使用最小花费爬楼梯](http://leetcode-cn.com/problems/min-cost-climbing-stairs) ++ 😊 [爬楼梯](http://leetcode-cn.com/problems/climbing-stairs) ++ 😊 [不同路径](http://leetcode-cn.com/problems/unique-paths/) ++ 🤔 [最小路径和](http://leetcode-cn.com/problems/minimum-path-sum/) ++ 😊 [最大子序和](http://leetcode-cn.com/problems/maximum-subarray/) ++ 🤔 [乘积最大子数组](http://leetcode-cn.com/problems/maximum-product-subarray/) ++ 😊 [买卖股票的最佳时机](http://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock) ++ 😊 [买卖股票的最佳时机 II ](http://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) ++ 🤯 [买卖股票的最佳时机 III ](http://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/) ++ 🤯 [买卖股票的最佳时机 IV ](http://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/) ++ 🤔 [最佳买卖股票时机含冷冻期](http://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/) ++ 🤔 [买卖股票的最佳时机含手续费](http://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee) ++ 🤔 [零钱兑换](http://leetcode-cn.com/problems/coin-change) ++ 🤔 [零钱兑换 II ](http://leetcode-cn.com/problems/coin-change-2) ++ 🤯 [编辑距离](http://leetcode-cn.com/problems/edit-distance) ++ 🤯 [不同的子序列](http://leetcode-cn.com/problems/distinct-subsequences/) ++ 🤯 [柱状图中最大的矩形](http://leetcode-cn.com/problems/largest-rectangle-in-histogram/) ++ 🤯 [最大矩形](http://leetcode-cn.com/problems/maximal-rectangle/) ++ 🤔 [最大正方形](http://leetcode-cn.com/problems/maximal-square/) ++ 🤔 [最低票价](http://leetcode-cn.com/problems/minimum-cost-for-tickets/) ++ 😊 [区域和检索 - 数组不可变](http://leetcode-cn.com/problems/range-sum-query-immutable/) ++ 🤔 [二维区域和检索 - 矩阵不可变](http://leetcode-cn.com/problems/range-sum-query-2d-immutable/) ++ 🤔 [最长上升子序列](http://leetcode-cn.com/problems/longest-increasing-subsequence) ++ 🤯 [鸡蛋掉落](http://leetcode-cn.com/problems/super-egg-drop/) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/summary.md" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/summary.md" new file mode 100644 index 00000000..847bd09b --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/summary.md" @@ -0,0 +1,80 @@ +# 毕业总结 + +> [毕业刷题路线](./road.md) + +> [visualgo](visualgo.net) + +> [常见的复杂度](https://www.bigocheatsheet.com/) + +**一些JS的算法资料** + +> [awesome-coding-js](http://www.conardli.top/docs/) + +> [javascript-algorithms](https://github.com/trekhleb/javascript-algorithms/blob/master/README.zh-CN.md) + +**感言** + +算法这东西,其实没啥感悟好写的,算法无非是熟练、熟练、再熟练。总结,多过遍数。工作、生活场景中,多想想算法的运用场景。立个flag,工作期间也要每天刷一题leetcode,早日刷穿题库。 + + +**算法练习注意事项** + ++ 可以记忆算法模版,但不要背诵题目代码 ++ 尽量理解算法细节,但尽量避免单步调试 + - 多练习输出调试和纸上模拟调试 ++ 平衡看题解和自己想出的时间 + - 20分钟想不出,可以看题解,根据个人情况调整 ++ 学习他人优秀解法,但不找他人帮忙查错 + - 学会构造测试数据,学会“对拍” + +**科学“三遍刷题法”** + ++ 初学分类刷 -> 后期综合刷 ++ 一刷 + - 每个“小类别”的代表性题目,各刷几道 + - 此时需要看题解,很正常 ++ 二刷 + - 复习“代表性”题目 + - “小类别”合成“大类别”,刷该分类更多的题目,聚义反三,在尽量少的提示下完成 ++ 三刷 + - 综合性题目,尽量独立实现+测试 + +**科学“五步刷题法”** + +1. 理解题面 +2. 朴素实现/部分实现 + - 尽量让自己的解法更优,覆盖更多的场景 +3. 有提示解答 + - 看题解 vs 看提示 +4. 独立解答 + - 同时注意测试 +5. 写题解 + - 讲给别人有助于加深自己的理解,同时与别人的做法对比 + +## 知识点 + +**基础** + ++ Array 数组 - 按下标访问 ++ Linked list 链表 ++ Stack 栈 - 括号匹配、水槽接水 ++ Queue 队列 - BFS、滑动窗口 ++ HashTable 哈希表 ++ Set、Map - HashMap、TreeMap ++ BinaryTree 二叉树 - 递归 ++ BST 二叉搜索树 ++ Search 搜索 ++ Recursion 递归 ++ DFS 深度优先搜索 ++ BFS 广度优先搜索 ++ Divide & Conquer 分治 ++ Backtracking 回溯 ++ Greedy 贪心 ++ Binary Search 二分查找 + +**进阶** + ++ Trie Tree 字典树 ++ Disjoint Set 并查集 ++ Bloom Filter 布隆过滤器(不能删除,只能清空) ++ LRU Cache(LFU、ARC) -> [cache基础知识](https://www.jianshu.com/p/8dee805a5c89) diff --git a/codetop/003lengthOfLongestSubstring.js b/codetop/003lengthOfLongestSubstring.js new file mode 100644 index 00000000..2658745a --- /dev/null +++ b/codetop/003lengthOfLongestSubstring.js @@ -0,0 +1,29 @@ +/** + * https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/ + * https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/ + * 3. 无重复字符的最长子串 | medium + * + * hashmap + 双指针 + */ + +var lengthOfLongestSubstring = function(s) { + const map = new Map(); // 记录字母上一次出现的位置 + let maxSize = 0; + for (let i = j = 0; j < s.length; ++j) { + const jChar = s[j]; + if (map.has(jChar) && map.get(jChar) >= i) { // 出现重复,更新 left + i = map.get(jChar) + 1; + } + map.set(jChar, j); + if (maxSize < j - i + 1) { + maxSize = j - i + 1; + } + } + return maxSize; +}; + +// ---- test case ---- +console.log(lengthOfLongestSubstring('abcabcbb')) // 3 +console.log(lengthOfLongestSubstring('bbbbb')) // 1 +console.log(lengthOfLongestSubstring('pwwkew')) // 3 +console.log(lengthOfLongestSubstring('')) // 0 diff --git a/codetop/019removeNthFromEnd.js b/codetop/019removeNthFromEnd.js new file mode 100644 index 00000000..52df7e08 --- /dev/null +++ b/codetop/019removeNthFromEnd.js @@ -0,0 +1,19 @@ +/** + * https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/ + * 19. 删除链表的倒数第 N 个结点 | medium + * + */ + +function removeNthFromEnd(head, n) { + let dummy = new ListNode(-1, head) + let fast = slow = dummy + while (n-- >= 0 && fast) { // 先走n+1步 + fast = fast.next + } + while (fast) { + fast = fast.next + slow = slow.next + } + slow.next = slow.next ? slow.next.next : null + return dummy.next +} diff --git a/codetop/054spiralOrder.js b/codetop/054spiralOrder.js new file mode 100644 index 00000000..85771b2c --- /dev/null +++ b/codetop/054spiralOrder.js @@ -0,0 +1,49 @@ +/** + * https://leetcode-cn.com/problems/spiral-matrix/ + * 54. 螺旋矩阵 | medium + */ + +/** + * @param {number[][]} matrix + * @return {number[]} + */ +var spiralOrder = function(matrix) { + const m = matrix.length; n = matrix[0].length; + const visited = Array(m).fill(true).map(() => Array(n).fill(false)); + + let dir = 0, r = 0, c = 0; // dir 指方向,0 右 1 下 2 左 3 上 + const dr = [0, 1, 0, -1]; + const dc = [1, 0, -1, 0]; + const res = []; + for (let i = 0; i < m * n; ++i) { + res.push(matrix[r][c]); + + visited[r][c] = true; + let nextR = r + dr[dir]; + let nextC = c + dc[dir]; + if (nextR < 0 || nextR >= m || nextC < 0 || nextC >= n || visited[nextR][nextC]) { + dir = (dir + 1) % 4; + nextR = r + dr[dir]; + nextC = c + dc[dir]; + } + r = nextR; + c = nextC; + } + return res; +}; + +// ---- test case ---- +console.log(spiralOrder( + [ + [1,2,3], + [4,5,6], + [7,8,9] + ] +)) +// console.log(spiralOrder( +// [ +// [1, 2, 3, 4], +// [5, 6, 7, 8], +// [9,10,11,12] +// ] +// )) diff --git a/codetop/056merge.js b/codetop/056merge.js new file mode 100644 index 00000000..a72820f9 --- /dev/null +++ b/codetop/056merge.js @@ -0,0 +1,26 @@ +/** + * https://leetcode-cn.com/problems/merge-intervals/ + * 56. 合并区间 | medium + */ + +function merge(intervals) { + if (!intervals.length) return intervals + intervals.sort((a, b) => a[0] !== b[0] ? a[0] - b[0] : a[1] - b[1]) + let prev = intervals[0] + let res = [prev] + for (var curr of intervals) { + if (curr[0] <= prev[1]) { + prev[1] = Math.max(prev[1], curr[1]) + } else { + res.push(curr) + prev = curr + } + } + return res +} + +// ---- test case ---- +console.log(merge([[1,3],[2,6],[8,10],[15,18]])) +// [[1,6],[8,10],[15,18]] +console.log(merge([[1,4],[4,5]])) +// [[1,5]] diff --git a/codetop/059generateMatrix.js b/codetop/059generateMatrix.js new file mode 100644 index 00000000..48bd1449 --- /dev/null +++ b/codetop/059generateMatrix.js @@ -0,0 +1,32 @@ +/** + * https://leetcode-cn.com/problems/spiral-matrix-ii/ + * 59. 螺旋矩阵 II | medium + */ + +// 1. 计算 nr,nc +// 2. 判断 nr,nc 是否合法 +// 3. 如果不合法,转向,并用新的方向重新计算nr,nc +// 4. nr,nc 赋值给 r, c +function generateMatrix (n) { + const res = [...Array(n)].map(() => Array(n).fill(0)) + const dr = [0, 1, 0, -1] + const dc = [1, 0, -1, 0] + let dir = 0, r = 0, c = 0 + for (let i = 0; i < n * n; ++i) { + res[r][c] = i + 1 + let nr = r + dr[dir] + let nc = c + dc[dir] + if (nr < 0 || nr >= n || nc < 0 || nc >= n || res[nr][nc] !== 0) { + dir = (dir + 1) % 4 + nr = r + dr[dir] + nc = c + dc[dir] + } + r = nr + c = nc + } + return res +} + +// ---- test case ---- +console.log(generateMatrix(3)) +console.log(generateMatrix(1)) diff --git a/codetop/112hasPathSum.js b/codetop/112hasPathSum.js new file mode 100644 index 00000000..20feeca9 --- /dev/null +++ b/codetop/112hasPathSum.js @@ -0,0 +1,34 @@ +/** + * https://leetcode-cn.com/problems/path-sum/ + * 112. 路径总和 + */ + +// 递归 +function hasPathSum(root, sum) { + if (root == null) return false + if (root.left == null && root.right == null) { + return sum === root.val + } + return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val) +} + +// BFS(有副作用,会改变树的val) +function hasPathSum(root, sum) { + if (root == null) return false + let queue = [root] + while (queue.length > 0) { + let node = queue.pop() + if (node.left == null && node.right == null && node.val === sum) { + return true + } + if (node.left) { + node.left.val += node.val + queue.unshift(node.left) + } + if (node.right) { + node.right.val += node.val + queue.unshift(node.right) + } + } + return false +} diff --git a/codetop/1163getSmallestString.js b/codetop/1163getSmallestString.js new file mode 100644 index 00000000..653bfd03 --- /dev/null +++ b/codetop/1163getSmallestString.js @@ -0,0 +1,20 @@ +/** + * https://leetcode-cn.com/problems/smallest-string-with-a-given-numeric-value/ + * 1663. 具有给定数值的最小字符串 | medium + */ + +// 贪心法, 从右边往左填最大能放的字母 +function getSmallestString(n, k) { + const alphbets = [...' abcdefghijklmnopqrstuvwxyz'] + let str = '' + for (let i = n - 1; i >= 0; --i) { + let target = Math.min(k - i, 26) + k -= target + str = alphbets[target] + str + } + return str +} + +// ---- test case ---- +console.log(getSmallestString(3, 27)) +console.log(getSmallestString(5, 73)) diff --git a/codetop/129sumNumbers.js b/codetop/129sumNumbers.js new file mode 100644 index 00000000..673191ad --- /dev/null +++ b/codetop/129sumNumbers.js @@ -0,0 +1,8 @@ +/** + * https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/ + * 129. 求根节点到叶节点数字之和 + */ + +function sumNumbers(root) { + +} diff --git a/codetop/160getIntersectionNode.js b/codetop/160getIntersectionNode.js new file mode 100644 index 00000000..93f45b04 --- /dev/null +++ b/codetop/160getIntersectionNode.js @@ -0,0 +1,15 @@ +/** + * https://leetcode-cn.com/problems/intersection-of-two-linked-lists/ + * https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/ + * 160. 相交链表 | easy + * + */ + +function getIntersectionNode(headA, headB) { + let a = headA, b = headB + while (a !== b) { + a = a == null ? headB : a.next + b = b == null ? headA : b.next + } + return a +} diff --git a/codetop/209minSubArrayLen.js b/codetop/209minSubArrayLen.js new file mode 100644 index 00000000..8419dd79 --- /dev/null +++ b/codetop/209minSubArrayLen.js @@ -0,0 +1,25 @@ +/** + * https://leetcode-cn.com/problems/minimum-size-subarray-sum/ + * 209. 长度最小的子数组 | medium + * + * 双指针 + */ + +const minSubArrayLen = function(target, nums) { + let distance = Infinity + let left = 0, sum = 0 + for (let right = 0; right < nums.length; ++right) { + sum += nums[right] + while (sum >= target) { // 缩短 + distance = Math.min(distance, right - left + 1) + sum -= nums[left] + ++left + } + } + return distance === Infinity ? 0 : distance +} + +// ---- test case ---- +console.log(minSubArrayLen(7, [2, 3, 1, 2, 4, 3])) // 2 +console.log(minSubArrayLen(4, [1, 4, 4])) // 1 +console.log(minSubArrayLen(11, [1, 1, 1, 1, 1, 1, 1, 1])) // 0 diff --git a/codetop/215findKthLargest.js b/codetop/215findKthLargest.js new file mode 100644 index 00000000..e546e7cc --- /dev/null +++ b/codetop/215findKthLargest.js @@ -0,0 +1,36 @@ +/** + * https://leetcode-cn.com/problems/kth-largest-element-in-an-array/ + * 215. 数组中的第K个最大元素 | medium + */ + +function partition(arr, left, right) { + const pivot = arr[left] + while (left < right) { + while (left < right && arr[right] >= pivot) --right + if (left < right) arr[left] = arr[right] + while (left < right && arr[left] <= pivot) ++left + if (left < right) arr[right] = arr[left] + } + arr[left] = pivot + return left +} + +function findKthLargest(arr, k, l = 0, r = arr.length - 1) { + // console.log("🚀", arr, k, l, r) + if (l === r) return arr[l] + const pivotIdx = partition(arr, l, r) + if (pivotIdx === r + 1 - k) { + return arr[pivotIdx] + } else if (pivotIdx < r + 1 - k) { // 继续在右边找 + return findKthLargest(arr, k, pivotIdx + 1, r) + } else { // 在左边找 + return findKthLargest(arr, k - (r - pivotIdx + 1), l, pivotIdx - 1) + } +} + +// ---- test case ---- +console.log(findKthLargest([3,2,1,5,6,4], 2)) // 5 +console.log(findKthLargest([3,2,3,1,2,4,5,5,6], 4)) // 4 +console.log(findKthLargest([2, 1], 2)) // 1 +console.log(findKthLargest([7,6,5,4,3,2,1], 2)) // 6 +console.log(findKthLargest([7,6,5,4,3,2,1], 5)) // 3 diff --git a/codetop/238productExceptSelf.js b/codetop/238productExceptSelf.js new file mode 100644 index 00000000..9ab2d47d --- /dev/null +++ b/codetop/238productExceptSelf.js @@ -0,0 +1,25 @@ +/** + * https://leetcode-cn.com/problems/product-of-array-except-self/ + * 238. 除自身以外数组的乘积 + * O(n) + O(1) + * + * trick + */ + +// 方法:第一轮从左往右,存储下标左边元素乘积; 第二轮从右往左,乘上右边的元素乘积 +var productExceptSelf = function(nums) { + const res = []; + for (let i = 0, k = 1; i < nums.length; ++i) { + res.push(k); // res 存储当前下标左边的元素的乘积 + k *= nums[i]; + } + for(let i = nums.length - 1, k = 1; i >= 0; --i) { + res[i] *= k; + k *= nums[i]; + } + return res; +}; + +// ---- test case ---- +console.log(productExceptSelf([1,2,3,4])) // [1, 1, 2, 6] -> [24, 12, 8, 6] +console.log(productExceptSelf([-1, 1, 0, -3, 3])) // -1, -1, 0, 0, 0 diff --git a/codetop/415addStrings.js b/codetop/415addStrings.js new file mode 100644 index 00000000..7d443491 --- /dev/null +++ b/codetop/415addStrings.js @@ -0,0 +1,21 @@ +/** + * https://leetcode-cn.com/problems/add-strings/ + * 415. 字符串相加 | easy + * + */ + +function addStrings(s1, s2) { + const res = [] + let i = s1.length - 1, j = s2.length - 1, carry = 0 + while (i >= 0 || j >= 0 || carry > 0) { + const x = i >= 0 ? Number(s1[i--]) : 0 + const y = j >= 0 ? Number(s2[j--]) : 0 + res.push((x + y + carry) % 10) + carry = Math.floor((x + y + carry) / 10) + } + return res.reverse().join('') +} + +// ---- test case ---- +console.log(addStrings('234', '8498')) +console.log(addStrings('1', '9')) diff --git a/codetop/600validPalindrome.js b/codetop/600validPalindrome.js new file mode 100644 index 00000000..2de6b8b2 --- /dev/null +++ b/codetop/600validPalindrome.js @@ -0,0 +1,34 @@ +/** + * https://leetcode-cn.com/problems/valid-palindrome-ii/ + * 680. 验证回文字符串 Ⅱ + * + */ + +function validPalindrome(s) { + if (s.length < 3) return true + const n = s.length + if (s[0] === s[n - 1]) { + // 递归 + return validPalindrome(s.slice(1, n - 1)) + } + // 子问题 + return isPalinDrome(s.slice(1)) || isPalinDrome(s.slice(0, n - 1)) +} + +// 判断是否是回文串 +function isPalinDrome(s) { + if (s.length < 2) return true + const n = s.length + const halfLen = s.length >> 1 + for (let i = 0; i < halfLen; ++i) { + if (s.charAt(i) !== s.charAt(n - i - 1)) { + return false + } + } + return true +} + +// ---- test case ---- +console.log(validPalindrome('aba')) // true +console.log(validPalindrome('abca')) // true +console.log(validPalindrome('abcda')) // false diff --git a/codetop/678checkValidString.js b/codetop/678checkValidString.js new file mode 100644 index 00000000..d565ae76 --- /dev/null +++ b/codetop/678checkValidString.js @@ -0,0 +1,19 @@ +/** + * https://leetcode-cn.com/problems/valid-parenthesis-string/ + * 678. 有效的括号字符串 | medium + */ + +// 贪心法,双向遍历,检查是否有不匹配括号 +function checkValidString(s) { + let left = 0, right = 0, n = s.length + for (let i = 0; i < n; ++i) { + left += s[i] === ')' ? -1 : 1 + right += s[n - i - 1] === '(' ? -1 : 1 + if (left < 0 || right < 0) return false + } + return true +} + +// ---- test case ---- +console.log(checkValidString('(*))')) +console.log(checkValidString('(')) diff --git a/codetop/704search.js b/codetop/704search.js new file mode 100644 index 00000000..e531a81f --- /dev/null +++ b/codetop/704search.js @@ -0,0 +1,23 @@ +/** + * https://leetcode-cn.com/problems/binary-search/ + * 704. 二分查找 + */ + +function search(arr, target) { + let left = 0, right = arr.length - 1 + while (left <= right) { + const mid = left + ((right - left) >> 1) + if (target === arr[mid]) { + return mid + } else if (target < arr[mid]) { + right = mid - 1 + } else { + left = mid + 1 + } + } + return -1 +} + +// ---- test case ---- +console.log(search([-1,0,3,5,9,12], 9)) +console.log(search([-1,0,3,5,9,12], 2)) diff --git a/codetop/912sortArray.js b/codetop/912sortArray.js new file mode 100644 index 00000000..581cd736 --- /dev/null +++ b/codetop/912sortArray.js @@ -0,0 +1,41 @@ +/** + * https://leetcode-cn.com/problems/sort-an-array/ + * 912. 排序数组 | medium + */ + +function sortArray(nums) { + quickSort(nums, 0, nums.length - 1); + return nums; +} + +function quickSort(nums, lo, hi) { + if (lo >= hi) return; + + const pivotIndex = partition(nums, lo, hi); + // 优化:跳过与 pivot 相等的元素 + let nextHi = pivotIndex - 1, nextLo = pivotIndex + 1; + while (nextHi > lo && nums[nextHi] === nums[pivotIndex]) --nextHi; + while (nextLo < hi && nums[nextLo] === nums[pivotIndex]) ++nextLo; + + quickSort(nums, lo, nextHi); + quickSort(nums, nextLo, hi); + return nums; +}; + +function partition(nums, left, right) { + const pivot = nums[left]; + while (left < right) { + // console.log(left, right, nums) + while (left < right && nums[right] >= pivot) --right; + if (left < right) nums[left] = nums[right]; + while (left < right && nums[left] <= pivot) ++left; + if (left < right) nums[right] = nums[left]; + } + nums[left] = pivot; + // console.log(nums, left, right) + return left; +} + +// ---- test case ---- +console.log(sortArray([5,2,3,1])) +console.log(sortArray([0])) diff --git a/codetop/major.js b/codetop/major.js new file mode 100644 index 00000000..d338553f --- /dev/null +++ b/codetop/major.js @@ -0,0 +1,24 @@ +function major(arr, k) { + const n = arr.length + const LOWLINE = n / k + const map = new Map() + for (let i = 0; i < n; ++i) { + if (map.has(arr[i])) { + map.set(arr[i], map.get(arr[i]) + 1) + } else { + map.set(arr[i], 1) + } + } + const ret = [...map.entries()].filter( + ([_item, times]) => { + return times > LOWLINE + } + ).map( + ([item, _times]) => item + ) + return ret +} + +// ---- test case ---- +console.log(major([1, 2, 3, 1, 2, 3, 4], 7)) // [1, 2, 3] +console.log(major([4, 1], 1)) // [] diff --git a/codetop/offer022-getKthFromEnd.js b/codetop/offer022-getKthFromEnd.js new file mode 100644 index 00000000..43732919 --- /dev/null +++ b/codetop/offer022-getKthFromEnd.js @@ -0,0 +1,16 @@ +/** + * https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/ + * 剑指 Offer 22. 链表中倒数第k个节点 | easy + * + * linklist、双指针 + */ + +const getKthFromEnd = function(head, k) { + let slow = fast = head + while (--k >= 0) fast = fast.next + while (fast) { + fast = fast.next + slow = slow.next + } + return slow +} diff --git a/codetop/offer039majorityElement.js b/codetop/offer039majorityElement.js new file mode 100644 index 00000000..7f1f8c9a --- /dev/null +++ b/codetop/offer039majorityElement.js @@ -0,0 +1,28 @@ +/** + * https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/ + * 剑指 Offer 39. 数组中出现次数超过一半的数字 + */ + +// 摩尔投票法 +function majorityElement(arr) { + const n = arr.length + if (n < 1) return + let res = arr[0], cnt = 1 + for (let i = 1; i < n; ++i) { + if (arr[i] === res) { + ++cnt + } else { + if (cnt === 0) { + cnt = 1 + res = arr[i] + } else { + --cnt + } + } + } + return res +} + +// ---- test case ---- +console.log(majorityElement([1, 2, 3, 2, 2, 2, 5, 4, 2])) +console.log(majorityElement([3, 2, 3])) diff --git a/hot100/031nextPermutation.js b/hot100/031nextPermutation.js new file mode 100644 index 00000000..9edd95c2 --- /dev/null +++ b/hot100/031nextPermutation.js @@ -0,0 +1,52 @@ +/** + * https://leetcode.cn/problems/next-permutation/description/ + * + * 下一个排列 + * + * 思路:找升序对,交换来个大一点儿的,后半部份逆序为升序。 + */ + + +/** + * @param {number[]} nums + * @return {void} Do not return anything, modify nums in-place instead. + */ +var nextPermutation = function(nums) { + if (!Array.isArray(nums) || nums.length < 1) return; + + // Step1: 从后往前,找到连续的升序对 A[pos] < A[pos + 1] + let pos = - 1; + for (let i = nums.length - 2; i >= 0; --i) { + if (nums[i] < nums[i + 1]) { // 升序对 + pos = i; + break; + } + } + if (pos === -1) { + nums.reverse(); + return + } + + // Step2: 从后往前,找到第一个大于 A[pos] 的值交换,如 12354 -> 12453 + for (let i = nums.length - 1; i > pos; --i) { + if (nums[i] > nums[pos]) { + const tmp = nums[pos]; + nums[pos] = nums[i]; + nums[i] = tmp; + break; + } + } + + // Step3: 逆置 pos+1, end + for (let l = pos + 1, r = nums.length -1; l < r; ++l, --r) { + const tmp = nums[l]; + nums[l] = nums[r]; + nums[r] = tmp; + } +}; + +// ---- test ---- +const arr1 = [1, 2, 3, 5, 4]; +nextPermutation(arr1); +console.log(arr1); // [1, 2, 4, 3, 5] + diff --git a/hot100/035searchInsert.js b/hot100/035searchInsert.js new file mode 100644 index 00000000..81dd2d41 --- /dev/null +++ b/hot100/035searchInsert.js @@ -0,0 +1,25 @@ +/** + * https://leetcode.cn/problems/search-insert-position + * + * 搜索插入位置 + * + * 思路:二分查找 + */ + +/** + * @param {number[]} nums + * @param {number} target + * @return {number} + */ +var searchInsert = function(nums, target) { + let lo = 0, hi = nums.length - 1; + while (lo <= hi) { + const mid = lo + ((hi - lo) >> 1); + if (nums[mid] < target) { + lo = mid + 1; + } else { + hi = mid - 1; + } + } + return lo; +}; diff --git a/hot100/039combinationSum.js b/hot100/039combinationSum.js new file mode 100644 index 00000000..b769d616 --- /dev/null +++ b/hot100/039combinationSum.js @@ -0,0 +1,11 @@ +/** + * https://leetcode.cn/problems/combination-sum + * + * 组合总和 + */ + +var combinationSum = function (candidates, target) { + if (!candidates || !candidates.length) return []; +// todo + +}; diff --git a/hot100/041firstMissingPositive.js b/hot100/041firstMissingPositive.js new file mode 100644 index 00000000..9d54e6b4 --- /dev/null +++ b/hot100/041firstMissingPositive.js @@ -0,0 +1,31 @@ +/** + * 缺失的第一个正数 + * + * 要求,不能开新空间 + * 思路:利用数组空间,原地置换 + */ + +var firstMissingPositive = function (nums) { + + const swap = (i, j) => { + if (i !== j) { + const tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + } + + const n = nums.length; + for (let i = 0; i < n; ++i) { + while (nums[i] > 0 && nums[i] <= n && nums[i] !== nums[nums[i] - 1]) { + swap(i, nums[i] - 1); // 放到正确的位置上 -> [1, ..., n] + } + } + + for(let i = 0; i < n; ++i) { + if (nums[i] !== i + 1) { + return i + 1; + } + } + return n + 1; +} diff --git a/hot100/048rotate.js b/hot100/048rotate.js new file mode 100644 index 00000000..b1b7efa1 --- /dev/null +++ b/hot100/048rotate.js @@ -0,0 +1,31 @@ +/** + * https://leetcode.cn/problems/rotate-image + * + * 旋转图像 + * + * 思路:上下翻转 + 对角线翻转 + */ + +/** + * @param {number[][]} matrix + * @return {void} Do not return anything, modify matrix in-place instead. + */ +var rotate = function(matrix) { + const swap = (i, j, k, l) => { + const tmp = matrix[i][j]; + matrix[i][j] = matrix[k][l]; + matrix[k][l] = tmp; + } + + const n = matrix.length; + for (let i = 0; i < (n >> 1); ++i) { + for (let j = 0; j < n; ++j) { + swap(i, j, n - i - 1 ,j); + } + } + for (let i = 1; i < n; ++i) { + for (let j = 0; j < i; ++j) { + swap(i, j, j, i); + } + } +}; diff --git a/hot100/073setZeros.js b/hot100/073setZeros.js new file mode 100644 index 00000000..16dc7c16 --- /dev/null +++ b/hot100/073setZeros.js @@ -0,0 +1,98 @@ +/** + * 矩阵置零 + * + * 思路: + * + */ + + +// 方法一:第一遍记录行号数组i, 列号数组j,第二遍置零。 其实也 ✅ +var setZeroes = function (matrix) { + const m = matrix.length, n = matrix[0].length; + const rows = new Set(), cols = new Set(); + + for (let i = 0; i < m; ++i) { + for (let j = 0; j < n; ++j) { + if (matrix[i][j] === 0) { + rows.add(i); + cols.add(j); + } + } + } + // console.log(rows, cols); + + for (const r of rows) { + for (let j = 0; j < n; ++j) { + matrix[r][j] = 0; + } + } + + for (const c of cols) { + for (let i = 0; i < m; ++i) { + matrix[i][c] = 0; + } + } +} + +// 方法二:优化空间复杂度为 O(1)。 用首行 0 标记该行有 0,首列 0 标记该列有 0。 +var setZeroes2 = function (matrix) { + const m = matrix.length, n = matrix[0].length; + + // Step1, 遍历首行,记录首行是否有零 + let isFirstRowHasZero = false; + for (let j = 0; j < n; ++j) { + console.log('...', j, matrix[0][j]) + if (matrix[0][j] === 0) { + isFirstRowHasZero = true; + break; + } + } + + // Step2, 遍历首列,记录首列是否有零 + let isFirstColHasZero = false; + for (let i = 0; i < m; ++i) { + console.log('...', i, matrix[i][0]) + if (matrix[i][0] === 0) { + isFirstColHasZero = true; + break; + } + } + console.log(isFirstRowHasZero, isFirstColHasZero); + // Step3: 遍历除首行首列的矩阵,用首行首列记录 + for (let i = 1; i < m; ++i) { + for (let j = 1; j < n; ++j) { + if (matrix[i][j] === 0) { + matrix[i][0] = 0; // 表示 i 行有零 + matrix[0][j] = 0; // 表示 j 行有零 + } + } + } + console.log(matrix); + // Step4: 置零 + for (let i = 1; i < m; ++i) { + for (let j = 1; j < n; ++j) { + if (matrix[i][0] === 0 || matrix[0][j] === 0) { + matrix[i][j] = 0; + } + } + } + + // Step5: 处理首行首列 + if (isFirstRowHasZero) { + for (let j = 0; j < n; ++j) { + matrix[0][j] = 0; + } + } + if (isFirstColHasZero) { + for (let i = 0; i < m; ++i) { + matrix[i][0] = 0; + } + } +} + +// ---- test ---- +// const matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]; +const matrix = [[1, 0]] +setZeroes2(matrix); +console.log(matrix); + diff --git a/hot100/118generate.js b/hot100/118generate.js new file mode 100644 index 00000000..71f3f75c --- /dev/null +++ b/hot100/118generate.js @@ -0,0 +1,28 @@ +/** + * https://leetcode.cn/problems/pascals-triangle + * + * 杨辉三角 + * + * 思路:生成器,f(i, j) = f(i-1, j-1) + f(i-1, j) + */ + +/** + * @param {number} numRows + * @return {number[][]} + */ +var generate = function(numRows) { + const res = []; + for (let i = 0; i < numRows; ++i) { + const row = Array(i + 1).fill(1); + for (let j = 1; j < i; ++j) { + row[j] = res[i - 1][j - 1] + res[i - 1][j]; + // console.log('inner', i, j, res[i - 1], row) + } + // console.log(row) + res.push(row); + } + return res; +}; + +// --- test --- +console.log(generate(5)) diff --git a/hot100/128longestConsecutive.js b/hot100/128longestConsecutive.js new file mode 100644 index 00000000..abb38c4b --- /dev/null +++ b/hot100/128longestConsecutive.js @@ -0,0 +1,60 @@ +/** + * https://leetcode.cn/problems/longest-consecutive-sequence + * 128. 最长连续序列 + * + * 【map】 + * + */ +// /** +// * @param {number[]} nums +// * @return {number} +// * 思路一:排序后,检查连续性 O(nlogn) +// */ +// var longestConsecutive = function(nums) { +// if (!Array.isArray(nums) || nums.length < 1) return 0; + +// nums.sort((x, y) => x - y); + +// let count = 1, max = 1; +// for (let i = 1; i < nums.length; ++i) { +// if (nums[i] === nums[i - 1]) { +// continue; +// } else if (nums[i] === nums[i - 1] + 1) { +// count++; +// max = Math.max(max, count); +// } else { +// count = 1; +// } +// } +// return max; +// }; + + +/** + * @param {number[]} nums + * @return {number} + * 思路二:set 去重,遍历set,判断是起点则往后找, O(n) + */ +var longestConsecutive = function(nums) { + if (!Array.isArray(nums) || nums.length < 1) return 0; + + const set = new Set(nums); + let maxCount = 0; + for (const num of set) { + if (!set.has(num - 1)) { + let currentNum = num; + let count = 1; + while (set.has(currentNum + 1)) { + currentNum += 1; + count += 1; + } + maxCount = Math.max(maxCount, count); + } + } + return maxCount; +}; + +// ---- test case ---- +console.log(longestConsecutive([100,4,200,1,3,2])) // 4 +console.log(longestConsecutive([0,3,7,2,5,8,4,6,0,1])) // 9 +console.log(longestConsecutive([1, 2, 0, 1])) // 3 diff --git a/hot100/287findDuplicate.js b/hot100/287findDuplicate.js new file mode 100644 index 00000000..43f2d498 --- /dev/null +++ b/hot100/287findDuplicate.js @@ -0,0 +1,29 @@ +/** + * https://leetcode.cn/problems/find-the-duplicate-number/ + * + * trick + * + * 思考,元素的范围是 1 ~ len-1,这些数可以看作下标,构成一个有环链表。 + * 思路:快慢指针变种 + */ + + +/** + * @param {number[]} nums + * @return {number} + */ +var findDuplicate = function(nums) { + let slow = fast = 0, hasCycle = false; + do { + slow = nums[slow]; + fast = nums[nums[fast]]; + } while (slow !== fast); + + fast = 0; + do { + slow = nums[slow]; + fast = nums[fast]; + } while (slow !== fast); + + return fast; +}; diff --git a/hot100/739dailyTemperatures.js b/hot100/739dailyTemperatures.js new file mode 100644 index 00000000..ba53324e --- /dev/null +++ b/hot100/739dailyTemperatures.js @@ -0,0 +1,26 @@ +/** + * https://leetcode.cn/problems/longest-consecutive-sequence + * 每日温度 + * + * 单调递减栈:遍历整个数组,如果栈不空,且当前数字大于栈顶元素,那么如果直接入栈的话就不是 递减栈 ,所以需要取出栈顶元素,由于当前数字大于栈顶元素的数字,而且一定是第一个大于栈顶元素的数,直接求出下标差就是二者的距离。 + * + * 思路: + */ + +/** + * @param {number[]} temperatures + * @return {number[]} + */ +var dailyTemperatures = function(A) { + const stack = [], res = Array(A.length).fill(0); + for (let i = 0; i < A.length; ++i) { + while(stack.length && A[i] > A[stack[stack.length - 1]]) { + const topIndex = stack.pop(); + res[topIndex] = i - topIndex; + } + stack.push(i); + } + return res; +}; + +// ---- test ---- diff --git a/hot100/dp/279numSquares.js b/hot100/dp/279numSquares.js new file mode 100644 index 00000000..966ae5b5 --- /dev/null +++ b/hot100/dp/279numSquares.js @@ -0,0 +1,23 @@ +/** + * https://leetcode.cn/problems/perfect-squares + * 完全平方数 + * + * dp[i] = min( i, f(i-j*j)+1 ) + * O(n ^ 3/2) + */ + +var numSquares = function(n) { + const dp = Array(n + 1).fill(0); + for (let i = 1; i <= n; ++i) { + dp[i] = i; + for (let j = 1; i - j * j >= 0; ++j) { + dp[i] = Math.min(dp[i], dp[i - j * j] + 1); + } + } + // console.log(n, dp); + return dp[n]; +} + +// ---- test case ---- +console.log(numSquares(12)); +console.log(numSquares(13)); diff --git a/hot100/graph/207canFinish.js b/hot100/graph/207canFinish.js new file mode 100644 index 00000000..8b3a0759 --- /dev/null +++ b/hot100/graph/207canFinish.js @@ -0,0 +1,44 @@ +/** + * https://leetcode.cn/problems/course-schedule + * 课程安排 + */ + +var canFinish = function (numCourses, prerequisites) { + const inDegree = Array(numCourses).fill(0); // 统计节点的入度 + const adjTable = new Map(); // 邻接表 + for (let i = 0; i < prerequisites.length; ++i) { + const [to, from] = prerequisites[i]; + inDegree[to]++; + if (adjTable.has(from)) { + const arr = adjTable.get(from); + arr.push(to); + } else { + adjTable.set(from, [to]); + } + } + + const queue = []; + for (let i = 0; i < inDegree.length; ++i) { + if (inDegree[i] === 0) queue.push(i); // 入度为零的课入队 + } + + let count = 0; + while (queue.length) { + const selected = queue.shift(); // 当前选中的课 + ++count; + const toList = adjTable.get(selected); // 这门课的后续课程 + if (toList && toList.length) { + for (const toItem of toList) { + --inDegree[toItem]; + if (inDegree[toItem] === 0) { + queue.push(toItem); + } + } + } + } + return count === numCourses; +}; + +// ---- test cases ---- +console.log(canFinish(2, [[1,0]])); // true +console.log(canFinish(2, [[1,0],[0,1]])); // false diff --git a/hot100/graph/994orangesRotting.js b/hot100/graph/994orangesRotting.js new file mode 100644 index 00000000..33171744 --- /dev/null +++ b/hot100/graph/994orangesRotting.js @@ -0,0 +1,45 @@ +/** + * https://leetcode.cn/problems/rotting-oranges + * + */ + +// BFS +var orangesRotting = function(A) { + const m = A.length, n = A[0].length; + const queue = []; + let freshCnt = 0; + // Step1: 统计新鲜橘子的数量 freshCnt,并且把烂橘子坐标放入队列 + for (let i = 0; i < m; ++i) { + for (let j = 0; j < n; ++j) { + if (A[i][j] === 1) { + ++freshCnt; + } else if (A[i][j] === 2) { + queue.push([i, j]); + } + } + } + + // 尝试感染位置 r,c + const mark = (r, c) => { + if (r >= 0 && r < m && c >= 0 && c < n && A[r][c] === 1) { + A[r][c] = 2; + --freshCnt; + queue.push([r, c]); + } + } + + // Step2: BFS 传染 + let round = 0; + while (freshCnt && queue.length) { + ++round; + const size = queue.length; + for (let i = 0; i < size; ++i) { + const [r, c] = queue.shift(); + mark(r - 1, c); + mark(r + 1, c); + mark(r, c + 1); + mark(r, c - 1); + } + } + return freshCnt > 0 ? -1 : round; +}; diff --git a/hot100/greedy/763partitionLabels.js b/hot100/greedy/763partitionLabels.js new file mode 100644 index 00000000..70459e85 --- /dev/null +++ b/hot100/greedy/763partitionLabels.js @@ -0,0 +1,29 @@ +/** + * https://leetcode.cn/problems/partition-labels + * 划分字母区间 + * + */ + +var partitionLabels = (S) => { + const lastPosMap = new Map(); // 字母的最远位置 + for (let i = 0; i < S.length; ++i) { + lastPosMap.set(S[i], i); + } + + const res = []; + let start = 0; // 切割点 + let maxPos = 0; // 已扫描的字符中,最远的位置 + for (let i = 0; i < S.length; ++i) { + const curLastPos = lastPosMap.get(S[i]); + maxPos = Math.max(maxPos, curLastPos); + if (i === maxPos) { + res.push(i - start + 1); + start = i + 1; + } + } + return res; +} + +// ---- test case ---- +console.log(partitionLabels('ababcbacadefegdehijhklij')); // [9, 7, 8] +console.log(partitionLabels('eccbbbbdec')); // [10] diff --git a/hot100/linklist/002addTwoNumbers.js b/hot100/linklist/002addTwoNumbers.js new file mode 100644 index 00000000..4bb47079 --- /dev/null +++ b/hot100/linklist/002addTwoNumbers.js @@ -0,0 +1,29 @@ +/** + * https://leetcode.cn/problems/add-two-numbers/ + * + * 两数相加 + * + * carry 存进位 + */ + +var addTwoNumbers = function(l1, l2) { + let p = dummy = new ListNode(-1); + let carry = 0; + + while (l1 || l2) { + const x = l1 ? l1.val : 0; + const y = l2 ? l2.val : 0; + let sum = x + y + carry; + carry = Math.floor(sum / 10); + sum = sum % 10; + + p.next = new ListNode(sum); + p = p.next; + l1 = l1 ? l1.next : null; + l2 = l2 ? l2.next : null; + } + if (carry) { + p.next = new ListNode(carry); + } + return dummy.next; +}; diff --git a/hot100/linklist/023mergeKLists.js b/hot100/linklist/023mergeKLists.js new file mode 100644 index 00000000..ebc8938b --- /dev/null +++ b/hot100/linklist/023mergeKLists.js @@ -0,0 +1,37 @@ +/** + * https://leetcode.cn/problems/merge-k-sorted-lists + * + * 合并 k 个升序链表 + */ + +// 思路1. 两两合并 -> 归并 +var merge2List = function (l1, l2) { + if (!l1) return l2; + if (!l2) return l1; + if (l1.val <= l2.val) { + l1.next = merge2List(l1.next, l2); + return l1; + } else { + l2.next = merge2List(l1, l2.next); + return l2; + } +}; + +var merge = function (lists, lo, hi) { + if (lo === hi) { + return lists[lo]; + } + let mid = lo + ((hi - lo) >> 1); + let l1 = merge(lists, lo, mid); + let l2 = merge(lists, mid + 1, hi); + return merge2List(l1, l2); +}; + +var mergeKLists = function (lists) { + if (lists.length === 0) { + return null; + } + return merge(lists, 0, lists.length - 1); +}; + +// 思路2. diff --git a/hot100/linklist/138copyRandomList.js b/hot100/linklist/138copyRandomList.js new file mode 100644 index 00000000..c403c042 --- /dev/null +++ b/hot100/linklist/138copyRandomList.js @@ -0,0 +1,26 @@ +/** + * https://leetcode.cn/problems/copy-list-with-random-pointer + * + * 随机链表的复制 + * + * 思路:第一轮存入 hash 表(oldNode -> newNode),第二轮连指针 + * O(n), O(n) + * + */ + +var copyRandomList = function (head) { + const map = new Map(); + // 第一轮遍历:创建节点,并存入 map(oldNode -> newNode) + for (let p = head; p !== null; p = p.next) { + const node = new Node(p.val, null, null); + map.set(p, node); + } + + // 第二轮遍历 + for (let p = head; p !== null; p = p.next) { + const node = map.get(p); + p.next && (node.next = map.get(p.next)); + p.random && (node.random = map.get(p.random)); + } + return map.get(head); +}; diff --git a/hot100/linklist/148sortList.js b/hot100/linklist/148sortList.js new file mode 100644 index 00000000..40737f90 --- /dev/null +++ b/hot100/linklist/148sortList.js @@ -0,0 +1,24 @@ +/** + * https://leetcode.cn/problems/sort-list + * + * 排序链表 + */ + +// 解法一:数组排序 +var sortList1 = function (head) { + const arr = []; + for (let p = head; p !== null; p = p.next) { + arr.push(p.val); + } + + arr.sort((x, y) => x - y); + const dummy = new ListNode(-1); + let p = dummy; + for (const val of arr) { + p.next = new ListNode(val); + p = p.next; + } + return dummy.next; +} + +// 解法二:归并~ diff --git a/hot100/linklist/234isPalindrome.js b/hot100/linklist/234isPalindrome.js new file mode 100644 index 00000000..1fd09c8e --- /dev/null +++ b/hot100/linklist/234isPalindrome.js @@ -0,0 +1,22 @@ +/** + * https://leetcode.cn/problems/palindrome-linked-list + * + * 回文链表 + * + * 思路:推进数组 + 双指针 O(n) + O(n) + */ + +var isPalindrome = function(head) { + const vals = []; + + while (head) { + vals.push(head.val); + head = head.next + } + for (let l = 0, r = vals.length - 1; l < r; ++l, --r) { + if (vals[l] !== vals[r]) { + return false; + } + } + return true; +}; diff --git a/hot100/stack/394decodeString.js b/hot100/stack/394decodeString.js new file mode 100644 index 00000000..9eae954f --- /dev/null +++ b/hot100/stack/394decodeString.js @@ -0,0 +1,41 @@ +/** + * https://leetcode.cn/problems/decode-string + * 字符串解码 + * + * 【stack】 + * + */ + +/** + * @param {string} s + * @return {string} + * + * 解法:辅助栈 item 存 [multi, res] + */ +var decodeString = function(s) { + const stack = []; + let res = '', multi = 0; + for (const ch of s) { + const isNumber = !Number.isNaN(Number(ch)); + if (isNumber) { + multi = multi * 10 + Number(ch); + } else if (ch === '[') { + stack.push([res, multi]); + res = ''; + multi = 0; + } else if (ch === ']') { + const [lastRes, curMulti] = stack.pop(); + res = lastRes + res.repeat(curMulti); + } else { + res = `${res}${ch}`; + } + console.log(ch, res); + } + return res; +}; + +// ---- test ---- +console.log(decodeString('3[a]2[bc]')); +console.log(decodeString('3[a2[c]]')); +console.log(decodeString('2[abc]3[cd]ef')); +console.log(decodeString('abc3[cd]xyz')); diff --git a/hot100/tree/101isSymmetric.js b/hot100/tree/101isSymmetric.js new file mode 100644 index 00000000..3bb1f0b6 --- /dev/null +++ b/hot100/tree/101isSymmetric.js @@ -0,0 +1,38 @@ +/** + * https://leetcode.cn/problems/symmetric-tree + * + * 对称二叉树 + * + * 思路:递归比较两颗子树 + */ + +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {boolean} + */ +var isSymmetric = function(root) { + if (!root) return true; + return dfs(root.left, root.right); +}; + +function dfs(left, right) { + if (!left && !right) { + return true; + } + if (!left || !right) { + return false; + } + if (left.val !== right.val) { + return false; + } + + return dfs(left.left, right.right) && dfs(left.right, right.left); +} diff --git a/hot100/tree/108sortedArrayToBST.js b/hot100/tree/108sortedArrayToBST.js new file mode 100644 index 00000000..ee6146bc --- /dev/null +++ b/hot100/tree/108sortedArrayToBST.js @@ -0,0 +1,16 @@ +/** + * https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree + * + * 思路:分而治之,建立根,左右为子问题。) + */ + +// 递归版 +var sortedArrayToBST = function(nums) { + const helper = (l, r) => { + if (l > r) return null; + let mid = l + ((r - l) >> 1); + return new TreeNode(nums[mid], helper(l, mid - 1), helper(mid + 1, r)); + } + + return helper(0, nums.length - 1); +}; diff --git a/hot100/tree/114flatten.js b/hot100/tree/114flatten.js new file mode 100644 index 00000000..9be8d2e0 --- /dev/null +++ b/hot100/tree/114flatten.js @@ -0,0 +1,26 @@ +/** + * https://leetcode.cn/problems/flatten-binary-tree-to-linked-list + * + * 二叉树展开为链表 + */ + +// 思路:dfs,先推入数组。再遍历数组,构造链表 +var flatten = function (root) { + + const arr = []; + const dfs = (node) => { + if (!node) return; + arr.push(node); + dfs(node.left); + dfs(node.right); + } + dfs(root); + + let p = root; + for (let i = 1; i < arr.length; ++i) { + p.left = null + p.right = arr[i]; + p = p.right; + } + return root; +} diff --git a/hot100/tree/199rightSideView.js b/hot100/tree/199rightSideView.js new file mode 100644 index 00000000..04185600 --- /dev/null +++ b/hot100/tree/199rightSideView.js @@ -0,0 +1,28 @@ +/** + * https://leetcode.cn/problems/binary-tree-right-side-view + * + * 二叉树的右视图 + */ + +// 思路1: bfs层序遍历 +var rightSideView = function (root) { + if (!root) return []; + const res = [], queue = [root]; + while (queue.length) { + const size = queue.length; + + for (let i = 0; i < size; ++i) { + const node = queue.pop(); + if (node.left) { + queue.unshift(node.left); + } + if (node.right) { + queue.unshift(node.right); + } + if (i === size - 1) { + res.add(node.val); + } + } + } + return res; +} diff --git a/hot100/tree/230kthSmallest.js b/hot100/tree/230kthSmallest.js new file mode 100644 index 00000000..8aa27fe3 --- /dev/null +++ b/hot100/tree/230kthSmallest.js @@ -0,0 +1,26 @@ +/** + * https://leetcode.cn/problems/kth-smallest-element-in-a-bst + * BST中第 k 小的元素 + * @param {*} root + * @param {*} k + * + * + * 思路: dfs 中序遍历,结果为数组第 k 个元素 + * O(n) O(n) + */ +var kthSmallest = function (root, k) { + const nums = []; + + const dfs = (p) => { + if (!p) return; + dfs(p.left); + nums.push(p.val); + // if (nums.length === k) { + // return + // } + dfs(p.right); + } + + dfs(root); + return nums[k - 1]; +} diff --git a/hot100/tree/543diameterOfBinaryTree.js b/hot100/tree/543diameterOfBinaryTree.js new file mode 100644 index 00000000..16dd8274 --- /dev/null +++ b/hot100/tree/543diameterOfBinaryTree.js @@ -0,0 +1,23 @@ +/** + * https://leetcode.cn/problems/diameter-of-binary-tree + * + * 二叉树的直径 + * + * 思路: + */ + +var diameterOfBinaryTree = function(root) { + + let count = 1; // 最长路径的节点个数 + + const depth = (node) => { + if (!node) return 0; + let l = depth(node.left); + let r = depth(node.right); + count = Math.max(count, l + r + 1); // 更新最长路径节点数 + return Math.max(l, r) + 1 // 返回根到叶子的最长路径的节点数 + } + + depth(root); + return count - 1 +}; diff --git a/interview/bytedance/react03.html b/interview/bytedance/react03.html new file mode 100644 index 00000000..ebc6087d --- /dev/null +++ b/interview/bytedance/react03.html @@ -0,0 +1,62 @@ + diff --git a/interview/bytedance/scheduler.js b/interview/bytedance/scheduler.js new file mode 100644 index 00000000..04bd9d0e --- /dev/null +++ b/interview/bytedance/scheduler.js @@ -0,0 +1,54 @@ +/** + * 带并发限制的异步调度器 + */ + +class Scheduler { + concurrency = 2; + running = 0; + queue = []; + + add(task) { + return new Promise(resolve => { + this.queue.push({ + taskGenerator: task, + resolve, + }); + this.schedule(); + }) + } + + schedule() { + while (this.queue.length > 0 && this.running < this.concurrency) { + const curTask = this.queue.shift(); + this.running += 1; + curTask.taskGenerator().then(result => { + this.running -= 1; + curTask.resolve(result); + this.schedule(); + }) + } + } +} + +// ---- test case ---- +const timeout = (time) => + new Promise((resolve) => { + setTimeout(resolve, time); + }); + +const scheduler = new Scheduler(); +const addTask = (time, order) => { + scheduler.add(() => timeout(time)).then(() => console.log(`[${new Date().toLocaleString()}] `, order)); +}; + +console.log(`[${new Date().toLocaleString()}] start...`); +addTask(10000, "1"); +addTask(5000, "2"); +addTask(3000, "3"); +addTask(4000, "4"); +// output: 2 3 1 4 +// 一开始,1、2两个任务进入队列 +// 500ms时,2完成,输出2,任务3进队 +// 800ms时,3完成,输出3,任务4进队 +// 1000ms时,1完成,输出1 +// 1200ms时,4完成,输出4 diff --git a/interview/handwrite/esapi/01_bind.js b/interview/handwrite/esapi/01_bind.js new file mode 100644 index 00000000..98fc9806 --- /dev/null +++ b/interview/handwrite/esapi/01_bind.js @@ -0,0 +1,14 @@ +// 手动绑定 this +Function.prototype.fakeBind = function (obj, ...args) { + return (...rest) => { + this.call(obj, ...args, ...rest); + } +} + +// ---- test case ---- +function f(b) { + console.log(this.a, b); +} + +f.fakeBind({a: 3})(4); +f.fakeBind({a: 3}, 10)(11); diff --git a/interview/handwrite/esapi/02_sleep_delay.js b/interview/handwrite/esapi/02_sleep_delay.js new file mode 100644 index 00000000..62e7d8a8 --- /dev/null +++ b/interview/handwrite/esapi/02_sleep_delay.js @@ -0,0 +1,33 @@ +const sleep = (time) => new Promise(resolve => setTimeout(resolve, time)); + +function delay(func, time, ...args) { + return new Promise((resolve) => { + setTimeout(() => { + Promise.resolve(func(...args)).then(resolve); + }, time); + }) +} + +// ---- test case ---- +// ;(async () => { +// console.log(1); +// await sleep(1000); +// console.log(2); +// })(); + + +console.log(new Date()) +delay( + (str) => { + return new Error(str) + }, + 3000, + 'wlwlwl' + ) + .then( + o => console.log('then:', o) + ).catch( + e => { + console.log('error:', e) + } + ) diff --git a/interview/handwrite/esapi/03_promiseAll.js b/interview/handwrite/esapi/03_promiseAll.js new file mode 100644 index 00000000..b5941812 --- /dev/null +++ b/interview/handwrite/esapi/03_promiseAll.js @@ -0,0 +1,21 @@ +// Promise.all + +function pAll(_promises) { + return new Promise((resolve, reject) => { + // Iterable => Array + const promises = Array.from(_promises); + // 结果用一个数组维护 + const r = []; + const len = promises.length; + let count = 0; + for (let i = 0; i < len; ++i) { + // Promise.resolve 把所有数据都转化为 Promise + Promise.resolve(promises[i]).then(o => { + r[i] = o; + if (++count === len) { + resolve(r); + } + }).catch(e => reject(e)); + } + }) +} diff --git a/interview/handwrite/esapi/04_isArray.js b/interview/handwrite/esapi/04_isArray.js new file mode 100644 index 00000000..64fd7f3f --- /dev/null +++ b/interview/handwrite/esapi/04_isArray.js @@ -0,0 +1,11 @@ +const isArray = (ele) => { + // return ele instanceof Array + return Object.prototype.toString.call(ele) === '[object Array]'; +} + +const log = console.log; +log(isArray(null)); +log(isArray(123)); +log(isArray('123')); +log(isArray([])); +log(isArray(new Array())); diff --git a/interview/handwrite/esapi/05_flat.js b/interview/handwrite/esapi/05_flat.js new file mode 100644 index 00000000..aee3b361 --- /dev/null +++ b/interview/handwrite/esapi/05_flat.js @@ -0,0 +1,14 @@ +// 思路:reduce + concat +function flatten(list, depth = 1) { + if (depth === 0) return list; + return list.reduce((a, b) => { + const target = Array.isArray(b) ? flatten(b, depth - 1) : b; + return a.concat(target); + }, []) +} + + +// ---- test case ---- +const a = flatten([1, 2, 3, [4, [5, 6]]]); +const b = flatten([1, 2, 3, [4, [5, 6]]], 2); +console.log(a, b); diff --git a/interview/handwrite/esapi/06_promise.js b/interview/handwrite/esapi/06_promise.js new file mode 100644 index 00000000..a2666e0c --- /dev/null +++ b/interview/handwrite/esapi/06_promise.js @@ -0,0 +1,128 @@ +// 异步链式有点难 + +const STATUS = { + PENDING: "PENDING", + FULFILLED: "FULFILLED", + REJECTED: "REJECTED", +}; + +class MyPromise { + + static resolve(value) { + if (value && value.then) { + return value; + } + return new MyPromise(resolve => resolve(value)); + } + + constructor(executer) { + this.status = STATUS.PENDING; + this.value = void 0; // return value when succ + this.reason = void 0; // return value when fail + this.resolveQueue = []; // callbacks of succ + this.rejectQueue = []; // callbacks of fail + + // revert status & execute resolveQueue + const resolve = (value) => { + setTimeout(() => { + if (this.status === STATUS.PENDING) { + this.status = STATUS.FULFILLED; + this.value = value; + // console.log(this.resolveQueue) + this.resolveQueue.forEach(({ fn, resolve: res}) => { + return res(fn(value)); + }); + } + }) + }; + + // revert status & execute rejectQueue + const reject = (reason) => { + setTimeout(() => { + if (this.status === STATUS.PENDING) { + this.status = STATUS.REJECTED; + this.reason = reason; + // console.log(this.rejectQueue) + this.rejectQueue.forEach(({ fn, reject: rej }) => { + return rej(fn(reason)); + }); + } + }) + }; + + try { + executer(resolve, reject); + } catch (error) { + reject(error); + } + } + + then(onFullfilled, onRejected) { + if (this.status === STATUS.FULFILLED) { + const result = fn(this.value); + return MyPromise.resolve(result); + } + if (this.status === STATUS.PENDING) { + return new MyPromise((resolve, reject) => { + this.resolveQueue.push({ fn: onFullfilled, resolve, reject }); + this.rejectQueue.push({ fn: onRejected, resolve, reject }); + }) + } + } +} + +// ---- test case ---- +// 同步调用 +// new MyPromise((resolve, reject) => { +// console.log("[sync promise]"); +// resolve("🙆‍♂️"); +// reject("🙅‍♂️"); +// }).then( +// (value) => { +// console.log("[sync fulfilled]", value); +// }, +// (reason) => { +// console.log("[sync rejected]", reason); +// } +// ); + +// 异步调用 +// new MyPromise((resolve, reject) => { +// console.log("[async promise]"); +// setTimeout(() => { +// resolve("🙆‍♂️"); +// }, 500); +// setTimeout(() => { +// reject("🙅‍♂️"); +// }, 100); +// }).then( +// (value) => { +// console.log("[async fulfilled]", value); +// }, +// (reason) => { +// console.log("[async rejected]", reason); +// } +// ); + +// 异步链式调用 +const startTime = new Date(); +var p2 = new MyPromise((resolve, reject) => { + console.log("promise start:", new Date() - startTime); + setTimeout(() => { + console.log("promise resolve:", new Date() - startTime); + resolve("value1"); + }, 2000); +}) + .then((value) => { + return new MyPromise((resolve) => { + console.log("then1", value, new Date() - startTime); + setTimeout(() => { + console.log("then resolve", new Date() - startTime); + resolve("value2"); + }, 3000); + }); + }) + .then((value) => { + console.log("then2", value, new Date() - startTime); + }, (err) => {console.log(err)}); +// console.log(p2); diff --git a/interview/handwrite/esapi/07_arrayReduce.js b/interview/handwrite/esapi/07_arrayReduce.js new file mode 100644 index 00000000..31c23a32 --- /dev/null +++ b/interview/handwrite/esapi/07_arrayReduce.js @@ -0,0 +1,13 @@ +const reduce = (list, fn, ...init) => { + let next = init.length ? init[0] : list[0]; + for (let i = init.length ? 0 : 1; i < list.length; ++i) { + next = fn(next, list[i], i); + } + return next; +} + +// ---- test case ---- +const res = reduce([1,2,3,4,5], (sum, num) => { + return sum + num; +}, 100); +console.log(res); // 115 diff --git a/interview/handwrite/esapi/08_trim.js b/interview/handwrite/esapi/08_trim.js new file mode 100644 index 00000000..8cb95bc9 --- /dev/null +++ b/interview/handwrite/esapi/08_trim.js @@ -0,0 +1,4 @@ +const trim = str => str.replace(/^\s+|\s+$/g, ''); + +// ---- test case ---- +console.log(trim(' abcd ')); diff --git a/interview/handwrite/javascript/eventEmitter.js b/interview/handwrite/javascript/eventEmitter.js new file mode 100644 index 00000000..12ba7e20 --- /dev/null +++ b/interview/handwrite/javascript/eventEmitter.js @@ -0,0 +1,77 @@ +/** + * emitter.on(name,fn) // 订阅name事件,监听函数为fn,可多次订阅 + * emitter.once(name,fn) // 功能与on类似,但监听函数为一次性的,触发后自动移除 + * emitter.emit(name,data1,data2,...,datan) // 发布name事件,所有订阅该事件的监听函数被触发,data1,…,datan作为参数传给监听函数,若有多个函数,按照顺序执行 + * emitter.remove(name,fn) // 移除name事件的监听函数fn + */ + +class EventEmitter { + constructor() { + this.cache = Object.create(null); + } + /** 订阅事件 */ + on(type, fn) { + const fns = this.cache[type] || []; + if (fns.indexOf(fn) < 0) { + fns.push(fn); + } + this.cache[type] = fns; + return this; + } + /** 触发一次就销毁的事件 */ + once(type, fn) { + const wrapFn = (...args) => { + fn.apply(this, args); + this.remove(type, fn); + } + this.on(type, wrapFn); + return this; + } + emit(type, ...args) { + const fns = this.cache[type]; + if (Array.isArray(fns)) { + fns.forEach(fn => { + fn(...args); + }) + } + return this; + } + remove(type, fn) { + const fns = this.cache[type]; + if (Array.isArray(fns)) { + if (fn) { + const index = fns.indexOf(fn); + if (index > -1) { + fns.splice(index, 1); + } + } else { + fns.length = 0; // clear + } + } + return this; + } +} + +// ---- test case ---- +function fn1(a, b, c) { + console.log(a, b, c); +} +function fn2(a, b) { + console.log(a * 2, b * 3); +} +var em = new EventEmitter(); + +// 测试 on 和 emit +em.on("e1", fn1) + .on("e1", fn2) + .emit("e1", 1, 2, 3) // 1, 2, 3 2, 6 + .emit("e1", 1, 2, 3); // 1, 2, 3 2, 6 +console.log(em.cache); // [fn1, fn2] + +// 测试 once 和 emit +em.once("e2", fn1) + .once("e2", fn2) + .emit("e2", 3, 4, 5) // 3, 4, 5 6, 12 + .emit("e2", 3, 4, 5) // 3, 4, 5 + .emit("e2", 3, 4, 5); // 3, 4, 5 +console.log(em.cache); // [] diff --git a/interview/handwrite/lodashapi/01_throttle_debounce.js b/interview/handwrite/lodashapi/01_throttle_debounce.js new file mode 100644 index 00000000..c953b5e9 --- /dev/null +++ b/interview/handwrite/lodashapi/01_throttle_debounce.js @@ -0,0 +1,33 @@ + +function debounce(f, wait) { + let timer; + return function(...args) { + clearTimeout(timer); + timer = setTimeout(() => { + f.apply(this, args); + }, wait); + } +} + +function throttle(f, wait) { + let timer; + return function (...args) { + if (timer) return; + timer = setTimeout(() => { + f.apply(this, args); + timer = null; + }, wait); + } +} + +// 时间戳版 throttle +function throttleByTime(f, wait) { + let lastTime = 0; + return function(...args) { + const now = Date.now(); + if (now - lastTime >= wait) { + f.apply(this, args); + lastTime = now; + } + } +} diff --git a/interview/handwrite/lodashapi/02_cloneDeep.js b/interview/handwrite/lodashapi/02_cloneDeep.js new file mode 100644 index 00000000..20fa36d2 --- /dev/null +++ b/interview/handwrite/lodashapi/02_cloneDeep.js @@ -0,0 +1,47 @@ +const myDeepClone = function (origin, vm = new WeakMap) { + if (typeof origin !== 'object' || origin == null) return origin; + if (origin instanceof Date) return new Date(origin); + if (origin instanceof RegExp) return new RegExp(origin); + // weakMap dispose circular deps + const stashed = vm.get(origin); + if (stashed) return stashed; + let target = Array.isArray(origin) ? [] : {}; + vm.set(origin, target); + for (const key in origin) { + if (origin.hasOwnProperty(key)) { + target[key] = myDeepClone(origin[key], vm); + } + } + return target; +} + +// ---- test case 1 ---- +var p1 = { + name: 'william', + age: 12, + birthday: new Date('1995-09-12'), + hobby: ['eat', 'sleep', 'code'], + address: { + city: 'shenzhen' + }, +} + +var p2 = myDeepClone(p1) +p1.address.city = 'beijing' +p2.hobby.push('music') +console.log(p1, p2) // p1、p2 相互独立 + +// ---- test case 2 ---- +var o1 = {x: 1}, o2 = {y: 2} +o1.a = o2 +o2.b = o1 +var o3 = myDeepClone(o1) +o1.z = 100 +console.log(o1, o3) + +// ---- test case 3 ---- +var arr1 = [1, 2, 3] +arr1.push(arr1) +var arr2 = myDeepClone(arr1) +arr2.push(1) +console.log(arr1, arr2) diff --git a/interview/handwrite/lodashapi/03_isEqual.js b/interview/handwrite/lodashapi/03_isEqual.js new file mode 100644 index 00000000..f3a66019 --- /dev/null +++ b/interview/handwrite/lodashapi/03_isEqual.js @@ -0,0 +1,20 @@ +function isEqual(x, y) { + if (x === y) return true; + if (typeof x === 'object' && !!x && typeof y === 'object' && !!y) { + const keysX = Object.keys(x); + const keysY = Object.keys(y); + if (keysX.length !== keysY.length) return false; + for (const key of keysX) { + if (!isEqual(x[key], y[key])) return false; // recursive + } + return true; + } + return false; +} + +// ---- test case ---- +const o1 = { a: [1,2,3], b:'222', c: { d: { e: [34] }, e: 1 } } +const o2 = { a: [1,2,3], b:'222', c: { d: { e: [34] }, e: 1 } } +const o3 = { a: [1,2,3], b:'222', c: { d: { e: [35] }, e: 1 } } +console.log(isEqual(o1, o2)); +console.log(isEqual(o3, o2)); diff --git a/interview/handwrite/lodashapi/04_get.js b/interview/handwrite/lodashapi/04_get.js new file mode 100644 index 00000000..4c799d18 --- /dev/null +++ b/interview/handwrite/lodashapi/04_get.js @@ -0,0 +1,7 @@ +function get(source, path, defaultValue) { + // a[3].b -> a.3.b -> [a, 3, b] + const paths = path.replace() +} + +// ---- test case ---- + diff --git a/interview/handwrite/utils/getAge.js b/interview/handwrite/utils/getAge.js new file mode 100644 index 00000000..5831d68f --- /dev/null +++ b/interview/handwrite/utils/getAge.js @@ -0,0 +1,25 @@ +function getAge(birthday) { + const validator = new RegExp(/^\d{4}-\d{2}-\d{2}$/); + if (!validator.test(birthday)) { + throw new Error(`invalidator birthday: ${birthday}`); + } + + const [birthYear, birthMonth, birthDate] = birthday.split('-').map(str => Number(str)); + const now = new Date(); + const nowYear = now.getFullYear(); + const nowMonth = now.getMonth() + 1; + const nowDate = now.getDate(); + + const age = nowYear - birthYear - 1; + if (nowMonth > birthMonth || (nowMonth === birthMonth && nowDate > birthDate)) { + return age + 1; + } + return age; +} + +// ---- test case ---- +var log = console.log +log(getAge('1995-09-12')) +log(getAge('1995-12-26')) +log(getAge('1997-12-02')) +log(getAge('1997-12-28')) diff --git a/interview/handwrite/utils/getFrequentTag.js b/interview/handwrite/utils/getFrequentTag.js new file mode 100644 index 00000000..b8d4e842 --- /dev/null +++ b/interview/handwrite/utils/getFrequentTag.js @@ -0,0 +1,11 @@ +function getFrequentTag() { + const o = $$('*') // = document.querySelectorAll('*') + .map(it => it.tagName.toLowerCase()) + .reduce((o, tagName) => { + o[tagName] = o[tagName] ? o[tagName] + 1 : 1; + return o; + }, {}); + + return Object.entries(o) + .reduce((x, y) => x[1] > y[1] ? x : y); +} diff --git a/interview/moe/1_placeholder.js b/interview/moe/1_placeholder.js new file mode 100644 index 00000000..c05d9734 --- /dev/null +++ b/interview/moe/1_placeholder.js @@ -0,0 +1,71 @@ +const __ = Symbol('PLACEHOLDER'); + +function hasSymbol(arr) { + // console.log('h:', arr); + for (const item of arr) { + if (typeof item === 'symbol') return true; + } + return false; +} + +function placeholder(fn, ...bound) { + while (bound.length < fn.length) { + bound.push(__); + } + return (...args) => { + const newArgs = bound.slice(); + for (let i = 0, j = 0; i < newArgs.length && j < args.length; ++i) { + if (newArgs[i] === __) { + newArgs[i] = args[j++]; + } + } + if (hasSymbol(newArgs)) { + return placeholder(fn, ...newArgs); + } else { + return fn(...newArgs); + } + } +} + +// ---- test case ---- + +function main() { + function dot(x1, y1, x2, y2) { + console.log('dot', x1, y1, x2, y2) + return x1 * x2 + y1 * y2; + } + + const p1 = placeholder(dot, 3, 4); + + // assert.deepStrictEqual(typeof placeholder(dot, 2, __, __, 4), 'function'); + console.log(typeof placeholder(dot, 2, __, __, 4), typeof p1); // 👉 function + // assert.deepStrictEqual(placeholder(dot, 10, __, 10, __)(2, 3), dot(10, 2, 10, 3)); + placeholder(dot, 10, __, 10, __)(2, 3); // 👉 10 2 10 3 + // assert.deepStrictEqual(placeholder(dot, __, __, __, 5)(4, __, 2)(__)(3), dot(4, 3, 2, 5)); + placeholder(dot, __, __, __, 5)(4, __, 2)(__)(3) // 👉 4 3 2 5 + // assert.deepStrictEqual(placeholder(dot, 3)(__, __)(4, __, 2)(3), dot(3, 4, 3, 2)); + placeholder(dot, 3)(__, __)(4, __, 2)(3) // 👉 3 4 3 2 + // assert.deepStrictEqual(placeholder(dot)(3, __, __, 4)(5, 6), dot(3, 5, 6, 4)); + placeholder(dot)(3, __, __, 4)(5, 6) // 👉 3 5 6 4 + // assert.deepStrictEqual(placeholder(dot, 3, 4, 5, 3)(), dot(3, 4, 5, 3)); + placeholder(dot, 3, 4, 5, 3)() // 👉 3 4 5 3 + + // assert.deepStrictEqual(p1(5, 6), dot(3, 4, 5, 6)); + p1(5, 6); // 👉 3 4 5 6 + // assert.deepStrictEqual(p1(7, 8), dot(3, 4, 7, 8)); + p1(7, 8); // 👉 3 4 7 8 + // assert.deepStrictEqual(p1(5)(6), dot(3, 4, 5, 6)); + p1(5)(6); // 👉 3 4 5 6 + // assert.deepStrictEqual(p1(7)(8), dot(3, 4, 7, 8)); + p1(7)(8); // 👉 3 4 7 8 + + const p2 = p1(__, 6); + console.log(typeof p2); // 👉 function + p2(5); // 👉 3 4 5 6 + p2(6); // 👉 3 4 6 6 + + console.log('PASSED!'); + } + + main() + diff --git a/interview/moe/2_numberToChinese.js b/interview/moe/2_numberToChinese.js new file mode 100644 index 00000000..883c049d --- /dev/null +++ b/interview/moe/2_numberToChinese.js @@ -0,0 +1,71 @@ +/** + * TODO + * 数字转化为中文描述 + */ +function chineseNumber(num) { + const chineseMap = new Map([ + [0, "零"], + [1, "一"], + [2, "二"], + [3, "三"], + [4, "四"], + [5, "五"], + [6, "六"], + [7, "七"], + [8, "八"], + [9, "九"], + ]); + const unitMap = new Map([ + [1, ""], + [10, "十"], + [100, "百"], + [1000, "千"], + [1_0000, "万"], + [1_0000_0000, "亿"], + ]); + + if (num < 10) { + return chineseMap.get(num); + } + + if (num < 100) { + const tenDigit = Math.floor(num / 10); + const remainder = num % 10; + let res = chineseMap.get(tenDigit) + unitMap.get(10); + if (remainder === 0) { + return res; + } else { + return res + chineseMap.get(remainder); + } + } + + // if (num > 10000) { + // const unitDigit = Math.floor(num / 10000); + // const remainder = num % 10000; + // return + // } + + for (const unit of unitMap.keys()) { + if (unit <= num && num < unit * 10) { + const unitDigit = Math.floor(num / unit); + const remainder = num % unit; + if (remainder === 0) { + return chineseNumber(unitDigit) + unitMap.get(unit); + } else { + return ( + chineseNumber(unitDigit) + + unitMap.get(unit) + + chineseNumber(remainder) + ); + } + } + } + return '' +} + +// 示例用法 +console.log(chineseNumber(1234)); +console.log(chineseNumber(5678)); +console.log(chineseNumber(1234_5678)); // 一千二百三十四万五千六百七十八 +console.log(chineseNumber(1234_5678_9098_7654)); // 一千二百三十四万五千六百七十八亿九千零九十八万七千六百五十四 +console.log(chineseNumber(1200_0000_0000_0200)); // 一千二百 万 亿 零 二百 diff --git a/interview/moe/3_gpt4.js b/interview/moe/3_gpt4.js new file mode 100644 index 00000000..02a68768 --- /dev/null +++ b/interview/moe/3_gpt4.js @@ -0,0 +1,65 @@ +function convertToChineseNumeral(num) { + const numStr = num.toString(); + const chineseNumerals = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; + const units = ['', '十', '百', '千']; + const bigUnits = ['', '万', '亿']; + const extraBigUnit = '万亿'; + + let result = ''; + let zeroFlag = false; // 标记连续的零 + let part = ''; // 每四位的读法 + let sectionCount = Math.ceil(numStr.length / 4); // 计算总共需要多少组(每组四位) + + // 处理每四位数为一组,从高位到低位 + for (let i = 0, start = 0; i < sectionCount; i++) { + let end = numStr.length - i * 4; + start = Math.max(end - 4, 0); + let currentSection = numStr.substring(start, end); + part = ''; + + for (let j = 0; j < currentSection.length; j++) { + const digit = currentSection[j]; + const numeral = chineseNumerals[parseInt(digit)]; + if (digit !== '0') { + if (zeroFlag) { + part += '零'; + zeroFlag = false; + } + part += numeral + units[currentSection.length - 1 - j]; + } else if (part.length !== 0) { + zeroFlag = true; + } + } + + if (part) { + // 添加大单位 + if (sectionCount > 2 && i === sectionCount - 2) { + // 对于万亿级别的数字,第一次使用“万亿”,之后使用“万” + part += (sectionCount === 3 && i === 0) ? extraBigUnit : bigUnits[1]; + } else if (i === sectionCount - 3) { + part += bigUnits[2]; + } + } else if (zeroFlag && result.length !== 0) { + part = '零'; + } + + result = part + result; + } + + // 处理一十开头的情况 + if (result.startsWith('一十')) { + result = result.substring(1); + } + + return result; +} + + +// 使用例子 +console.log(convertToChineseNumeral(10_0000_0000_0000)); // 十万亿 +console.log(convertToChineseNumeral(1234)); // 一千二百三十四 +console.log(convertToChineseNumeral(5678)); // 五千六百七十八 +console.log(convertToChineseNumeral(1234_5678)); // 一千二百三十四万五千六百七十八 +console.log(convertToChineseNumeral(1234_5678_9098_7654)); // 一千二百三十四万五千六百七十八亿九千零九十八万七千六百五十四 +console.log(convertToChineseNumeral(1200_0000_0000_0200)); // 一千二百万亿零二百 +// console.log(convertToChineseNumeral(1200_0000_0000_0200)); // 一千二百 万 亿 零 二百 diff --git a/interview/moe/4_gtp4.js b/interview/moe/4_gtp4.js new file mode 100644 index 00000000..11891ca0 --- /dev/null +++ b/interview/moe/4_gtp4.js @@ -0,0 +1,42 @@ +function numToChinese(num) { + if (num === 0) return '零'; + + const strNum = String(num); + const len = strNum.length; + + if (len > 16) return '数字太大'; + + const cnNums = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; + const cnUnits = ['', '十', '百', '千']; + const cnBigUnits = ['', '万', '亿', '万亿']; + const result = []; + + let zeroCount = 0; + + for (let i = 0; i < len; i++) { + const digit = strNum[len - 1 - i]; + const cnNum = cnNums[parseInt(digit)]; + const unitPos = i % 4; + const bigUnitPos = Math.floor(i / 4); + + if (digit === '0') { + zeroCount++; + if (unitPos === 0 && zeroCount < 4) { + result.unshift(cnBigUnits[bigUnitPos]); + } + } else { + zeroCount = 0; + result.unshift(cnNum + cnUnits[unitPos] + (unitPos === 0 ? cnBigUnits[bigUnitPos] : '')); + } + } + + return result.join('').replace(/零+/g, '零').replace(/零万/g, '万').replace(/零亿/g, '亿').replace(/亿万/g, '亿'); +} + +// 测试您提供的例子 +console.log(numToChinese(10000000000000)); // 十万亿 +console.log(numToChinese(1234)); // 一千二百三十四 +console.log(numToChinese(5678)); // 五千六百七十八 +console.log(numToChinese(12345678)); // 一千二百三十四万五千六百七十八 +console.log(numToChinese(1234567890987654)); // 一千二百三十四万五千六百七十八亿九千零九十八万七千六百五十四 +console.log(numToChinese(1200000000000200)); // 一千二百万亿零二百 diff --git a/interview/myFlat.js b/interview/myFlat.js new file mode 100644 index 00000000..3ebc0f16 --- /dev/null +++ b/interview/myFlat.js @@ -0,0 +1,36 @@ +// ---- test ----- +const arr = [ + 1, 2, 3, 4, + [ + 1, 2, 3, + [ + 1, 2, 3, 4, + [ + 1, 2, 3 + ] + ] + ], + 5, + 'string', + { + name: '前端收割机' + } +]; + +console.log(myFlat(arr, 3)); + + +function myFlat(arr,depath){ + let target =[] + + arr.forEach(el => { + if (Array.isArray(el) && depath >= 0){ + target = target.concat(arguments.callee(el, depath - 1)) + } else { + target.push(el) + } + }) + + return target +} + diff --git a/interview/redbook/arrayToTree.js b/interview/redbook/arrayToTree.js new file mode 100644 index 00000000..ade05f58 --- /dev/null +++ b/interview/redbook/arrayToTree.js @@ -0,0 +1,59 @@ +const arr = [ + {id: 2, title: '部门2', pid: 1}, + {id: 3, title: '部门3', pid: 1}, + {id: 1, title: '部门1', pid: 0}, + {id: 4, title: '部门4', pid: 3}, + {id: 5, title: '部门5', pid: 4}, +] + +/** + * 1 + * / \ + * 2 3 + * | + * 4 + * | + * 5 + */ + +// 解法一:递归 +const arrayToTree1 = (source, rootId) => { + if (!Array.isArray(source)) return []; + + const res = []; + for (const item of source) { + if (item.pid === rootId) { + item.children = arrayToTree1(source, item.id) || []; + res.push(item); + } + } + return res; +} + +// 解法二:构建缓存 +const arrayToTree2 = (source, rootId) => { + if (!Array.isArray(source)) return []; + + const map = new Map(); + for (const item of source) { + map.set(item.id, item); + } + + // console.log(JSON.stringify(Object.fromEntries(map.entries()), null, 2)); + const res = []; + for (const item of source) { + const { pid }= item; + if (pid === rootId) { + res.push(item); + } else { + const parentItem = map.get(pid); + const origChildren = parentItem.children || []; + console.log(parentItem) + parentItem.children = [origChildren, item]; + } + } + return res; +} + +const res = arrayToTree2(arr, 0); +console.log(JSON.stringify(res, null, 2)); diff --git a/interview/redbook/eventQueue.js b/interview/redbook/eventQueue.js new file mode 100644 index 00000000..ba279e49 --- /dev/null +++ b/interview/redbook/eventQueue.js @@ -0,0 +1,12 @@ + +setTimeout(() => { + Promise.resolve(1).then(console.log); + console.log(2); +}, 0); + +setTimeout(() => { + console.log(3); +}, 0); +console.log(4); + +// 4 2 1 3 diff --git a/interview/redbook/reactEvent.jsx b/interview/redbook/reactEvent.jsx new file mode 100644 index 00000000..9c0a74d1 --- /dev/null +++ b/interview/redbook/reactEvent.jsx @@ -0,0 +1,19 @@ +import { useRef } from "react" + +export function App() { + const ref = useRef(null); + + document.addEventListener('click', (e) => { + console.log('document click', e); + }); + + ref.addEventListener('click', (e) => { + console.log('ref dom click', e); + }); + + return
+
{console.log('react event click', e)}} ref={ref} /> +
+} + +// react 合成事件有什么好处 diff --git a/interview/this/01_1.html b/interview/this/01_1.html new file mode 100644 index 00000000..aa3ea4da --- /dev/null +++ b/interview/this/01_1.html @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/interview/this/01_2.html b/interview/this/01_2.html new file mode 100644 index 00000000..d3585146 --- /dev/null +++ b/interview/this/01_2.html @@ -0,0 +1,16 @@ + diff --git a/interview/this/01_3.html b/interview/this/01_3.html new file mode 100644 index 00000000..ade5d62e --- /dev/null +++ b/interview/this/01_3.html @@ -0,0 +1,18 @@ + diff --git a/interview/this/02.html b/interview/this/02.html new file mode 100644 index 00000000..7a7b4eaf --- /dev/null +++ b/interview/this/02.html @@ -0,0 +1,11 @@ + diff --git a/interview/this/03.html b/interview/this/03.html new file mode 100644 index 00000000..a1c40471 --- /dev/null +++ b/interview/this/03.html @@ -0,0 +1,21 @@ + diff --git a/interview/this/04.html b/interview/this/04.html new file mode 100644 index 00000000..9c8f875e --- /dev/null +++ b/interview/this/04.html @@ -0,0 +1,41 @@ + \ No newline at end of file diff --git a/interview/this/05.html b/interview/this/05.html new file mode 100644 index 00000000..e50c4957 --- /dev/null +++ b/interview/this/05.html @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/interview/this/06.html b/interview/this/06.html new file mode 100644 index 00000000..0d1433f9 --- /dev/null +++ b/interview/this/06.html @@ -0,0 +1,37 @@ + \ No newline at end of file diff --git a/interview/unique.js b/interview/unique.js new file mode 100644 index 00000000..88ea733d --- /dev/null +++ b/interview/unique.js @@ -0,0 +1,21 @@ +function unique(arr) { + const target = [] + const res = {} + arr.forEach(el => { + if (typeof el == 'object') { + const str = JSON.stringify(el) + if (!res[str]) { + target.push(el) + res[str] = el + } + } else if (!target.includes(el)) { + target.push(el) + } + }); + return target +} +const obj1 = { age: 12 } +const arr = [123, '123', {}, {}, null, undefined, void 0, "abc", "abc", obj1, obj1, { age: 12 }, { age: 12, name: 'lyn' }] +console.log(unique(arr)) + + diff --git a/js_basic/01basic.html b/js_basic/01basic.html new file mode 100644 index 00000000..a9d845cc --- /dev/null +++ b/js_basic/01basic.html @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/js_basic/this.html b/js_basic/this.html new file mode 100644 index 00000000..60361176 --- /dev/null +++ b/js_basic/this.html @@ -0,0 +1,40 @@ + diff --git a/js_basic/this0.html b/js_basic/this0.html new file mode 100644 index 00000000..06097261 --- /dev/null +++ b/js_basic/this0.html @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/js_basic/this1.html b/js_basic/this1.html new file mode 100644 index 00000000..efee65d7 --- /dev/null +++ b/js_basic/this1.html @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/js_basic/this2.html b/js_basic/this2.html new file mode 100644 index 00000000..f818b39b --- /dev/null +++ b/js_basic/this2.html @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/linkedlist/021mergeTwoLists.js b/linkedlist/021mergeTwoLists.js new file mode 100644 index 00000000..8f65060c --- /dev/null +++ b/linkedlist/021mergeTwoLists.js @@ -0,0 +1,35 @@ +/** + * https://leetcode-cn.com/problems/merge-two-sorted-lists/ + * 21. 合并两个有序链表 | easy + */ + +const { ListNode, LinkedList } = require('./LinkedList.js') + +function mergeTwoLists(l1, l2) { + const dummy = new ListNode() + let curr = dummy + while (l1 && l2) { + if (l1.val < l2.val) { + curr.next = l1 + l1 = l1.next + } else { + curr.next = l2 + l2 = l2.next + } + curr = curr.next + } + curr.next = l1 || l2 + return dummy.next +} + + +// ---- test case ---- +let l1 = new LinkedList() +let l2 = new LinkedList() + +l1.append(1).append(2).append(4) +l2.append(1).append(3).append(4) + +let ret = mergeTwoLists(l1.head, l2.head) +let l3 = new LinkedList(ret) +l3.display() diff --git a/linkedlist/083deleteDuplicates.js b/linkedlist/083deleteDuplicates.js new file mode 100644 index 00000000..cc254b48 --- /dev/null +++ b/linkedlist/083deleteDuplicates.js @@ -0,0 +1,34 @@ +/** + * https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/ + * 83. 删除排序链表中的重复元素 + */ +const { ListNode, LinkedList } = require('./LinkedList.js') + +function deleteDuplicates(head) { + let node = head + while (node && node.next) { + if (node.val === node.next.val) { + node.next = node.next.next + } else { + node = node.next + } + } + return head +} + +// ---- test case ---- + +// ---- test case ---- +var link = new LinkedList() +link.append(1) + .append(1) + .append(2) + .append(3) + .append(3) + .display() + +link = deleteDuplicates(link.head) +// console.log(JSON.stringify(link, null, 2)) +link = new LinkedList(link) + +link.display() diff --git a/linkedlist/141hasCycle.js b/linkedlist/141hasCycle.js new file mode 100644 index 00000000..f4fb68c4 --- /dev/null +++ b/linkedlist/141hasCycle.js @@ -0,0 +1,28 @@ +/** + * https://leetcode-cn.com/problems/linked-list-cycle/ + * + * 141. 环形链表 | easy + * + */ + +const { ListNode, LinkedList } = require('./LinkedList.js') + +function hasCycle(head) { + let fast = slow = head + while (fast && fast.next && slow) { + fast = fast.next.next + slow = slow.next + if (fast == slow) return true + } + return false +} + +// ---- test case ---- +var link = new LinkedList() +link.append(3) + .append(2) + .append(0) + .append(4) + .display() + +console.log(hasCycle(link.head)) diff --git a/linkedlist/160getIntersectionNode.js b/linkedlist/160getIntersectionNode.js new file mode 100644 index 00000000..0ddc73c0 --- /dev/null +++ b/linkedlist/160getIntersectionNode.js @@ -0,0 +1,15 @@ +/** + * https://leetcode-cn.com/problems/intersection-of-two-linked-lists/ + * https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/ + * 160. 相交链表 | easy + * + */ + +const { ListNode, LinkedList } = require('./LinkedList.js') + +function getIntersectionNode(headA, headB) { + +} + +// ---- test case ---- + diff --git a/linkedlist/206reverseList.js b/linkedlist/206reverseList.js new file mode 100644 index 00000000..479629ff --- /dev/null +++ b/linkedlist/206reverseList.js @@ -0,0 +1,36 @@ +/** + * https://leetcode-cn.com/problems/reverse-linked-list/ + * + * 206. 反转链表 + */ +const { ListNode, LinkedList } = require('./LinkedList.js') + + +function reverseList(head) { + let prev = null + while (head) { + const curr = head + head = head.next + curr.next = prev + prev = curr + } + return prev +} + +// ---- test case ---- +let link = new LinkedList() + +link.append(1) + .append(2) + .append(3) + .append(4) + .append(5) + .display() + +link.head = reverseList(link.head) + +link.display() + +link.head = reverseList(link.head) + +link.display() diff --git a/linkedlist/LinkedList.js b/linkedlist/LinkedList.js new file mode 100644 index 00000000..e67f3522 --- /dev/null +++ b/linkedlist/LinkedList.js @@ -0,0 +1,95 @@ +// JavaScript 实现一个单向链表 +class ListNode { + constructor (val, next) { + this.val = val == null ? 0 : val + this.next = next == null ? null : next + } +} + +class LinkedList { + constructor (head = null) { + this.dummyHead = new ListNode('head', head) + } + // 末尾追加节点 + append (val) { + let node = this.dummyHead + while (node && node.next) { + node = node.next + } + node.next = new ListNode(val) + return this + } + // 删除第一个值为 val 的节点 + delete (val) { + let prev = this.dummyHead + while (prev && prev.next && prev.next.val != val) { + prev = prev.next + } + prev.next = prev.next.next + return this + } + // 反转链表 + reverse () { + let prev = null + let node = this.dummyHead.next + while (node) { + const curr = node + node = node.next + curr.next = prev + prev = curr + } + this.dummyHead.next = prev + return this + } + // 获取链表长度 + get size() { + let cnt = 0 + let node = this.dummyHead.next + while (node) { + ++cnt + node = node.next + } + return cnt + } + // 获取链表头节点 + get head() { + return this.dummyHead.next + } + // 设置新的头节点 + set head(newHead) { + this.dummyHead.next = newHead + } + // 显示链表 + display() { + let node = this.dummyHead.next + const res = [] + while (node) { + res.push(node.val) + node = node.next + } + console.log(res.join(' -> ')) + return this + } +} + +// // ---- test case ---- +// let link = new LinkedList() +// link.display() // '' +// .append(1) +// .display() // 1 +// .append(2) +// .append(3) +// .append(4) +// .append(5) +// .delete(5) +// .delete(2) +// .display() // 1 -> 3 -> 4 +// .reverse() +// .display() // 4 -> 3 -> 1 +// .reverse() +// .display() // 1 -> 3 -> 4 + +// console.log(link.size) + +// ---- export ---- +module.exports = { ListNode, LinkedList } diff --git "a/nc_practice/1_dfs/38_\350\267\263\345\217\260\351\230\266_dp.js" "b/nc_practice/1_dfs/38_\350\267\263\345\217\260\351\230\266_dp.js" new file mode 100644 index 00000000..deb63e4c --- /dev/null +++ "b/nc_practice/1_dfs/38_\350\267\263\345\217\260\351\230\266_dp.js" @@ -0,0 +1,14 @@ +/** + * @param {number} n 台阶数量 + * @return {number} 跳法数量 + */ +function jumpFloor(n) { + if (n <= 3) return n + let pre = 2, cur = 3 + for (let i = 4; i <= n; i++) { + let temp = cur + cur = pre + cur + pre = temp + } + return cur +} diff --git "a/nc_practice/1_dfs/_10_\345\260\217\347\272\242\346\213\274\345\233\276.js" "b/nc_practice/1_dfs/_10_\345\260\217\347\272\242\346\213\274\345\233\276.js" new file mode 100644 index 00000000..99b6d230 --- /dev/null +++ "b/nc_practice/1_dfs/_10_\345\260\217\347\272\242\346\213\274\345\233\276.js" @@ -0,0 +1 @@ +// 小红拼图 diff --git "a/nc_practice/1_dfs/_13_\345\260\217\347\272\242\347\232\204\346\225\260\344\275\215\346\223\215\344\275\234.js" "b/nc_practice/1_dfs/_13_\345\260\217\347\272\242\347\232\204\346\225\260\344\275\215\346\223\215\344\275\234.js" new file mode 100644 index 00000000..fc4136ca --- /dev/null +++ "b/nc_practice/1_dfs/_13_\345\260\217\347\272\242\347\232\204\346\225\260\344\275\215\346\223\215\344\275\234.js" @@ -0,0 +1 @@ +// 小红的数位操作 \ No newline at end of file diff --git "a/nc_practice/1_dfs/_19_\347\233\270\351\201\207.js" "b/nc_practice/1_dfs/_19_\347\233\270\351\201\207.js" new file mode 100644 index 00000000..d3bdd730 --- /dev/null +++ "b/nc_practice/1_dfs/_19_\347\233\270\351\201\207.js" @@ -0,0 +1,3 @@ +// 相遇问题 +// + diff --git "a/nc_practice/2_\351\200\222\345\275\222/29_\345\260\217\347\220\203\346\212\225\347\233\222_set.js" "b/nc_practice/2_\351\200\222\345\275\222/29_\345\260\217\347\220\203\346\212\225\347\233\222_set.js" new file mode 100644 index 00000000..833b3196 --- /dev/null +++ "b/nc_practice/2_\351\200\222\345\275\222/29_\345\260\217\347\220\203\346\212\225\347\233\222_set.js" @@ -0,0 +1,30 @@ +// 小球投盒 +// 思路:集合删除法,记录空盒序号,每次操作,删除非空盒子。 +// 备注:集合遍历可以用 forEach +// 时间复杂度:O(n) + +// 使用集合 set 存储空盒序号。 +// 1. 如果操作类型为 1,从 set 中删除当前盒子序号; +// 2. 如果操作类型为 2,从 set 中删除其他序号; +function solve(n, lines) { + const emptyBox = new Set(new Array(n).fill(0).map((_, idx) => String(idx + 1))); + for (let i = 1; i < lines.length; ++i) { // 从 1 开始为每一步操作 + const [opType, boxId] = lines[i].split(" "); + if (opType === "1") { + emptyBox.delete(boxId); + } else { + emptyBox.forEach(id => { + if (id !== boxId) { + emptyBox.delete(id); + } + }) + } + if (emptyBox.size === 0) { + return i; + } + } + return -1; +} + +console.log(solve(3, ['', "1 1", "1 2", "1 3"])) +console.log(solve(3, ['', "1 1", "2 2", "1 3", "1 2"])) diff --git "a/nc_practice/2_\351\200\222\345\275\222/37_\347\211\233\347\211\233\347\232\204Ackmann.js" "b/nc_practice/2_\351\200\222\345\275\222/37_\347\211\233\347\211\233\347\232\204Ackmann.js" new file mode 100644 index 00000000..2562d925 --- /dev/null +++ "b/nc_practice/2_\351\200\222\345\275\222/37_\347\211\233\347\211\233\347\232\204Ackmann.js" @@ -0,0 +1,22 @@ +// https://www.nowcoder.com/practice/3a7a4c26420c4358a1a5cda3da2fa1c8 + +// 递归 +// 解法一:直接按公式递归(自顶向下) +function ack(m, n) { + if (m === 0) { + return n + 1; + } + if (m > 0 && n === 0) { + return ack(m - 1, 1); + } + if (m > 0 && n > 0) { + return ack(m - 1, ack(m, n - 1)); + } + return -1; +} + +// 解法二:动态规划递推(自底向上) +// todo + +console.log(ack(3, 4)); +console.log(ack(0, 10)); diff --git "a/nc_practice/2_\351\200\222\345\275\222/_02_\345\260\217\347\272\242\347\232\204\345\255\220\345\272\217\345\210\227\351\200\206\345\272\217\345\257\271.js" "b/nc_practice/2_\351\200\222\345\275\222/_02_\345\260\217\347\272\242\347\232\204\345\255\220\345\272\217\345\210\227\351\200\206\345\272\217\345\257\271.js" new file mode 100644 index 00000000..e69de29b diff --git "a/nc_practice/2_\351\200\222\345\275\222/_03_\345\260\217\347\276\216\347\232\204\345\275\251\345\270\246.js" "b/nc_practice/2_\351\200\222\345\275\222/_03_\345\260\217\347\276\216\347\232\204\345\275\251\345\270\246.js" new file mode 100644 index 00000000..e69de29b diff --git "a/nc_practice/2_\351\200\222\345\275\222/_40_\345\260\217\347\272\242\347\232\204\346\225\260\347\273\204_\344\272\214\345\210\206\346\237\245\346\211\276.js" "b/nc_practice/2_\351\200\222\345\275\222/_40_\345\260\217\347\272\242\347\232\204\346\225\260\347\273\204_\344\272\214\345\210\206\346\237\245\346\211\276.js" new file mode 100644 index 00000000..aa78611d --- /dev/null +++ "b/nc_practice/2_\351\200\222\345\275\222/_40_\345\260\217\347\272\242\347\232\204\346\225\260\347\273\204_\344\272\214\345\210\206\346\237\245\346\211\276.js" @@ -0,0 +1,56 @@ +// 小红的数组 +// https://ac.nowcoder.com/acm/contest/11218/D + +/** + * 思路:(二分查找) + * 1. 先对数组进行排序 + * 2. + */ +function lowerBound(arr, target, left, right) { + while (left < right) { + const mid = (left + right) >> 1; + if (arr[mid] >= target) { + right = mid; + } else { + left = mid + 1; + } + } + return left; +} + +function upperBound(arr, target, left, right) { + while (left < right) { + const mid = (left + right) >> 1; + if (arr[mid] > target) { + right = mid; + } else { + left = mid + 1; + } + } + return left; +} + +function solve(arr, k) { + arr.sort((a, b) => a - b); // O(nlogn) + let s1 = 0, s2 = 0, s3 = 0; + for (let i = 0; i < arr.length; i++) { + const target = k / arr[i]; + const left = i + 1; + const right = arr.length; + + const l = lowerBound(arr, target, left, right); + const r = upperBound(arr, target, left, right); + + if (l === right) { + s3 += right - i - 1 + } else { + s1 += right - r; + s2 += r - l; + s3 += l - left; + } + } + return [s1, s2, s3] +} + +console.log(solve([1, 2, 3, 4], 7)) +console.log(solve([3, 3, 3, 3, 3], 9)) diff --git "a/nc_practice/3_\345\244\215\346\235\202\345\272\246/25_\345\260\217\346\254\247\347\232\204\351\200\211\346\225\260\344\271\230\347\247\257_set.js" "b/nc_practice/3_\345\244\215\346\235\202\345\272\246/25_\345\260\217\346\254\247\347\232\204\351\200\211\346\225\260\344\271\230\347\247\257_set.js" new file mode 100644 index 00000000..7e37e01e --- /dev/null +++ "b/nc_practice/3_\345\244\215\346\235\202\345\272\246/25_\345\260\217\346\254\247\347\232\204\351\200\211\346\225\260\344\271\230\347\247\257_set.js" @@ -0,0 +1,14 @@ +// 小欧的选数乘积 + +function solve(x, y, arr) { + if (x >= y) return 0 + if (!arr.length) return -1 + const set = new Set(arr.map(Number).sort((x,y) => y - x)) + let ans = 0 + for (const times of set) { + x *= times + ++ans + if (x >= y) return ans + } + return -1 +} diff --git "a/nc_practice/3_\345\244\215\346\235\202\345\272\246/39_\350\266\205\347\272\247\345\234\243\350\257\236\346\240\221*.js" "b/nc_practice/3_\345\244\215\346\235\202\345\272\246/39_\350\266\205\347\272\247\345\234\243\350\257\236\346\240\221*.js" new file mode 100644 index 00000000..ab58f1e1 --- /dev/null +++ "b/nc_practice/3_\345\244\215\346\235\202\345\272\246/39_\350\266\205\347\272\247\345\234\243\350\257\236\346\240\221*.js" @@ -0,0 +1,58 @@ +// 超级圣诞树 +// https://www.nowcoder.com/practice/470d26c9a73e4e17be8cc45cac843423 + +function solve(n) { + let arr = Array.from({ length: 500 }, () => new Array(800).fill(0)); + // 初始化小三角形 + arr[0][2] = 1; + arr[1][1] = 1; arr[1][3] = 1; + arr[2][0] = 1; arr[2][2] = 1; arr[2][4] = 1; + + let length = 3, width = 5; + + for (let i = 2; i <= n; i++) { + // 加工左下和右下的小三角形 + for (let j = length; j < length * 2; j++) { + for (let k = 0; k < width; k++) { + arr[j][k] = arr[j - length][k]; + arr[j][k + width + 1] = arr[j - length][k]; + } + } + // 清空原三角形 + for (let j = 0; j < length; j++) { + for (let k = 0; k < width; k++) { + arr[j][k] = 0; + } + } + // 将原三角形挪到中间 + for (let j = 0; j < length; j++) { + for (let k = Math.floor((width + 1) / 2); k < width + Math.floor((width + 1) / 2); k++) { + arr[j][k] = arr[j + length][k - Math.floor((width + 1) / 2)]; + } + } + length *= 2; + width = 2 * width + 1; + } + + // 打印圣诞树 + for (let i = 0; i < length; i++) { + let line = ''; + for (let j = 0; j < width; j++) { + line += arr[i][j] === 0 ? ' ' : '*'; + } + console.log(line); + } + // 打印树干 + for (let i = 0; i < n; i++) { + let trunk = ''; + for (let j = 0; j < Math.floor(width / 2); j++) { + trunk += ' '; + } + trunk += '*'; + console.log(trunk); + } +} + +solve(1) +solve(2) +solve(3) diff --git "a/nc_practice/3_\345\244\215\346\235\202\345\272\246/45_\345\257\271\347\247\260\344\271\213\347\276\216_set.js" "b/nc_practice/3_\345\244\215\346\235\202\345\272\246/45_\345\257\271\347\247\260\344\271\213\347\276\216_set.js" new file mode 100644 index 00000000..2375a163 --- /dev/null +++ "b/nc_practice/3_\345\244\215\346\235\202\345\272\246/45_\345\257\271\347\247\260\344\271\213\347\276\216_set.js" @@ -0,0 +1,32 @@ +// 对称之美 +// 思路:双指针 + hash +// https://ac.nowcoder.com/acm/contest/10746/H +// 类似题,非对称之美(最长回文子串):https://ac.nowcoder.com/acm/problem/214851 + +// 思路:核心在于判断两个字符串是否有同一个 char。找相同 char 可以用 set 降低复杂度 +function hasSameChar(str1, str2) { + const set = new Set(str1.split('')) + for (const ch of str2) { + if (set.has(ch)) { + return true; + } + } + return false; +} + +function solve(strArr) { + const n = strArr.length; + const times = n >> 1; + let ans = true; + for (let i = 0; i < times; ++i) { + if (!hasSameChar(strArr[i], strArr[n - i - 1])) { + ans = false; + break; + } + } + return ans; +} + +console.log(solve(['a'])) // true +console.log(solve(['a', 'b', 'c'])) // false +console.log(solve(['a', 'b', 'ac'])) // true diff --git "a/nc_practice/3_\345\244\215\346\235\202\345\272\246/_41_\346\267\273\346\225\260\345\215\232\345\274\210.js" "b/nc_practice/3_\345\244\215\346\235\202\345\272\246/_41_\346\267\273\346\225\260\345\215\232\345\274\210.js" new file mode 100644 index 00000000..e69de29b diff --git "a/nc_practice/4_\346\236\232\344\270\276/11_\345\260\217\347\272\242\347\232\204\345\245\207\345\201\266\346\212\275\345\217\226.js" "b/nc_practice/4_\346\236\232\344\270\276/11_\345\260\217\347\272\242\347\232\204\345\245\207\345\201\266\346\212\275\345\217\226.js" new file mode 100644 index 00000000..9fd99257 --- /dev/null +++ "b/nc_practice/4_\346\236\232\344\270\276/11_\345\260\217\347\272\242\347\232\204\345\245\207\345\201\266\346\212\275\345\217\226.js" @@ -0,0 +1,16 @@ + +// 小红的奇偶抽取 +function solve(numStr) { + let oddNum = 0, + evenNum = 0; + + for (let i = 0; i < numStr.length; ++i) { + const digit = numStr[i] - '0' + if (digit & 1) { + oddNum = oddNum * 10 + digit; + } else { + evenNum = evenNum * 10 + digit; + } + } + return Math.abs(oddNum - evenNum) +} diff --git "a/nc_practice/4_\346\236\232\344\270\276/12_\346\270\270\346\270\270\347\232\204\346\225\264\346\225\260\345\210\207\345\211\262.js" "b/nc_practice/4_\346\236\232\344\270\276/12_\346\270\270\346\270\270\347\232\204\346\225\264\346\225\260\345\210\207\345\211\262.js" new file mode 100644 index 00000000..7e310dcd --- /dev/null +++ "b/nc_practice/4_\346\236\232\344\270\276/12_\346\270\270\346\270\270\347\232\204\346\225\264\346\225\260\345\210\207\345\211\262.js" @@ -0,0 +1,14 @@ +// 游游的整数切割 +function solve(numStr) { + const n = numStr.length + const lastDigit = numStr[n - 1] - '0' + const isLastOdd = lastDigit & 1 + let ans = 0 + for (let i = 0; i < n - 1; ++i) { + const digit = numStr[i] - '0' + const isCurOdd = digit & 1 + if (isCurOdd && isLastOdd) ++ans + if (!isCurOdd && !isLastOdd) ++ans + } + return ans +} diff --git "a/nc_practice/4_\346\236\232\344\270\276/14_\345\260\217\347\276\216\350\265\260\345\205\254\350\267\257.js" "b/nc_practice/4_\346\236\232\344\270\276/14_\345\260\217\347\276\216\350\265\260\345\205\254\350\267\257.js" new file mode 100644 index 00000000..1b7c043a --- /dev/null +++ "b/nc_practice/4_\346\236\232\344\270\276/14_\345\260\217\347\276\216\350\265\260\345\205\254\350\267\257.js" @@ -0,0 +1,22 @@ +function solve(arr, start, end) { + let sum1 = 0, sum2 = 0 + for (let i = 0; i < arr.length; ++i) { + if (start <= i && i < end) { + sum1 += arr[i] + } else { + sum2 += arr[i] + } + } + return Math.min(sum1, sum2) +} + +void async function () { + // Write your code here + const lines = [] + while(line = await readline()){ + lines.push(line) + } + const arr = lines[1].split(' ').map(Number) + const [start, end] = lines[2].split(' ').map(str => parseInt(str) - 1).sort((x, y) => x - y) // 排序一下 + console.log(solve(arr, start, end)) +}() diff --git "a/nc_practice/4_\346\236\232\344\270\276/15_\345\260\217\347\276\216\347\232\204\346\216\222\345\210\227\350\257\242\351\227\256.js" "b/nc_practice/4_\346\236\232\344\270\276/15_\345\260\217\347\276\216\347\232\204\346\216\222\345\210\227\350\257\242\351\227\256.js" new file mode 100644 index 00000000..b0db3723 --- /dev/null +++ "b/nc_practice/4_\346\236\232\344\270\276/15_\345\260\217\347\276\216\347\232\204\346\216\222\345\210\227\350\257\242\351\227\256.js" @@ -0,0 +1,7 @@ +function isNeighbor(arr, x, y) { + for (let i = 0; i < arr.length - 1; ++i) { + if (arr[i] === x && arr[i + 1] === y) return true + if (arr[i] === y && arr[i + 1] === x) return true + } + return false; +} diff --git "a/nc_practice/4_\346\236\232\344\270\276/43_\345\274\200\345\277\203\350\277\230\346\230\257\351\232\276\350\277\207.js" "b/nc_practice/4_\346\236\232\344\270\276/43_\345\274\200\345\277\203\350\277\230\346\230\257\351\232\276\350\277\207.js" new file mode 100644 index 00000000..c2d6045a --- /dev/null +++ "b/nc_practice/4_\346\236\232\344\270\276/43_\345\274\200\345\277\203\350\277\230\346\230\257\351\232\276\350\277\207.js" @@ -0,0 +1,12 @@ +function solve(str) { + const happyCnt = str.split(':-)').length - 1 + const sadCnt = str.split(':-(').length - 1 + if (happyCnt > sadCnt) { + return 'Happy' + } else if (happyCnt < sadCnt) { + return 'Sad' + } else if (happyCnt === 0) { + return 'None' + } + return 'Just so so' +} diff --git "a/nc_practice/5_\346\240\221/33_\346\240\221\344\270\212\346\234\200\347\237\255\350\267\257_\345\205\254\345\205\261\347\210\266\350\212\202\347\202\271.js" "b/nc_practice/5_\346\240\221/33_\346\240\221\344\270\212\346\234\200\347\237\255\350\267\257_\345\205\254\345\205\261\347\210\266\350\212\202\347\202\271.js" new file mode 100644 index 00000000..829bebf8 --- /dev/null +++ "b/nc_practice/5_\346\240\221/33_\346\240\221\344\270\212\346\234\200\347\237\255\350\267\257_\345\205\254\345\205\261\347\210\266\350\212\202\347\202\271.js" @@ -0,0 +1,15 @@ +// 33. 树上最短路 + +function solve(x, y) { + let ans = 0; + while (x !== y) { + y = y >> 1 + ++ans + if (x > y) { // 交换 + const tmp = x + x = y + y = tmp + } + } + return ans; +} diff --git "a/nc_practice/5_\346\240\221/_23_Monica\347\232\204\346\240\221_\351\201\215\345\216\206.js" "b/nc_practice/5_\346\240\221/_23_Monica\347\232\204\346\240\221_\351\201\215\345\216\206.js" new file mode 100644 index 00000000..44c029f5 --- /dev/null +++ "b/nc_practice/5_\346\240\221/_23_Monica\347\232\204\346\240\221_\351\201\215\345\216\206.js" @@ -0,0 +1,3 @@ +// +// 邻接表? + diff --git "a/nc_practice/5_\346\240\221/_26_\345\260\217\347\272\242\347\232\204\345\255\220\346\240\221\346\223\215\344\275\234.js" "b/nc_practice/5_\346\240\221/_26_\345\260\217\347\272\242\347\232\204\345\255\220\346\240\221\346\223\215\344\275\234.js" new file mode 100644 index 00000000..7b23a241 --- /dev/null +++ "b/nc_practice/5_\346\240\221/_26_\345\260\217\347\272\242\347\232\204\345\255\220\346\240\221\346\223\215\344\275\234.js" @@ -0,0 +1 @@ +// 小红的子树操作 diff --git "a/nc_practice/5_\346\240\221/_31_\344\272\214\345\217\211\346\240\221.js" "b/nc_practice/5_\346\240\221/_31_\344\272\214\345\217\211\346\240\221.js" new file mode 100644 index 00000000..20a86278 --- /dev/null +++ "b/nc_practice/5_\346\240\221/_31_\344\272\214\345\217\211\346\240\221.js" @@ -0,0 +1,7 @@ +// 31. 二叉树 + +// n: 节点个数 +// m: 最大高度 +function solve(n, m) { + +} diff --git "a/nc_practice/5_\346\240\221/_35_\346\270\270\346\270\270\347\232\204\344\270\211\350\211\262\346\240\221_\350\277\236\351\200\232\345\233\276.js" "b/nc_practice/5_\346\240\221/_35_\346\270\270\346\270\270\347\232\204\344\270\211\350\211\262\346\240\221_\350\277\236\351\200\232\345\233\276.js" new file mode 100644 index 00000000..7c50a2d5 --- /dev/null +++ "b/nc_practice/5_\346\240\221/_35_\346\270\270\346\270\270\347\232\204\344\270\211\350\211\262\346\240\221_\350\277\236\351\200\232\345\233\276.js" @@ -0,0 +1 @@ +// 游游的三色树 diff --git "a/nc_practice/6_\350\264\252\345\277\203/*06_\345\260\217\347\272\242\347\232\204\346\225\260\345\255\227\345\210\240\351\231\244_\347\273\237\350\256\241\344\275\231\346\225\260.js" "b/nc_practice/6_\350\264\252\345\277\203/*06_\345\260\217\347\272\242\347\232\204\346\225\260\345\255\227\345\210\240\351\231\244_\347\273\237\350\256\241\344\275\231\346\225\260.js" new file mode 100644 index 00000000..eee99795 --- /dev/null +++ "b/nc_practice/6_\350\264\252\345\277\203/*06_\345\260\217\347\272\242\347\232\204\346\225\260\345\255\227\345\210\240\351\231\244_\347\273\237\350\256\241\344\275\231\346\225\260.js" @@ -0,0 +1,54 @@ +// 小红的数字删除 + +// 思路: +// 1. 统计余数 0、1、2 的个数 cntArr 和总余数 totalRemain +// 2. 如果总余数为 0,说明随便删。但要注意前导零 +// 3. 如果总余数为 1,说明需要先删除一个余数为 1 的数字,再回到第二步 +// 4. 如果总余数为 2,说明需要先删除一个余数为 2 的数字,再回到第二步 + +function solve(numStr) { + const cntArr = [0, 0, 0] + for (let i = 0; i < numStr.length; ++i) { + const digit = numStr[i] - '0' + const remain = digit % 3 + ++cntArr[remain] + } + const totalRemain = (cntArr[1] + 2 * cntArr[2]) % 3 + let ans = 0; + if (totalRemain) { + if (cntArr[totalRemain] === 0) { // 没得删 + return 0; + } else { + ans = 1 + --cntArr[totalRemain] + // console.log(cntArr, totalRemain) + if (cntArr[totalRemain] === 0 && !['3', '6', '9'].includes(numStr[0])) { // '10000003' + for (let i = 1; i < numStr.length; ++i) { + if (numStr[i] === '0') { + --cntArr[0] + } else { + break; + } + } + } + } + } + ans += cntArr[0] + if (cntArr[1] || cntArr[2]) { + return ans + } + return ans - 1 +} + +console.log(solve('0')) // 0 +// console.log(solve('2251')) +// console.log(solve('30021')) +console.log(solve('3000000')) // 6 +console.log(solve('30000001')) // 7 +console.log(solve('10000003')) // 1 +console.log(solve('3000000103')) // 9 +console.log(solve('1000000303')) // 3 +console.log(solve('1000000')) // 0 +// console.log(solve('333')) +// console.log(solve('123')) +// console.log(solve('300')) diff --git "a/nc_practice/6_\350\264\252\345\277\203/*08_\345\260\217\347\272\242\347\232\20401\344\270\262_.js" "b/nc_practice/6_\350\264\252\345\277\203/*08_\345\260\217\347\272\242\347\232\20401\344\270\262_.js" new file mode 100644 index 00000000..cb931a0b --- /dev/null +++ "b/nc_practice/6_\350\264\252\345\277\203/*08_\345\260\217\347\272\242\347\232\20401\344\270\262_.js" @@ -0,0 +1,15 @@ +// 小红的 01 串 +// https://www.nowcoder.com/practice/09ca882b363a480aa33ab15e8cd2b039 +// https://www.nowcoder.com/practice/9b072237ebdd4dd99562f01cbf594fac + +// 不是上面这题,例题里是好串(坏串变好串) +// https://www.nowcoder.com/feed/main/detail/6aea3ae1ac0c40b483c04b14ff60bc0f?sourceSSR=search +// https://www.nowcoder.com/discuss/731944613962866688 +// 游酷盛世 3.27 笔试题 1 + +// 思路:? +function solve(str) { + let ans = 0 + + return ans +} diff --git "a/nc_practice/6_\350\264\252\345\277\203/*16_\345\207\275\346\225\260_\345\200\237\344\275\215\345\207\217\346\263\225_0.js" "b/nc_practice/6_\350\264\252\345\277\203/*16_\345\207\275\346\225\260_\345\200\237\344\275\215\345\207\217\346\263\225_0.js" new file mode 100644 index 00000000..9ec9d9ba --- /dev/null +++ "b/nc_practice/6_\350\264\252\345\277\203/*16_\345\207\275\346\225\260_\345\200\237\344\275\215\345\207\217\346\263\225_0.js" @@ -0,0 +1,38 @@ +// 函数 + +// 思路:借位减法 +function fn(numStr) { + const len = numStr.length + let ans = '', flag = 0 // 向前借位 + for (let i = len - 1; i >= 0; --i) { + const digit = numStr[i] - '0' - flag + if (digit > 3) { + flag = 0 + ans = '3'.repeat(ans.length + 1) // 遇到大哥了,需要把后面刷成 3 + } else if (digit < 1) { + flag = 1 + ans = '3' + ans // 借位后,当前位变 3 + } else { + flag = 0 + ans = `${digit}${ans}` + } + } + // console.log(flag, ans, 'x') + return flag && ans[0] === '3' ? ans.slice(1) : ans +} + +console.log(fn('')) // '' +console.log(fn('0')) // '' +console.log(fn('1')) // 1 +console.log(fn('10')) // 3 +console.log(fn('1010')) // 333 +console.log(fn('2010')) // 1333 +console.log(fn('100')) // 33 +console.log(fn('400')) // 333 +console.log(fn('125')) // 123 +console.log(fn('12517')) // 12333 +console.log(fn('100517')) // 33333 +console.log(fn('200517')) // 133333 +console.log(fn('300517')) // 133333 +console.log(fn('400517')) // 133333 +console.log(fn('999999999999999999')) // diff --git "a/nc_practice/6_\350\264\252\345\277\203/*32_\351\255\224\345\241\224.js" "b/nc_practice/6_\350\264\252\345\277\203/*32_\351\255\224\345\241\224.js" new file mode 100644 index 00000000..cb034d2a --- /dev/null +++ "b/nc_practice/6_\350\264\252\345\277\203/*32_\351\255\224\345\241\224.js" @@ -0,0 +1,2 @@ +// 魔塔 +// H A D diff --git "a/nc_practice/6_\350\264\252\345\277\203/01_\345\260\217\350\213\257\347\232\204\346\225\260\345\255\227\346\235\203\345\200\274_\350\264\250\345\233\240\346\225\260\345\210\206\350\247\243.js" "b/nc_practice/6_\350\264\252\345\277\203/01_\345\260\217\350\213\257\347\232\204\346\225\260\345\255\227\346\235\203\345\200\274_\350\264\250\345\233\240\346\225\260\345\210\206\350\247\243.js" new file mode 100644 index 00000000..2ebd905a --- /dev/null +++ "b/nc_practice/6_\350\264\252\345\277\203/01_\345\260\217\350\213\257\347\232\204\346\225\260\345\255\227\346\235\203\345\200\274_\350\264\250\345\233\240\346\225\260\345\210\206\350\247\243.js" @@ -0,0 +1,44 @@ +// 小红的数字权值 +// https://www.nowcoder.com/questionTerminal/aeacca655eec45999a6dc4d998dfd4a5?answerType=1&f=discussion + +// 思路: +// 分为不拆(pi + 1)乘积和拆(pi 求和翻倍)和两种策略。 +// 先算出所有质因数,再根据质因数的个数和指数和,判断哪种策略更优。 + +function solve(x) { + if (x === 2) return 2; + let sum = 0, // 拆 (p1+p2+p3+...)*2 + mmm = 1; // 不拆 (p1+1)*(p2+1)*... + for (let i = 2; i * i <= x; ++i) { + if (x % i === 0) { + let cnt = 0; + while (x % i === 0) { + ++cnt; + x /= i; + } + mmm *= cnt + 1; + sum += cnt; + } + } + if (x > 1) { // x 是剩余的最后质因子,可能是 1、2、3、5、7、11、13、17、19 等等 + mmm *= 2; + sum += 1; + } + // console.log(x, ':', mmm, 2 * sum); + return Math.max(mmm, 2 * sum); +} + +// 当只有 1 个质因子时,设指数为 p,不拆(1+p) <= 拆开(2p),拆开更优 +console.log(solve(2)); // = 2^1 | 不拆=1+1 = 拆=2*1 2 +console.log(solve(4)); // = 2^2 | 不拆=1+2 < 拆=2*2 4 +console.log(solve(8)); // = 2^3 | 不拆=1+3 < 拆=2*3 6 +console.log(solve(9)); // = 3^2 | 不拆=1+2 < 拆=2*2 4 +console.log(solve(16)); // = 2^4 | 不拆=1+4 < 拆=2*4 8 + +// 当有2个质因子时,设指数为 p1, p2。不拆(p1+1)*(p2+1) >= 拆2*(p1+p2),不拆更优 +console.log(solve(6)); // = 2^1 * 3^1 | 不拆=2*2 = 拆=2*2 4 +console.log(solve(10)); // = 2^1 * 5^1 | 不拆=2*2 = 拆=2*2 4 +console.log(solve(123)); // = 3^1 * 41^1 | 不拆=2*2 = 拆=2*2 4 +console.log(solve(12)); // = 2^2 * 3^1 | 不拆=3*2 > 拆=2*3 6 +console.log(solve(24)); // = 3^1 * 2^3 | 不拆=2*4 = 拆=2*4 8 +console.log(solve(36)); // = 2^2 * 3^2 | 不拆=3*3 > 拆=2*4 9 diff --git "a/nc_practice/6_\350\264\252\345\277\203/42_\345\220\203\347\263\226\346\236\234_\350\264\252\345\277\203.js" "b/nc_practice/6_\350\264\252\345\277\203/42_\345\220\203\347\263\226\346\236\234_\350\264\252\345\277\203.js" new file mode 100644 index 00000000..bb731824 --- /dev/null +++ "b/nc_practice/6_\350\264\252\345\277\203/42_\345\220\203\347\263\226\346\236\234_\350\264\252\345\277\203.js" @@ -0,0 +1,15 @@ +// 吃糖果 + +// 思路:贪心法,从最不甜的开始吃 O(nlogn) +function solve(arr, k) { + arr.sort((x, y) => x - y) + let ans = 0 + for (let i = 0; i < arr.length; ++i) { + k -= arr[i] + if (k < 0) { + break + } + ++ans + } + return ans; +} diff --git "a/nc_practice/6_\350\264\252\345\277\203/_05_\345\260\217\347\276\216\347\232\204\346\225\260\347\273\204\345\210\240\351\231\244.js" "b/nc_practice/6_\350\264\252\345\277\203/_05_\345\260\217\347\276\216\347\232\204\346\225\260\347\273\204\345\210\240\351\231\244.js" new file mode 100644 index 00000000..59bd8c4f --- /dev/null +++ "b/nc_practice/6_\350\264\252\345\277\203/_05_\345\260\217\347\276\216\347\232\204\346\225\260\347\273\204\345\210\240\351\231\244.js" @@ -0,0 +1,7 @@ +// + +function solve(arr, k, x) { + let ans; + + return ans; +} diff --git "a/nc_practice/6_\350\264\252\345\277\203/_24_\345\260\217\346\254\247\347\232\204\346\213\254\345\217\267\346\223\215\344\275\234_stack.js" "b/nc_practice/6_\350\264\252\345\277\203/_24_\345\260\217\346\254\247\347\232\204\346\213\254\345\217\267\346\223\215\344\275\234_stack.js" new file mode 100644 index 00000000..25734add --- /dev/null +++ "b/nc_practice/6_\350\264\252\345\277\203/_24_\345\260\217\346\254\247\347\232\204\346\213\254\345\217\267\346\223\215\344\275\234_stack.js" @@ -0,0 +1,2 @@ +// 小欧的括号操作 + diff --git "a/nc_practice/6_\350\264\252\345\277\203/_30_\346\225\260\345\255\227\345\217\230\346\215\242.js" "b/nc_practice/6_\350\264\252\345\277\203/_30_\346\225\260\345\255\227\345\217\230\346\215\242.js" new file mode 100644 index 00000000..e69de29b diff --git "a/nc_practice/6_\350\264\252\345\277\203/_36_\346\264\273\345\212\250\345\256\211\346\216\222.js" "b/nc_practice/6_\350\264\252\345\277\203/_36_\346\264\273\345\212\250\345\256\211\346\216\222.js" new file mode 100644 index 00000000..e7c2feab --- /dev/null +++ "b/nc_practice/6_\350\264\252\345\277\203/_36_\346\264\273\345\212\250\345\256\211\346\216\222.js" @@ -0,0 +1 @@ +// 活动安排 diff --git "a/nc_practice/7_\344\275\215\350\277\220\347\256\227/50_dd\347\210\261\347\247\221\345\255\2461.0.js" "b/nc_practice/7_\344\275\215\350\277\220\347\256\227/50_dd\347\210\261\347\247\221\345\255\2461.0.js" new file mode 100644 index 00000000..04dc46fe --- /dev/null +++ "b/nc_practice/7_\344\275\215\350\277\220\347\256\227/50_dd\347\210\261\347\247\221\345\255\2461.0.js" @@ -0,0 +1,33 @@ +// dd爱科学1.0 + +function solve(s) { + // q 数组维护当前最优的非递减子序列 + const q = []; + + for (let c of s) { + // 二分查找第一个大于 c 的位置 + let left = 0, right = q.length; + while (left < right) { + const mid = Math.floor((left + right) / 2); + if (q[mid] > c) { + right = mid; + } else { + left = mid + 1; + } + } + + const idx = left; + if (idx === q.length) { + // 当前字符可以接在 q 末尾,扩展子序列 + q.push(c); + } else { + // 替换第一个大于 c 的元素,使 q 尽可能小 + q[idx] = c; + } + } + console.log(q) + // 最小修改次数 = 总长度 - 最长非递减子序列长度 + return s.length - q.length; +} + +console.log(solve('ACEBF')) diff --git "a/nc_practice/7_\344\275\215\350\277\220\347\256\227/_21_\345\260\217\347\272\242\347\232\204\344\270\216\350\277\220\347\256\227.js" "b/nc_practice/7_\344\275\215\350\277\220\347\256\227/_21_\345\260\217\347\272\242\347\232\204\344\270\216\350\277\220\347\256\227.js" new file mode 100644 index 00000000..acbe0e97 --- /dev/null +++ "b/nc_practice/7_\344\275\215\350\277\220\347\256\227/_21_\345\260\217\347\272\242\347\232\204\344\270\216\350\277\220\347\256\227.js" @@ -0,0 +1 @@ +// 小红的与运算 \ No newline at end of file diff --git "a/nc_practice/7_\344\275\215\350\277\220\347\256\227/_28_\350\277\233\345\210\266\350\275\254\346\215\242.js" "b/nc_practice/7_\344\275\215\350\277\220\347\256\227/_28_\350\277\233\345\210\266\350\275\254\346\215\242.js" new file mode 100644 index 00000000..25ccd0f6 --- /dev/null +++ "b/nc_practice/7_\344\275\215\350\277\220\347\256\227/_28_\350\277\233\345\210\266\350\275\254\346\215\242.js" @@ -0,0 +1 @@ +// 进制转换 \ No newline at end of file diff --git "a/nc_practice/7_\344\275\215\350\277\220\347\256\227/_44_\346\225\260\345\255\227\346\270\270\346\210\217.js" "b/nc_practice/7_\344\275\215\350\277\220\347\256\227/_44_\346\225\260\345\255\227\346\270\270\346\210\217.js" new file mode 100644 index 00000000..e359eacd --- /dev/null +++ "b/nc_practice/7_\344\275\215\350\277\220\347\256\227/_44_\346\225\260\345\255\227\346\270\270\346\210\217.js" @@ -0,0 +1 @@ +// 数字游戏 \ No newline at end of file diff --git "a/nc_practice/7_\344\275\215\350\277\220\347\256\227/_46_\345\274\202\346\210\226\345\222\214\344\271\213\345\222\214.js" "b/nc_practice/7_\344\275\215\350\277\220\347\256\227/_46_\345\274\202\346\210\226\345\222\214\344\271\213\345\222\214.js" new file mode 100644 index 00000000..a716ab19 --- /dev/null +++ "b/nc_practice/7_\344\275\215\350\277\220\347\256\227/_46_\345\274\202\346\210\226\345\222\214\344\271\213\345\222\214.js" @@ -0,0 +1 @@ +// 异或和之和 \ No newline at end of file diff --git "a/nc_practice/8_\347\272\277\346\200\247 dp/_04_\345\260\217\347\276\216\345\222\214\345\244\247\345\257\214\347\277\201.js" "b/nc_practice/8_\347\272\277\346\200\247 dp/_04_\345\260\217\347\276\216\345\222\214\345\244\247\345\257\214\347\277\201.js" new file mode 100644 index 00000000..8777c542 --- /dev/null +++ "b/nc_practice/8_\347\272\277\346\200\247 dp/_04_\345\260\217\347\276\216\345\222\214\345\244\247\345\257\214\347\277\201.js" @@ -0,0 +1 @@ +// 小美的大富翁 \ No newline at end of file diff --git "a/nc_practice/8_\347\272\277\346\200\247 dp/_18_\346\225\243\350\220\275\347\232\204\351\207\221\345\270\201.js" "b/nc_practice/8_\347\272\277\346\200\247 dp/_18_\346\225\243\350\220\275\347\232\204\351\207\221\345\270\201.js" new file mode 100644 index 00000000..6662adf0 --- /dev/null +++ "b/nc_practice/8_\347\272\277\346\200\247 dp/_18_\346\225\243\350\220\275\347\232\204\351\207\221\345\270\201.js" @@ -0,0 +1 @@ +// 散落的金币 \ No newline at end of file diff --git "a/nc_practice/8_\347\272\277\346\200\247 dp/_22_\345\260\217\347\272\242\347\232\204v\344\270\211\345\205\203\347\273\204.js" "b/nc_practice/8_\347\272\277\346\200\247 dp/_22_\345\260\217\347\272\242\347\232\204v\344\270\211\345\205\203\347\273\204.js" new file mode 100644 index 00000000..c19526db --- /dev/null +++ "b/nc_practice/8_\347\272\277\346\200\247 dp/_22_\345\260\217\347\272\242\347\232\204v\344\270\211\345\205\203\347\273\204.js" @@ -0,0 +1 @@ +// 小红的v三元组 \ No newline at end of file diff --git "a/nc_practice/8_\347\272\277\346\200\247 dp/_47_\345\233\233\344\270\252\351\200\211\351\241\271.js" "b/nc_practice/8_\347\272\277\346\200\247 dp/_47_\345\233\233\344\270\252\351\200\211\351\241\271.js" new file mode 100644 index 00000000..b4192832 --- /dev/null +++ "b/nc_practice/8_\347\272\277\346\200\247 dp/_47_\345\233\233\344\270\252\351\200\211\351\241\271.js" @@ -0,0 +1 @@ +// 四个选项 \ No newline at end of file diff --git "a/nc_practice/8_\347\272\277\346\200\247 dp/_48_COUNT\346\225\260\345\255\227\350\256\241\346\225\260.js" "b/nc_practice/8_\347\272\277\346\200\247 dp/_48_COUNT\346\225\260\345\255\227\350\256\241\346\225\260.js" new file mode 100644 index 00000000..89e5ea93 --- /dev/null +++ "b/nc_practice/8_\347\272\277\346\200\247 dp/_48_COUNT\346\225\260\345\255\227\350\256\241\346\225\260.js" @@ -0,0 +1 @@ +// COUNT数字计数 \ No newline at end of file diff --git "a/nc_practice/8_\347\272\277\346\200\247 dp/_49_\345\222\214\351\233\266\345\234\250\344\270\200\350\265\267.js" "b/nc_practice/8_\347\272\277\346\200\247 dp/_49_\345\222\214\351\233\266\345\234\250\344\270\200\350\265\267.js" new file mode 100644 index 00000000..09959dcd --- /dev/null +++ "b/nc_practice/8_\347\272\277\346\200\247 dp/_49_\345\222\214\351\233\266\345\234\250\344\270\200\350\265\267.js" @@ -0,0 +1 @@ +// 和零在一起 \ No newline at end of file diff --git "a/nc_practice/9_\345\255\227\347\254\246\344\270\262/07_\345\260\217\347\272\242\347\232\204\345\255\227\347\254\246\344\270\262.js" "b/nc_practice/9_\345\255\227\347\254\246\344\270\262/07_\345\260\217\347\272\242\347\232\204\345\255\227\347\254\246\344\270\262.js" new file mode 100644 index 00000000..005e71b7 --- /dev/null +++ "b/nc_practice/9_\345\255\227\347\254\246\344\270\262/07_\345\260\217\347\272\242\347\232\204\345\255\227\347\254\246\344\270\262.js" @@ -0,0 +1,22 @@ +// 小红的字符串 + +// 一:暴力 O(n^2) +function solve(numStr, kStr) { + const nLen = numStr.length, + kLen = kStr.length; + if (nLen < kLen) return 0; + let ans = (2 * nLen - kLen + 2) * (kLen - 1) / 2; // 长度小于 kLen 的方案数 + for (let pos = 0; pos <= nLen - kLen; ++pos) { // 长度等于 kLen 的方案 + const curStr = numStr.slice(pos, pos + kLen); + const curNum = parseInt(curStr); + if (curNum < parseInt(kStr)) { + ++ans; + } + } + return ans; +} + +// 二:滑动窗口? + + +console.log(solve('1234', '23')) diff --git "a/nc_practice/9_\345\255\227\347\254\246\344\270\262/_09_\345\260\217\347\272\242\347\232\204\347\210\206\347\202\270\344\270\262\344\272\214_\346\273\221\345\212\250\347\252\227\345\217\243.js" "b/nc_practice/9_\345\255\227\347\254\246\344\270\262/_09_\345\260\217\347\272\242\347\232\204\347\210\206\347\202\270\344\270\262\344\272\214_\346\273\221\345\212\250\347\252\227\345\217\243.js" new file mode 100644 index 00000000..3bd08bdf --- /dev/null +++ "b/nc_practice/9_\345\255\227\347\254\246\344\270\262/_09_\345\260\217\347\272\242\347\232\204\347\210\206\347\202\270\344\270\262\344\272\214_\346\273\221\345\212\250\347\252\227\345\217\243.js" @@ -0,0 +1,2 @@ +// 小红的爆炸串二 + diff --git "a/nc_practice/9_\345\255\227\347\254\246\344\270\262/_17_\345\255\220\345\272\217\345\210\227.js" "b/nc_practice/9_\345\255\227\347\254\246\344\270\262/_17_\345\255\220\345\272\217\345\210\227.js" new file mode 100644 index 00000000..e69de29b diff --git "a/nc_practice/9_\345\255\227\347\254\246\344\270\262/_20_\345\260\217\347\272\242\347\232\204red\344\270\262.js" "b/nc_practice/9_\345\255\227\347\254\246\344\270\262/_20_\345\260\217\347\272\242\347\232\204red\344\270\262.js" new file mode 100644 index 00000000..6564ab17 --- /dev/null +++ "b/nc_practice/9_\345\255\227\347\254\246\344\270\262/_20_\345\260\217\347\272\242\347\232\204red\344\270\262.js" @@ -0,0 +1 @@ +// 小红的red串 \ No newline at end of file diff --git "a/nc_practice/9_\345\255\227\347\254\246\344\270\262/_27_\345\260\217\347\272\242\347\232\204\345\245\275red\344\270\262\344\270\200.js" "b/nc_practice/9_\345\255\227\347\254\246\344\270\262/_27_\345\260\217\347\272\242\347\232\204\345\245\275red\344\270\262\344\270\200.js" new file mode 100644 index 00000000..144c8b03 --- /dev/null +++ "b/nc_practice/9_\345\255\227\347\254\246\344\270\262/_27_\345\260\217\347\272\242\347\232\204\345\245\275red\344\270\262\344\270\200.js" @@ -0,0 +1 @@ +// 小红的好red串一 \ No newline at end of file diff --git "a/nc_practice/9_\345\255\227\347\254\246\344\270\262/_34_\346\270\270\346\270\270\347\232\204\345\255\227\346\257\215\344\270\262.js" "b/nc_practice/9_\345\255\227\347\254\246\344\270\262/_34_\346\270\270\346\270\270\347\232\204\345\255\227\346\257\215\344\270\262.js" new file mode 100644 index 00000000..b4d28155 --- /dev/null +++ "b/nc_practice/9_\345\255\227\347\254\246\344\270\262/_34_\346\270\270\346\270\270\347\232\204\345\255\227\346\257\215\344\270\262.js" @@ -0,0 +1 @@ +// 游游的字母串 \ No newline at end of file diff --git a/nc_practice/README.md b/nc_practice/README.md new file mode 100644 index 00000000..0ced706a --- /dev/null +++ b/nc_practice/README.md @@ -0,0 +1,13 @@ +# 例题 50 (4 + 5 + 4 + 5 + 5 + 10 + 5 + 6 + 6 = 50) + +## AC + +01_小苯的数字权值.js 贪心❎ 质因子分解✅ +07_小红的字符串.js 字符串❎ 暴力遍历✅ +11_小红的奇偶抽取.js 枚举✅ + +## 未 AC + +06_小红的数字删除.js 95.45% +08_小红的01串.js 27.27% 蒙的 +09_小红的爆炸串二.js 滑动窗口 有答案 diff --git a/road/01twoSum.js b/road/01twoSum.js new file mode 100644 index 00000000..b7dbad0d --- /dev/null +++ b/road/01twoSum.js @@ -0,0 +1,23 @@ +/** + * https://leetcode-cn.com/problems/two-sum/ + * 1. 两数之和 | easy + */ + +// 解法:哈希表 O(n) +function twoSum(nums, target) { + const map = new Map() + for (let i = 0; i < nums.length; ++i) { + const num = nums[i] + if (map.has(num)) { + return [map.get(num), i] + } else { + map.set(target - num, i) + } + } + return [] +} + +// ---- test case ---- +console.log(twoSum([2, 7, 11, 15], 9)) +console.log(twoSum([3, 2, 4], 6)) +console.log(twoSum([3, 3], 6)) diff --git a/shanyue_official_account/1108.js b/shanyue_official_account/1108.js new file mode 100644 index 00000000..549b134f --- /dev/null +++ b/shanyue_official_account/1108.js @@ -0,0 +1,38 @@ +const data = [ + { userId: 8, title: 'title1' }, + { userId: 11, title: 'other' }, + { userId: 15, title: null }, + { userId: 19, title: 'title2' }, +] + +const result = find(data).where({ + "title": /\d$/, + // "userId": 19, +}).orderBy('userId', 'desc'); + +console.log(result.value); + + +// 实现 find +function find(data) { + return { + value: data, + where(match) { + this.value = this.value.filter( + item => Object.entries(match).every(([key, value]) => { + if (value instanceof RegExp) { + return value.test(item[key]) + } + return item[key] === value + }) + ); + return this; + }, + orderBy(key, type) { + this.value.sort((x, y) => type === 'desc' ? y[key] - x[key] : x[key] - y[key]); + return this; + } + } +} + +