From 3202e286ccd89f17127115ad2fa6bc522882d8b4 Mon Sep 17 00:00:00 2001 From: yk Date: Wed, 21 Feb 2024 18:46:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=BB=91=E5=8A=A8=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierignore | 6 + .prettierrc.js | 11 + content/posts/2024/02/0214.md | 212 --------- content/posts/2024/new_Life.md | 2 +- ...54\347\256\227\346\263\225\351\242\230.md" | 311 ++++++------ ...22\345\210\227\347\273\204\345\220\210.md" | 103 ++-- ...04\344\270\216\351\223\276\350\241\250.md" | 4 +- ...30\345\205\210\351\230\237\345\210\227.md" | 2 +- .../\345\211\215\347\274\200\346\240\221.md" | 122 ++--- .../\345\215\225\350\260\203\346\240\210.md" | 20 +- ...25\350\260\203\351\230\237\345\210\227.md" | 86 ++-- ...00\345\222\214\346\225\260\347\273\204.md" | 407 ++++++++-------- ...56\345\210\206\346\225\260\347\273\204.md" | 79 ++- .../\351\223\276\350\241\250.md" | 348 +++++++------- ...50\346\200\201\350\247\204\345\210\222.md" | 22 +- ...\344\271\21301\350\203\214\345\214\205.md" | 8 +- ...14\345\205\250\350\203\214\345\214\205.md" | 16 +- ...60\345\215\226\350\202\241\347\245\250.md" | 14 +- ...13\345\255\220\345\272\217\345\210\227.md" | 22 +- ...23\345\256\266\345\212\253\350\210\215.md" | 6 +- ...66\346\200\201\345\216\213\347\274\251.md" | 6 +- ...23\346\211\221\346\216\222\345\272\217.md" | 14 +- content/posts/algorithm/trick/LRU.md | 14 +- .../\344\272\214\345\210\206\346\263\225.md" | 16 +- .../\344\275\215\350\277\220\347\256\227.md" | 116 ++--- ...345\216\211\345\256\263\347\232\204KMP.md" | 2 +- .../\345\274\202\344\275\215\350\257\215.md" | 4 +- ...22\345\272\217\347\256\227\346\263\225.md" | 6 +- ...4-\345\217\214\346\214\207\351\222\210.md" | 116 ++--- ...21\345\212\250\347\252\227\345\217\243.md" | 449 ++++++++++++++++++ ...36\346\226\207\347\233\270\345\205\263.md" | 8 +- ...15\345\216\206\346\214\207\345\215\227.md" | 20 +- ...344\272\216DOM\351\203\250\345\210\206.md" | 2 +- ...44\272\216DOM\351\203\250\345\210\2062.md" | 2 +- ...37\345\221\275\345\221\250\346\234\237.md" | 2 +- content/posts/base-js/EventLoop.md | 10 +- "content/posts/base-js/Map\345\222\214Set.md" | 6 +- content/posts/base-js/async&await.md | 10 +- .../base-js/curry\345\222\214compose.md" | 6 +- content/posts/base-js/promiseA+.md | 14 +- .../promise\345\271\266\345\217\221.md" | 4 +- ...20\345\211\215\347\273\223\346\235\237.md" | 6 +- ...36\346\224\266\346\234\272\345\210\266.md" | 2 +- ...345\277\275\347\225\245\347\232\204API.md" | 16 +- ...346\267\267\346\267\206\347\232\204API.md" | 6 +- ...05\347\275\256\345\216\237\347\220\206.md" | 14 +- ...7\224\237\345\270\270\350\260\210-This.md" | 16 +- ...76\345\222\214\347\273\247\346\211\277.md" | 14 +- ...21\345\270\203\350\256\242\351\230\205.md" | 4 +- .../\351\207\215\345\255\246TypeScript.md" | 2 +- ...62\346\212\226\350\212\202\346\265\201.md" | 8 +- ...13\350\221\261\346\250\241\345\236\213.md" | 14 +- .../node/node\345\237\272\347\241\200.md" | 14 +- content/posts/react/React-hooks.md | 16 +- content/posts/react/React-setState.md | 14 +- ...72\347\241\200\345\216\237\347\220\206.md" | 16 +- ...2\347\241\200\345\216\237\347\220\2062.md" | 6 +- ...2\347\241\200\345\216\237\347\220\2063.md" | 10 +- ...2\347\241\200\345\216\237\347\220\2064.md" | 4 +- ...2\347\241\200\345\216\237\347\220\2065.md" | 24 +- ...2\347\241\200\345\216\237\347\220\2066.md" | 18 +- ...\220\2067-Diff\347\256\227\346\263\225.md" | 20 +- ...200\345\216\237\347\220\2068-Scheduler.md" | 18 +- .../posts/tool/CommonJS\345\222\214ESM.md" | 6 +- ...17\345\214\226\345\273\272\350\256\256.md" | 4 +- content/posts/tool/babel.md | 28 +- ...24\345\274\217\345\216\237\347\220\206.md" | 10 +- content/posts/webpack/webpack.md | 10 +- .../webpack/webpack\344\271\213loader.md" | 4 +- .../webpack/webpack\344\271\213plugin.md" | 8 +- ...16\347\237\245\350\257\206\347\202\271.md" | 2 +- 71 files changed, 1610 insertions(+), 1352 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc.js delete mode 100644 content/posts/2024/02/0214.md create mode 100644 "content/posts/algorithm/trick/\346\225\260\347\273\204-\346\273\221\345\212\250\347\252\227\345\217\243.md" diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..2a16201 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +node_modules +dist +env +.gitignore +pnpm-lock.yaml +README.md diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..27908e4 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,11 @@ +module.exports = { + tabWidth: 4, // 一个tab代表几个空格数,默认就是2 + useTabs: false, // 是否启用tab取代空格符缩进,.editorconfig设置空格缩进,所以设置为false + printWidth: 100, // 一行的字符数,如果超过会进行换行 + semi: false, // 行尾是否使用分号,默认为true + singleQuote: true, // 字符串是否使用单引号 + trailingComma: 'none', // 对象或数组末尾是否添加逗号 none| es5| all + jsxSingleQuote: true, // 在jsx里是否使用单引号,你看着办 + bracketSpacing: true, // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar } + arrowParens: 'avoid' // 箭头函数如果只有一个参数则省略括号 +} diff --git a/content/posts/2024/02/0214.md b/content/posts/2024/02/0214.md deleted file mode 100644 index a82acbb..0000000 --- a/content/posts/2024/02/0214.md +++ /dev/null @@ -1,212 +0,0 @@ ---- -title: '0214-滑动窗口' -date: 2024-02-14T20:36:50+08:00 -lastmod: -tags: [] -series: [] -categories: [] -draft: true ---- - -## 大体算法 - -```js -function slideWindow() { - // 前后快慢双指针 - let left = 0 - let right = 0 - /** 具体的条件逻辑根据实际问题实际处理,多做练习 */ - while(slide condition) { - window.push(s[left]) // s 为总数据(字符串、数组) - right++ - while(shrink condition) { - window.shift(s[left]) - left++ - } - } -} -``` - -> 滑动窗口的算法时间复杂度为 `O(n)`,适用于处理大型数据集 - -## 练习 - -### 最小覆盖子串 lc.76 - -```js -/** - * @param {string} s - * @param {string} t - * @return {string} - */ -var minWindow = function (s, t) { - if (s.length < t.length) return '' - const tMap = {} - for (const char of t) { - tMap[char] = tMap[char] ? ++tMap[char] : 1 - } - const keyLength = Object.keys(tMap).length - let left = 0 - let right = 0 - const window = {} - let validCharCount = 0 - let minStr = '' - while (right < s.length) { - const c = s[right] - window[c] = window[c] ? ++window[c] : 1 - if (window[c] === tMap[c]) validCharCount++ - right++ - - while (validCharCount === keyLength) { - const d = s[left] - if (window[d] === tMap[d]) { - validCharCount-- - // 分析出,此时字符串区间 应当为[left, right),因为 此时 right 已经++,left 还未++ - const str = s.substring(left, right) - if (!minStr) { - minStr = str - } else { - minStr = str.length < minStr.length ? str : minStr - } - } - window[d] = window[d] - 1 - left++ - } - } - return minStr -} -``` - -### 无重复字符的最长子串 lc.3 - -```js -/** - * @param {string} s - * @return {number} - */ -var lengthOfLongestSubstring = function (s) { - let res = 0 - const window = {} - let left = 0, - right = 0 - while (right < s.length) { - window[s[right]] = window[s[right]] ? ++window[s[right]] : 1 - while (window[s[right]] > 1) { - const char = s[left] - window[s[left]]-- - left++ - } - const len = right - left + 1 - res = len > res ? len : res - right++ - } - return res -} -``` - -### 字符串的排列 lc.567 - -```js -/** - * @param {string} s1 - * @param {string} s2 - * @return {boolean} - */ -var checkInclusion = function (s1, s2) { - const len = s1.length - const s1Map = {} - for (const c of s1) { - s1Map[c] = s1Map[c] ? ++s1Map[c] : 1 - } - - let left = 0, - right = 0 - const window = {} - let valid = 0 - while (right < s2.length) { - const c = s2[right] - window[c] = window[c] ? ++window[c] : 1 - if (window[c] === s1Map[c]) valid++ - right++ - - if (right - left == len) { - if (valid == Object.keys(s1Map).length) return true - const d = s2[left] - if (window[d] === s1Map[d]) valid-- - window[d] = window[d] - 1 - left++ - } - } - - return false -} -``` - -### 找到字符串中所有字母异位词 lc.438 - -```js -/** - * @param {string} s - * @param {string} p - * @return {number[]} - */ -var findAnagrams = function (s, p) { - const need = {} - for (const c of p) { - need[c] = need[c] ? ++need[c] : 1 - } - - const res = [] - const window = {} - let left = 0, - right = 0 - let valid = 0 - while (right < s.length) { - const c = s[right] - window[c] = window[c] ? ++window[c] : 1 - if (window[c] === need[c]) valid++ - right++ - - while (right - left >= p.length) { - if (valid === Object.keys(need).length) { - res.push(left) - } - const d = s[left] - if (window[d] === need[d]) { - valid-- - } - window[d]-- - left++ - } - /** 容易犯错,这时候可以考虑一下是不是收缩条件有问题 */ - // while(valid === Object.keys(need).length) { - // const d = s[left] - // if(window[d] === need[d]) { - // valid-- - // res.push(left) - // } - // window[d] = window[d] - 1 - // left++ - // } - } - return res -} -``` - ---- - -## 应用 - -滑动窗口算法适用于解决以下类型的问题: - -- 查找最大子数组和 -- 查找具有 K 个不同字符的最长子串 -- 查找具有 K 个不同字符的最短子串 -- 查找具有特定条件的最长子串或子数组 -- 查找具有特定条件的最短子串或子数组 -- 查找连续 1 的最大序列长度(可以在允许将最多 K 个 0 替换为 1 的情况下) -- 查找具有特定和的子数组 -- 查找具有不同元素的子数组 -- 查找具有特定条件的最大或最小子数组 - -滑动窗口算法通常用于解决需要在数组或字符串上维护一个固定大小的窗口,并在窗口内执行特定操作或计算的问题。这种算法技术可以有效降低时间复杂度,通常为 `O(n)`,适用于处理大型数据集。 diff --git a/content/posts/2024/new_Life.md b/content/posts/2024/new_Life.md index ea88fe2..c4f32ac 100644 --- a/content/posts/2024/new_Life.md +++ b/content/posts/2024/new_Life.md @@ -1,5 +1,5 @@ --- -title: 'New_Life' +title: 'Change in 2024' date: 2024-01-03T14:33:13+08:00 lastmod: tags: [] diff --git "a/content/posts/algorithm/JS\347\261\273\345\270\270\350\247\201\344\270\200\350\210\254\347\256\227\346\263\225\351\242\230.md" "b/content/posts/algorithm/JS\347\261\273\345\270\270\350\247\201\344\270\200\350\210\254\347\256\227\346\263\225\351\242\230.md" index ee6fdc4..39f9382 100644 --- "a/content/posts/algorithm/JS\347\261\273\345\270\270\350\247\201\344\270\200\350\210\254\347\256\227\346\263\225\351\242\230.md" +++ "b/content/posts/algorithm/JS\347\261\273\345\270\270\350\247\201\344\270\200\350\210\254\347\256\227\346\263\225\351\242\230.md" @@ -9,43 +9,43 @@ date: 2022-09-23T14:32:40+08:00 题目: -```JavaScript +```js // 扁平数组 const arr = [ - {id: 1, name: '1', pid: 0}, - {id: 2, name: '2', pid: 1}, - {id: 3, name: '3', pid: 1}, - {id: 4, name: '4', pid: 3}, - {id: 5, name: '5', pid: 3}, + { id: 1, name: '1', pid: 0 }, + { id: 2, name: '2', pid: 1 }, + { id: 3, name: '3', pid: 1 }, + { id: 4, name: '4', pid: 3 }, + { id: 5, name: '5', pid: 3 } ] // tree const tree = [ - { - id: 1, - name: '1', - pid: 0, - children: [ - { - id: 2, - name: '2', - pid: 1, - children: [] - }, - { - id: 3, - name: '3', - pid: 1, + { + id: 1, + name: '1', + pid: 0, children: [ - { - id: 4, - name: '4', - pid: 3, - children: [] - } + { + id: 2, + name: '2', + pid: 1, + children: [] + }, + { + id: 3, + name: '3', + pid: 1, + children: [ + { + id: 4, + name: '4', + pid: 3, + children: [] + } + ] + } ] - } - ] - } + } ] ``` @@ -53,36 +53,36 @@ const tree = [ 规律不要太明显,最容易想到的就是递归喽~ -```JavaScript +```js // tree扁平化 就是个树的遍历而已 function treeToArr(tree) { - const res = [] - const getChildren = tree => { - for (const node of tree) { - const { id, name, pid } = node - res.push({ id, name, pid }) - if (node.children) getChildren(node.children) + const res = [] + const getChildren = tree => { + for (const node of tree) { + const { id, name, pid } = node + res.push({ id, name, pid }) + if (node.children) getChildren(node.children) + } } - } - getChildren(tree) - return res + getChildren(tree) + return res } -const transToArr = (arr) => { - const res = [] - const getChildren = (arr) => { - arr.forEach((item) => { - const obj = { - id: item.id, - pid: item.pid, - name: item.name - } - res.push(obj) - if (item.children.length) getChildren(item.children) - }) - } - getChildren(arr) - return res +const transToArr = arr => { + const res = [] + const getChildren = arr => { + arr.forEach(item => { + const obj = { + id: item.id, + pid: item.pid, + name: item.name + } + res.push(obj) + if (item.children.length) getChildren(item.children) + }) + } + getChildren(arr) + return res } ``` @@ -90,61 +90,61 @@ const transToArr = (arr) => { 扁平化转树,往往有些人写不出来,是因为对于递归不够熟悉。如果能够联想到使用 pid 去寻找子集,那么我觉得还是比较容易的吧。 -```JavaScript +```js // 扁平化转tree function arrToTree(arr) { - const res = [] - // 递归: 根据pid寻找子节点塞入child - const getChildren = (pid, child) => { - for (const item of arr) { - if (item.pid === pid) { - const newItem = { ...item, children: [] } - getChildren(newItem.id, newItem.children) - child.push(newItem) - } + const res = [] + // 递归: 根据pid寻找子节点塞入child + const getChildren = (pid, child) => { + for (const item of arr) { + if (item.pid === pid) { + const newItem = { ...item, children: [] } + getChildren(newItem.id, newItem.children) + child.push(newItem) + } + } } - } - getChildren(0, res) - return res + getChildren(0, res) + return res } /** * 2024.02.15 重新写了一版,应该更简单 */ const transToTree = (arr, pid) => { - if (!arr.length) return - const rootItems = arr.filter((item) => item.pid === pid) - for (let i = 0; i < rootItems.length; ++i) { - const item = rootItems[i] - item.children = arr.filter((_item) => _item.pid === item.id) - transToTree(arr, item.id) - } - return rootItems + if (!arr.length) return + const rootItems = arr.filter(item => item.pid === pid) + for (let i = 0; i < rootItems.length; ++i) { + const item = rootItems[i] + item.children = arr.filter(_item => _item.pid === item.id) + transToTree(arr, item.id) + } + return rootItems } ``` 上方是写出来了,但是呢,这个复杂度有点高,怎么优化呢?往往需要借助数据结构 Map: -```JavaScript +```js function arrToTree(arr) { - const res = [] - const map = new Map() // 便于查找 - - for (const item of arr) { - map.set(item.id, { ...item, children: [] }) - } - - for (const item of arr) { - const newItem = map.get(item.id) - if (item.pid === 0) { - res.push(newItem) - } else { - if (map.has(item.pid)) { - map.get(item.pid).children.push(newItem) - } + const res = [] + const map = new Map() // 便于查找 + + for (const item of arr) { + map.set(item.id, { ...item, children: [] }) } - } - return res + + for (const item of arr) { + const newItem = map.get(item.id) + if (item.pid === 0) { + res.push(newItem) + } else { + if (map.has(item.pid)) { + map.get(item.pid).children.push(newItem) + } + } + } + return res } ``` @@ -154,16 +154,18 @@ function arrToTree(arr) { 核心就是滚动数组的思想。 -```JavaScript +```js function fib(n) { - if(n <= 1) return n - let p = 0, q = 0, r = 1; - for(let i = 2; i <= n; ++i) { - p = q - q = r - r = p + q - } - return r + if (n <= 1) return n + let p = 0, + q = 0, + r = 1 + for (let i = 2; i <= n; ++i) { + p = q + q = r + r = p + q + } + return r } ``` @@ -175,23 +177,23 @@ JS 的数值是有范围的,超过范围后就会损失精度,大数相加 在相加的过程中考虑进位即可。 -```JavaScript +```js function bigSum(a, b) { - // 先补齐长度 - const maxLength = Math.max(a.length, b.length) - a = a.padStart(maxLength, 0) - b = b.padStart(maxLength, 0) - - // 再从末尾开始相加 - let c = 0 // 进位 - let sum = '' - for (let i = maxLength - 1; i >= 0; --i) { - let t = parseInt(a[i]) + parseInt(b[i]) + c - c = Math.floor(t / 10) - sum = (t % 10) + sum - } - if (c == 1) sum = c + sum // 注意不要遗漏最后的进位 - return sum + // 先补齐长度 + const maxLength = Math.max(a.length, b.length) + a = a.padStart(maxLength, 0) + b = b.padStart(maxLength, 0) + + // 再从末尾开始相加 + let c = 0 // 进位 + let sum = '' + for (let i = maxLength - 1; i >= 0; --i) { + let t = parseInt(a[i]) + parseInt(b[i]) + c + c = Math.floor(t / 10) + sum = (t % 10) + sum + } + if (c == 1) sum = c + sum // 注意不要遗漏最后的进位 + return sum } console.log(bigSum('9007199254740991', '1234567899999999999')) @@ -201,56 +203,57 @@ console.log(bigSum('9007199254740991', '1234567899999999999')) 可以通过一个 counter 变量计数,也可以每次从末尾截取 3 个。 -```JavaScript +```js const num = 20230102 // expect: 20,230,102 function toThousand(num) { - let counter = 0; - let temp = num.toString(); - let res = ''; - for (let i = temp.length - 1; i >= 0; --i) { - counter++; - res = temp[i] + res; - if (counter % 3 === 0 && i !== 0) { - res = ',' + res; + let counter = 0 + let temp = num.toString() + let res = '' + for (let i = temp.length - 1; i >= 0; --i) { + counter++ + res = temp[i] + res + if (counter % 3 === 0 && i !== 0) { + res = ',' + res + } } - } - return res; + return res } function toThousand2(num) { - let temp = num.toString(); - let res = ''; - while (temp.length) { - res = ',' + temp.slice(-3) + res; - temp = temp.slice(0, -3); - } - return res.slice(1); + let temp = num.toString() + let res = '' + while (temp.length) { + res = ',' + temp.slice(-3) + res + temp = temp.slice(0, -3) + } + return res.slice(1) } ``` ### faltten 多种实现 -```JavaScript +```js // 1. api arr.flat(Infinity) // 2. 递归 -const arr = [1, 2, 3, [4, [5, 6], [7, [8, [9]]]], 10]; -const flatten = (arr) => { - const res = []; - // 定义递归遍历 入参为数组 过程中加入 res - const traverse = (arr) => { - for (let i = 0; i < arr.length; ++i) { - if (Array.isArray(arr[i])) { - traverse(arr[i]); - } else { - res.push(arr[i]); - } +const arr = [1, 2, 3, [4, [5, 6], [7, [8, [9]]]], 10] +const flatten = arr => { + const res = [] + // 定义递归遍历 入参为数组 过程中加入 res + const traverse = arr => { + for (let i = 0; i < arr.length; ++i) { + if (Array.isArray(arr[i])) { + traverse(arr[i]) + } else { + res.push(arr[i]) + } + } } - }; - traverse(arr); - return res; -}; + traverse(arr) + return res +} // 3. reduce -const flatten = (arr) => arr.reduce((t, v) => (Array.isArray(v) ? t.push(...flatten(v)) : t.push(v), t), []); +const flatten = arr => + arr.reduce((t, v) => (Array.isArray(v) ? t.push(...flatten(v)) : t.push(v), t), []) ``` diff --git "a/content/posts/algorithm/backtrack/\345\233\236\346\272\257\344\271\213\346\216\222\345\210\227\347\273\204\345\220\210.md" "b/content/posts/algorithm/backtrack/\345\233\236\346\272\257\344\271\213\346\216\222\345\210\227\347\273\204\345\220\210.md" index f227171..bf59f81 100644 --- "a/content/posts/algorithm/backtrack/\345\233\236\346\272\257\344\271\213\346\216\222\345\210\227\347\273\204\345\220\210.md" +++ "b/content/posts/algorithm/backtrack/\345\233\236\346\272\257\344\271\213\346\216\222\345\210\227\347\273\204\345\220\210.md" @@ -9,8 +9,8 @@ date: 2022-10-09T20:49:13+08:00 1. backtrack 函数用来进行深度遍历(函数嘛,不断进调用栈喽),for 循环用来控制每一层能遍历元素。 2. 子集与组合是一类题无需考虑顺序,而排列需要考虑顺序。所以子集和组合需要一个参数 start 来过滤后续的树枝。 - - 组合:`backtrack = (start) => {}` - - 排列:`backtrack = () => {}` + - 组合:`backtrack = (start) => {}` + - 排列:`backtrack = () => {}` 3. 无重复元素时,不需要剪枝;有重复元素时,需要先排序,然后剪枝,这一步在 for 循环内。 4. 元素可以复选,`backtrack(i)` 否则 `backtrack(i + 1)`,这一步在 for 循环内。 5. 针对排列,需要使用 used 剪枝。 @@ -28,23 +28,23 @@ date: 2022-10-09T20:49:13+08:00 1. 无重复元素 2. 元素可以重复选。 -```JavaScript +```js /** * @param {number[]} candidates * @param {number} target * @return {number[][]} */ -var combinationSum = function(candidates, target) { +var combinationSum = function (candidates, target) { const res = [] const track = [] let sum = 0 - const backtrack = (start) => { - if(sum === target) { + const backtrack = start => { + if (sum === target) { res.push([...track]) return } - if(sum > target) return - for(let i = start; i < candidates.length; ++i) { + if (sum > target) return + for (let i = start; i < candidates.length; ++i) { const v = candidates[i] sum += v track.push(v) @@ -55,7 +55,7 @@ var combinationSum = function(candidates, target) { } backtrack(0) return res -}; +} ``` ### [40.组合总和 II](https://leetcode.cn/problems/combination-sum-ii/) @@ -66,25 +66,25 @@ var combinationSum = function(candidates, target) { 2. 不可以重复选择 3. 要求 不能包含重复的组合 -```JavaScript +```js /** * @param {number[]} candidates * @param {number} target * @return {number[][]} */ -var combinationSum2 = function(candidates, target) { - candidates.sort((a,b) => a - b) +var combinationSum2 = function (candidates, target) { + candidates.sort((a, b) => a - b) const res = [] const track = [] let sum = 0 - const backtrack = (start) => { - if(sum === target) { + const backtrack = start => { + if (sum === target) { res.push([...track]) return } - for(let i = start; i < candidates.length; ++i) { - if(sum + candidates[i] > target) continue - if(i > start && candidates[i] === candidates[i - 1]) continue + for (let i = start; i < candidates.length; ++i) { + if (sum + candidates[i] > target) continue + if (i > start && candidates[i] === candidates[i - 1]) continue const v = candidates[i] track.push(v) sum += v @@ -95,7 +95,7 @@ var combinationSum2 = function(candidates, target) { } backtrack(0) return res -}; +} ``` ### [77.组合](https://leetcode.cn/problems/combinations/) @@ -107,21 +107,21 @@ var combinationSum2 = function(candidates, target) { 1. 无重复元素 2. 不可以复选 -```JavaScript +```js /** * @param {number} n * @param {number} k * @return {number[][]} */ -var combine = function(n, k) { +var combine = function (n, k) { const res = [] const track = [] - const backtrack = (start) => { - if(track.length === k) { + const backtrack = start => { + if (track.length === k) { res.push([...track]) return } - for(let i = start; i <= n; ++i) { + for (let i = start; i <= n; ++i) { track.push(i) backtrack(i + 1) track.pop() @@ -129,7 +129,7 @@ var combine = function(n, k) { } backtrack(1) return res -}; +} ``` ### [78.子集](https://leetcode.cn/problems/subsets/) @@ -139,27 +139,26 @@ var combine = function(n, k) { 1. 无重复元素 2. 不可复选 -```JavaScript +```js /** * @param {number[]} nums * @return {number[][]} */ -var subsets = function(nums) { +var subsets = function (nums) { const res = [] const track = [] - const backtrack = (start) => { + const backtrack = start => { res.push([...track]) - if(track.length === nums.length) return - for(let i = start; i < nums.length; ++i) { + if (track.length === nums.length) return + for (let i = start; i < nums.length; ++i) { track.push(nums[i]) backtrack(i + 1) track.pop() } - } backtrack(0) return res -}; +} ``` > 这道题的递归结束条件,看似没有设置,其实是通过 `start` 来控制的,若 `start >= nums.length` 是不会进入 `for` 循环的,也就结束的递归 @@ -171,20 +170,20 @@ var subsets = function(nums) { 1. 有重复元素 2. 不可重复选择 -```JavaScript +```js /** * @param {number[]} nums * @return {number[][]} */ -var subsetsWithDup = function(nums) { - nums.sort((a,b) => a - b) +var subsetsWithDup = function (nums) { + nums.sort((a, b) => a - b) const res = [] const track = [] - const backtrack = (start) => { + const backtrack = start => { res.push([...track]) - if(track.length === nums.length) return - for(let i = start; i < nums.length; ++i) { - if(i > start && nums[i] === nums[i - 1]) continue + if (track.length === nums.length) return + for (let i = start; i < nums.length; ++i) { + if (i > start && nums[i] === nums[i - 1]) continue track.push(nums[i]) backtrack(i + 1) track.pop() @@ -192,7 +191,7 @@ var subsetsWithDup = function(nums) { } backtrack(0) return res -}; +} ``` ### [46.全排列](https://leetcode.cn/problems/permutations/) @@ -202,23 +201,23 @@ var subsetsWithDup = function(nums) { 1. 无重复元素 2. 不可复选 -```JavaScript +```js /** * @param {number[]} nums * @return {number[][]} */ -var permute = function(nums) { +var permute = function (nums) { const res = [] const track = [] const uesd = [] const backtrack = () => { - if(track.length === nums.length) { + if (track.length === nums.length) { res.push([...track]) return } - for(let i = 0; i < nums.length; ++i) { + for (let i = 0; i < nums.length; ++i) { const v = nums[i] - if(uesd.includes(v)) continue + if (uesd.includes(v)) continue track.push(v) uesd.push(v) backtrack() @@ -228,7 +227,7 @@ var permute = function(nums) { } backtrack() return res -}; +} ``` ### [47.全排列 II](https://leetcode.cn/problems/permutations-ii/) @@ -238,24 +237,24 @@ var permute = function(nums) { 1. 有重复元素 2. 不可复选 -```JavaScript +```js /** * @param {number[]} nums * @return {number[][]} */ -var permuteUnique = function(nums) { +var permuteUnique = function (nums) { nums.sort((a, b) => a - b) const res = [] const track = [] const used = [] const backtrack = () => { - if(track.length === nums.length) { + if (track.length === nums.length) { res.push([...track]) return } - for(let i = 0; i < nums.length; ++i) { - if(used[i]) continue - if(i > 0 && nums[i] === nums[i-1] && !used[i-1]) continue // 新的剪枝逻辑 就是 保证相同元素的顺序固定不变 + for (let i = 0; i < nums.length; ++i) { + if (used[i]) continue + if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) continue // 新的剪枝逻辑 就是 保证相同元素的顺序固定不变 track.push(nums[i]) used[i] = true backtrack() @@ -265,5 +264,5 @@ var permuteUnique = function(nums) { } backtrack() return res -}; +} ``` diff --git "a/content/posts/algorithm/data structure/JS\344\270\255\347\232\204\346\225\260\347\273\204\344\270\216\351\223\276\350\241\250.md" "b/content/posts/algorithm/data structure/JS\344\270\255\347\232\204\346\225\260\347\273\204\344\270\216\351\223\276\350\241\250.md" index b93c411..4b2f356 100644 --- "a/content/posts/algorithm/data structure/JS\344\270\255\347\232\204\346\225\260\347\273\204\344\270\216\351\223\276\350\241\250.md" +++ "b/content/posts/algorithm/data structure/JS\344\270\255\347\232\204\346\225\260\347\273\204\344\270\216\351\223\276\350\241\250.md" @@ -40,7 +40,7 @@ JavaScript 中的数组分为快慢数组。 2. 数组寻址时减少 CPU 指令运算 3. 物理内存的地址是从 0 开始的 -```JavaScript +```js // 寻址公式 arr[i] = base_address + i * type_size // 其中base_address为数组arr首地址,arr0就是 **偏移量** 为0的数组,即数组arr首地址; @@ -54,7 +54,7 @@ arr[i] = base_address + (i -1)* type_size ### 二维数组初始化 -```JavaScript +```js const dp = Array.from(Array(m), () => Array(n).fill(0)) ``` diff --git "a/content/posts/algorithm/data structure/\344\274\230\345\205\210\351\230\237\345\210\227.md" "b/content/posts/algorithm/data structure/\344\274\230\345\205\210\351\230\237\345\210\227.md" index 279f71b..f4375b3 100644 --- "a/content/posts/algorithm/data structure/\344\274\230\345\205\210\351\230\237\345\210\227.md" +++ "b/content/posts/algorithm/data structure/\344\274\230\345\205\210\351\230\237\345\210\227.md" @@ -10,7 +10,7 @@ JavaScript 中没有内置优先队列这个数据结构,需要自己来实现 - 精髓之一:**数组的第一个索引 0 空着不用** - 精髓之二:插入或者删除元素的时候,需要元素自动排序 -```JavaScript +```js class PriorityQueue { constructor(data, cmp) { // 使用堆顶守卫,更方便上浮时父节点的获取 p = i >> 1, 子节点本身就比较好获取倒是无所谓 diff --git "a/content/posts/algorithm/data structure/\345\211\215\347\274\200\346\240\221.md" "b/content/posts/algorithm/data structure/\345\211\215\347\274\200\346\240\221.md" index c8d62cc..f0ea152 100644 --- "a/content/posts/algorithm/data structure/\345\211\215\347\274\200\346\240\221.md" +++ "b/content/posts/algorithm/data structure/\345\211\215\347\274\200\346\240\221.md" @@ -14,80 +14,80 @@ categories: [algorithm] 核心:**字符在树的树枝上,节点上保存着信息** -- p:通过下接树枝字符的字符串数量. -- 可以查询前缀数量 -- e:以上接树枝字符结尾的字符串数量. -- 可以查询字符串 +- p:通过下接树枝字符的字符串数量. -- 可以查询前缀数量 +- e:以上接树枝字符结尾的字符串数量. -- 可以查询字符串 对应的数据结构如下: ```js // ### 实现前缀树 lc.208 class TrieNode { - constructor(pass = 0, end = 0) { - // this.isEnd = false // 这个是另一种实现方式,leetcode 208 题题解,下面这种能多做一种统计 - this.pass = pass // 通过下接树枝字符的字符串数量 - this.end = end // 以上接树枝字符结尾的字符串数量 - this.next = {} // {char: TrieNode} 的 map 集, 字符有限,有些教程也用数组实现 - } + constructor(pass = 0, end = 0) { + // this.isEnd = false // 这个是另一种实现方式,leetcode 208 题题解,下面这种能多做一种统计 + this.pass = pass // 通过下接树枝字符的字符串数量 + this.end = end // 以上接树枝字符结尾的字符串数量 + this.next = {} // {char: TrieNode} 的 map 集, 字符有限,有些教程也用数组实现 + } } class Trie { - constructor() { - this.root = new TrieNode() - } - insert(str) { - if (!str) return - const chars = str.split('') - let node = this.root - for (const s of chars) { - if (!node.next[s]) { - node.next[s] = new TrieNode() - } - node = node.next[s] - node.pass++ + constructor() { + this.root = new TrieNode() } - node.end++ - } - search(str) { - if (!str) return 0 // 根据实际问题看返回 0 还是 false 值 - const chars = str.split('') - let node = this.root - for (const s of chars) { - if (node.next[s]) { - node = node.next[s] - } else { - return 0 // 根据实际问题看返回 0 还是 false 值 - } + insert(str) { + if (!str) return + const chars = str.split('') + let node = this.root + for (const s of chars) { + if (!node.next[s]) { + node.next[s] = new TrieNode() + } + node = node.next[s] + node.pass++ + } + node.end++ } - return node.end // 根据实际问题 看是返回 end, 还是 返回 true 值 - } - // 有几个以 str 为前缀的字符串。根据实际问题,看是返回 Boolean 还是 number - startWidth(str) { - const chars = str.split('') - let node = this.root - for (const s of chars) { - if (node.next[s]) { - node = node.next[s] - } else { - return 0 - } + search(str) { + if (!str) return 0 // 根据实际问题看返回 0 还是 false 值 + const chars = str.split('') + let node = this.root + for (const s of chars) { + if (node.next[s]) { + node = node.next[s] + } else { + return 0 // 根据实际问题看返回 0 还是 false 值 + } + } + return node.end // 根据实际问题 看是返回 end, 还是 返回 true 值 + } + // 有几个以 str 为前缀的字符串。根据实际问题,看是返回 Boolean 还是 number + startWidth(str) { + const chars = str.split('') + let node = this.root + for (const s of chars) { + if (node.next[s]) { + node = node.next[s] + } else { + return 0 + } + } + return node.pass } - return node.pass - } - delete(str) { - if (this.search(str) !== 0) { - const chars = str.split('') - let node = this.root - node.pass-- - for (const s of chars) { - node.next[s].pass-- - if (node.next[s].pass == 0) { - node.next[s] = null // 有一个节点的 pass 为 0 的时候,说明后面就都没得了,可以直接把后续置 null 了 - return + delete(str) { + if (this.search(str) !== 0) { + const chars = str.split('') + let node = this.root + node.pass-- + for (const s of chars) { + node.next[s].pass-- + if (node.next[s].pass == 0) { + node.next[s] = null // 有一个节点的 pass 为 0 的时候,说明后面就都没得了,可以直接把后续置 null 了 + return + } + node = node.next[s] + } + node.end-- } - node = node.next[s] - } - node.end-- } - } } ``` diff --git "a/content/posts/algorithm/data structure/\345\215\225\350\260\203\346\240\210.md" "b/content/posts/algorithm/data structure/\345\215\225\350\260\203\346\240\210.md" index be56627..c2b6fcc 100644 --- "a/content/posts/algorithm/data structure/\345\215\225\350\260\203\346\240\210.md" +++ "b/content/posts/algorithm/data structure/\345\215\225\350\260\203\346\240\210.md" @@ -11,7 +11,7 @@ categories: [algorithm] 怎么做到呢?就是让每一个元素入栈时,递归检查栈顶,如果大于或小于栈顶(视单调情况而定)就把栈顶 pop 掉,直到满足能使得新加入的元素保持栈的单调性。 -```JavaScript +```js // 递增栈 for(let i = 0; i < arr.length; ++i) { while(stack.length && stack[stack.length - 1] >= arr[i]) { @@ -37,7 +37,7 @@ for(let i = 0; i < arr.length; ++i) { ### [496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/) -```JavaScript +```js /** * @param {number[]} nums1 * @param {number[]} nums2 @@ -91,7 +91,7 @@ var nextGreaterElement = function(nums1, nums2) { 相比上一题,这里的数组是个循环数组,遍历循环数组的方法最简单的就是直接拼接一次也就是让数组长度翻倍,但我们也可以使用一个小技巧:取模,来遍历循环数组。 -```JavaScript +```js const arr = [1, 2, 3, 4, 5] const n = arr.length let index = 0 @@ -103,7 +103,7 @@ while (index < 2 * n) { } ``` -```JavaScript +```js /** * @param {number[]} nums * @return {number[]} @@ -128,7 +128,7 @@ var nextGreaterElements = function(nums) { ### [739.每日温度](https://leetcode.cn/problems/daily-temperatures/) -```JavaScript +```js /** * @param {number[]} temperatures * @return {number[]} @@ -157,7 +157,7 @@ var dailyTemperatures = function(temperatures) { 单调栈的解法是有点绕头的,需要按照上图的方式去计算面积,这么想就容易理解点了。 -```JavaScript +```js /** * @param {number[]} height * @return {number} @@ -182,7 +182,7 @@ var trap = function(height) { 补充:双指针法 -```JavaScript +```js // 相比单调栈横向计算面积, 双指针是纵向计算面积的,主要根据两边高度的较小个 var trap = function(height) { // 每个坐标点能装下的水是 左右最高柱子较小的那一个 减去自身的高度 @@ -211,12 +211,12 @@ var trap = function(height) { 结合上题,可以帮助我们更好理解单调栈如何确定是单调递增还是单调递减。 -- 接雨水需要找到「盛水的凹点」,所以栈顶要小于左右两侧,因此需要一个「单调递减」的栈 -- 最大矩形需要找到「左右的低点」,所以栈顶要高于左右两侧,因此需要一个「单调递增」的栈 +- 接雨水需要找到「盛水的凹点」,所以栈顶要小于左右两侧,因此需要一个「单调递减」的栈 +- 最大矩形需要找到「左右的低点」,所以栈顶要高于左右两侧,因此需要一个「单调递增」的栈 另外这道题还用到了一个技巧:哨兵守卫,因为需要使用到首尾索引,所以使用这种技巧来简化算法。 -```JavaScript +```js /** * @param {number[]} heights * @return {number} diff --git "a/content/posts/algorithm/data structure/\345\215\225\350\260\203\351\230\237\345\210\227.md" "b/content/posts/algorithm/data structure/\345\215\225\350\260\203\351\230\237\345\210\227.md" index 19fde3a..8d04640 100644 --- "a/content/posts/algorithm/data structure/\345\215\225\350\260\203\351\230\237\345\210\227.md" +++ "b/content/posts/algorithm/data structure/\345\215\225\350\260\203\351\230\237\345\210\227.md" @@ -11,26 +11,26 @@ categories: [algorithm] ### JS 的单调队列实现 -```JavaScript +```js class MonoQueue { - constructor() { - this.queue = [] - } - // 重点就是在入队时保证单调性,与单调栈一样,挤压式入队~ - push(num) { - const q = this.queue - while(q.length && q[q.length - 1] < num) { - q.pop() + constructor() { + this.queue = [] + } + // 重点就是在入队时保证单调性,与单调栈一样,挤压式入队~ + push(num) { + const q = this.queue + while (q.length && q[q.length - 1] < num) { + q.pop() + } + q.push(num) + } + pop(num) { + const q = this.queue + if (num === q[q.length - 1]) this.queue.shift() + } + peek() { + return this.queue[0] } - q.push(num) - } - pop(num) { - const q = this.queue - if(num === q[q.length - 1]) this.queue.shift() - } - peek() { - return this.queue[0] - } } ``` @@ -38,50 +38,50 @@ class MonoQueue { 滑动窗口嘛,完美符合一边进,一边出的条件,所以用单调队列来实现这个滑动窗口再好不过了。 -```JavaScript +```js /** * @param {number[]} nums * @param {number} k * @return {number[]} */ -var maxSlidingWindow = function(nums, k) { +var maxSlidingWindow = function (nums, k) { // 单调队列window, 当这个窗口内保持单调递减, // 也就是每次新进数据如果够大就把阻拦在其之前的数据拍掉,与单调栈类似 const monoWindow = [] const res = [] - for(let i = 0; i < nums.length; ++i) { - while(monoWindow.length && nums[i] >= nums[monoWindow[monoWindow.length - 1]]) { + for (let i = 0; i < nums.length; ++i) { + while (monoWindow.length && nums[i] >= nums[monoWindow[monoWindow.length - 1]]) { monoWindow.pop() } monoWindow.push(i) // 存的是索引 // 根据区间 [l..r] 的长度 与 k 的关系来判断是否需要退出队首 - if(i - monoWindow[0] + 1 > k) monoWindow.shift() + if (i - monoWindow[0] + 1 > k) monoWindow.shift() // r - l + 1 == k 所以 l = r - k + 1 保证有意义 - if(i - k + 1 >= 0) { + if (i - k + 1 >= 0) { res[i - k + 1] = nums[monoWindow[0]] } } return res -}; +} ``` 当然啦,如果直接用自己实现的单调队列数据结构来处理的话,就更好理解了。 -```JavaScript -var maxSlidingWindow = function(nums, k) { +```js +var maxSlidingWindow = function (nums, k) { const res = [] const monoQueue = new MonoQueue() // 见上方 - for(let i = 0; i < nums.length; ++i) { - if(i < k - 1) { + for (let i = 0; i < nums.length; ++i) { + if (i < k - 1) { monoQueue.push(nums[i]) - }else { + } else { monoQueue.push(nums[i]) res.push(monoQueue.peek()) monoQueue.pop(nums[i - k + 1]) } } return res -}; +} ``` ### [剑指 offer 59 - II. 队列的最大值](https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/) @@ -90,38 +90,38 @@ var maxSlidingWindow = function(nums, k) { 真相只有一个:双队列,用一个普通队列来记录所有元素,另一个队列作为单调队列即可。 -```JavaScript -var MaxQueue = function() { +```js +var MaxQueue = function () { this.queue = [] // 借助它来帮助 monoQueue 的 pop 实现 this.monoQueue = [] -}; +} /** * @return {number} */ -MaxQueue.prototype.max_value = function() { +MaxQueue.prototype.max_value = function () { return this.monoQueue.length ? this.monoQueue[0] : -1 -}; +} /** * @param {number} value * @return {void} */ -MaxQueue.prototype.push_back = function(value) { +MaxQueue.prototype.push_back = function (value) { this.queue.push(value) - while(this.monoQueue.length && value > this.monoQueue[this.monoQueue.length - 1]) { + while (this.monoQueue.length && value > this.monoQueue[this.monoQueue.length - 1]) { this.monoQueue.pop() } this.monoQueue.push(value) -}; +} /** * @return {number} */ -MaxQueue.prototype.pop_front = function() { - if (!this.queue.length) return -1; // 题目要求 与单调队列核心无关 +MaxQueue.prototype.pop_front = function () { + if (!this.queue.length) return -1 // 题目要求 与单调队列核心无关 const shiftEl = this.queue.shift() - if(shiftEl === this.monoQueue[0]) this.monoQueue.shift() + if (shiftEl === this.monoQueue[0]) this.monoQueue.shift() return shiftEl // 题目要求 与单调队列核心无关 -}; +} ``` diff --git "a/content/posts/algorithm/data structure/\346\225\260\347\273\204-\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204.md" "b/content/posts/algorithm/data structure/\346\225\260\347\273\204-\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204.md" index 6462950..e08aa02 100644 --- "a/content/posts/algorithm/data structure/\346\225\260\347\273\204-\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204.md" +++ "b/content/posts/algorithm/data structure/\346\225\260\347\273\204-\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204.md" @@ -13,39 +13,38 @@ categories: [algorithm] 核心思路:开辟新数组 `preSum[i]` 来存储原数组 `nums[0..i-1] `的累加和,`preSum[0] = 0`。这样,当求原数组区间和就比较容易了,区间 `[i..j]` 的和等于 `preSum[j+1] - preSum[i]` 的结果值。 -```JavaScript +```js const preSum = [0] // 一般可使用虚拟 0 节点,来避免边界条件 -for(let i = 0; i < arr.length; ++i) { - preSum[i + 1] = preSum[i] + nums[i] // 构建前缀和数组 +for (let i = 0; i < arr.length; ++i) { + preSum[i + 1] = preSum[i] + nums[i] // 构建前缀和数组 } - // 查询 sum([i..j]) preSum[j + 1] - preSum[i] ``` ### 构造:[lc.303 区域和检索-数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) -```JavaScript +```js /** * @param {number[]} nums */ -var NumArray = function(nums) { +var NumArray = function (nums) { this.preSum = [0] // preSum 首位为0 便于计算 - for(let i = 1; i <= nums.length; ++i) { - this.preSum[i] = this.preSum[i - 1] + nums[i -1] + for (let i = 1; i <= nums.length; ++i) { + this.preSum[i] = this.preSum[i - 1] + nums[i - 1] } -}; +} /** * @param {number} left * @param {number} right * @return {number} */ -NumArray.prototype.sumRange = function(left, right) { +NumArray.prototype.sumRange = function (left, right) { return this.preSum[right + 1] - this.preSum[left] -}; +} /** * Your NumArray object will be instantiated and called as such: @@ -61,16 +60,16 @@ NumArray.prototype.sumRange = function(left, right) { * @param {number[][]} matrix */ var NumMatrix = function (matrix) { - const row = matrix.length - const col = matrix[0].length - this.sums = Array.from(Array(row + 1), () => Array(col + 1).fill(0)) - for (let i = 0; i < row; ++i) { - for (let j = 0; j < col; ++j) { - this.sums[i + 1][j + 1] = - this.sums[i + 1][j] + this.sums[i][j + 1] - this.sums[i][j] + matrix[i][j] + const row = matrix.length + const col = matrix[0].length + this.sums = Array.from(Array(row + 1), () => Array(col + 1).fill(0)) + for (let i = 0; i < row; ++i) { + for (let j = 0; j < col; ++j) { + this.sums[i + 1][j + 1] = + this.sums[i + 1][j] + this.sums[i][j + 1] - this.sums[i][j] + matrix[i][j] + } } - } - console.log(this.sums) + console.log(this.sums) } /** @@ -81,12 +80,12 @@ var NumMatrix = function (matrix) { * @return {number} */ NumMatrix.prototype.sumRegion = function (row1, col1, row2, col2) { - return ( - this.sums[row2 + 1][col2 + 1] - - this.sums[row1][col2 + 1] - - this.sums[row2 + 1][col1] + - this.sums[row1][col1] - ) + return ( + this.sums[row2 + 1][col2 + 1] - + this.sums[row1][col2 + 1] - + this.sums[row2 + 1][col1] + + this.sums[row1][col1] + ) } /** @@ -106,22 +105,22 @@ NumMatrix.prototype.sumRegion = function (row1, col1, row2, col2) { ```js var checkSubarraySum = function (nums, k) { - if (nums.length <= 1) return false - const map = { 0: -1 } - let preSum = 0 - for (let i = 0; i < nums.length; ++i) { - preSum += nums[i] - let remainder = preSum % k - if (map[remainder] >= -1) { - // 左开右闭区 - if (i - map[remainder] >= 2) { - return true - } - } else { - map[remainder] = i + if (nums.length <= 1) return false + const map = { 0: -1 } + let preSum = 0 + for (let i = 0; i < nums.length; ++i) { + preSum += nums[i] + let remainder = preSum % k + if (map[remainder] >= -1) { + // 左开右闭区 + if (i - map[remainder] >= 2) { + return true + } + } else { + map[remainder] = i + } } - } - return false + return false } ``` @@ -134,22 +133,22 @@ var checkSubarraySum = function (nums, k) { * @return {boolean} */ var checkSubarraySum = function (nums, k) { - if (nums.length <= 1) return false - // 注意这里:初始 key 为 0,value 为 -1,是为了计算第一个可以整除 k 的子数组长度 - const map = { 0: -1 } - let remainder = 0 - for (let i = 0; i < nums.length; ++i) { - remainder = (remainder + nums[i]) % k - if (map[remainder] >= -1) { - if (i - map[remainder] >= 2) { - // 左开右闭区间 - return true - } - } else { - map[remainder] = i + if (nums.length <= 1) return false + // 注意这里:初始 key 为 0,value 为 -1,是为了计算第一个可以整除 k 的子数组长度 + const map = { 0: -1 } + let remainder = 0 + for (let i = 0; i < nums.length; ++i) { + remainder = (remainder + nums[i]) % k + if (map[remainder] >= -1) { + if (i - map[remainder] >= 2) { + // 左开右闭区间 + return true + } + } else { + map[remainder] = i + } } - } - return false + return false } ``` @@ -163,21 +162,21 @@ var checkSubarraySum = function (nums, k) { * @return {number} */ var findMaxLength = function (nums) { - if (nums.length <= 1) return 0 - let max = 0 - const map = { 0: -1 } - let counter = 0 - for (let i = 0; i < nums.length; ++i) { - nums[i] === 1 ? ++counter : --counter - // 哈希表中就有记录,表明此刻 区间前缀和之差 中 0 和 1 的数量相等 - // 举个例子 [1,1,1,0(-1)] 对应的前缀和为 1,2,3,2, 那么 (1, 3] 区间 1 个 0 和 1 个 1,长度为 3-1=2 - if (map[counter] >= -1) { - max = Math.max(max, i - map[counter]) - } else { - map[counter] = i + if (nums.length <= 1) return 0 + let max = 0 + const map = { 0: -1 } + let counter = 0 + for (let i = 0; i < nums.length; ++i) { + nums[i] === 1 ? ++counter : --counter + // 哈希表中就有记录,表明此刻 区间前缀和之差 中 0 和 1 的数量相等 + // 举个例子 [1,1,1,0(-1)] 对应的前缀和为 1,2,3,2, 那么 (1, 3] 区间 1 个 0 和 1 个 1,长度为 3-1=2 + if (map[counter] >= -1) { + max = Math.max(max, i - map[counter]) + } else { + map[counter] = i + } } - } - return max + return max } ``` @@ -191,33 +190,37 @@ var findMaxLength = function (nums) { 例如 w=[3,1,2,4]时,权重之和 total=10,那么我们按照 [1,3],[4,4],[5,6],[7,10]对 [1,10] 进行划分,使得它们的长度恰好依次为 3,1,2,4。 -```JavaScript +```js /** * @param {number[]} w */ var Solution = function (w) { - this.preSum = [0] - for(let i = 0; i < w.length; ++i) { - this.preSum[i + 1] = this.preSum[i] + w[i] - } + this.preSum = [0] + for (let i = 0; i < w.length; ++i) { + this.preSum[i + 1] = this.preSum[i] + w[i] + } } /** * @return {number} */ -Solution.prototype.pickIndex = function() { +Solution.prototype.pickIndex = function () { // 随机数 randomX 应该落在 pre[i] >= randomX >= pre[i] - w[i] + 1 - const randomX = Math.random() * (this.preSum[this.preSum.length - 1]) + 1 | 0 + const randomX = (Math.random() * this.preSum[this.preSum.length - 1] + 1) | 0 // 又因为 pre[i] 是单调递增的,那么 pre[i] >= randomX 转化为了一个二分搜索左边界的问题了 - const binarySearchlow = (x) => { - let low = 1, high = this.preSum.length - while(low < high) { - const mid = low + (high - low >> 1) - if(this.preSum[mid] < x) { // target > mid 接着去搜索右边,low 进化 + const binarySearchlow = x => { + let low = 1, + high = this.preSum.length + while (low < high) { + const mid = low + ((high - low) >> 1) + if (this.preSum[mid] < x) { + // target > mid 接着去搜索右边,low 进化 low = mid + 1 - }else if(this.preSum[mid] > x){ // target < mid 接着去搜索左边,high 进化 + } else if (this.preSum[mid] > x) { + // target < mid 接着去搜索左边,high 进化 high = mid - }else if(this.preSum[mid] === x) { // target === mid, 因为是搜索左边界,所以排除 mid, high = mid (牢记可行解区间为 [low...high)) + } else if (this.preSum[mid] === x) { + // target === mid, 因为是搜索左边界,所以排除 mid, high = mid (牢记可行解区间为 [low...high)) high = mid } } @@ -225,31 +228,31 @@ Solution.prototype.pickIndex = function() { } return binarySearchlow(randomX) - 1 // 我们定义的前缀和的索引比原数组索引大 1,所以要减去 1,按照官解定义的前缀和就不需要了 -}; +} ``` ### [lc.560 和为 k 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) -```JavaScript +```js /** * @param {number[]} nums * @param {number} k * @return {number} */ -var subarraySum = function(nums, k) { +var subarraySum = function (nums, k) { const preSum = [0] - for(let i = 0; i < nums.length; ++i) { + for (let i = 0; i < nums.length; ++i) { preSum[i + 1] = preSum[i] + nums[i] } let count = 0 // 遍历出所有区间 - for(let i = 0; i < nums.length; ++i) { - for(let j = i; j < nums.length; ++j) { - preSum[j+1] - preSum[i] === k && ++count + for (let i = 0; i < nums.length; ++i) { + for (let j = i; j < nums.length; ++j) { + preSum[j + 1] - preSum[i] === k && ++count } } return count -}; +} ``` 这样做能得到正确结果,但是并不能 AC,时间复杂度 O(n^2),不知道谁搞了个恶心的测试用例。。。会超时~ @@ -258,23 +261,23 @@ var subarraySum = function(nums, k) { ```js var subarraySum = function (nums, k) { - const map = { 0: 1 } - let preSum = 0 - let res = 0 - for (const num of nums) { - preSum += num - if (map[preSum - k]) { - res += map[preSum - k] - } + const map = { 0: 1 } + let preSum = 0 + let res = 0 + for (const num of nums) { + preSum += num + if (map[preSum - k]) { + res += map[preSum - k] + } - if (map[preSum]) { - map[preSum]++ - } else { - map[preSum] = 1 + if (map[preSum]) { + map[preSum]++ + } else { + map[preSum] = 1 + } } - } - return res + return res } ``` @@ -286,18 +289,18 @@ var subarraySum = function (nums, k) { * @return {number} */ var pivotIndex = function (nums) { - const preSum = [0] - for (let i = 0; i < nums.length; ++i) { - preSum[i + 1] = preSum[i] + nums[i] - } - console.log(preSum) - for (let i = 1; i < preSum.length; ++i) { - // 根据题意很容易写出来 - if (preSum[i - 1] === preSum[preSum.length - 1] - preSum[i]) { - return i - 1 // 如果使用了 0 虚拟节点,那么前缀和的索引 == 原数组的的索引 + 1 的 + const preSum = [0] + for (let i = 0; i < nums.length; ++i) { + preSum[i + 1] = preSum[i] + nums[i] } - } - return -1 + console.log(preSum) + for (let i = 1; i < preSum.length; ++i) { + // 根据题意很容易写出来 + if (preSum[i - 1] === preSum[preSum.length - 1] - preSum[i]) { + return i - 1 // 如果使用了 0 虚拟节点,那么前缀和的索引 == 原数组的的索引 + 1 的 + } + } + return -1 } ``` @@ -313,25 +316,25 @@ var pivotIndex = function (nums) { ```js var maxSubarraySumCircular = function (nums) { - const n = nums.length - const queue = [] - let pre = nums[0], - res = nums[0] - queue.push([0, pre]) // 单调队列保存 [index, preSum] - for (let i = 1; i < 2 * n; i++) { - // 根据索引控制 窗口大小 - while (queue.length !== 0 && i - queue[0][0] > n) { - queue.shift() - } - pre += nums[i % n] - res = Math.max(res, pre - queue[0][1]) // 求当前窗口内的 最大子数组和, 那么单调队列顶部应该是越小越好 - // 所以当新的前缀和小于等于单调队列里的前缀和时,直接“压扁” -- 即单调队列尾部 pop,并 push 新的 preSum - while (queue.length !== 0 && queue[queue.length - 1][1] >= pre) { - queue.pop() + const n = nums.length + const queue = [] + let preSum = nums[0], + res = nums[0] + queue.push([0, preSum]) // 单调队列保存 [index, preSum] + for (let i = 1; i < 2 * n; i++) { + // 根据索引控制 窗口大小 + while (queue.length !== 0 && i - queue[0][0] > n) { + queue.shift() + } + preSum += nums[i % n] + res = Math.max(res, preSum - queue[0][1]) // 求当前窗口内的 最大子数组和, 那么单调队列顶部应该是越小越好 + // 所以当新的前缀和小于等于单调队列里的前缀和时,直接“压扁” -- 即单调队列尾部 pop,并 push 新的 preSum + while (queue.length !== 0 && queue[queue.length - 1][1] >= preSum) { + queue.pop() + } + queue.push([i, preSum]) } - queue.push([i, pre]) - } - return res + return res } ``` @@ -339,10 +342,10 @@ var maxSubarraySumCircular = function (nums) { 解题思路:分两种情况,一种为没有跨越边界的情况,一种为跨越边界的情况 -- 没有跨越边界的情况直接求子数组的最大和即可; -- 跨越边界的情况可以对数组求和再减去无环的子数组的最小和,即可得到跨越边界情况下的子数组最大和; +- 没有跨越边界的情况直接求子数组的最大和即可; +- 跨越边界的情况可以对数组求和再减去无环的子数组的最小和,即可得到跨越边界情况下的子数组最大和; - 求以上两种情况的大值即为结果,另外需要考虑全部为负数的情况 + 求以上两种情况的大值即为结果,另外需要考虑全部为负数的情况 ```js /** @@ -350,25 +353,25 @@ var maxSubarraySumCircular = function (nums) { * @return {number} */ var maxSubarraySumCircular = function (nums) { - // 1. 没有跨边界,直接求 子数组的最大和 - // 2. 跨了边界,等价与求 最大(前缀和 - 子数组的最小和) - // 两种情况取最大那个即可 - let preSum = nums[0] - let preMax = nums[0] - let preMin = nums[0] - let resMax = nums[0] - let resMin = nums[0] - - for (let i = 1; i < nums.length; ++i) { - preSum += nums[i] - preMax = Math.max(preMax + nums[i], nums[i]) - resMax = Math.max(resMax, preMax) - preMin = Math.min(preMin + nums[i], nums[i]) - resMin = Math.min(resMin, preMin) - } - // 最大都小于 0 了,意味着数组中所有元素都小于 0 - if (resMax < 0) return resMax // 考虑全部为负数的情况 - return Math.max(resMax, preSum - resMin) + // 1. 没有跨边界,直接求 子数组的最大和 + // 2. 跨了边界,等价与求 最大(前缀和 - 子数组的最小和) + // 两种情况取最大那个即可 + let preSum = nums[0] + let preMax = nums[0] + let preMin = nums[0] + let resMax = nums[0] + let resMin = nums[0] + + for (let i = 1; i < nums.length; ++i) { + preSum += nums[i] + preMax = Math.max(preMax + nums[i], nums[i]) + resMax = Math.max(resMax, preMax) + preMin = Math.min(preMin + nums[i], nums[i]) + resMin = Math.min(resMin, preMin) + } + // 最大都小于 0 了,意味着数组中所有元素都小于 0 + if (resMax < 0) return resMax // 考虑全部为负数的情况 + return Math.max(resMax, preSum - resMin) } ``` @@ -383,22 +386,22 @@ var maxSubarraySumCircular = function (nums) { * @return {number} */ var subarraysDivByK = function (nums, k) { - let res = 0 - let remainder = 0 - const map = { 0: 1 } // 存储 { %k : count } 这里求数量,则初始化为 1,之前有题是求距离,初始化为了 -1 - for (let i = 0; i < nums.length; ++i) { - // 当有负数时,js 语言的取模和数学上的取模是不一样的,所以为了修正这种逻辑,先 +个 k 再去模即可 - remainder = (((remainder + nums[i]) % k) + k) % k - // if(remainder < 0) remainder += k // 评论里看到也可以这样修正 - - if (map[remainder]) { - res += map[remainder] - map[remainder]++ - } else { - map[remainder] = 1 + let res = 0 + let remainder = 0 + const map = { 0: 1 } // 存储 { %k : count } 这里求数量,则初始化为 1,之前有题是求距离,初始化为了 -1 + for (let i = 0; i < nums.length; ++i) { + // 当有负数时,js 语言的取模和数学上的取模是不一样的,所以为了修正这种逻辑,先 +个 k 再去模即可 + remainder = (((remainder + nums[i]) % k) + k) % k + // if(remainder < 0) remainder += k // 评论里看到也可以这样修正 + + if (map[remainder]) { + res += map[remainder] + map[remainder]++ + } else { + map[remainder] = 1 + } } - } - return res + return res } ``` @@ -416,22 +419,22 @@ var subarraysDivByK = function (nums, k) { * @return {number[]} */ var productExceptSelf = function (nums) { - const n = nums.length - const front = new Array(n) - const end = new Array(n) - front[0] = 1 - end[n - 1] = 1 - for (let i = 1; i < n; i++) { - front[i] = front[i - 1] * nums[i - 1] - } - for (let i = n - 2; i >= 0; i--) { - end[i] = end[i + 1] * nums[i + 1] - } - const res = [] - for (let i = 0; i < n; i++) { - res[i] = front[i] * end[i] - } - return res + const n = nums.length + const front = new Array(n) + const end = new Array(n) + front[0] = 1 + end[n - 1] = 1 + for (let i = 1; i < n; i++) { + front[i] = front[i - 1] * nums[i - 1] + } + for (let i = n - 2; i >= 0; i--) { + end[i] = end[i + 1] * nums[i + 1] + } + const res = [] + for (let i = 0; i < n; i++) { + res[i] = front[i] * end[i] + } + return res } ``` @@ -439,23 +442,23 @@ var productExceptSelf = function (nums) { ```js var productExceptSelf = function (nums) { - // 优化 动态构造前缀和后缀 一次遍历, 让返回数组自身来承载 - const res = new Array(nums.length).fill(1) - // 求出左侧所有乘积 - for (let i = 1; i < nums.length; i++) { - res[i] = nums[i - 1] * res[i - 1] - } - // 右侧的乘积需要动态的求出, 倒叙遍历 - let r = 1 - for (let i = nums.length - 1; i >= 0; --i) { - res[i] = res[i] * r - r *= nums[i] - } - return res + // 优化 动态构造前缀和后缀 一次遍历, 让返回数组自身来承载 + const res = new Array(nums.length).fill(1) + // 求出左侧所有乘积 + for (let i = 1; i < nums.length; i++) { + res[i] = nums[i - 1] * res[i - 1] + } + // 右侧的乘积需要动态的求出, 倒叙遍历 + let r = 1 + for (let i = nums.length - 1; i >= 0; --i) { + res[i] = res[i] * r + r *= nums[i] + } + return res } ``` -> lc.327 lc.862 两道 hard 题,后续有时间再看看 😁 +> lc.327 lc.862 (也有用到滑动窗口) 两道 hard 题,后续有时间再看看 😁 ### 同余定理 diff --git "a/content/posts/algorithm/data structure/\346\225\260\347\273\204-\345\267\256\345\210\206\346\225\260\347\273\204.md" "b/content/posts/algorithm/data structure/\346\225\260\347\273\204-\345\267\256\345\210\206\346\225\260\347\273\204.md" index cbae309..084ba79 100644 --- "a/content/posts/algorithm/data structure/\346\225\260\347\273\204-\345\267\256\345\210\206\346\225\260\347\273\204.md" +++ "b/content/posts/algorithm/data structure/\346\225\260\347\273\204-\345\267\256\345\210\206\346\225\260\347\273\204.md" @@ -12,26 +12,26 @@ categories: [algorithm] ## 概念 -```JavaScript +```js const diff = [nums[0]] -for(let i = 1; i < arr.length; ++i) { - diff[i] = nums[i] - nums[i - 1] // 构建差分数组 +for (let i = 1; i < arr.length; ++i) { + diff[i] = nums[i] - nums[i - 1] // 构建差分数组 } ``` 对区间 `[i..j]` 进行加减 val 操作只需要对差分数组 **`diff[i] += val`, `diff[j+1] -= val`** 进行更新,然后依据更新后的差分数组还原出最终数组即可: -```JavaScript +```js nums[0] = diff[0] -for(let i = 1; i < diff[i]; ++i) { - nums[i] = diff[i] + nums[i - 1] +for (let i = 1; i < diff[i]; ++i) { + nums[i] = diff[i] + nums[i - 1] } ``` 原理也很简单: -- `diff[i] += val`,等于对 `[i...]` 之后的所有元素都加了 val -- `diff[j+1] -=val`,等于对 `[j+1...]` 之后的所有元素都减了 val +- `diff[i] += val`,等于对 `[i...]` 之后的所有元素都加了 val +- `diff[j+1] -=val`,等于对 `[j+1...]` 之后的所有元素都减了 val 这样就使用了常数级的时间对区间 `[i..j]` 内的元素进行了修改,最后一次性还原即可。 @@ -55,73 +55,72 @@ for(let i = 1; i < diff[i]; ++i) { * @return {number[]} */ var getModifiedArray = function (length, updates) { - if (updates.length < 0) return [] - // 构建 - const diff = new Array(length).fill(0) - for (let i = 0; i < updates.length; ++i) { - const step = updates[i] - const [start, end, num] = step - diff[start] += num - end + 1 < length && (diff[end + 1] -= num) - } - // 还原 - const res = [] - res[0] = diff[0] - for (let i = 1; i < length; ++i) { - res[i] = res[i - 1] + diff[i] - } - return res + if (updates.length < 0) return [] + // 构建 + const diff = new Array(length).fill(0) + for (let i = 0; i < updates.length; ++i) { + const step = updates[i] + const [start, end, num] = step + diff[start] += num + end + 1 < length && (diff[end + 1] -= num) + } + // 还原 + const res = [] + res[0] = diff[0] + for (let i = 1; i < length; ++i) { + res[i] = res[i - 1] + diff[i] + } + return res } ``` -### [1094.拼车](https://leetcode.cn/problems/car-pooling/) +### lc.1094 拼车 -```JavaScript +```js /** * @param {number[][]} trips * @param {number} capacity * @return {boolean} */ -var carPooling = function(trips, capacity) { +var carPooling = function (trips, capacity) { // 1. 因为初始都为 0 所以差分数组也都为 0 // 2. 初始化差分数组的容量时,根据题意来即可,不用遍历 const diff = Array(1001).fill(0) - for(const [people, from, to] of trips) { + for (const [people, from, to] of trips) { diff[from] += people diff[to] -= people // 根据题意,乘客在车上的区间是 [form..to - 1],即需要变动的区间 } - if(diff[0] > capacity) return false + if (diff[0] > capacity) return false let arr = [diff[0]] - for(let i = 1; i < diff.length; ++i){ + for (let i = 1; i < diff.length; ++i) { arr[i] = arr[i - 1] + diff[i] - if(arr[i] > capacity) return false + if (arr[i] > capacity) return false } return true -}; +} ``` -### [1109.航班预定统计](https://leetcode.cn/problems/corporate-flight-bookings/) +### lc.1109 航班预定统计 -```JavaScript +```js /** * @param {number[][]} bookings * @param {number} n * @return {number[]} */ -var corpFlightBookings = function(bookings, n) { +var corpFlightBookings = function (bookings, n) { // 1. 初始化预定记录都为 0,所以差分数组也都为 0 // 2. 根据题意,需要变动的区间为 [first...last] const diff = Array(n + 1).fill(0) - for(const [from, to, seat] of bookings) { + for (const [from, to, seat] of bookings) { diff[from] += seat - console.log(diff, to + 1) - if(to + 1 < diff.length) diff[to + 1] -= seat // 确保 diff 的容量大小 不要越界影响后续还原时的计算 + if (to + 1 < diff.length) diff[to + 1] -= seat // 确保 diff 的容量大小 不要越界影响后续还原时的计算 } const ans = [diff[0]] - for(let i = 1; i < diff.length; ++i){ + for (let i = 1; i < diff.length; ++i) { ans[i] = ans[i - 1] + diff[i] } return ans.slice(1) -}; +} ``` diff --git "a/content/posts/algorithm/data structure/\351\223\276\350\241\250.md" "b/content/posts/algorithm/data structure/\351\223\276\350\241\250.md" index 833adec..b58ebd8 100644 --- "a/content/posts/algorithm/data structure/\351\223\276\350\241\250.md" +++ "b/content/posts/algorithm/data structure/\351\223\276\350\241\250.md" @@ -138,8 +138,8 @@ while (f != null && f.next != null) { 注意:因为 f 一次走两步,所以: -- 想要获取中点往前的节点,修改 f 初始节点时 f=head.next.next,两个 next 才会让结果往前偏移一步 -- 想要获取中点往后的节点,修改 s 的初始节点 s=head.next,一个 next 就可以让结果往后偏移一步 +- 想要获取中点往前的节点,修改 f 初始节点时 f=head.next.next,两个 next 才会让结果往前偏移一步 +- 想要获取中点往后的节点,修改 s 的初始节点 s=head.next,一个 next 就可以让结果往后偏移一步 ### 练习 @@ -151,33 +151,33 @@ while (f != null && f.next != null) { * @return {ListNode} */ var deleteDuplicates = function (head) { - if (!head) return null - let left = head, - right = head - while (right) { - if (right.val !== left.val) { - left.next = right - left = left.next + if (!head) return null + let left = head, + right = head + while (right) { + if (right.val !== left.val) { + left.next = right + left = left.next + } + right = right.next } - right = right.next - } - left.next = null - return head + left.next = null + return head } var deleteDuplicates = function (head) { - if (!head) return null - let left = head, - right = head - while (right) { - if (right.val !== left.val) { - left = right - } else { - left.next = right.next + if (!head) return null + let left = head, + right = head + while (right) { + if (right.val !== left.val) { + left = right + } else { + left.next = right.next + } + right = right.next } - right = right.next - } - return head + return head } ``` @@ -326,156 +326,156 @@ public Node copyRandomList(Node head) { #### 两个单链表相交系列问题 1. 单个单链表有环问题 - - 仅判断是否有环 lc.141 - ```java - public class Solution { - public boolean hasCycle(ListNode head) { - if (head == null || head.next == null) return false; - // 由于循环条件是slow != fast, 所以让初始化错开,当然也可以把while和if条件对换位置,稍作修改,就可以都设置为head了~自行尝试 - ListNode slow = head, fast = head.next; - while (slow != fast) { - if(fast == null || fast.next == null) return false; - slow = slow.next; - fast = fast.next.next; - } - return true; + - 仅判断是否有环 lc.141 + ```java + public class Solution { + public boolean hasCycle(ListNode head) { + if (head == null || head.next == null) return false; + // 由于循环条件是slow != fast, 所以让初始化错开,当然也可以把while和if条件对换位置,稍作修改,就可以都设置为head了~自行尝试 + ListNode slow = head, fast = head.next; + while (slow != fast) { + if(fast == null || fast.next == null) return false; + slow = slow.next; + fast = fast.next.next; + } + return true; + } } - } - ``` - - 返回环的起点 lc.142 - ```java - /** - * 快慢指针,第一次相遇后快指针回到头部和慢指针一起走,再次相遇就是环的起点, - * 这就是 Floyd’s cycle-finding 算法 - * - * 假设 head 到环起点的距离为 a,环起点到快慢指针相遇的距离为 b,环长度为 c ,则: - * - 慢指针走了 a + b, - * - 快指针走了 a + b + n*c, n 为圈数 - * 第一次相遇时,慢指针走 k,快指针走 2k,快比慢多走了 k 步,所以k是环的整数倍 - * - * 所以回到起点的指针要再走 k - m 步才到起点,而从第一次相遇点走到环起点的距离也 - * 恰好为 k - m - */ - public class Solution { - public ListNode detectCycle(ListNode head) { - if (head == null || head.next == null) return null; - ListNode slow = head, fast = head; - while (true) { - if (fast == null || fast.next == null) return null; - slow = slow.next; - fast = fast.next.next; - if (slow == fast) break; - } - fast = head; - while (slow != fast) { - slow = slow.next; - fast = fast.next; - } - return fast; - } - } - ``` - > 注意一个细节,这里寻找第一次相遇的节点,一定是两个节点一起从 head 出发的,而不能像上一题那样 slow 从 head 出发,fast 从 head.next 出发,这种只用来判断是否有环还是可以的。 + ``` + - 返回环的起点 lc.142 + ```java + /** + * 快慢指针,第一次相遇后快指针回到头部和慢指针一起走,再次相遇就是环的起点, + * 这就是 Floyd’s cycle-finding 算法 + * + * 假设 head 到环起点的距离为 a,环起点到快慢指针相遇的距离为 b,环长度为 c ,则: + * - 慢指针走了 a + b, + * - 快指针走了 a + b + n*c, n 为圈数 + * 第一次相遇时,慢指针走 k,快指针走 2k,快比慢多走了 k 步,所以k是环的整数倍 + * + * 所以回到起点的指针要再走 k - m 步才到起点,而从第一次相遇点走到环起点的距离也 + * 恰好为 k - m + */ + public class Solution { + public ListNode detectCycle(ListNode head) { + if (head == null || head.next == null) return null; + ListNode slow = head, fast = head; + while (true) { + if (fast == null || fast.next == null) return null; + slow = slow.next; + fast = fast.next.next; + if (slow == fast) break; + } + fast = head; + while (slow != fast) { + slow = slow.next; + fast = fast.next; + } + return fast; + } + } + ``` + > 注意一个细节,这里寻找第一次相遇的节点,一定是两个节点一起从 head 出发的,而不能像上一题那样 slow 从 head 出发,fast 从 head.next 出发,这种只用来判断是否有环还是可以的。 2. 两个无环单链表相交问题 - - 仅判断是否相交 - - 返回相交点 lc.160 - ```java - /** - * 判断是否相交很简单,两个单链走到头的指针如果一样,则说明有相交 - * - * 若要返回相交的点,如果两个链表一样长,则一起从头走到相等的位置即可, - * 不一样长,那就找到长的比短的长了多少,然后提前多走这几步即可。 - */ - public class Solution { - public ListNode getIntersectionNode(ListNode headA, ListNode headB) { - if (headA == null || headB == null) return null; - ListNode l = headA, s = headB; - int n = 0; - while (l != null) { - n++; - l = l.next; - } - while (s != null) { - n--; - s = s.next; - } - if (l != s) return null; - l = n > 0 ? headA : headB; - s = n > 0 ? headB : headA; - n = Math.abs(n); - while (n != 0) { - l = l.next; - n--; - } - while (l != s) { - l = l.next; - s = s.next; - } - return l; + - 仅判断是否相交 + - 返回相交点 lc.160 + ```java + /** + * 判断是否相交很简单,两个单链走到头的指针如果一样,则说明有相交 + * + * 若要返回相交的点,如果两个链表一样长,则一起从头走到相等的位置即可, + * 不一样长,那就找到长的比短的长了多少,然后提前多走这几步即可。 + */ + public class Solution { + public ListNode getIntersectionNode(ListNode headA, ListNode headB) { + if (headA == null || headB == null) return null; + ListNode l = headA, s = headB; + int n = 0; + while (l != null) { + n++; + l = l.next; + } + while (s != null) { + n--; + s = s.next; + } + if (l != s) return null; + l = n > 0 ? headA : headB; + s = n > 0 ? headB : headA; + n = Math.abs(n); + while (n != 0) { + l = l.next; + n--; + } + while (l != s) { + l = l.next; + s = s.next; + } + return l; + } } - } - /** - * 其实还有一个取巧的办法,当链表遍历完之后,指针分别去对方链表上继续走, - * 如果有相交节点,就停止遍历,否则最后都会走到 null,停止遍历。 - * 原理上还是抹平了长短链表的差距。 - */ - ``` + /** + * 其实还有一个取巧的办法,当链表遍历完之后,指针分别去对方链表上继续走, + * 如果有相交节点,就停止遍历,否则最后都会走到 null,停止遍历。 + * 原理上还是抹平了长短链表的差距。 + */ + ``` 3. 两个有环单链表相交问题 - - 返回相交点 - ```java - /** - * 有环单链表相交要分清楚情况: - * 1. 不相交 - * 2. 环的起始节点一样,则把这个起始节点看成两个无环链表的终点,利用上题的解法求出相交节点即可 - * 3. 环的起始节点不一样,则这两个节点都是相交节点,返回任意一个即可 - * - * 1 和 3 情况的区分是,一个环的节点继续走,走回自己之前能遇到另一个环的节点就是情况3,否则就是情况1 - */ - public ListNode bothLoop(ListNode head1, ListNode head2, ListNode loop1, ListNode loop2) { - /** - * 第一种情况,两个有环链表共用环起点, - * 那么此时可以把环的起点看成是head1和head2到的无环链表的终点, - * 也就转化成了寻找两个无环单链表相交点的问题了。 - */ - if (loop1 == loop2) { - ListNode p1 = head1, p2 = head2; - int n = 0; - while (p1 != loop1) { - n++; - p1 = p1.next; - } - while (p2 != loop2) { - n--; - p2 = p2.next; - } - p1 = n > 0 ? head1 : head2; - p2 = n > 0 ? head2 : head1; - n = Math.abs(n); - while (n > 0) { - p1 = p1.next; - n--; - } - while (p1 != p2) { - p1 = p1.next; - p2 = p2.next; - } - return p1; - } else { - /** - * 第二种情况不相交和第三种情况相交在环上不同的节点 - * 区分方式是让节点从 loop1 开始继续绕环走,能遇到 loop2 则说明相交了,否则为不相交 - */ - ListNode p = loop1.next; // 先前进一步,否则while进不去喽~ - while (p != loop1) { - if (p == loop2) { - return loop2; // loop1 loop2都是相交点,随意返回一个 - } - p = p.next; - } - return null; - } - } - ``` + - 返回相交点 + ```java + /** + * 有环单链表相交要分清楚情况: + * 1. 不相交 + * 2. 环的起始节点一样,则把这个起始节点看成两个无环链表的终点,利用上题的解法求出相交节点即可 + * 3. 环的起始节点不一样,则这两个节点都是相交节点,返回任意一个即可 + * + * 1 和 3 情况的区分是,一个环的节点继续走,走回自己之前能遇到另一个环的节点就是情况3,否则就是情况1 + */ + public ListNode bothLoop(ListNode head1, ListNode head2, ListNode loop1, ListNode loop2) { + /** + * 第一种情况,两个有环链表共用环起点, + * 那么此时可以把环的起点看成是head1和head2到的无环链表的终点, + * 也就转化成了寻找两个无环单链表相交点的问题了。 + */ + if (loop1 == loop2) { + ListNode p1 = head1, p2 = head2; + int n = 0; + while (p1 != loop1) { + n++; + p1 = p1.next; + } + while (p2 != loop2) { + n--; + p2 = p2.next; + } + p1 = n > 0 ? head1 : head2; + p2 = n > 0 ? head2 : head1; + n = Math.abs(n); + while (n > 0) { + p1 = p1.next; + n--; + } + while (p1 != p2) { + p1 = p1.next; + p2 = p2.next; + } + return p1; + } else { + /** + * 第二种情况不相交和第三种情况相交在环上不同的节点 + * 区分方式是让节点从 loop1 开始继续绕环走,能遇到 loop2 则说明相交了,否则为不相交 + */ + ListNode p = loop1.next; // 先前进一步,否则while进不去喽~ + while (p != loop1) { + if (p == loop2) { + return loop2; // loop1 loop2都是相交点,随意返回一个 + } + p = p.next; + } + return null; + } + } + ``` --- @@ -484,11 +484,11 @@ public Node copyRandomList(Node head) { 在 javascript 中的 Set 和 Map 咱就不说啥了,非正规军 😄。来看看 java 的: 1. 无序表 - - HashSet: add remove contains,对呀 c++ 中的 unordered_set - - HashMap: put remove containsKey,对应 c++ 中的 unordered_map + - HashSet: add remove contains,对呀 c++ 中的 unordered_set + - HashMap: put remove containsKey,对应 c++ 中的 unordered_map 2. 有序表 - - TreeSet:它是按升序对元素进行排序,对应 c++ 中的 ordered_set - - TreeMap:基于红黑树的 NavigableMap 实现,它根据其键的自然顺序排序,对应 c++ 中的 ordered_map + - TreeSet:它是按升序对元素进行排序,对应 c++ 中的 ordered_set + - TreeMap:基于红黑树的 NavigableMap 实现,它根据其键的自然顺序排序,对应 c++ 中的 ordered_map 记得之前学 java 的时候, HashSet 底层就是 HashMap,只不过没有 value 只有 key 罢了,而常规的 HashMap 的底层则是 链表,在 java 中,当链表的长度到达 8 时,就会自动扩容,链表也会重新分配,好像有红黑树什么的(不清楚,后序学习再回头补充),后面了解到哈希函数的时候,就更加清楚了,离散函数使得分配空间时基本是离散均匀的。 diff --git "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222.md" "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222.md" index 8c37d0a..a6e7f96 100644 --- "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222.md" +++ "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222.md" @@ -15,7 +15,7 @@ date: 2022-10-12T00:31:28+08:00 下方是 labuladong 大佬给出的动态规划的两种方式,重在递归、递推的思路,不是照搬模板哦~ -```JavaScript +```js // 思路1: 自顶向下的动态规划 (递归) function dp(staus1, ...other_staus) { for (const 选择 of 所有可能的选择) { @@ -56,7 +56,7 @@ for(const 状态1 of 状态1中的所有取值) { ### [70.爬楼梯](https://leetcode.cn/problems/climbing-stairs/) -```JavaScript +```js /** * @param {number} n * @return {number} @@ -80,7 +80,7 @@ var climbStairs = function(n) { ### [746.使用最小花费爬楼梯](https://leetcode.cn/problems/min-cost-climbing-stairs/) -```JavaScript +```js /** * @param {number[]} cost * @return {number} @@ -112,7 +112,7 @@ var minCostClimbingStairs = function(cost) { 我的感觉是(不一定对哈,欢迎留言讨论),题目出给了提示:只能向右或向下,这是在提示我们可以做「选择」,那么就可以联想到用动态规划去做了。👻 -```JavaScript +```js /** * @param {number} m * @param {number} n @@ -136,7 +136,7 @@ var uniquePaths = function(m, n) { ### [63.不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) -```JavaScript +```js /** * @param {number[][]} obstacleGrid * @return {number} @@ -169,7 +169,7 @@ var uniquePathsWithObstacles = function(obstacleGrid) { 这道题给出的提示是:最大乘积!求极值--这就是一种选择,可以 dfs 出所有的结果,那么动态规划就值得一试! -```JavaScript +```js /** * @param {number} n * @return {number} @@ -199,7 +199,7 @@ var integerBreak = function(n) { 这道题的重点是求不同种数数量,它不关心节点值是否相同,只关心数的骨架结构是否不一样,二叉树只有`/`和`\`这两种连接结构,所以用动态规划也能做到找出所有种类。(其实这个挺难想到的,我建议用下面的解法二更好) -```JavaScript +```js /** * @param {number} n * @return {number} @@ -222,7 +222,7 @@ var numTrees = function(n) { (推荐) 除了动规,这道题也可以用 dfs 来解决: -```JavaScript +```js /** * @param {number} n * @return {number} @@ -256,7 +256,7 @@ var numTrees = function(n) { 这道题不属于动规范围,呼应上一题的解法,所以写在此处。因为这里要给出最后的解,所以动态规划就不灵了,需要使用 dfs 的解法和备忘录技巧。 -```JavaScript +```js /** * Definition for a binary tree node. * function TreeNode(val, left, right) { @@ -303,7 +303,7 @@ var generateTrees = function(n) { 比如 198.打家劫舍 这一题,如果定义 dp[i] 为区间 [0...i] 一夜之间能偷取的最大金额,这么看也没有什么毛病,看代码: -```JavaScript +```js /** * @param {number[]} nums * @return {number} @@ -328,7 +328,7 @@ var rob = function(nums) { 很简单,其实类似于链表虚拟头结点,也是添加哨兵守卫似的技巧。`dp`,`dynamic programming`,其实也可以看成是 `dynamic process`,这个数组主要是描述过程变化的,需要一个初始头 dp[0],经过 nums[0] 产生了 dp[1],dp[1] 再经过 nums[1] 产生 dp[2],......,类似这样下去,最终 dp 是比 nums 多一位的,可以看成 nums 是 dp 中间插入的值。 所以可以产生如下定义:`dp[i] 表示区间 [0...i-1]` 能偷取到的最大值,代码如下: -```JavaScript +```js var rob = function(nums) { const n = nums.length // dp[i] 表示在nums区间 [0..i - 1] 能偷取的最大金额 diff --git "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\21301\350\203\214\345\214\205.md" "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\21301\350\203\214\345\214\205.md" index f258046..de27b69 100644 --- "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\21301\350\203\214\345\214\205.md" +++ "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\21301\350\203\214\345\214\205.md" @@ -27,7 +27,7 @@ date: 2022-10-12T03:31:28+08:00 - 当 j 为 0,即背包容量为 0,`dp[i][0] = 0` - 当 i 为 0,即物品个数为 0,`dp[0][j] = 0` -```JavaScript +```js /** * @desc 0-1 背包问题 * @param {Number[]} weight - 物品重量集合 @@ -65,7 +65,7 @@ console.log(zeroOne(weight, value, size)) // 8 其实这道题就是 01 背包问题,只不过没明说而已:对数组求和得 sum,是否存在子集和为 sum / 2?这不就是背包容量为 sum/2,从数组中选择元素且每个元素都只能使用一次来填充背包喽。 -```JavaScript +```js /** * @param {number[]} nums * @return {boolean} @@ -96,7 +96,7 @@ var canPartition = function(nums) { 不得不说,没有一定经验的人很难想得到这道题也能转为 01 背包问题 🙄。 其实就是:尽量分为两堆石头,这两个堆重量尽量靠近,也就是说让其中一个堆尽量靠近 `总重 / 2`。 -```JavaScript +```js /** * @param {number[]} stones * @return {number} @@ -126,7 +126,7 @@ var lastStoneWeightII = function(stones) { 这道题可以用回溯剪枝和动态规划 01 背包来解,先用动规来解决一下吧。 -```JavaScript +```js /** * @param {number[]} nums * @param {number} target diff --git "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\21302\345\256\214\345\205\250\350\203\214\345\214\205.md" "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\21302\345\256\214\345\205\250\350\203\214\345\214\205.md" index e9e2492..03c53b5 100644 --- "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\21302\345\256\214\345\205\250\350\203\214\345\214\205.md" +++ "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\21302\345\256\214\345\205\250\350\203\214\345\214\205.md" @@ -11,7 +11,7 @@ date: 2022-10-12T04:31:28+08:00 公式: -```JavaScript +```js // 求组合 for (int i = 0; i < coins.size(); i++) { // 遍历物品 for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量 @@ -29,7 +29,7 @@ for (int j = 0; j <= amount; j++) { // 遍历背包容量 为什么要固定这种顺序,很简单,因为先遍历被爆容量的花,会有产生重复的问题: -```JavaScript +```js 如果交换两层循环顺序的话会先遍历金额j后再遍历每个coin: f[i][j] = f[i][j-c[0]] + f[i][j-c[1]] +...+ f[i][j-c[i]] ① ①中,每一项表示在f[i][j-c[k]]基础上加上c[k]达成金额j的组合数,也即由前i个coin组成金额j且至少存在一个coin[k]的组合数。 @@ -61,7 +61,7 @@ f[i][j] = f[0][j-c[0]] + f[1][j-c[1]] +...+ f[i][j-c[i]] ② ### [518. 零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) -```JavaScript +```js /** * @param {number} amount * @param {number[]} coins @@ -90,7 +90,7 @@ var change = function(amount, coins) { 完全背包的一维: -```JavaScript +```js // 这道题是求组合 var change = function(amount, coins) { const m = coins.length @@ -109,7 +109,7 @@ var change = function(amount, coins) { 题目实际上求的是排列!一定要先遍历背包! -```JavaScript +```js /** * @param {number[]} nums * @param {number} target @@ -139,7 +139,7 @@ var combinationSum4 = function(nums, target) { 可以无限使用凑数这不就是完全背包问题吗?动态规划走起: -```JavaScript +```js /** * @param {number[]} coins * @param {number} amount @@ -164,7 +164,7 @@ var coinChange = function(coins, amount) { 动态规划 定义 dp[i] 表示数字 i 的完全平方数最少。 -```JavaScript +```js /** * @param {number} n * @return {number} @@ -185,7 +185,7 @@ var numSquares = function(n) { 没错,这也可以看成是完全背包~ -```JavaScript +```js // 完全背包 s为背包, 但是物品wordDict中的每个单词的使用有讲究,看下方重点 var wordBreak = function (s, wordDict) { // 定义dp[i] 表示s[0..i-1](前i个)可以用字典中的词组成 diff --git "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\344\271\260\345\215\226\350\202\241\347\245\250.md" "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\344\271\260\345\215\226\350\202\241\347\245\250.md" index 88adc2c..dda9eb2 100644 --- "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\344\271\260\345\215\226\350\202\241\347\245\250.md" +++ "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\344\271\260\345\215\226\350\202\241\347\245\250.md" @@ -7,7 +7,7 @@ date: 2022-10-12T09:31:28+08:00 这道题最直观的方法是,贪心算法 --- 此题中,只需要左边最小,右边最大,那么就是最大利润啦 🕶。 -```JavaScript +```js /** * @param {number[]} prices * @return {number} @@ -29,7 +29,7 @@ var maxProfit = function(prices) { - 状态 天数/持有状态(未持有/持有),所以需要一个二维 dp 数组,算出每个第 i 天持有或不持有状态下的最大收益 - 选择 买入/卖出/不操作 -```JavaScript +```js /** * @param {number[]} prices * @return {number} @@ -57,7 +57,7 @@ var maxProfit = function(prices) { 与上一题不同的地方只有一个,就是可以连续交易多次。状态与选择都不变,影响的只是买入的时候的状态变化。 -```JavaScript +```js /** * @param {number[]} prices * @return {number} @@ -82,7 +82,7 @@ var maxProfit = function(prices) { 选择从 「买入/卖出」 拓展为 「第一次买入/第一次卖出/第二次买入/第二次卖出」,直接影响到了持有状态,好,那么就来把状态机发扬光大吧! -```JavaScript +```js /** * @param {number[]} prices * @return {number} @@ -113,7 +113,7 @@ var maxProfit = function(prices) { 相对于 121,122 题等于是多了一个 「交易次数」 的状态,那么需要对每天的状态进行拓展。 -```JavaScript +```js /** * @param {number} k * @param {number[]} prices @@ -151,7 +151,7 @@ var maxProfit = function(k, prices) { 相比 121 题,多了个冷冻期的限制,转换过来就是每天的状况又多了一种 -- 冷冻期即卖出后第二天不能操作。 所以当天的状态就是 「持有/未持有(非冷冻期)/未持有(冷冻期)」,其实就是把状态机拓展了一位。 -```JavaScript +```js /** * @param {number[]} prices * @return {number} @@ -175,7 +175,7 @@ var maxProfit = function(prices) { ### [714.买卖股票的最佳时机含手续费](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) -```JavaScript +```js /** * @param {number[]} prices * @param {number} fee diff --git "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\255\220\345\272\217\345\210\227.md" "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\255\220\345\272\217\345\210\227.md" index 0e5ec12..8be264d 100644 --- "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\255\220\345\272\217\345\210\227.md" +++ "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\255\220\345\272\217\345\210\227.md" @@ -27,7 +27,7 @@ date: 2022-10-12T14:53:55+08:00 正确的做法是:选择和 i 之前的所有元素进行比较(dp[0..i-1]是已经求得的),然后不断更新 dp[i] 的最大长度即可。 -```JavaScript +```js /** * @param {number[]} nums * @return {number} @@ -52,7 +52,7 @@ var lengthOfLIS = function(nums) { 这道题就简单点了,就是上一题的错误思想了哈哈,就是只和前一个比,如果大,那就 +1,否则就重头记为 1。 -```JavaScript +```js /** * @param {number[]} nums * @return {number} @@ -76,7 +76,7 @@ var findLengthOfLCIS = function(nums) { - 状态 这里状态就是 A 数组的 [0..i] 和 B 数组的 [0..j],可以看出是 i、j 两个指针在数组上游走 - 选择 只有当 A[i] === B[j] 时,dp[i][j] = dp[i-1][j-1] + 1 -```JavaScript +```js /** * @param {number[]} nums1 * @param {number[]} nums2 @@ -119,7 +119,7 @@ var findLength = function(nums1, nums2) { 与上题类似,不同的就是从 「子数组」 变成了 「子序列」,也就是从连续变成了非连续。 -```JavaScript +```js /** * @param {string} text1 * @param {string} text2 @@ -153,7 +153,7 @@ var longestCommonSubsequence = function(text1, text2) { 换种 dp 定义,就可以减少边界条件的判断: -```JavaScript +```js /** * @param {string} text1 * @param {string} text2 @@ -197,7 +197,7 @@ var longestCommonSubsequence = function(text1, text2) { - 状态: - 选择:与之前的数组相加变成更大的或者自成一派 -```JavaScript +```js /** * @param {number[]} nums * @return {number} @@ -220,7 +220,7 @@ var maxSubArray = function(nums) { 1. 双指针法 -```JavaScript +```js /** * @param {string} s * @param {string} t @@ -244,7 +244,7 @@ var isSubsequence = function(s, t) { 这里主要就是转变一下思想:判断 A 是否是 B 的子序列 等价于 ==> A 和 B 的最长子序列长度是必须是 A 的长度。 -```JavaScript +```js /** * @param {string} s * @param {string} t @@ -280,7 +280,7 @@ var isSubsequence = function(s, t) { - A[i] != B[j] - dp[i][j] = dp[i-1][j] -```JavaScript +```js /** * @param {string} s * @param {string} t @@ -312,7 +312,7 @@ var numDistinct = function(s, t) { 转换思想:找到最长公共子序列,把剩下的删掉不就是我需要做的所有操作了嘛~ -```JavaScript +```js /** * @param {string} word1 * @param {string} word2 @@ -346,7 +346,7 @@ var minDistance = function(word1, word2) { - 状态: A 和 B 的子序列 [0..i], [0..j] 相同的最少操作数 - 选择: 对其中一个序列 比如 A,进行 插入、替换或者删除操作 -```JavaScript +```js /** * @param {string} word1 * @param {string} word2 diff --git "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\346\211\223\345\256\266\345\212\253\350\210\215.md" "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\346\211\223\345\256\266\345\212\253\350\210\215.md" index c47ec8c..2403c07 100644 --- "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\346\211\223\345\256\266\345\212\253\350\210\215.md" +++ "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\346\211\223\345\256\266\345\212\253\350\210\215.md" @@ -10,7 +10,7 @@ date: 2022-10-12T09:31:28+08:00 - 状态:持续影响偷 💰 结果的因素,是 --> 房屋索引区间 [0...i] 的变化。状态只有一个,所以一维 dp 数组即可 - 选择:到这个房屋偷/还是不偷 -```JavaScript +```js /** * @param {number[]} nums * @return {number} @@ -33,7 +33,7 @@ var rob = function(nums) { 与上题类似,唯一的不同是,房屋首尾相连了。 -```JavaScript +```js /** * @param {number[]} nums * @return {number} @@ -69,7 +69,7 @@ var rob = function(nums) { 其实无非就是从遍历数组变成了遍历树而已。 而动规中对于子树的遍历,因为需要推导,后序遍历可以有返回值,拿到子树的推导结果,所以一般的都是后续遍历。 -```JavaScript +```js /** * @param {TreeNode} root * @return {number} diff --git "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\347\212\266\346\200\201\345\216\213\347\274\251.md" "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\347\212\266\346\200\201\345\216\213\347\274\251.md" index 473f137..76eefb4 100644 --- "a/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\347\212\266\346\200\201\345\216\213\347\274\251.md" +++ "b/content/posts/algorithm/dp/\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\347\212\266\346\200\201\345\216\213\347\274\251.md" @@ -9,7 +9,7 @@ date: 2022-10-12T01:26:36+08:00 **对于状态压缩要有一副二维 table 的脑图,以及对遍历方向掌握到位。** -```JavaScript +```js for (int i = n - 2; i >= 0; i--) { for (int j = i + 1; j < n; j++) { // 状态转移方程 @@ -42,7 +42,7 @@ i 从下往上遍历, j 从左往右遍历,即 i 是控制遍历层数的,i+ - dp[j-1]: 因为 j 之前的已经被覆盖过了,所以代表 dp[i][j-1] - 现在就差一个 dp[i+1][j-1] 了,因为一维,它被 dp[i][j-1] 无情地覆盖掉了,可是按照原来的二维 dp 必须得知道这个值才行,咋办呢?其实可以很自然的想到,用个变量缓存住不就好了?Yes,you got it! -```JavaScript +```js for (int i = n - 2; i >= 0; i--) { // 每层循环都定义一个变量协助储存一维数组要求 j 时 j-1 位置被覆盖前的值 let pre = 0; @@ -75,7 +75,7 @@ for (int i = n - 2; i >= 0; i--) { 比如 1049 题的状态压缩: -```JavaScript +```js /** * @param {number[]} stones * @return {number} diff --git "a/content/posts/algorithm/graph/\346\213\223\346\211\221\346\216\222\345\272\217.md" "b/content/posts/algorithm/graph/\346\213\223\346\211\221\346\216\222\345\272\217.md" index 4daf24a..55cbe25 100644 --- "a/content/posts/algorithm/graph/\346\213\223\346\211\221\346\216\222\345\272\217.md" +++ "b/content/posts/algorithm/graph/\346\213\223\346\211\221\346\216\222\345\272\217.md" @@ -9,7 +9,7 @@ date: 2023-02-19T16:03:41+08:00 ![图基础](https://raw.githubusercontent.com/yokiizx/picgo/main/images/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-2022-07-12.png) -```JavaScript +```js /** 0 -> 1, 0 -> 2; 1 -> 3; 2 -> 4 这么一副图的具体表现 */ // 邻接表 graph = [[1,2], [3], [4]] @@ -28,7 +28,7 @@ graph = [ ### 图的遍历 -```JavaScript +```js // 记录被遍历过的节点, 有环时需要这个来辅助一下 const visited = [] // boolean[]; // 记录从起点到当前节点的路径 @@ -56,7 +56,7 @@ function traverse(graph, s) { ### [797. 所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/description/) -```JavaScript +```js /** * @param {number[][]} graph * @return {number[][]} @@ -105,7 +105,7 @@ var allPathsSourceTarget = function(graph) { DFS 版本环检测: -```JavaScript +```js /** * @param {number} numCourses * @param {number[][]} prerequisites @@ -147,7 +147,7 @@ var canFinish = function(numCourses, prerequisites) { BFS 环检测: -```JavaScript +```js var canFinish = function(numCourses, prerequisites) { // 构图, 同时构建 indegree 入度数组 const indegree = new Array(numCourses).fill(0) @@ -194,7 +194,7 @@ var canFinish = function(numCourses, prerequisites) { 就是在 DFS 环检测基础上的后序位置进行节点收集,此时是倒序的,再翻转一下即可。 当然也可以利用逆邻接表 `graph[to].push(from)`,这样就不用倒序了。 -```JavaScript +```js /** * @param {number} numCourses * @param {number[][]} prerequisites @@ -244,7 +244,7 @@ var findOrder = function(numCourses, prerequisites) { 基于 BFS 的环检测,出队的顺序就是拓扑排序的结果。 -```JavaScript +```js var findOrder = function(numCourses, prerequisites) { // 构图, 同时构建 indegree const indegree = new Array(numCourses).fill(0) diff --git a/content/posts/algorithm/trick/LRU.md b/content/posts/algorithm/trick/LRU.md index ac95c73..d0071ec 100644 --- a/content/posts/algorithm/trick/LRU.md +++ b/content/posts/algorithm/trick/LRU.md @@ -16,7 +16,7 @@ LRU(Least recently used,最近最少使用)。 一般使用双向链表可以实现,我们在 JavaScript 中使用 Map 这个数据结构来实现缓存,它可以保证加入缓存的先后顺序,(不过不同的是,这里是把 map 的尾当头,头当尾)。 -```JavaScript +```js class LRU { constructor(size) { this.cache = new Map(); @@ -50,7 +50,7 @@ class LRU { 双向链表版: -```JavaScript +```js /** 双向链表实现 */ function Node(key, value) { this.key = key @@ -100,11 +100,11 @@ LFU(Least Frequently Used,最少频繁使用)。 1. 需要一个 key, value 映射 2. 需要一个 key, freq 映射 3. 这个需求应该是 LFU 算法的核心,所以我们分开说。 - - 首先,肯定是需要 freq 到 key 的映射,用来找到 freq 最小的 key。 - - 将 freq 最小的 key 删除,那你就得快速得到当前所有 key 最小的 freq 是多少。想要时间复杂度 O(1) 的话,肯定不能遍历一遍去找,那就用一个变量 minFreq 来记录当前最小的 freq 吧。 - - 可能有多个 key 拥有相同的 freq,所以 freq 对 key 是一对多的关系,即一个 freq 对应一个 key 的列表。 - - 希望 freq 对应的 key 的列表是存在时序的,便于快速查找并删除最旧的 key。 - - 希望能够快速删除 key 列表中的任何一个 key,因为如果频次为 freq 的某个 key 被访问,那么它的频次就会变成 freq+1,就应该从 freq 对应的 key 列表中删除,加到 freq+1 对应的 key 的列表中。 + - 首先,肯定是需要 freq 到 key 的映射,用来找到 freq 最小的 key。 + - 将 freq 最小的 key 删除,那你就得快速得到当前所有 key 最小的 freq 是多少。想要时间复杂度 O(1) 的话,肯定不能遍历一遍去找,那就用一个变量 minFreq 来记录当前最小的 freq 吧。 + - 可能有多个 key 拥有相同的 freq,所以 freq 对 key 是一对多的关系,即一个 freq 对应一个 key 的列表。 + - 希望 freq 对应的 key 的列表是存在时序的,便于快速查找并删除最旧的 key。 + - 希望能够快速删除 key 列表中的任何一个 key,因为如果频次为 freq 的某个 key 被访问,那么它的频次就会变成 freq+1,就应该从 freq 对应的 key 列表中删除,加到 freq+1 对应的 key 的列表中。 > 小提示:LinkedHashSet > 这篇文章不错:https://halfrost.com/lru_lfu_interview/ diff --git "a/content/posts/algorithm/trick/\344\272\214\345\210\206\346\263\225.md" "b/content/posts/algorithm/trick/\344\272\214\345\210\206\346\263\225.md" index 3ef5e66..96cdab0 100644 --- "a/content/posts/algorithm/trick/\344\272\214\345\210\206\346\263\225.md" +++ "b/content/posts/algorithm/trick/\344\272\214\345\210\206\346\263\225.md" @@ -42,7 +42,7 @@ date: 2023-01-03T11:24:54+08:00 经典题,一眼过去,全是细节~ -```JavaScript +```js /** * @param {number[]} nums * @return {number} @@ -74,7 +74,7 @@ var findMin = function (nums) { 第二种做法:`while(left <= right)` -```JavaScript +```js /** * @param {number[]} nums * @return {number} @@ -102,7 +102,7 @@ var findMin = function (nums) { 这道题呢,也类似,只不过是存在重复的元素值,其实相比上一题无非就是多了一个 `nums[mid] === nums[right]` 的条件而已,上一题没有这个判断实际上是因为我们省略了,因为上一题中 nums[mid] 永远不可能等于 nums[right]。 -```JavaScript +```js /** * @param {number[]} nums * @return {number} @@ -130,7 +130,7 @@ var findMin = function(nums) { ### [33. 搜索渲染排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) -```JavaScript +```js /** * @param {number[]} nums * @param {number} target @@ -169,7 +169,7 @@ var search = function(nums, target) { ### [704.二分查找](https://leetcode.cn/problems/binary-search/) -```JavaScript +```js /** * @param {number[]} nums * @param {number} target @@ -223,7 +223,7 @@ var search = function(nums, target) { 这是一道简单题,但是二分法如果运用不熟练,那可真的不简单~ -```JavaScript +```js /** * @param {number[]} nums * @param {number} target @@ -271,7 +271,7 @@ var search = function(nums, target) { 几乎与上题一样,试试看~ -```JavaScript +```js /** * @param {number[]} nums * @param {number} target @@ -315,7 +315,7 @@ var searchRange = function(nums, target) { 简单题,不过注意下初始化 right。 -```JavaScript +```js /** * @param {number[]} nums * @param {number} target diff --git "a/content/posts/algorithm/trick/\344\275\215\350\277\220\347\256\227.md" "b/content/posts/algorithm/trick/\344\275\215\350\277\220\347\256\227.md" index 5035ed7..ff63740 100644 --- "a/content/posts/algorithm/trick/\344\275\215\350\277\220\347\256\227.md" +++ "b/content/posts/algorithm/trick/\344\275\215\350\277\220\347\256\227.md" @@ -10,71 +10,71 @@ tags: [JavaScript] ## 常用操作 -- `&` (有 0 为 0): 判断奇偶性: `4 & 1 === 0 ? 偶数 : 奇数` -- `|` (有 1 为 1): 向 0 取整: `4.9 | 0 === 4`; `-4.9 | 0 === -4` -- `~` (按位取反): 加 1 取反: `!~-1 === true`,只有-1 加 1 取反后为真值; - `~~x`也可以取整或把 Boolean 转为 0/1 -- `^` : 自己异或自己为 0: `A ^ B ^ A = B` 异或可以很方便的找出这个单个的数字(也叫无进位相加) -- `x >> n` :`x / 2^n`: 取中并取整 `x >> 1` -- `x << n` :`x * 2^n` -- `x >>> n`: 无符号右移,有个骚操作是在 splice 时,`x >>> 0`获取要删除的索引可以用这个来避免对 -1 的判断,因为 `-1 >>> 0` 符号位的 1 会让右移 0 位后变成了一个超大的数字 42 亿多... +- `&` (有 0 为 0): 判断奇偶性: `4 & 1 === 0 ? 偶数 : 奇数` +- `|` (有 1 为 1): 向 0 取整: `4.9 | 0 === 4`; `-4.9 | 0 === -4` +- `~` (按位取反): 加 1 取反: `!~-1 === true`,只有-1 加 1 取反后为真值; + `~~x`也可以取整或把 Boolean 转为 0/1 +- `^` : 自己异或自己为 0: `A ^ B ^ A = B` 异或可以很方便的找出这个单个的数字(也叫无进位相加) +- `x >> n` :`x / 2^n`: 取中并取整 `x >> 1` +- `x << n` :`x * 2^n` +- `x >>> n`: 无符号右移,有个骚操作是在 splice 时,`x >>> 0`获取要删除的索引可以用这个来避免对 -1 的判断,因为 `-1 >>> 0` 符号位的 1 会让右移 0 位后变成了一个超大的数字 42 亿多... ### 技巧 1. `n & (n-1)`:消除二进制中 n 的最后一位 1 - [191. 位 1 的个数](https://leetcode.cn/problems/number-of-1-bits/) - - ```js - /** - * @param {number} n - a positive integer - * @return {number} - */ - var hammingWeight = function (n) { - let res = 0 - while (n) { - n &= n - 1 - res++ - } - return res - } - ``` - - [231. 2 的幂](https://leetcode.cn/problems/power-of-two/) - - ```js - /** - * @param {number} n - * @return {boolean} - */ - var isPowerOfTwo = function (n) { - if (n <= 0) return false - // 2的n次方 它的二进制数一定只有一个1 去掉最后一个1应该为0 - return (n & (n - 1)) === 0 - } - ``` + [191. 位 1 的个数](https://leetcode.cn/problems/number-of-1-bits/) + + ```js + /** + * @param {number} n - a positive integer + * @return {number} + */ + var hammingWeight = function (n) { + let res = 0 + while (n) { + n &= n - 1 + res++ + } + return res + } + ``` + + [231. 2 的幂](https://leetcode.cn/problems/power-of-two/) + + ```js + /** + * @param {number} n + * @return {boolean} + */ + var isPowerOfTwo = function (n) { + if (n <= 0) return false + // 2的n次方 它的二进制数一定只有一个1 去掉最后一个1应该为0 + return (n & (n - 1)) === 0 + } + ``` 2. `n & (~n + 1)`,提取出最右边的 1,这个式子即:n 和它的补码与。等价于 `n & -n` 3. `A ^ A === 0`:找出未成对的那个数字 - [268.丢失的数字](https://leetcode.cn/problems/missing-number/submissions/) - - ```js - /** - * @param {number[]} nums - * @return {number} - */ - var missingNumber = function (nums) { - const n = nums.length - let res = 0 - for (let i = 0; i < n; ++i) { - res ^= nums[i] ^ i - } - res ^= n - return res - } - ``` + [268.丢失的数字](https://leetcode.cn/problems/missing-number/submissions/) + + ```js + /** + * @param {number[]} nums + * @return {number} + */ + var missingNumber = function (nums) { + const n = nums.length + let res = 0 + for (let i = 0; i < n; ++i) { + res ^= nums[i] ^ i + } + res ^= n + return res + } + ``` ## 补充:异或的运用 @@ -133,9 +133,9 @@ public static int[] findTwoOdd(int[] arr) { } ``` -- 取最右边为 1 的数:`n & -n` 或者 `n & (~n + 1)` -- 消灭最右边的 1:`n & (n - 1)`,可以用来计算整数的二进制 1 的个数 +- 取最右边为 1 的数:`n & -n` 或者 `n & (~n + 1)` +- 消灭最右边的 1:`n & (n - 1)`,可以用来计算整数的二进制 1 的个数 ## 其他位运算的技巧(待学习) -- [Bit Twiddling Hacks](http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel) +- [Bit Twiddling Hacks](http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel) diff --git "a/content/posts/algorithm/trick/\345\216\211\345\256\263\347\232\204KMP.md" "b/content/posts/algorithm/trick/\345\216\211\345\256\263\347\232\204KMP.md" index f03065e..ab313fd 100644 --- "a/content/posts/algorithm/trick/\345\216\211\345\256\263\347\232\204KMP.md" +++ "b/content/posts/algorithm/trick/\345\216\211\345\256\263\347\232\204KMP.md" @@ -63,7 +63,7 @@ c ### 试一试:[28. 找出字符串中第一个匹配项的下标](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/) -```JavaScript +```js var strStr = function (haystack, needle) { const m = haystack.length const n = needle.length diff --git "a/content/posts/algorithm/trick/\345\274\202\344\275\215\350\257\215.md" "b/content/posts/algorithm/trick/\345\274\202\344\275\215\350\257\215.md" index 3117c3b..8184576 100644 --- "a/content/posts/algorithm/trick/\345\274\202\344\275\215\350\257\215.md" +++ "b/content/posts/algorithm/trick/\345\274\202\344\275\215\350\257\215.md" @@ -11,7 +11,7 @@ date: 2022-10-11T21:42:41+08:00 使用计数的技巧会更加简单,因为异位词之间每个字母出现的频率一定是一样的,使用这个频率去做 Map 的键,就可以很容易找到它的异位词了。 -```JavaScript +```js /** * @param {string[]} strs * @return {string[][]} @@ -33,7 +33,7 @@ var groupAnagrams = function(strs) { ### [242.有效的字母异位词](https://leetcode.cn/problems/valid-anagram/) -```JavaScript +```js /** * @param {string} s * @param {string} t diff --git "a/content/posts/algorithm/trick/\345\277\205\346\216\214\346\217\241\347\232\204\346\216\222\345\272\217\347\256\227\346\263\225.md" "b/content/posts/algorithm/trick/\345\277\205\346\216\214\346\217\241\347\232\204\346\216\222\345\272\217\347\256\227\346\263\225.md" index d1d94a5..78183e9 100644 --- "a/content/posts/algorithm/trick/\345\277\205\346\216\214\346\217\241\347\232\204\346\216\222\345\272\217\347\256\227\346\263\225.md" +++ "b/content/posts/algorithm/trick/\345\277\205\346\216\214\346\217\241\347\232\204\346\216\222\345\272\217\347\256\227\346\263\225.md" @@ -520,7 +520,7 @@ function heapSort(arr) { 插入排序的升级版,也叫缩小增量排序,用 gap 分组,没每个组内进行插入排序,当 gap 为 1 时,就排好序了,相比插入排序多了设定 gap 这一层最外部 for 循环 -```JavaScript +```js function shellSort(nums) { for (let gap = arr.length >> 1; gap > 0; gap >>= 1) { // 多了设定gap增量这一层 for (let i = gap; i < arr.lenght; i++) { @@ -653,7 +653,7 @@ public static int maxBit(int[] arr) { /** * 取出数x进位d上的数字 * - * @param x; + * @param x * @param d * @return */ @@ -670,7 +670,7 @@ public static int getDigit(int x, int d) { 时间复杂度 `O(n)` -```JavaScript +```js function bucketSort(nums) { // 先确定桶的数量,要找出最大最小值,再根据 scope 求出桶数 const scope = 3 // 每个桶的存储的范围 diff --git "a/content/posts/algorithm/trick/\346\225\260\347\273\204-\345\217\214\346\214\207\351\222\210.md" "b/content/posts/algorithm/trick/\346\225\260\347\273\204-\345\217\214\346\214\207\351\222\210.md" index 1e77ef4..143ecd7 100644 --- "a/content/posts/algorithm/trick/\346\225\260\347\273\204-\345\217\214\346\214\207\351\222\210.md" +++ "b/content/posts/algorithm/trick/\346\225\260\347\273\204-\345\217\214\346\214\207\351\222\210.md" @@ -21,17 +21,17 @@ categories: [algorithm] * @return {number[]} */ var twoSum = function (numbers, target) { - let left = 0, - right = numbers.length - 1 - while (left < right) { - if (numbers[left] + numbers[right] < target) { - left++ - } else if (numbers[left] + numbers[right] > target) { - right-- - } else if (numbers[left] + numbers[right] === target) { - return [left + 1, right + 1] + let left = 0, + right = numbers.length - 1 + while (left < right) { + if (numbers[left] + numbers[right] < target) { + left++ + } else if (numbers[left] + numbers[right] > target) { + right-- + } else if (numbers[left] + numbers[right] === target) { + return [left + 1, right + 1] + } } - } } ``` @@ -43,16 +43,16 @@ var twoSum = function (numbers, target) { * @return {number} */ var removeDuplicates = function (nums) { - let left = 0, - right = 1 - while (right < nums.length) { - if (nums[left] === nums[right]) { - right++ - } else { - nums[++left] = nums[right++] + let left = 0, + right = 1 + while (right < nums.length) { + if (nums[left] === nums[right]) { + right++ + } else { + nums[++left] = nums[right++] + } } - } - return left + 1 + return left + 1 } ``` @@ -65,17 +65,17 @@ var removeDuplicates = function (nums) { * @return {number} */ var removeElement = function (nums, val) { - let left = 0, - right = nums.length - 1 - while (left <= right) { - if (nums[left] === val) { - nums[left] = nums[right] - right-- - } else { - left++ + let left = 0, + right = nums.length - 1 + while (left <= right) { + if (nums[left] === val) { + nums[left] = nums[right] + right-- + } else { + left++ + } } - } - return left + return left } ``` @@ -87,15 +87,15 @@ var removeElement = function (nums, val) { * @return {void} Do not return anything, modify nums in-place instead. */ var moveZeroes = function (nums) { - let left = 0, - right = 0 - while (right < nums.length) { - if (nums[right] !== 0) { - ;[nums[left++], nums[right++]] = [nums[right], nums[left]] - } else { - right++ + let left = 0, + right = 0 + while (right < nums.length) { + if (nums[right] !== 0) { + ;[nums[left++], nums[right++]] = [nums[right], nums[left]] + } else { + right++ + } } - } } ``` @@ -107,11 +107,11 @@ var moveZeroes = function (nums) { * @return {void} Do not return anything, modify s in-place instead. */ var reverseString = function (s) { - let left = 0, - right = s.length - 1 - while (left <= right) { - ;[s[left++], s[right--]] = [s[right], s[left]] - } + let left = 0, + right = s.length - 1 + while (left <= right) { + ;[s[left++], s[right--]] = [s[right], s[left]] + } } ``` @@ -119,8 +119,8 @@ var reverseString = function (s) { 回文子串的自身上,基本都是用双指针,比如: -- 判断是否是回文子串,两边往中间走 -- 寻找回文子串,中间往两边走,只不过需要注意,这里的**中间**,要看字符是奇数还是偶数,所以一般两种情况都要考虑 +- 判断是否是回文子串,两边往中间走 +- 寻找回文子串,中间往两边走,只不过需要注意,这里的**中间**,要看字符是奇数还是偶数,所以一般两种情况都要考虑 ```js /** @@ -128,21 +128,21 @@ var reverseString = function (s) { * @return {string} */ var longestPalindrome = function (s) { - const getPalindrome = (s, l, r) => { - while (l >= 0 && r < s.length && s[l] == s[r]) { - l-- - r++ + const getPalindrome = (s, l, r) => { + while (l >= 0 && r < s.length && s[l] == s[r]) { + l-- + r++ + } + return s.substring(l + 1, r) + } + let res = '' + for (let i = 0; i < s.length; ++i) { + const s1 = getPalindrome(s, i, i) + const s2 = getPalindrome(s, i, i + 1) + res = res.length > s1.length ? res : s1 + res = res.length > s2.length ? res : s2 } - return s.substring(l + 1, r) - } - let res = '' - for (let i = 0; i < s.length; ++i) { - const s1 = getPalindrome(s, i, i) - const s2 = getPalindrome(s, i, i + 1) - res = res.length > s1.length ? res : s1 - res = res.length > s2.length ? res : s2 - } - return res + return res } ``` diff --git "a/content/posts/algorithm/trick/\346\225\260\347\273\204-\346\273\221\345\212\250\347\252\227\345\217\243.md" "b/content/posts/algorithm/trick/\346\225\260\347\273\204-\346\273\221\345\212\250\347\252\227\345\217\243.md" new file mode 100644 index 0000000..08f57dd --- /dev/null +++ "b/content/posts/algorithm/trick/\346\225\260\347\273\204-\346\273\221\345\212\250\347\252\227\345\217\243.md" @@ -0,0 +1,449 @@ +--- +title: '滑动窗口' +date: 2024-02-21 +lastmod: +tags: [Array, String] +series: [trick] +categories: [algorithm] +--- + +## 核心 + +滑动窗口的核心就是维持一个 `[i..j)` 的区间窗口,在数据上游走,来获取到需要的信息,在数组,字符串等中的表现,往往如下: + +```js +function slideWindow() { + // 前后快慢双指针 + let left = 0 + let right = 0 + /** 具体的条件逻辑根据实际问题实际处理,多做练习 */ + while(slide condition) { + window.push(s[left]) // s 为总数据(字符串、数组) + right++ + while(shrink condition) { + window.shift(s[left]) + left++ + } + } +} +``` + +> 滑动窗口的算法时间复杂度为 `O(n)`,适用于处理大型数据集 + +## 练一练 + +### lc.3 无重复字符的最长子串 + +```js +/** + * @param {string} s + * @return {number} + */ +var lengthOfLongestSubstring = function (s) { + let max = 0 + let l = 0, + r = 0 + let window = {} + while (r < s.length) { + const c = s[r] + window[c] ? ++window[c] : (window[c] = 1) + r++ + while (window[c] > 1) { + const d = s[l] + window[d]-- + l++ + } + const len = r - l + max = Math.max(max, len) + } + return max +} +``` + +### lc.76 最小覆盖子串 + +```js +/** + * @param {string} s + * @param {string} t + * @return {string} + */ +var minWindow = function (s, t) { + if (s.length < t.length) return '' + let minCoverStr = '' + + let need = {} + for (const c of t) { + need[c] ? ++need[c] : (need[c] = 1) + } + const ValidCount = Object.keys(need).length + + let l = 0, + r = 0 + let window = {} + let validCount = 0 + while (r < s.length) { + const c = s[r] + window[c] ? ++window[c] : (window[c] = 1) + if (window[c] == need[c]) validCount++ + r++ + + while (validCount === ValidCount) { + const d = s[l] + if (window[d] === need[d]) { + validCount-- + // 分析出,此时字符串区间 应当为[left, right),因为 此时 right 已经++,left 还未++ + const str = s.slice(l, r) + if (!minCoverStr) minCoverStr = str // 这一步很容易忘记。。。 + minCoverStr = str.length < minCoverStr.length ? str : minCoverStr + } + window[d]-- + l++ + } + } + + return minCoverStr +} +``` + +### lc.438 找到字符串中所有字母异位词 + +```js +/** + * @param {string} s + * @param {string} p + * @return {number[]} + */ +var findAnagrams = function (s, p) { + if (s.length < p.length) return [] + let res = [] + const need = {} + for (const c of p) { + need[c] ? need[c]++ : (need[c] = 1) + } + const ValidCount = Object.keys(need).length + + let l = 0, + r = 0 + const window = {} + let count = 0 + while (r < s.length) { + const c = s[r] + window[c] ? window[c]++ : (window[c] = 1) + if (window[c] === need[c]) count++ + r++ + // 收缩条件容易犯错的地方,不能 AC的时候可以考虑一下是不是收缩条件有问题 + while (r - l >= p.length) { + if (count === ValidCount) res.push(l) + const d = s[l] + if (window[d] === need[d]) { + count-- + } + window[d]-- + l++ + } + + /** + 奇葩的我写第二遍的时候也是这么写的。。。。 + 因为比如 s=abbc,p=abc,这种情况,在统计 count 的时候就会有漏洞 + while(count === ValidCount) { + const d = s[l] + if(window[d] === need[d]) { + res.push(l) + count-- + } + window[d]-- + l++ + } + */ + } + + return res +} +``` + +### lc.567 字符串的排列 + +```js +/** + * @param {string} s1 + * @param {string} s2 + * @return {boolean} + */ +var checkInclusion = function (s1, s2) { + if (s1.length > s2.length) return false + const need = {} + for (const c of s1) { + need[c] ? need[c]++ : (need[c] = 1) + } + const ValidCount = Object.keys(need).length + const size = s1.length + + let l = 0, + r = 0 + let window = {} + let count = 0 + while (r < s2.length) { + const c = s2[r] + window[c] ? window[c]++ : (window[c] = 1) + if (window[c] === need[c]) count++ + r++ + + while (r - l == size) { + if (count === ValidCount) return true + const d = s2[l] + if (window[d] === need[d]) count-- + window[d]-- + l++ + } + } + + return false +} +``` + +### lc.209 长度最小的子数组 + +```js +/** + * @param {number} target + * @param {number[]} nums + * @return {number} + */ +var minSubArrayLen = function (target, nums) { + // 求数组区间和 自然想到前缀和数组的啦~ + const preSum = [0] + for (let i = 0; i < nums.length; ++i) { + preSum[i + 1] = preSum[i] + nums[i] + } + + let l = 0, + r = 0 + let min = Infinity + while (r < nums.length) { + r++ + while (preSum[r] - preSum[l] >= target) { + min = Math.min(min, r - l) + l++ + } + } + + return min === Infinity ? 0 : min +} + +/** + * 一般这种都可以空间优化一下 + */ +var minSubArrayLen = function (target, nums) { + let res = Infinity + let sum = 0 + let i = 0 + for (let j = 0; j <= nums.length - 1; j++) { + sum += nums[j] + while (sum >= target) { + res = j - i + 1 < res ? j - i + 1 : res + sum -= nums[i] + i++ + } + } + return res == Infinity ? 0 : res +} +``` + +### lc.219 存在重复元素 II easy (形式) + +这道题是 easy 题,用哈希表做会非常容易,但是面试和做链表题一样,最好能做出空间复杂度更小的解法。 + +```js +/** + * @param {number[]} nums + * @param {number} k + * @return {boolean} + */ +var containsNearbyDuplicate = function (nums, k) { + const set = new Set() + for (let i = 0; i < nums.length; ++i) { + if (set.has(nums[i])) return true + set.add(nums[i]) + if (set.size > k) set.delete(nums[i - k]) + } + return false +} +``` + +这道题的窗口和上方其他题的窗口形式,略有不同,首先没有使用双指针,其次使用了 set 集合而不是 map,这样就可以**通过固定 set 的大小来作为窗口**,就很巧妙。 + +> 在前缀和 lc.918 中,通过控制单调队列的大小来控制窗口,与本题类似,此种形式,也应当熟练运用 + + + +### lc.395 至少有 K 个重复字符的最长子串 + +这道题还是比较特殊的,因为窗口也与常规的不一样,需要换个思路。 + +枚举最长子串中的字符种类数目,它最小为 1,最大为 26,所以把字符种类数作为窗口,同时统计每个字符在窗口内出现的次数,这样: + +- 当字符种类数 total 固定时,一旦 total > t,就去移动左指针,同时更新 window 内的字符出现次数统计 +- 判断字符出现次数是否小于 k,可以通过 less 变量来控制 + +当限定字符种类数目为 t 时,满足题意的最长子串,就一定出自某个 s[l..r]。因此,在滑动窗口的维护过程中,就可以直接得到最长子串的大小。 + +```js +var longestSubstring = function (s, k) { + let res = 0 + // 限定字符种类数,创造窗口,注意是 [1..26] + for (let t = 1; t <= 26; t++) { + let l = 0, + r = 0 + const window = {} // 维护窗口内每个字符出现的次数 + let total = 0 // 字符种类数 + let less = 0 // 当前出现次数小于 k 的字符的数量 (避免遍历整个 window) + while (r < s.length) { + const c = s[r] + window[c] ? window[c]++ : (window[c] = 1) + if (window[c] === 1) { + total++ + less++ + } + if (window[c] === k) { + less-- + } + r++ + + while (total > t) { + const d = s[l] + if (window[d] === k) { + less++ + } + if (window[d] === 1) { + total-- + less-- + } + window[d]-- + l++ + } + // 当没有存在小于 k 的字符时,此时满足条件 + if (less == 0) { + res = Math.max(res, r - l) + } + } + } + return res +} +/** 此题还有分治解法,见官解 */ +``` + +### lc.424 替换后的最长重复字符 + +这道题算是滑动窗口的进阶了,收缩时,left 只向右移动一步(即与 right 一起向右平移),原因是更小的窗口没有考虑的必要了。 + +```js +/** + * @param {string} s + * @param {number} k + * @return {number} + */ +var characterReplacement = function (s, k) { + let l = 0, + r = 0 + let window = {} + let maxN = 0 // 记录窗口内最大的相同字符出现次数 + while (r < s.length) { + const c = s[r] + window[c] ? window[c]++ : (window[c] = 1) + maxN = Math.max(maxN, window[c]) + r++ + + // 窗口大到 k 不够换下除了最大出现次数字符外的其他所有字符时,收缩左指针 + if (r - l - maxN > k) { + // 好像换成 while 也没问题,但是小于 r - l 的长度没有必要考虑 + window[s[l]]-- + l++ + } + } + + return r - l +} +``` + +### lc.713 乘积小于 K 的子数组 + +这道题是变长窗口 + +```js +/** + * @param {number[]} nums + * @param {number} k + * @return {number} + */ +var numSubarrayProductLessThanK = function (nums, k) { + // 读完题目,感觉要用到前缀积 + 滑动窗口 + let res = 0 + let multi = 1, + l = 0 + + for (let r = 0; r < nums.length; ++r) { + multi *= nums[r] + while (multi >= k && l <= r) { + multi /= nums[l] + l++ + } + // 每次右指针位移到一个新位置,应该加上 x 种数组组合: + // nums[right] + // nums[right-1], nums[right] + // nums[right-2], nums[right-1], nums[right] + // nums[left], ......, nums[right-2], nums[right-1], nums[right] + // 共有 right - left + 1 种 + res += r - l + 1 + } + return res +} +/** 本题 与 lc.209 相似 */ +``` + +> 这个[「题解」](https://leetcode.cn/problems/subarray-product-less-than-k/solutions/1373555/hua-dong-chuang-kou-by-fenjue-xvg5) 不错 + +--- + +## 应用 + +滑动窗口算法适用于解决以下类型的问题: + +- 查找最大子数组和 +- 查找具有 K 个不同字符的最长(短)子串 +- 查找具有特定条件的最长(短)子串或子数组 +- 查找连续 1 的最大序列长度(可以在允许将最多 K 个 0 替换为 1 的情况下) +- 查找具有特定和的子数组 +- 查找具有不同元素的子数组 +- 查找具有特定条件的最大或最小子数组 +- ... + +滑动窗口算法通常用于解决需要在数组或字符串上维护一个固定大小的窗口,并在窗口内执行特定操作或计算的问题。这种算法技术可以有效降低时间复杂度,通常为 `O(n)`,适用于处理大型数据集。 + +> 对于窗口大小的区间可以设计两端都开或者两端都闭的区间,但设计为左闭右开区间是最方便处理的。因为这样初始化 left = right = 0 时区间 [0, 0) 中没有元素,但只要让 right 向右移动(扩大)一位,区间 [0, 1) 就包含一个元素 0 了。如果你设置为两端都开的区间,那么让 right 向右移动一位后开区间 (0, 1) 仍然没有元素;如果你设置为两端都闭的区间,那么初始区间 [0, 0] 就包含了一个元素。这两种情况都会给边界处理带来不必要的麻烦。 diff --git "a/content/posts/algorithm/\345\233\236\346\226\207\347\233\270\345\205\263.md" "b/content/posts/algorithm/\345\233\236\346\226\207\347\233\270\345\205\263.md" index 6c0b2f4..4cbf49d 100644 --- "a/content/posts/algorithm/\345\233\236\346\226\207\347\233\270\345\205\263.md" +++ "b/content/posts/algorithm/\345\233\236\346\226\207\347\233\270\345\205\263.md" @@ -13,7 +13,7 @@ date: 2022-12-19T17:22:17+08:00 - 状态:区间[i..j] - 选择:由内而外 'ababa',指针 i 和 j 相等时,得看 i+1 和 j-1 -```JavaScript +```js /** * @param {string} s * @return {number} @@ -41,7 +41,7 @@ var countSubstrings = function(s) { 另外再使用一下常规的中心拓展法来解决一下这道题: -```JavaScript +```js /** * @param {string} s * @return {number} @@ -72,7 +72,7 @@ function getNum(l, r, m, str) { 这道题也是可以用动态规划和中心拓展法去解决,就不多说了,开干。 -```JavaScript +```js /** * @param {string} s * @return {string} @@ -102,7 +102,7 @@ function getPalindrome(s, l, r) { 因为不是连着的,所以中心拓展法没办法施展,只能动态规划了。 -```JavaScript +```js /** * @param {string} s * @return {number} diff --git "a/content/posts/algorithm/\346\225\260\347\273\204\351\201\215\345\216\206\346\214\207\345\215\227.md" "b/content/posts/algorithm/\346\225\260\347\273\204\351\201\215\345\216\206\346\214\207\345\215\227.md" index d6b8f21..c711c63 100644 --- "a/content/posts/algorithm/\346\225\260\347\273\204\351\201\215\345\216\206\346\214\207\345\215\227.md" +++ "b/content/posts/algorithm/\346\225\260\347\273\204\351\201\215\345\216\206\346\214\207\345\215\227.md" @@ -26,8 +26,8 @@ for (let i = 6; i >= 3; --i) console.log(Arr[i]) 重点就是 `循环不变式` 值的确定,对于区间 `[l, r]`:。 -- 正序,`i < (r + 1)` 即 `i <= r` -- 倒序,`i >= l` +- 正序,`i < (r + 1)` 即 `i <= r` +- 倒序,`i >= l` 那么如果某个闭区间变成开区间呢?稍加变化即可,不再赘述 @@ -102,8 +102,8 @@ while (count <= 25) { ### 斜向遍历 -- 主对角线:左上角到右下角,坐标 i === j -- 副对角线:右上角到左下角,坐标 i + j === len - 1 +- 主对角线:左上角到右下角,坐标 i === j +- 副对角线:右上角到左下角,坐标 i + j === len - 1 ![](https://cdn.jsdelivr.net/gh/yokiizx/picgo@main/img/202303291708525.png) 如图,按照这样的顺序怎么遍历呢? @@ -146,16 +146,16 @@ for(let l = 0; l <= m - 1; ++l) { 其实总结起来很简单: -- 主对角线 `i - j` 是固定值 -- 副对角线 `i + j` 是固定值 +- 主对角线 `i - j` 是固定值 +- 副对角线 `i + j` 是固定值 而固定值就是线段 `l`,找出三者的关系就很容易了。 ## 补充一点经验 -- [485.最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/),简单题,就是 max 更新过程最后的一次是有可能遍历到结束,从而触发不到更新,最后再与状态累加器进行一次比较即可 -- [495.提莫攻击](https://leetcode.cn/problems/teemo-attacking/),对区间理解清楚,就不会迷糊,区间截取,添加增量即可 -- [414.第三大的数](https://leetcode.cn/problems/third-maximum-number/),一般第 k 大的数可以用优先队列来搞定,这里只有 3,那么直接利用三个状态值的方式来解决即可 -- [516. 最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/),推导出状态转移方程后,根据依赖关系确定遍历方向/方式,可以使用斜向遍历 +- [485.最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/),简单题,就是 max 更新过程最后的一次是有可能遍历到结束,从而触发不到更新,最后再与状态累加器进行一次比较即可 +- [495.提莫攻击](https://leetcode.cn/problems/teemo-attacking/),对区间理解清楚,就不会迷糊,区间截取,添加增量即可 +- [414.第三大的数](https://leetcode.cn/problems/third-maximum-number/),一般第 k 大的数可以用优先队列来搞定,这里只有 3,那么直接利用三个状态值的方式来解决即可 +- [516. 最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/),推导出状态转移方程后,根据依赖关系确定遍历方向/方式,可以使用斜向遍历 > 读完本文,相信你对数组的各种遍历能更加游刃有余,加油,打工人! diff --git "a/content/posts/base-html/JS\345\205\263\344\272\216DOM\351\203\250\345\210\206.md" "b/content/posts/base-html/JS\345\205\263\344\272\216DOM\351\203\250\345\210\206.md" index 539addf..f60d9ef 100644 --- "a/content/posts/base-html/JS\345\205\263\344\272\216DOM\351\203\250\345\210\206.md" +++ "b/content/posts/base-html/JS\345\205\263\344\272\216DOM\351\203\250\345\210\206.md" @@ -46,7 +46,7 @@ content + padding 的宽度,不包括滚动条 ## 滚动 -```JavaScript +```js scrollBy(x,y) // 相对自身偏移 scrollTo(pageX,pageY) // 滚动到绝对坐标 scrollToView() // 滚到视野里 diff --git "a/content/posts/base-html/JS\345\205\263\344\272\216DOM\351\203\250\345\210\2062.md" "b/content/posts/base-html/JS\345\205\263\344\272\216DOM\351\203\250\345\210\2062.md" index 9463432..d1651be 100644 --- "a/content/posts/base-html/JS\345\205\263\344\272\216DOM\351\203\250\345\210\2062.md" +++ "b/content/posts/base-html/JS\345\205\263\344\272\216DOM\351\203\250\345\210\2062.md" @@ -101,7 +101,7 @@ DOM3 在 DOM2 的基础上添加了更多事件类型。 ## 自定义事件 -```JavaScript +```js const event = new Event(type[, options]); const customEvent = new CustomEvent(type[, options]); diff --git "a/content/posts/base-html/\351\241\265\351\235\242\347\224\237\345\221\275\345\221\250\346\234\237.md" "b/content/posts/base-html/\351\241\265\351\235\242\347\224\237\345\221\275\345\221\250\346\234\237.md" index 5322143..c8c2e5d 100644 --- "a/content/posts/base-html/\351\241\265\351\235\242\347\224\237\345\221\275\345\221\250\346\234\237.md" +++ "b/content/posts/base-html/\351\241\265\351\235\242\347\224\237\345\221\275\345\221\250\346\234\237.md" @@ -21,7 +21,7 @@ tags: [HTML, DOM] 发送分析数据: -```JavaScript +```js // 以 post 请求方式发送 // 数据大小限制在 64kb // 一般是一个字符序列化对象 diff --git a/content/posts/base-js/EventLoop.md b/content/posts/base-js/EventLoop.md index f0cb9fb..17f9e57 100644 --- a/content/posts/base-js/EventLoop.md +++ b/content/posts/base-js/EventLoop.md @@ -19,7 +19,7 @@ HTML5 新增了 Web Worker,但完全受控于主线程,不能操作 I/O,DOM 之前作用域的文章里提到过三个上下文,全局上下文,函数上下文,eval 上下文。当代码执行的时候需要一个栈来存储各个上下文的,这样才能保证后进入的上下文先执行结束(这里的后是指内部的嵌套函数)。 -```JavaScript +```js // ... // 代码扫描到此处时,执行上下文栈栈底有个全局上下文 function a() { // 函数a创建执行上下文,并推入栈中 @@ -66,7 +66,7 @@ function a() { // 函数a创建执行上下文,并推入栈中 练一练 😏 -```JavaScript +```js function test3() { console.log(1); @@ -165,7 +165,7 @@ setTimeout VS setImmediate 拿 `setTimeout` 和 `setImmediate` 对比,这是一个常见的例子,基于被调用的时机和定时器可能会受到计算机上其它正在运行的应用程序影响,它们的输出顺序,不总是固定的。具体可以见文末参考文章。 但是一旦把这两个函数放入一个 I/O 循环内调用,setImmediate 将总是会被优先调用。因为 setImmediate 属于 check 阶段,在事件循环中总是在 poll 阶段结束后运行,这个顺序是确定的。 -```JavaScript +```js fs.readFile(__filename, () => { setTimeout(() => log('setTimeout')); setImmediate(() => log('setImmediate')); @@ -179,7 +179,7 @@ Node 中宏任务分为了六大阶段执行,微任务执行时机在 Node11 `process.nextTick()`,从技术上讲,它不是事件循环的一部分,同步代码执行完会立马执行 `proces.nextTick()`,也就是说是先于 `promsie.then` 执行。如果出现递归 `process.nextTick()` 会阻断事件循环,陷入无限循环中,与同步的递归不同的是,它不会触碰 v8 最大调用堆栈限制。但是会破坏事件循环调度,setTimeout 将永远得不到执行。 -```JavaScript +```js fs.readFile(__filename, () => { process.nextTick(() => { log('nextTick'); @@ -199,7 +199,7 @@ nextTick 将 process.nextTick 改为 setImmediate 虽然是递归的,但它不会影响事件循环调度,setTimeout 在下一次事件循环中被执行。 -```JavaScript +```js fs.readFile(__filename, () => { process.nextTick(() => { log('nextTick'); diff --git "a/content/posts/base-js/Map\345\222\214Set.md" "b/content/posts/base-js/Map\345\222\214Set.md" index 600ec4b..733fc46 100644 --- "a/content/posts/base-js/Map\345\222\214Set.md" +++ "b/content/posts/base-js/Map\345\222\214Set.md" @@ -34,7 +34,7 @@ Set 的特性除了没有键,就是值不重复,常被用来做去重处理 关于内存清除,JavaScript 引擎在值“可达”和可能被使用时会将其保持在内存中。见以下代码: -```JavaScript +```js let demo = {name: 'yokiizx'} demo = null // 该对象将会被从内存中清除 @@ -66,7 +66,7 @@ console.log(weakMap) // WeakMap { } 私有属性应该是不能被外界访问到,不能被多个实例共享。闭包中的变量会在实例中共享 -```JavaScript +```js const Demo = (function () { let priv = new WeakMap() return class { @@ -100,7 +100,7 @@ console.log(d2.getPriv(), '--', d2.priv) // 2 -- undefined - 必须具有 `Symbol.iterator` 方法, 该方法就是迭代器。(方法加到原型上) - 迭代器是具有 `next()` 方法的对象,`next()` 方法返回 `{done:.., value :...}` 格式的迭代器对象 -```JavaScript +```js const range = { from: 1, to: 5 diff --git a/content/posts/base-js/async&await.md b/content/posts/base-js/async&await.md index 66d266e..0b0305d 100644 --- a/content/posts/base-js/async&await.md +++ b/content/posts/base-js/async&await.md @@ -17,7 +17,7 @@ tags: [JavaScript, promise] 核心原理大致如下: -```JavaScript +```js // 1. 不同实例,返回的迭代器互不干扰,所以应该是有个生成上下文的构造器 class Context { constructor() { @@ -74,7 +74,7 @@ function gen() { 理解了上面 generator 的原理,理解 async/await 就容易得多了。 -```JavaScript +```js async function demo() { await new Promise((resolve, reject) => { setTimeout(() => { @@ -101,7 +101,7 @@ demo() // 1 2 3 现在来打开这个语法糖的魔盒,看看怎么来实现它: -```JavaScript +```js function* demo() { yield new Promise((resolve, reject) => { setTimeout(() => { @@ -132,7 +132,7 @@ gen.next().value.then(() => { 上方是最朴实无华的语法糖解密,但是不够优雅,n 个 await 那不得回调地狱了?可以很自然的联想到使用一个递归函数来处理: -```JavaScript +```js function co(gen) { const curr = gen.next() if(curr.done) return @@ -146,7 +146,7 @@ async/await 的原理挺简单的,要善于理解。 再记录一个常用的 async/await 处理错误比较好的库,`await-to-js`,源码如下(短小精炼): -```JavaScript +```js export function to ( promise: Promise, errorExt?: object diff --git "a/content/posts/base-js/curry\345\222\214compose.md" "b/content/posts/base-js/curry\345\222\214compose.md" index 526ab0b..61103dc 100644 --- "a/content/posts/base-js/curry\345\222\214compose.md" +++ "b/content/posts/base-js/curry\345\222\214compose.md" @@ -53,7 +53,7 @@ console.log(currySum(1, 2)(3)); // 6 柯里化的孪生兄弟 -- 偏函数,个人见解: 就是在 curry 之上,初始化时固定了部分的参数,注意两者累加参数的方式不太一样哦。 -```JavaScript +```js /** * args 为固定的参数 */ @@ -78,7 +78,7 @@ console.log(partialSum(1)(2)) // 103 compose 合成函数是把多层函数嵌套调用扁平化,内部函数执行的结果作为外部面函数的参数。 -```JavaScript +```js function compose() { // 可以加个判断参数是否合格, 此处省略 const fns = [...arguments] @@ -104,7 +104,7 @@ console.log(test(10, 20)) // 40 与之对应的是 pipe 函数,只不过是从左往右执行。这里就用 reduce 来实现一下吧: -```JavaScript +```js function pipe(...fns) { return function (args) { return fns.reduce((t, cb) => cb(t), args); diff --git a/content/posts/base-js/promiseA+.md b/content/posts/base-js/promiseA+.md index b6f95a3..ff8f946 100644 --- a/content/posts/base-js/promiseA+.md +++ b/content/posts/base-js/promiseA+.md @@ -19,7 +19,7 @@ tags: [JavaScript, promise] 一个 `promise` 必须有个 `then` 方法。 -```JavaScript +```js promise.then(onFulfilled, onRejected) ``` @@ -32,7 +32,7 @@ promise.then(onFulfilled, onRejected) 5. 一个 `promise` 可以注册多个 `then` 方法,按照初始声明时的调用顺序执行 `then` 6. `then` 必须返回一个 `promise:` - ```JavaScript + ```js promise2 = promise1.then(onFulfilled, onRejected) ``` @@ -40,7 +40,7 @@ promise.then(onFulfilled, onRejected) - 如果`onFulfilled`或`onRejected` 抛出了错 `e`,promise2 必须用 `e` 作为 `onRejected` 的 `reason` - 如果`onFulfilled`或`onRejected` 不为函数,则 promise2 必须采用 promise1 的 value 或 reason (即会发生值/异常传递) - ```JavaScript + ```js // 案例1 resolve console.log(new Promise((resolve) => { resolve(1) @@ -76,7 +76,7 @@ promise.then(onFulfilled, onRejected) ### 代码实现 -```JavaScript +```js /** * @description : promise 实现 * @date : 2022-09-28 21:42:08 @@ -196,7 +196,7 @@ function resolvePromise(promise, x, resolve, reject) { ### 其他方法的实现 -```JavaScript +```js class _Promise { // ... 主代码省略,见上方 @@ -277,7 +277,7 @@ npm install promises-aplus-tests -D 2. 在实现的 promise 中添加以下代码 -```JavaScript +```js _Promise.deferred = function () { var result = {}; result.promise = new _Promise(function (resolve, reject) { @@ -292,7 +292,7 @@ module.exports = _Promise; 3. 配置启动 script -```JavaScript +```js "test": "promises-aplus-tests MyPromise" ``` diff --git "a/content/posts/base-js/promise\345\271\266\345\217\221.md" "b/content/posts/base-js/promise\345\271\266\345\217\221.md" index 9c529dd..77432c9 100644 --- "a/content/posts/base-js/promise\345\271\266\345\217\221.md" +++ "b/content/posts/base-js/promise\345\271\266\345\217\221.md" @@ -11,7 +11,7 @@ tags: [JavaScript, promise] 关键点,一个限定数量的请求池,一个 promise 有结果后,再去加入下一个请求,递归直到所有结束。 -```JavaScript +```js const mockReq = function (time) { return new Promise((resolve, reject) => { setTimeout(() => { @@ -53,7 +53,7 @@ concurrentPromise(3); 通过 aync + promise.race 能更简单的控制。 -```JavaScript +```js async function concurrentPromise(limit) { const pool = []; for (let i = 0; i < reqList.length; ++i) { diff --git "a/content/posts/base-js/promise\346\217\220\345\211\215\347\273\223\346\235\237.md" "b/content/posts/base-js/promise\346\217\220\345\211\215\347\273\223\346\235\237.md" index cd72925..1754cdf 100644 --- "a/content/posts/base-js/promise\346\217\220\345\211\215\347\273\223\346\235\237.md" +++ "b/content/posts/base-js/promise\346\217\220\345\211\215\347\273\223\346\235\237.md" @@ -8,7 +8,7 @@ tags: [JavaScript, promise] axios 已经有了取消请求的功能: -```JavaScript +```js const CancelToken = axios.CancelToken; let cancel; @@ -46,7 +46,7 @@ source.cancel('Operation canceled by the user.'); 上方的 CancelToken 从 v0.22.0 版本被废弃,最新的 axios 使用的是 fetch 的 api `AbortController`: -```JavaScript +```js const controller = new AbortController(); axios.get('/foo/bar', { @@ -62,7 +62,7 @@ controller.abort() 首先我在"中断"上打了引号,因为 promise 一旦创建是无法取消的,所谓的”中断“,只是在合适的时候,提前结束 pending 状态而已,所以本文题目才是提前结束而不是“中断”。 -```JavaScript +```js const mockReq = function (time) { const req = new Promise((resolve, reject) => { setTimeout(() => { diff --git "a/content/posts/base-js/v8\345\236\203\345\234\276\345\233\236\346\224\266\346\234\272\345\210\266.md" "b/content/posts/base-js/v8\345\236\203\345\234\276\345\233\236\346\224\266\346\234\272\345\210\266.md" index 8f7f12a..c9e49c0 100644 --- "a/content/posts/base-js/v8\345\236\203\345\234\276\345\233\236\346\224\266\346\234\272\345\210\266.md" +++ "b/content/posts/base-js/v8\345\236\203\345\234\276\345\233\236\346\224\266\346\234\272\345\210\266.md" @@ -12,7 +12,7 @@ tags: [JavaScript, node] 缺点: 对象循环引用的时候,就会产生不被回收的情况,从而造成内存泄露。比如: -```JavaScript +```js function fn () { const obj1 = {} const obj2 = {} diff --git "a/content/posts/base-js/\345\256\271\346\230\223\345\277\275\347\225\245\347\232\204API.md" "b/content/posts/base-js/\345\256\271\346\230\223\345\277\275\347\225\245\347\232\204API.md" index 9b0e711..c73f4c0 100644 --- "a/content/posts/base-js/\345\256\271\346\230\223\345\277\275\347\225\245\347\232\204API.md" +++ "b/content/posts/base-js/\345\256\271\346\230\223\345\277\275\347\225\245\347\232\204API.md" @@ -8,7 +8,7 @@ tags: [JavaScript, DOM] 监测 DOM,发生变动时触发。 -```JavaScript +```js const observer = new MutationObserver(callback); // callback 回调参数是 MutationRecord 对象 的数组,第二个参数是观察器自身 @@ -30,7 +30,7 @@ observer.takeRecords() // 获取已经发生但未处理的变动 监听元素是否进入了视口(viewport)。 -```JavaScript +```js // callback 一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。 var io = new IntersectionObserver(callback, option) // callback 参数是 IntersectionObserverEntry 对象 的数组,观察了几个元素就有几个对象 @@ -50,7 +50,7 @@ io.disconnect() 适合场景比如图片懒加载,无线滚动等,但是如果需要 buffer 好像就不行了。 -```JavaScript +```js const imgs = document.querySelectorAll('img[data-src]') const config = { rootMargin: '0px', @@ -86,7 +86,7 @@ imgs.forEach((image) => { 兼容性处理: -```JavaScript +```js window._requestAnimationFrame = (function () { return ( window.requestAnimationFrame || @@ -105,7 +105,7 @@ eg: - 平滑滚动到顶部 -```JavaScript +```js const scrollToTop = () => { const c = document.documentElement.scrollTop || document.body.scrollTop if (c > 0) { @@ -117,7 +117,7 @@ const scrollToTop = () => { - 十万条数据渲染 -```JavaScript +```js //需要插入的容器 let ul = document.getElementById('container') // 插入十万条数据 @@ -155,13 +155,13 @@ loop(total, index) 发送 -```JavaScript +```js window.postMessage(message, targetOrigin, [transfer]);; ``` 接收 -```JavaScript +```js window.addEventListener("message", function(event) { if (event.origin != 'http://javascript.info') { // 来自未知的源的内容,我们忽略它 diff --git "a/content/posts/base-js/\345\256\271\346\230\223\346\267\267\346\267\206\347\232\204API.md" "b/content/posts/base-js/\345\256\271\346\230\223\346\267\267\346\267\206\347\232\204API.md" index 5026416..6a40d77 100644 --- "a/content/posts/base-js/\345\256\271\346\230\223\346\267\267\346\267\206\347\232\204API.md" +++ "b/content/posts/base-js/\345\256\271\346\230\223\346\267\267\346\267\206\347\232\204API.md" @@ -79,7 +79,7 @@ history 顾名思义,主要是对浏览器的浏览历史进行操作。 1. 改变 hash 值 2. 监听 hashchange 事件即可实现页面跳转 - ```JavaScript + ```js window.addEventListener('hashchange', () => { const hash = window.location.hash.slice(1) // 根据hash值渲染不同的dom @@ -92,7 +92,7 @@ history 顾名思义,主要是对浏览器的浏览历史进行操作。 1. 改变 url (通过 `pushState()` 和 `replaceState()`) - ```JavaScript + ```js // 第一个参数:状态对象,在监听变化的事件中能够获取到 // 第二个参数:标题 // 第三个参数:跳转地址url @@ -101,7 +101,7 @@ history 顾名思义,主要是对浏览器的浏览历史进行操作。 2. 监听 `popstate` 事件 - ```JavaScript + ```js window.addEventListener("popstate", () => { const path = window.location.pathname // 根据path不同可渲染不同的dom diff --git "a/content/posts/base-js/\345\270\270\347\224\250\345\206\205\347\275\256\345\216\237\347\220\206.md" "b/content/posts/base-js/\345\270\270\347\224\250\345\206\205\347\275\256\345\216\237\347\220\206.md" index 02d7cb3..b41739c 100644 --- "a/content/posts/base-js/\345\270\270\347\224\250\345\206\205\347\275\256\345\216\237\347\220\206.md" +++ "b/content/posts/base-js/\345\270\270\347\224\250\345\206\205\347\275\256\345\216\237\347\220\206.md" @@ -6,7 +6,7 @@ tags: [JavaScript] ### new -```JavaScript +```js function _new(constructor, ...args) { const obj = Object.create(constructor.prototype) const ret = constructor.call(obj, ...args) @@ -18,7 +18,7 @@ function _new(constructor, ...args) { ### instanceof -```JavaScript +```js /** * ins - instance * ctor - constructor @@ -41,7 +41,7 @@ function _instanceof(ins, ctor) { 加载到函数原型上,注意:不能使用箭头函数哦,否则 this 指向会指向全局对象去了~ -```JavaScript +```js Function.prototype._call = function(ctx = window, ...args) { ctx.fn = this const res = ctx.fn(...args) @@ -62,7 +62,7 @@ bind 与 call/apply 不同的是返回的是函数,而不是改变上下文后 另外需要考虑返回的函数如果能做构造函数的情况。 -```JavaScript +```js Funtion.prototype._bind = function (ctx = window, ...args) { const fn = this const resFn = function () { @@ -82,7 +82,7 @@ Funtion.prototype._bind = function (ctx = window, ...args) { 上方原型式继承也可以这么写: -```JavaScript +```js let p = Object.create(this.prototype) p.constructor = resFn resFn.prototype = p @@ -93,7 +93,7 @@ resFn.prototype = p - 需要注意 函数 正则 日期 ES 新对象等,需要用他们的构造器创建新的对象 - 需要注意循环引用的问题 -```JavaScript +```js /** * 借助 WeakMap 解决循环引用问题 */ @@ -119,7 +119,7 @@ function deepClone(target, wm = new WeakMap()) { ### Promise.all & Promise.race -```JavaScript +```js function promiseAll(promises) { let count = 0; const res = []; diff --git "a/content/posts/base-js/\350\200\201\347\224\237\345\270\270\350\260\210-This.md" "b/content/posts/base-js/\350\200\201\347\224\237\345\270\270\350\260\210-This.md" index 44be6a1..d6af2bb 100644 --- "a/content/posts/base-js/\350\200\201\347\224\237\345\270\270\350\260\210-This.md" +++ "b/content/posts/base-js/\350\200\201\347\224\237\345\270\270\350\260\210-This.md" @@ -12,13 +12,13 @@ tags: [JavaScript] 非严格模式下,this 指向 window/global,严格模式下 this 指向 undefined -```JavaScript +```js function demo() { console.log(this) // window } ``` -```JavaScript +```js 'use strict' function demo() { console.log(this) // undefined @@ -36,7 +36,7 @@ function demo() { 普通函数在的执行时,创建它的执行上下文,此时才能决定 this 的指向,谁调用 this 指向这个对象。 -```JavaScript +```js var name = 'yokiizx'; var demo = { name: '94yk', @@ -65,7 +65,7 @@ learn(); // 上下文为window 平时所见到的箭头函数中的 this,其实指向的就是是它定义时所处的上下文,这是与普通函数的区别 -```JavaScript +```js var name = 'yokiizx'; var demo = { name: '94yk', @@ -80,7 +80,7 @@ demo.learn() // yokiizx is learning JS 小结: -```JavaScript +```js var name = 'outer_name' var obj = { name: 'inner_name', @@ -116,7 +116,7 @@ log_4.call(demo) // inner_name this 指向新创建的实例对象 -```JavaScript +```js function _new(fn, ...args) { // 创建新对象,修改原型链 obj.__proto__ 指向 fn.prototype const obj = Object.create(fn.prototype) @@ -129,7 +129,7 @@ function _new(fn, ...args) { ## Class -```JavaScript +```js class Button { constructor(value) { this.value = value; @@ -159,7 +159,7 @@ setTimeout(button.click, 1000); // undefined,因为变更了上下文,this } ``` 2. 把方法赋值给类字段(推荐,更优雅),因为类字段不是加在 `类.prototype`,而是在每个独立对象中。 - ```JavaScript + ```js class Button { constructor(value) { this.value = value; diff --git "a/content/posts/base-js/\350\200\201\347\224\237\345\270\270\350\260\210-\345\216\237\345\236\213\351\223\276\345\222\214\347\273\247\346\211\277.md" "b/content/posts/base-js/\350\200\201\347\224\237\345\270\270\350\260\210-\345\216\237\345\236\213\351\223\276\345\222\214\347\273\247\346\211\277.md" index 58e6c67..382653f 100644 --- "a/content/posts/base-js/\350\200\201\347\224\237\345\270\270\350\260\210-\345\216\237\345\236\213\351\223\276\345\222\214\347\273\247\346\211\277.md" +++ "b/content/posts/base-js/\350\200\201\347\224\237\345\270\270\350\260\210-\345\216\237\345\236\213\351\223\276\345\222\214\347\273\247\346\211\277.md" @@ -21,7 +21,7 @@ tags: [JavaScript] 这种继承是直接把子类的原型对象修改为父类的实例。 -```JavaScript +```js function Super(father) { this.father = father } @@ -49,7 +49,7 @@ Sub.prototype = new Super() 在子类构造函数中调用父类构造函数,解决了原型链继承的问题 -```JavaScript +```js function Super(name) { this.name = name } @@ -71,7 +71,7 @@ function Sub(age, name) { 1. 用盗用构造函数继承属性 2. 用原型链继承方法 -```JavaScript +```js function Sub(name) { Super.call(this, name) this.age = age @@ -86,7 +86,7 @@ Sub.prototype = new Super() 借用临时构造函数来继承,这样就无自定义一个新的子类类型。 适用于在一个对象基础之上进行创建一个新对象。 -```JavaScript +```js function Object(obj) { function F() {} // 创建临时构造函数 F.prototype = obj @@ -101,7 +101,7 @@ function Object(obj) { 主要是利用工厂模式的思想和寄生构造函数。 -```JavaScript +```js function Obj(obj) { let newObj = Object.create(obj) newObj.func = function () { @@ -118,7 +118,7 @@ function Obj(obj) { ### 寄生式组合继承 -```JavaScript +```js function Demo(Super, Sub) { let super = Object(Super.prototype) // 创建新对象 super.constructor = Sub // 增强对象(修正constructor) @@ -140,7 +140,7 @@ function Demo(Super, Sub) { 使用 extends 关键字 -```JavaScript +```js class A { } diff --git "a/content/posts/base-js/\350\247\202\345\257\237\350\200\205\345\222\214\345\217\221\345\270\203\350\256\242\351\230\205.md" "b/content/posts/base-js/\350\247\202\345\257\237\350\200\205\345\222\214\345\217\221\345\270\203\350\256\242\351\230\205.md" index 4d433a6..c988d73 100644 --- "a/content/posts/base-js/\350\247\202\345\257\237\350\200\205\345\222\214\345\217\221\345\270\203\350\256\242\351\230\205.md" +++ "b/content/posts/base-js/\350\247\202\345\257\237\350\200\205\345\222\214\345\217\221\345\270\203\350\256\242\351\230\205.md" @@ -12,7 +12,7 @@ tags: [JavaScript, design mode] 这个过程其实就是一个简单的观察者模式。这里被观察者是: promise,观察者是: 两个队列里的回调函数,promise 的 resolve 通知回调执行。 两者的关系是通过 **被观察者** 建立起来的,被观察者有**添加**,**删除**和**通知**观察者的功能。 -```JavaScript +```js // 简单实现一下 class Subject { constructor() { @@ -60,7 +60,7 @@ subject.notify('world') 发布订阅模式将发布者与订阅者通过消息中心/事件池来联系起来。 -```JavaScript +```js class EventCenter { constructor() { this.eventMap = {} diff --git "a/content/posts/base-js/\351\207\215\345\255\246TypeScript.md" "b/content/posts/base-js/\351\207\215\345\255\246TypeScript.md" index 89a3834..90fb0e3 100644 --- "a/content/posts/base-js/\351\207\215\345\255\246TypeScript.md" +++ "b/content/posts/base-js/\351\207\215\345\255\246TypeScript.md" @@ -308,7 +308,7 @@ type OneOrManyOrNullStrings = OneOrManyOrNull; // 等价于 string | str
一个高阶知识:1. 父类构造器总是会使用它自己字段的值,而不是被重写的那一个,2. 但是它会使用被重写的方法。这是类字段和类方法一大区别,另一大区别就是this的指向问题了,类字段赋值的方法可以保证this不丢失。 -```JavaScript +```js class Animal { showName = () => { console.log('animal'); diff --git "a/content/posts/base-js/\351\230\262\346\212\226\350\212\202\346\265\201.md" "b/content/posts/base-js/\351\230\262\346\212\226\350\212\202\346\265\201.md" index 280faa1..1b835e4 100644 --- "a/content/posts/base-js/\351\230\262\346\212\226\350\212\202\346\265\201.md" +++ "b/content/posts/base-js/\351\230\262\346\212\226\350\212\202\346\265\201.md" @@ -10,7 +10,7 @@ tags: [JavaScript] 如果事件再设定事件内再次触发,就取消之前的一次事件。 -```JavaScript +```js function debounce(fn, wait) { let timeout = null return function () { @@ -25,7 +25,7 @@ function debounce(fn, wait) { 是否立即先执行一次版: -```JavaScript +```js /** * @description: 防抖函数 * @param {Function}: fn - 需要添加防抖的函数 @@ -56,7 +56,7 @@ function debounce(fn, time, immediate) { 事件高频触发,让事件根据设定的时间,按照一定的频率触发。 -```JavaScript +```js // timeout 版本 function throttle(fn, time) { let timeout; @@ -72,7 +72,7 @@ function throttle(fn, time) { } ``` -```JavaScript +```js // 时间戳版本 function throttle(fn, time) { /** 若想先执行,把 prev 设为 0 即可 */ diff --git "a/content/posts/node/koa\346\264\213\350\221\261\346\250\241\345\236\213.md" "b/content/posts/node/koa\346\264\213\350\221\261\346\250\241\345\236\213.md" index 04daaf4..2dff941 100644 --- "a/content/posts/node/koa\346\264\213\350\221\261\346\250\241\345\236\213.md" +++ "b/content/posts/node/koa\346\264\213\350\221\261\346\250\241\345\236\213.md" @@ -42,7 +42,7 @@ app.listen(3000); 显而易见,`app.use` 貌似被分割成了下面的样子: -```JavaScript +```js async function customMiddleware(ctx, next) { // ctx.request 请求部分处理 // ... @@ -62,7 +62,7 @@ async function customMiddleware(ctx, next) { 核心代码如下,很简单,就是往 `Application` 实例 即 `new Koa` 的属性 `middleware` 中推入函数 `fn`: -```JavaScript +```js use (fn) { this.middleware.push(fn) return this @@ -73,7 +73,7 @@ use (fn) { 再来看下 `listen` 方法: -```JavaScript +```js listen (...args) { const server = http.createServer(this.callback()) return server.listen(...args) @@ -84,7 +84,7 @@ listen (...args) { ### [callback](https://github.com/koajs/koa/blob/master/lib/application.js#L156) -```JavaScript +```js callback () { const fn = this.compose(this.middleware) @@ -106,7 +106,7 @@ handleRequest(ctx, fnMiddleware) { ### [koa-compose](https://github.com/koajs/compose) -```JavaScript +```js function compose (middleware) { // 错误处理省略... return function (context, next) { @@ -138,7 +138,7 @@ function compose (middleware) { 简易版 compose 实现: -```JavaScript +```js const middleware = []; let mw1 = async function (ctx, next) { console.log('next前,第一个中间件'); @@ -183,7 +183,7 @@ fn(); 通过不断调用 generator 函数的 next 方法来达到自动执行 generator 函数的,类似 async、await 函数自动执行。 -```JavaScript +```js function co(gen) { var ctx = this; var args = slice.call(arguments, 1) diff --git "a/content/posts/node/node\345\237\272\347\241\200.md" "b/content/posts/node/node\345\237\272\347\241\200.md" index 09e6f3c..87aee16 100644 --- "a/content/posts/node/node\345\237\272\347\241\200.md" +++ "b/content/posts/node/node\345\237\272\347\241\200.md" @@ -34,7 +34,7 @@ tags: [node] > [process.nextTick 和 setImmediate 的区别](https://juejin.cn/post/7102633430713630750) -```JavaScript +```js const EventEmitter = require('events'); const event = new EventEmitter() event.on('demo', (a, b) => { @@ -80,7 +80,7 @@ process 对象是一个全局变量,是一个 EventEmitter 实例,提供了 Node 中的异步默认是回调风格,`callback(err, returnValue)`: -```JavaScript +```js const fs = require('fs') fs.stat('.', (err, stats) => { // ... @@ -89,14 +89,14 @@ fs.stat('.', (err, stats) => { v14 之后,文件系统提供了 `fs/promises` 支持 promise 风格的使用方法: -```JavaScript +```js const fs = require('fs/promises'); fs.stat('.').then((stats) => {}).catch((err) => {}); ``` 为了统一,内置的 `util` 模块提供了 `promisify` 方法可以把所有标准 callback 风格方法转成 promise 风格方法: -```JavaScript +```js const fs = require('fs'); const { promisify } = require('util'); @@ -133,7 +133,7 @@ Buffer 类的实例类似于 0 到 255 之间的整型数组(其他整数会 补充:为了比较 Buffer 与 String 的效率,顺便学习呀一下 ab 这个命令,见[使用 Apache Bench 对网站性能进行测试](https://blog.csdn.net/dongdong9223/article/details/49248979) -```JavaScript +```js const http = require('http'); let s = ''; for (let i=0; i<1024*10; i++) { @@ -171,7 +171,7 @@ Node.js 本身就使用的事件驱动模型,为了解决单进程单线程对 Node.js 提供了 child_process 模块支持多进程,通过 child_process.fork(modulePath) 方法可以调用指定模块,衍生新的 Node.js 进程 。 -```JavaScript +```js const { fork } = require('child_process'); const os = require('os'); @@ -184,7 +184,7 @@ for (let i = 0, len = os.cpus().length; i < len; i++) { node 内置模块`cluster` 基于 child_process.fork 实现. -```JavaScript +```js const cluster = require('cluster'); // | | const http = require('http'); // | | const numCPUs = require('os').cpus().length; // | | 都执行了 diff --git a/content/posts/react/React-hooks.md b/content/posts/react/React-hooks.md index e8372b5..fa1b11c 100644 --- a/content/posts/react/React-hooks.md +++ b/content/posts/react/React-hooks.md @@ -30,7 +30,7 @@ const [demo, setDemo] = useState({name: 'yokiizx', age: 18}) 重点之一是 Update,这个在之前学习过:HostRoot 和 ClassComponent 共用一套 Update 数据结构,回顾一下: -```JavaScript +```js export type Update = {| eventTime: number, // 任务时间,通过performance.now()获取的毫秒数 lane: Lane, // 优先级相关字段 @@ -60,7 +60,7 @@ export type UpdateQueue = {| 而 Hook 使用的是另一种 Update 数据结构: -```JavaScript +```js // ReactFiberHooks.old.js type Update = {| lane: Lane, @@ -132,7 +132,7 @@ const fiber = { `dispatchAction` 最终会去调用 `scheduleUpdateOnFiber` 进入调度更新阶段。在 `beginWork` 时,对 `FunctionComponent` 调用 `updateFunctionComponent` 函数,最终调用 `renderWithHooks` 这个方法。 -```JavaScript +```js export function renderWithHooks( current: Fiber | null, workInProgress: Fiber, @@ -365,7 +365,7 @@ export type Effect = {| - 清理函数的执行 -```JavaScript +```js // pendingPassiveHookEffectsUnmount 中保存了所有需要执行销毁的 useEffect const unmountEffects = pendingPassiveHookEffectsUnmount; pendingPassiveHookEffectsUnmount = []; @@ -415,7 +415,7 @@ function schedulePassiveEffects(finishedWork: Fiber) { - 回调函数的执行 -```JavaScript +```js const mountEffects = pendingPassiveHookEffectsMount; pendingPassiveHookEffectsMount = []; for (let i = 0; i < mountEffects.length; i += 2) { @@ -449,7 +449,7 @@ for (let i = 0; i < mountEffects.length; i += 2) { `useRef(state)` 对应 hook 的 `memoizedState` 保存的就是 `{current: state}`。 -```JavaScript +```js function mountRef(initialValue: T): {|current: T|} { // 获取当前useRef hook const hook = mountWorkInProgressHook(); @@ -473,7 +473,7 @@ function updateRef(initialValue: T): {|current: T|} { - render 阶段给 fiber 添加 Ref flags -```JavaScript +```js // beginWork function markRef(current: Fiber | null, workInProgress: Fiber) { const ref = workInProgress.ref; @@ -493,7 +493,7 @@ function markRef(workInProgress: Fiber) { - commit 阶段对具有 Ref flags 的 fiber 执行对应的操作 -```JavaScript +```js // commit mutation 阶段对于ref属性改变的情况会先移除之前的 ref function commitDetachRef(current: Fiber) { const currentRef = current.ref; diff --git a/content/posts/react/React-setState.md b/content/posts/react/React-setState.md index b845dc6..4a75d7d 100644 --- a/content/posts/react/React-setState.md +++ b/content/posts/react/React-setState.md @@ -18,7 +18,7 @@ setState 最终调用的是 `this.updater.enqueueSetState(this, partialState, ca 首先要明确一点,针对 React 中的 state,不像 vue 那样,是没有做任何数据绑定的,当 state 发生变化时,是发生了一次重新渲染,**每一次渲染都能拿到独立的 state 状态,这个状态值是函数中的一个常量**。 -```JavaScript +```js const classComponentUpdater = { isMounted, enqueueSetState(inst, payload, callback) { @@ -71,7 +71,7 @@ const classComponentUpdater = { - `setTimeout`、`setInterval`、`DOM2级事件回调`、`Promise.then()的回调` 中,`setState` 触发的状态更新是同步的 - 其它直接处在 React 生命周期和 React 合成事件内的 `setState` 状态更新都是异步的 -```JavaScript +```js // 老版的 批量更新 export function batchedUpdates(fn: A => R, a: A): R { const prevExecutionContext = executionContext; @@ -97,7 +97,7 @@ export function batchedUpdates(fn: A => R, a: A): R { - 默认所有的 `setState` 会自动进行批处理,表现都是异步渲染的。 -```JavaScript +```js // 新版批量更新 依赖于 lane 模型:ensureRootIsScheduled 方法内 // Check if there's an existing task. We may be able to reuse it. if (existingCallbackNode !== null) { @@ -123,7 +123,7 @@ if (existingCallbackNode !== null) { 如果想要同步的更新行为,使用 `react-dom 的 flushSync`,把 `setState` 用 `flushSync` 的回调包裹一下。 -```JavaScript +```js // 注意写法,在同一个 flushSync内是无效的 import { flushSync } from 'react-dom'; @@ -141,7 +141,7 @@ console.log(this.state.count) // 2 从之前的文章中应该很清楚,状态更新都会走入 `scheduleUpdateOnFiber` 这个方法,此处我们关注这里: -```JavaScript +```js // scheduleUpdateOnFiber 部分代码: // export const NoContext = 0b0000000; 是一个二进制常量 // let executionContext = NoContext; 也是 executionContext 的初始值 @@ -174,7 +174,7 @@ newCallbackNode = scheduleSyncCallback( `setState` 的合并策略见 `getStateFromUpdate` 这个方法: -```JavaScript +```js function getStateFromUpdate( workInProgress: Fiber, queue: UpdateQueue, update: Update, @@ -210,7 +210,7 @@ function getStateFromUpdate( workInProgress: Fiber, 它的更新流程最终也是要走入 `scheduleUpdateOnFiber`,所以与 `this.setState` 的区别不大。但是,hook 的 "setState" 不会做自动的合并。 -```JavaScript +```js this.state = { name: 'yokiizx', age: 18 diff --git "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\206.md" "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\206.md" index 82b8895..cc3a00c 100644 --- "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\206.md" +++ "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\206.md" @@ -57,7 +57,7 @@ const demo = React.createElement( 朴实无华的 API `createElement`,如名字一样,就是用来创建 element 的。 `React.createElement(type, config, children)`,这也是为什么 React17 之前每个文件需要手动引入 React,React17 之后则不需要了。 -```JavaScript +```js export function createElement(type, config, children) { let propName; @@ -133,7 +133,7 @@ export function createElement(type, config, children) { 再来看看 `ReactElement`: -```JavaScript +```js const ReactElement = function(type, key, ref, self, source, owner, props) { const element = { // This tag allows us to uniquely identify this as a React Element @@ -173,7 +173,7 @@ export function isValidElement(object) { - [介绍全新的 JSX 转换](https://zh-hans.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) - [@babel/plugin-transform-react-jsx](https://www.babeljs.cn/docs/babel-plugin-transform-react-jsx#react-automatic-runtime) -```JavaScript +```js function App() { return

Hello World

; } @@ -189,7 +189,7 @@ function App() { 看看源码: -```JavaScript +```js // 1.react/jsx/ReactJSXElement ReactElement与上方的基本一样 // 2.替代 React.createElement()的jsx() export function jsx(type, config, maybeKey) { @@ -253,7 +253,7 @@ export function jsx(type, config, maybeKey) { 这个 api 在 React18 之前使用,也是有必要学习一下的。 -```JavaScript +```js // render 就是调用了 legacyRenderSubtreeIntoContainer 个方法 export function render( element: React$Element, @@ -332,7 +332,7 @@ React18 引入的方法,取代了 `ReactDom.render`,在 `packages/react-dom/ 1. ClassComponent 和 pureComponent,都导出自 ReactBaseClasses.js -```JavaScript +```js function Component(props, context, updater) { this.props = props; this.context = context; @@ -394,7 +394,7 @@ JSX 转为的 ReactElement 只是一个简单的数据结构,携带着 key,r - 除了在空闲时触发回调的功能外,Scheduler 还提供了多种调度优先级供任务设置 - Reconciler(协调器)—— 负责找出变化的组件 - React 15, 协调器是递归处理处理虚拟 DOM,16 后可以中断/恢复了,看代码: - ```JavaScript + ```js function workLoopConcurrent() { // Perform work until Scheduler asks us to yield while (workInProgress !== null && !shouldYield()) { @@ -403,7 +403,7 @@ JSX 转为的 ReactElement 只是一个简单的数据结构,携带着 key,r } ``` - React 16 解决中断更新时 DOM 渲染不完全的方法是,Reconciler 与 Renderer 不再是交替工作。当 Scheduler 将任务交给 Reconciler 后,Reconciler 会为变化的虚拟 DOM 打上代表增/删/更新的标记。 - ```JavaScript + ```js // ReactFiberFlags.js 中 export const Placement = /* */ 0b0000000000010; export const Update = /* */ 0b0000000000100; diff --git "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2062.md" "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2062.md" index 2813fff..79a4a0f 100644 --- "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2062.md" +++ "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2062.md" @@ -13,7 +13,7 @@ tags: [React] 那么来看看 Fiber 的真实面容: -```JavaScript +```js function FiberNode( tag: WorkTag, pendingProps: mixed, @@ -66,7 +66,7 @@ React 中最多存在两条 Fiber 树: - currentFiber 树,当前在屏幕上内容对应的 Fiber 树 - workInProgressFiber 树,正在内存中构建的 Fiber 树。 -```JavaScript +```js // 两棵树通过 alternate 连接 currentFiber.alternate === workInProgressFiber; workInProgressFiber.alternate === currentFiber; @@ -92,7 +92,7 @@ function App() { 1. 调用 `createFiberRoot`,`new FiberRootNode` 去创建一个根节点,这个根节点暂且称为 `FiberRoot` 2. `createFiberRoot` 内部创建了 `FiberRoot` 后,调用 `createHostRootFiber`,创建 `RootFiber`,并加入初始化更新队列去 - ```JavaScript + ```js export function createFiberRoot( containerInfo: any, tag: RootTag, diff --git "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2063.md" "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2063.md" index 7f6c0d6..8dcaa39 100644 --- "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2063.md" +++ "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2063.md" @@ -18,7 +18,7 @@ React 工作的整个流程: 在 `render --> legacyRenderSubtreeIntoContainer` 方法中,创建完 fiberRoot 后,就会调用 `updateContainer` 方法,创建 Update 对象,并把 update 加入更新队列,最后调度更新。 -```JavaScript +```js export function updateContainer( element: ReactNodeList, container: OpaqueRoot, @@ -71,7 +71,7 @@ export function updateContainer( 上方一共有三种组件,HostRoot 和 ClassComponent 共用一套 Update 数据结构,FunctionComponent 使用另一种 Update 数据结构。 -```JavaScript +```js export function createUpdate(eventTime: number, lane: Lane): Update<*> { const update: Update<*> = { eventTime, // 任务时间,通过performance.now()获取的毫秒数 @@ -93,7 +93,7 @@ export function createUpdate(eventTime: number, lane: Lane): Update<*> { 再来看看 UpdateQueue 对象: -```JavaScript +```js // 在创建 fiberRoot 和 mount 阶段都会调用该方法 export function initializeUpdateQueue(fiber: Fiber): void { const queue: UpdateQueue = { @@ -119,7 +119,7 @@ export function initializeUpdateQueue(fiber: Fiber): void { 对于 `shared.pending` 关注一下 enqueueUpdate: -```JavaScript +```js export function enqueueUpdate(fiber: Fiber, update: Update) { const updateQueue = fiber.updateQueue if (updateQueue === null) { @@ -146,7 +146,7 @@ export function enqueueUpdate(fiber: Fiber, update: Update) { 在进入 render 阶段后,`shared.pending` 会被剪开 接在 `lastBaseUpdate` 之后形成 `baseUpdate` 这个单链表。接下来遍历这个单链表,`fiber.updateQueue.baseState` 为初始 state,依次与遍历到的每个 `Update` 计算并产生新的 `state`。这些步骤发生在 `processUpdateQueue` 里: -```JavaScript +```js export function processUpdateQueue( workInProgress: Fiber, props: any, diff --git "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2064.md" "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2064.md" index df1f7f1..3d19994 100644 --- "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2064.md" +++ "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2064.md" @@ -8,7 +8,7 @@ tags: [React] ### markUpdateLaneFromFiberToRoot (获取到 fiberRoot) -```JavaScript +```js function markUpdateLaneFromFiberToRoot(sourceFiber: Fiber, lane: Lane): FiberRoot | null { // Update the source fiber's lanes sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane) @@ -49,7 +49,7 @@ function markUpdateLaneFromFiberToRoot(sourceFiber: Fiber, lane: Lane): FiberRoo 该方法的源码的注释需要认真理解,非常之重要。 -```JavaScript +```js // Use this function to schedule a task for a root. There's only one task per // root; if a task was already scheduled, we'll check to make sure the priority // of the existing task is the same as the priority of the next level that the diff --git "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2065.md" "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2065.md" index 169cd53..dc5c0c8 100644 --- "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2065.md" +++ "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2065.md" @@ -8,7 +8,7 @@ tags: [React] 这两个方法又分别调用了 `renderRootSync` 和 `renderRootConcurrent`(这两个方法返回 `exitStatus` 供后续使用),其内部又分别调用了 `workLoopSync` 和 `workLoopConcurrent`: -```JavaScript +```js function workLoopSync() { // Already timed out, so perform work without checking if we need to yield. while (workInProgress !== null) { @@ -28,7 +28,7 @@ function workLoopConcurrent() { 源码中追踪到最后是 `throw new Error('This module must be shimmed by a specific build.')`,就是这个模块必须由特定的构建进行微调,下面是这个方法的模拟实现: -```JavaScript +```js export function shouldYieldToHost(): boolean { if ( (expectedNumberOfYields !== -1 && @@ -48,7 +48,7 @@ export function shouldYieldToHost(): boolean { `performUnitOfWork(unitOfWork: Fiber)`,入参即是 workInProgress Fiber,这是一个全局变量。 -```JavaScript +```js function performUnitOfWork(unitOfWork: Fiber): void { // The current, flushed, state of this fiber is the alternate. Ideally // nothing should rely on this, but relying on it here means that we don't @@ -83,7 +83,7 @@ function performUnitOfWork(unitOfWork: Fiber): void { 举个例子: -```JavaScript +```js function App() { return (
@@ -115,7 +115,7 @@ ReactDOM.render(, document.getElementById("root")); beginWork 源码比较长,这里简化一下主要逻辑: -```JavaScript +```js function beginWork( current: Fiber | null, workInProgress: Fiber, @@ -204,7 +204,7 @@ function beginWork( 在 update 阶段,可以调用 `bailoutOnAlreadyFinishedWork` 来复用 current 上的节点。 在 mount 阶段,直接根据 tag 不同,创建不同的子 Fiber 节点。而根据 tag 不同来创建 Fiber 节点,对于常见的组件(FunctionComponent/ClassComponent/HostComponent)最终都会进入 `reconcileChildren` 这个方法: -```JavaScript +```js export function reconcileChildren( current: Fiber | null, workInProgress: Fiber, @@ -247,7 +247,7 @@ mountChildFibers 和 reconcileChildFibers 逻辑基本相同,唯一不同的 `ReactFiberFlags.js` 这个文件中存储着 flags(v16叫effectTag) 对应的操作: -```JavaScript +```js // DOM需要插入到页面中 export const Placement = /* */ 0b00000000000010; // DOM需要更新 @@ -270,7 +270,7 @@ beginWork 的流程图: `completeWork` 的源码非常长,不过与 `beginWork` 一样,也是根据 fiber.tag 调用不同的处理逻辑,方法内就一个 `switch...case`,有些组件要处理的逻辑较多,下面只关注部分组件类型的主要逻辑。 -```JavaScript +```js function completeWork( current: Fiber | null, workInProgress: Fiber, @@ -310,7 +310,7 @@ function completeWork( 先重点关注页面渲染所必须的 `HostComponent`(即原生 DOM 组件对应的 Fiber 节点): -```JavaScript +```js case HostComponent: { popHostContext(workInProgress) const rootContainerInstance = getRootHostContainer() @@ -375,7 +375,7 @@ case HostComponent: { 也是根据 current 是否为 null 来判断是 mount 还是 update;同时根据 workInProgress.stateNode 是否已存在对应 DOM 节点来判断是否进入更新还是去新建。 - 如果进入 update,则会走入 `updateHostComponent` 方法,这个方法最终会生成 `updatePayload` 挂载到 `workInProgress.updateQueue` 上,最后在 `commit` 阶段渲染到页面上。 - ```JavaScript + ```js // updatePayload为数组形式,他的偶数索引的值为变化的prop key,奇数索引的值为变化的prop value workInProgress.updateQueue = (updatePayload: any); ``` @@ -388,7 +388,7 @@ case HostComponent: { 继续归--继续退栈,依次 `completeUnitOfWork`,`performUnitOfWork`,`workLoopSync`,`renderRootSync`,`performSyncWorkOnRoot`,最后执行 `performSyncWorkOnRoot` 的代码: -```JavaScript +```js commitRoot(root); // 进入 commit 阶段 ``` @@ -396,7 +396,7 @@ case HostComponent: { 还是因为在 "归" 阶段,最终就是会形成从 rootFiber 到最后一个 fiber 的 effectList: -```JavaScript +```js nextEffect nextEffect rootFiber.firstEffect -----------> fiber -----------> fiber ``` diff --git "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2066.md" "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2066.md" index ba5c366..737b3a2 100644 --- "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2066.md" +++ "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2066.md" @@ -14,7 +14,7 @@ commit 阶段主要分为:before mutation,mutation,layout 这三个阶段 开始三个阶段之前先看下 `commitRootImpl` 的主要内容: -```JavaScript +```js // 保存之前的优先级,以同步优先级执行,执行完毕后恢复之前优先级 const previousLanePriority = getCurrentUpdateLanePriority(); setCurrentUpdateLanePriority(SyncLanePriority); @@ -32,7 +32,7 @@ shouldFireAfterActiveInstanceBlur = false; 遍历 effectList,进入主函数 `commitBeforeMutationEffects`: -```JavaScript +```js function commitBeforeMutationEffects() { while (nextEffect !== null) { const current = nextEffect.alternate @@ -82,7 +82,7 @@ function commitBeforeMutationEffects() { 现在到了执行 DOM 操作的阶段: -```JavaScript +```js // commitImpl // 同样也是遍历 effectList, before mutation/mutation/layout 都类似 nextEffect = firstEffect; @@ -99,7 +99,7 @@ do { 主函数 `commitMutationEffects`: -```JavaScript +```js function commitMutationEffects(root: FiberRoot, renderPriorityLevel: ReactPriorityLevel) { // 遍历 effectList while (nextEffect !== null) { @@ -173,7 +173,7 @@ function commitMutationEffects(root: FiberRoot, renderPriorityLevel: ReactPriori `commitPlacement` 函数: -```JavaScript +```js function commitPlacement(finishedWork: Fiber): void { if (!supportsMutation) { return; @@ -249,7 +249,7 @@ rootFiber -----> App -----> div -----> p - tag 为 HostComponent,会调用 `commitUpdate`,最终会调用 `updateDOMProperties`: -```JavaScript +```js for (let i = 0; i < updatePayload.length; i += 2) { const propKey = updatePayload[i]; const propValue = updatePayload[i + 1]; @@ -271,7 +271,7 @@ for (let i = 0; i < updatePayload.length; i += 2) { 递归的将 fiber 节点对应的 DOM 节点从页面中删除。 -```JavaScript +```js function commitDeletion( finishedRoot: FiberRoot, current: Fiber, @@ -302,7 +302,7 @@ function commitDeletion( 这阶段在 DOM 渲染完成之后,所以该阶段触发的生命周期钩子和 hook 可以直接访问到已经改变后的 DOM。 layout 阶段也是递归遍历 effectList。具体执行函数是 `commitLayoutEffects`。 -```JavaScript +```js function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) { // ... while (nextEffect !== null) { @@ -341,7 +341,7 @@ layout 阶段结束。 注意点: fiberRootNode 的 current 指针切换时机 -- mutation 和 layout 之间: -```JavaScript +```js // 递归 mutation root.current = finishedWork // 递归 layout diff --git "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2067-Diff\347\256\227\346\263\225.md" "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2067-Diff\347\256\227\346\263\225.md" index a3d6458..16ef946 100644 --- "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2067-Diff\347\256\227\346\263\225.md" +++ "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2067-Diff\347\256\227\346\263\225.md" @@ -19,7 +19,7 @@ diff 算法的本质是:JSX 对象和 current Fiber 对比,生成 workInProg ### reconcileChildFibers -```JavaScript +```js function reconcileChildFibers( returnFiber: Fiber, currentFirstChild: Fiber | null, @@ -75,7 +75,7 @@ function reconcileChildFibers( 单个节点,关注一下 `reconcileSingleElement` 这个方法: -```JavaScript +```js function reconcileSingleElement( returnFiber: Fiber, currentFirstChild: Fiber | null, @@ -164,7 +164,7 @@ ul -> p (变成了单个节点) 2. 节点新增/减少 3. 节点位置变化 -```JavaScript +```js function reconcileChildrenArray( returnFiber: Fiber, currentFirstChild: Fiber | null, @@ -315,7 +315,7 @@ function reconcileChildrenArray( - 都遍历完了,diff 结束 - newChildren 遍历完, oldFiber 没有遍历完,把剩下的所有 oldFiber 标记为 `DELETION` - ```JavaScript + ```js if (newIdx === newChildren.length) { // We've reached the end of the new children. We can delete the rest. deleteRemainingChildren(returnFiber, oldFiber); @@ -323,7 +323,7 @@ function reconcileChildrenArray( } ``` - oldFiber 遍历完,newChildren 没有遍历完,剩下的 newChildren 可以全部为插入 - ```JavaScript + ```js // oldFiber 遍历完了, 从newIndex的位置继续遍历剩下的 newChildren, // 剩下的全部是可以直接 PLACEMENT 的 if (oldFiber === null) { @@ -333,7 +333,7 @@ function reconcileChildrenArray( ``` - 都未遍历完,找到移动的节点,并插入正确的位置,就是 - ```JavaScript + ```js // 能走到这里,说明单链和数组都没有遍历完,那么一定是发生了位置的变换 const existingChildren = mapRemainingChildren(returnFiber, oldFiber); ``` @@ -343,7 +343,7 @@ function reconcileChildrenArray( 找到可复用节点之后要插入正确的位置,是通过比较 `lastPlacedIndex` 和 `oldIndex`。 - ```JavaScript + ```js // placeChild 主要逻辑 newFiber.index = newIndex; // 把新的位置给newFiber const current = newFiber.alternate; @@ -366,7 +366,7 @@ function reconcileChildrenArray( 最后,如果 `existingChildren` 不为空,说明剩下的 oldFiber 也都无用,需要被标记为 DELETION: - ```JavaScript + ```js existingChildren.forEach((child) => deleteChild(returnFiber, child)) ``` @@ -374,7 +374,7 @@ function reconcileChildrenArray( 1. abcd --> acdb (字母表示 key) -```JavaScript +```js ===第一轮遍历开始=== a(之后)vs a(之前) key不变,可复用 @@ -441,7 +441,7 @@ oldIndex 1 < lastPlacedIndex 3 // 之前节点为 abcd,所以b.index === 1 2. abcd --> dabc (字母表示 key) -```JavaScript +```js ===第一轮遍历开始=== d(之后)vs a(之前) key改变,不能复用,跳出遍历 diff --git "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2068-Scheduler.md" "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2068-Scheduler.md" index 6590581..e6cedc5 100644 --- "a/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2068-Scheduler.md" +++ "b/content/posts/react/React\345\237\272\347\241\200\345\216\237\347\220\2068-Scheduler.md" @@ -31,7 +31,7 @@ MacroTask --> MicroTask--> requestAnimationFrame--> 浏览器重排 / 重绘--> 时间切片选择使用 `MessageChannel` 实现,它的执行时机比 `setTimeout` 更靠前。 -```JavaScript +```js // Scheduler 将需要被执行的回调函数作为 MessageChannel 的回调执行 const channel = new MessageChannel(); const port = channel.port2; @@ -72,7 +72,7 @@ requestHostCallback = function(cb) { 之前学习过 `workLoopSync`,是时候看看 `workLoopConcurrent`了: -```JavaScript +```js function workLoopConcurrent() { // Perform work until Scheduler asks us to yield while (workInProgress !== null && !shouldYield()) { @@ -83,7 +83,7 @@ function workLoopConcurrent() { 唯一的不同就是多了个 `shouldYield` 是否暂停的判断,这个方法是从 `shceduler` 内部抛出来的。 -```JavaScript +```js shouldYieldToHost = function() { const currentTime = getCurrentTime(); // deadline = currentTime + yieldInterval; @@ -113,7 +113,7 @@ shouldYieldToHost = function() { > 注释写的很明白,主要就是看是否有剩余时间是否用完,在 Schdeduler 中,为任务分配的初始剩余时间为 5ms,随着应用的运行,根据 fps 动态调整可执行时间。 -```JavaScript +```js forceFrameRate = function(fps) { if (fps < 0 || fps > 125) return if (fps > 0) { @@ -133,7 +133,7 @@ OK,到这里,`performUnitOfWork` 是怎么暂停的已经清除,主要是 `Scheduler` 是独立于 React 的包,**它的优先级也是独立于 React 的优先级**。 -```JavaScript +```js // SchedulerPriorities.js export const NoPriority = 0; export const ImmediatePriority = 1; @@ -167,7 +167,7 @@ function unstable_runWithPriority(priorityLevel, eventHandler) { 可见,`Scheduler` 有 5 种优先级,默认是 `NormalPriority`,`ImmediatePriority` 是最高优先级,会立即执行。 -```JavaScript +```js function commitRoot(root) { // 返回 scheduler 中的 currentPriorityLevel const renderPriorityLevel = getCurrentPriorityLevel(); @@ -184,7 +184,7 @@ function commitRoot(root) { 看一下 `scheduler` 的这个方法 `unstable_scheduleCallback`,对外抛出一般是 `schedulerCallback`,直译过来就是安排回调,也就可以理解为是调度任务: -```JavaScript +```js // Times out immediately var IMMEDIATE_PRIORITY_TIMEOUT = -1; // Eventually times out @@ -284,7 +284,7 @@ function unstable_scheduleCallback(priorityLevel, callback, options) { 继续往下走,任务的重启就在 `requestHostCallback` 这个方法,这个方法根据是否支持 `MessageChannel` 也有两种实现,暂且不关注,主要关注它后面的流程,`requestHostCallback` 调用了 `flushWork`,再调用 `workLoop`: -```JavaScript +```js function workLoop(hasTimeRemaining, initialTime) { let currentTime = initialTime; advanceTimers(currentTime); @@ -346,7 +346,7 @@ function workLoop(hasTimeRemaining, initialTime) { 重点是:如果 `continuationCallback` 即调度注册的回调函数,它的返回值为 `function` 时,会把 `continuationCallback` 作为当前任务的回调函数,否则 `pop(taskQueue);` 把当前执行的任务清除 `taskQueue`,而在 `render` 阶段 `performConcurrentWorkOnRoot` 函数的末尾有这么段代码: -```JavaScript +```js if (root.callbackNode === originalCallbackNode) { // The task node scheduled for this root is the same one that's // currently executed. Need to return a continuation. diff --git "a/content/posts/tool/CommonJS\345\222\214ESM.md" "b/content/posts/tool/CommonJS\345\222\214ESM.md" index e726350..d91a0a6 100644 --- "a/content/posts/tool/CommonJS\345\222\214ESM.md" +++ "b/content/posts/tool/CommonJS\345\222\214ESM.md" @@ -14,7 +14,7 @@ CJS 输出的是值的拷贝。 `module.exports`: -```JavaScript +```js // node 对 js 文件编译后添加了呃顶层变量 (function(exports,require,module,__filename,__dirname) { // 文件模块 @@ -24,7 +24,7 @@ CJS 输出的是值的拷贝。 `require` 模块加载一次就会被缓存,后续再加载就是加载的缓存。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值 -```JavaScript +```js // main.js const {a, aPlusOne} = require('./b') // b 中 a = 100 @@ -35,7 +35,7 @@ console.log(a); // 100 `require`: -```JavaScript +```js // id 为路径标识符 function require(id) { /* 查找 Module 上有没有已经加载的 js 对象*/ diff --git "a/content/posts/tool/VsCode\346\240\274\345\274\217\345\214\226\345\273\272\350\256\256.md" "b/content/posts/tool/VsCode\346\240\274\345\274\217\345\214\226\345\273\272\350\256\256.md" index 3c243fd..f21db9a 100644 --- "a/content/posts/tool/VsCode\346\240\274\345\274\217\345\214\226\345\273\272\350\256\256.md" +++ "b/content/posts/tool/VsCode\346\240\274\345\274\217\345\214\226\345\273\272\350\256\256.md" @@ -63,13 +63,13 @@ categories: [tool] 接着修改 `.eslintrc` - ```JavaScript + ```js "extends": ["some others...", "plugin:prettier/recommended"] ``` 看看 `plugin:prettier/recommended` 干了什么 - ```JavaScript + ```js // node_modules/eslint-plugin-prettier/eslint-plugin-prettier.js module.exports = { configs: { diff --git a/content/posts/tool/babel.md b/content/posts/tool/babel.md index 463d108..7a303ae 100644 --- a/content/posts/tool/babel.md +++ b/content/posts/tool/babel.md @@ -19,7 +19,7 @@ tags: [babel] - 词法分析:把源代码转换成 tokens 数组(令牌流),形式如下: PS: 旧的`babylon`中解析完是有 tokens 的,新的`@babel/parser`中没了这个字段,如有大佬知道原因,请留言告知,感谢~ - ```JavaScript + ```js [ { type: { ... }, value: "n", start: 0, end: 1, loc: { ... } }, { type: { ... }, value: "*", start: 2, end: 3, loc: { ... } }, @@ -30,7 +30,7 @@ tags: [babel] 每一个 type 有一组属性来描述该令牌: - ```JavaScript + ```js { type: { label: 'name', @@ -53,7 +53,7 @@ tags: [babel] - 语法分析:把词法分析后的 tokens 数组(令牌流)转换成 AST(抽象语法树),形式如下: - ```JavaScript + ```js { type: "FunctionDeclaration", id: { @@ -116,7 +116,7 @@ tags: [babel] visitor 内方法访问的实际上是 `path` ---> `path` 是表示两个节点之间连接的对象,这个对象还有很多其他的元数据,如: -```JavaScript +```js /* ------ 节点 ------ */ { type: "FunctionDeclaration", @@ -187,7 +187,7 @@ visitor 内方法访问的实际上是 `path` ---> `path` 是表示两个节点 ``` - `babel-loader`,前端项目中 `webpack` 更常用的是 `babel-loader` 在 webpack 中,loader 本质上就是一个函数: - ```JavaScript + ```js // loader 自身实际上只是识别文件,然后注入参数,真正的编译依赖 @babel/core const core = require('@babel/core') /** @@ -309,13 +309,13 @@ const p = new Promise() - `false`,说简单点就是不使用 polyfill - `entry`,全量引入 polyfill,同时需要项目入口文件的头部引入: - ```JavaScript + ```js import "@babel/polyfill" // babel 7.4 后被拆成了 core-js/stable 和 regenerator-runtime/runtime ``` - `usage`,按需添加 polyfill,根据配置的浏览器兼容,以及代码中 使用到的 Api 添加,不需要去项目入口文件引入: - ```JavaScript + ```js { "presets": [ [ @@ -347,7 +347,7 @@ npm i @babel/plugin-transform-runtime -D `@babel/plugin-transform-runtime` 的作用是,将 `helper` 函数,都转换成为对` @babel/runtime` 内模块的引用。 -```JavaScript +```js // @babel/runtime 配置 { "presets": [ @@ -458,7 +458,7 @@ var Foo = npm i @babel/parser -s ``` -```JavaScript +```js /* ------- test.js -------*/ import * as babelParser from '@babel/parser'; @@ -524,7 +524,7 @@ Node { npm i @babel/traverse -D ``` -```JavaScript +```js import * as babelParser from '@babel/parser'; import _traverse from '@babel/traverse'; const traverse = _traverse.default; @@ -554,7 +554,7 @@ Babel Types 模块是一个用于 AST 节点的 Lodash 式工具库,它包含 npm i @babel/types -s ``` -```JavaScript +```js // ... import * as t from "babel-types"; @@ -580,7 +580,7 @@ npm install @babel/generator -D 一个完整的例子: -```JavaScript +```js import generate from "@babel/generator"; import * as babelParser from '@babel/parser'; import _traverse from '@babel/traverse'; @@ -624,13 +624,13 @@ console.log('📌📌📌 ~ output', output); 实践第一步先小试牛刀把一个 `hello` 方法,改名为 `world` 方法: -```JavaScript +```js const hello = () => {} ``` > 前期不熟悉 AST 各种标识的情况下,可以去[AST 在线平台转换(基于 acorn)](https://astexplorer.net/)查看对应的 AST。 -```JavaScript +```js /** * 从上方的网站找到了 hello 的节点位置: * "id": { diff --git "a/content/posts/vue/Vue\345\223\215\345\272\224\345\274\217\345\216\237\347\220\206.md" "b/content/posts/vue/Vue\345\223\215\345\272\224\345\274\217\345\216\237\347\220\206.md" index 9ba15d8..93b6931 100644 --- "a/content/posts/vue/Vue\345\223\215\345\272\224\345\274\217\345\216\237\347\220\206.md" +++ "b/content/posts/vue/Vue\345\223\215\345\272\224\345\274\217\345\216\237\347\220\206.md" @@ -27,7 +27,7 @@ Vue 初始化实例时,通过 `Object.defineProperty` 为 `data` 中的所有 看下 `defineReactive` 源码: -```JavaScript +```js // 以下所有代码为简化后的核心代码,详细的见vue2的gihub仓库哈 export function defineReactive(obj: object, key: string, val?: any, ...otehrs) { const dep = new Dep() @@ -53,7 +53,7 @@ export function defineReactive(obj: object, key: string, val?: any, ...otehrs) { 再看下 `Dep` 源码: -```JavaScript +```js /** * 被观察者,依赖收集,收集的是使用到了这个数据的组件对应的 watcher */ @@ -92,7 +92,7 @@ export default class Dep { 先看下生命周期 `mountComponent` 函数: -```JavaScript +```js // Watcher 在此处被实例化 export function mountComponent( vm: Component, @@ -118,7 +118,7 @@ export function mountComponent( 再看看 `Watcher` 源码 -```JavaScript +```js export default class Watcher implements DepTarget { constructor(vm: Component | null,expOrFn: string | (() => any), /* ... */) { this.getter = expOrFn @@ -158,7 +158,7 @@ export default class Watcher implements DepTarget { 当 data 中的数据发生改变时,就会触发 setter 函数的执行,进而触发 Dep 的 notify 函数。 -```JavaScript +```js notify() { for (let i = 0, l = subs.length; i < l; i++) { const sub = subs[i] diff --git a/content/posts/webpack/webpack.md b/content/posts/webpack/webpack.md index 386c9dc..5dc8ad9 100644 --- a/content/posts/webpack/webpack.md +++ b/content/posts/webpack/webpack.md @@ -85,7 +85,7 @@ module.exports = { `const complier = webpack(config)`,[./lib/webpack.js](https://github.com/webpack/webpack/blob/main/lib/webpack.js#L102): -```JavaScript +```js // 部分代码省略 const webpack = (options, callback) => { const create = () => { @@ -148,7 +148,7 @@ const createCompiler = rawOptions => { `compiler.run()`,[./lib/Compiler](https://github.com/webpack/webpack/blob/main/lib/Compiler.js) -```JavaScript +```js class Compiler { constructor(context, options = {}) { this.hooks = Object.freeze({ @@ -248,7 +248,7 @@ class Compiler { 从 VsCode 的调用栈来看,之后进入到了 `EntryPlugin`,在这里注册了 make 钩子的回调,这个是在初始化阶段 `webpack(config)` 内 `new WebpackOptionsApply().process(options, compiler)` 时就已经注册好的,当 make 钩子被触发时进入: -```JavaScript +```js compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => { compilation.addEntry(context, dep, options, err => { callback(err); @@ -258,7 +258,7 @@ compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => { 来看看 `addEntry`,[./lib/Compilation.js](https://github.com/webpack/webpack/blob/main/lib/Compilation.js) -```JavaScript +```js class Compilation { constructor(compiler, params) { this.hooks = Object.freeze({ @@ -392,7 +392,7 @@ class Compilation { 在执行完成 `compilation.emitAsset` 后会回到 `compiler` 文件中执行代码如下: -```JavaScript +```js // lib/compiler.js class Compiler { /** 最终执行这个方法 */ diff --git "a/content/posts/webpack/webpack\344\271\213loader.md" "b/content/posts/webpack/webpack\344\271\213loader.md" index 3182688..8081dcb 100644 --- "a/content/posts/webpack/webpack\344\271\213loader.md" +++ "b/content/posts/webpack/webpack\344\271\213loader.md" @@ -13,7 +13,7 @@ Loader 就像一个翻译员,能将源文件经过转化后输出新的结果 也就很好理解为什么 loader 的配置是在 module 内的: -```JavaScript +```js module.exports = { module: { rules: [ @@ -34,7 +34,7 @@ module.exports = { 开发一个 loader 的基本形式: -```JavaScript +```js module.exports = function (source ) { // 做你想做的~ return source; diff --git "a/content/posts/webpack/webpack\344\271\213plugin.md" "b/content/posts/webpack/webpack\344\271\213plugin.md" index 9cc4fe8..6b85a3c 100644 --- "a/content/posts/webpack/webpack\344\271\213plugin.md" +++ "b/content/posts/webpack/webpack\344\271\213plugin.md" @@ -39,7 +39,7 @@ const { 我们从命名就能看出来大致的区别,分为同步/异步,串行/并行/瀑布流/循环类型等。钩子的目的是为了显示的声明触发监听事件时传入的参数,以及订阅该钩子的 callback 函数所接收到的参数,举个简单例子: -```JavaScript +```js const demo = new SyncHook(['hello']) // hello 字符串为参数占位符 demo.tap('Say', (str1, str2) => { console.log(str1, str2) // hello-world, undefined @@ -71,7 +71,7 @@ demo.call('hello-world') 说了一堆 Tapable 的概念,插件到底咋整呢?看官网示例: -```JavaScript +```js // ./CustomPlugin.js class HelloWorldPlugin { apply(compiler) { @@ -100,7 +100,7 @@ module.exports = { 在之前学习 webpack 核心流程时,`createCompiler` 方法体内有这么一段代码: -```JavaScript +```js /** 加载自定义配置的插件 注意这就是为什么插件需要一个 apply 方法 */ if (Array.isArray(options.plugins)) { for (const plugin of options.plugins) { @@ -123,7 +123,7 @@ apply 虽然是一个函数,但是从设计上就只有输入,webpack 不 ca apply 函数运行时会得到参数 compiler ,以此为起点可以调用 hook 对象注册各种钩子回调,例如:compiler.hooks.make.tapAsync ,这里面 make 是钩子名称,tapAsync 定义了钩子的调用方式,webpack 的插件架构基于这种模式构建而成,插件开发者可以使用这种模式在钩子回调中,插入特定代码。webpack 各种内置对象都带有 hooks 属性,比如 compilation 对象: -```JavaScript +```js class SomePlugin { apply(compiler) { compiler.hooks.thisCompilation.tap('SomePlugin', (compilation) => { diff --git "a/content/posts/webpack/webpack\344\271\213\347\220\220\347\242\216\347\237\245\350\257\206\347\202\271.md" "b/content/posts/webpack/webpack\344\271\213\347\220\220\347\242\216\347\237\245\350\257\206\347\202\271.md" index ebc7d92..2c75b17 100644 --- "a/content/posts/webpack/webpack\344\271\213\347\220\220\347\242\216\347\237\245\350\257\206\347\202\271.md" +++ "b/content/posts/webpack/webpack\344\271\213\347\220\220\347\242\216\347\237\245\350\257\206\347\202\271.md" @@ -32,7 +32,7 @@ webpack-dev-server 启动服务后,当文件发生了变动,会触发重新 我们平时说的代码分割优化指的就是第三种 `optimization.splitChunks`,一般配置长这样: -```JavaScript +```js module.exports = { optimization:{ splitChunks:{