Skip to main content

2-28

1 算法2.28

题目:210.课程表 II(😕)

拓扑排序BFS(广度优先搜索)

现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。

可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。

示例

示例1

输入: 2, [[1,0]]

输出: [0,1]

解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1]

示例2

输入: 4, [[1,0],[2,0],[3,1],[3,2]]

输出: [0,1,2,3] or [0,2,1,3]

解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。 因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3]

说明

  1. 输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
  2. 你可以假定输入的先决条件中没有重复的边。

前提知识

图、拓扑排序、DFS、BFS(深度、广度优先搜索)

分析

此题虽然根据他人代码,我也做出来了,但过程还是没理解。由于本身对图认识不够,后续还要加强练习

题解

function main() {
const numCourses = 4
const prerequisites = [
[1, 0],
[2, 0],
[3, 1],
[3, 2],
]

console.log('[]:', findOrder(numCourses, prerequisites))
}

main()

function findOrder(numCourses: number, prerequisites: number[][]): number[] {
let res = []

// 计算入度和关系
// 这里的关系是 依赖课程: [课程1,课程2],也就是指课程1和2依赖于依赖课程
const inDeeps = new Array(numCourses).fill(0)
const relationship: {
[key: number]: number[]
} = {}
for (let i = 0; i < prerequisites.length; ++i) {
const value = prerequisites[i][0]
const dep = prerequisites[i][1]

inDeeps[value]++

if (relationship[dep]) {
relationship[dep].push(value)
} else {
relationship[dep] = [value]
}
}

// 生成队列
const queue = []
for (let i = 0; i < inDeeps.length; ++i) {
// 只需要入度为0的,因为这是开始条件,注意queue中放的是课程编号
if (inDeeps[i] == 0) queue.push(i)
}

console.log('[queue]:', queue)
// 开始广度优先搜索
while (queue.length) {
const dep = queue.shift()
res.push(dep)

// 后续的课程
const courses = relationship[dep]
for (let i = 0; courses && i < courses.length; ++i) {
const course = courses[i]

// 向后推进
const depAfter = --inDeeps[course]
if (depAfter == 0) {
queue.push(course)
}
}
}

// 结果的数量,一定与总课程数一致
return res.length == numCourses ? res : []
}

这是根据代码,分析的一次各个变量的状态

4, [[1,0],[2,0],[3,1],[3,2]]

inDeep:
[
0,1,1,2
]

queue:
[
0
]

relationship:
[
'0': [1, 2],
'1': [3],
'2': [3]
]

扩展1:图、拓扑序列

图基础知识

入度:指向改点的边数

207-1.png

扩展2:贪心算法

贪心算法简而言之:每一步最优,则全局最优。

2 通过CSS实现 文字渐变色

span {
background: linear-gradient(to right, red, blue);
-webkit-background-clip: text;
color: transparent;
}

[1] 简单说 通过CSS实现 文字渐变色 的两种方式.https://segmentfault.com/a/1190000011882933

3 通过CSS是实现 模糊效果

filter: blur(5px);

[1] filter.https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter

[1] CSS3实现模糊背景的三种效果.https://blog.csdn.net/csu_passer/article/details/78406702

4 Nextjs图像优化

TODO

5 算法2.29

题目:152. 乘积最大的子数组(😕)

动态规划

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

测试用例的答案是一个 32-位 整数。

子数组是数组的连续子序列。

示例

示例 1:

输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6

示例 2:

输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是连续子数组。

所需基础

  • 动态规划

    什么是动态规划(Dynamic Programming)?动态规划的意义是什么?.https://www.zhihu.com/question/23995189

    该文章有一个关于凑硬币的问题,有三种面值(1,5,11),给定一个数量K,找出最少数量凑出K值的方式

    比如:K=15,则最少数量为3,即(5,5,5)三张5元的

分析

该题可以采用:1.暴力法 2.动态规划

  • 暴力法比较好想,只是会超过时间限制

  • 该题的动态规划比 扩展1:53. 最大子数组和 要难一些,主要要处理 [1,2,-3,4,-5] 这种包含多个负号的问题,详细推论见 乘积最大子数组

题解

暴力法

function maxProduct(nums: number[]): number {
let res = -Infinity

for (let i = 0; i < nums.length; ++i) {
let mul = nums[i]
res = Math.max(res, mul)

for (let j = i + 1; j < nums.length; ++j) {
mul *= nums[j]

res = Math.max(res, mul)
}
}

return res == -Infinity ? undefined : res
}

动态规划

function main() {
// const nums = [2, 3, -2, 4]
// const nums = [-2, 0, -1]
// const nums = []
// const nums = [-2]
const nums = [-3, 0, 1, -2]

// console.log('[]:', maxProduct(nums))
console.log('[]:', maxProduct111(nums))
}

[1,5,11]
15

f(15) = min(f(15-1), f(15-5), f(15-11))
f(n) = min(f(n-1), f(n-5), f(n-11))

minF = []

minF[i] = Math.min(f)

main()

function maxProduct111(nums: number[]): number {
const len = nums.length
let maxF = new Array(len).fill(0)
let minF = new Array(len).fill(0)

maxF[0] = nums[0]
minF[0] = nums[0]

for (let i = 1; i < len; ++i) {
maxF[i] = Math.max(maxF[i - 1] * nums[i], Math.max(minF[i - 1] * nums[i], nums[i]))
minF[i] = Math.min(minF[i - 1] * nums[i], Math.min(maxF[i - 1] * nums[i], nums[i]))
}

let res = maxF[0]
for (let i = 1; i < len; ++i) {
res = Math.max(res, maxF[i])
}

return res
}

扩展1:53. 最大子数组和(😄)

动态规划

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6

示例 2:

输入:nums = [1]
输出:1

分析:可以采用 1.暴力法 2.动态规划

题解

暴力法

function maxSubArray(arr: number[]): number {
let max = -Infinity

for (let i = 0; i < nums.length; ++i) {
let sum = 0
for (let j = i; j < nums.length; ++j) {
sum += nums[j]
max = Math.max(max, sum)
}
}

return max
}

动态规划

function main() {
const arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4]

console.log('[]:', maxSubArray(arr))
}

main()

function maxSubArray(arr: number[]): number {
let max = arr[0]
let pre = arr[0]

for (let i = 0; i < arr.length; ++i) {
pre = Math.max(pre + arr[i], arr[i])
max = Math.max(max, pre)
}

return max
}

6 Nextjs配置i18n-lingui

问题一:自动重定向?

表现:访问首页,会自动重定向到 /zh-TW

为什么会自动重定向?

这个在官方文档中叫做 Automatic Locale Detection (自动区域检测),会自动检测 Accept-Language 头部,然后做出对应的重定向

如何取消自动重定向?

next.config.js文件中配置 localeDetection 为false即可,其他回答见 [2]

{
i18n: {
locales,
defaultLocale: sourceLocale,
// realDefaultLocale: sourceLocale,

// not redirect
localeDetection: false
}
}

问题二:next epxort 不支持i18n怎么办?

有两种解决方式:

  • 只渲染默认语言的页面
  • 渲染所有语言的页面

第一种:这种比较简单,首先,去掉next.config.js的 i18n 配置字段

切换语言,动态加载语言即可

第二种:详见 [3][4]

问题二扩展:那为什么非要用 next export 呢?

为什么不直接部署一个next的node服务呢?

deployment 章节中介绍了多种方式部署,其中可以通过docker部署

参考

[1] nextjs配置lingui-i18n示例.https://stackblitz.com/github/akellbl4/next.js/tree/examples/with-lingui-update/examples/with-lingui?file=lingui.config.js

[2][i18n] Prefix for default locale.https://github.com/vercel/next.js/discussions/18419

[3] i18n routes in Static Sites using NextJS.https://medium.com/schmiedeone/i18n-routes-in-static-sites-using-nextjs-b6a547477bb1

[4] i18n with next export calls getStaticProps for each defined lang, but then errors.https://github.com/vercel/next.js/issues/18318

7 Duplicate declaration "Trans"

https://github.com/lingui/js-lingui/issues/952

8 Nextjs与图像懒加载

https://blog.logrocket.com/next-js-automatic-image-optimization-next-image/

9 Support for the experimental syntax 'jsx' isn't currently enabled

愿意:没有配置预设babel

{
"presets": [
"next/babel"
],
"plugins": [
"macros"
]
}

10 导航栏跟随滚动高亮

TODO写一个示例项目出来

image-20220301201717757

一些知识点:

  • 1.元素的距离信息(距离窗口的位置,自身的大小信息)
  • 2.浏览器滚动距离
  • 3.浏览器平缓的滑动到指定锚点

核心算法,这里只涉及到 getBoundingClientRect 的使用

const { top, height } = el.getBoundingClientRect()
const distance1 = top
const distance2 = top + height

// 这里会有三种情况:
// 1.distance1和distance2都小于0,那么表示它已经在窗口上面了
// 2.distance1小于0,distance2大于0,那么表示它正在窗口中
// 3.distance1和distance2都大于0,那么表示它还在窗口下面

if (distance1 <= 0 && distance2 >= 0) {
// 进入该元素的范围
// 可以进行一些激活操作
// setActive(_page)
}

扩展1:如何获取浏览器滚动的距离?

document.documentElement.scrollTop

=>> 600

扩展2:如何让浏览器平缓的滑动到指定锚点?

scrollIntoView 是元素具有的一个方法,smooth 表示平滑的滚动,其他参数见 [1]

一般搜索关键字 Ref实现导航滚动定位 就可以找到一些实现,方法使用示例:

el.scrollIntoView({
behavior: "smooth"
})

[1] Element.scrollIntoView().https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollIntoView

11 如何让搜索引擎收录自己的博客?

一般常用搜索引擎为:1.Google 2.Baidu

操作地址为:1.搜索资源平台 2.Google Search Console

具体操作可以参考 111112

扩展1:如何检查网站收录情况?

在搜索引擎中搜索,例如:

site:https://gincool.com

12 算法3.3

题目:680.验证回文字符串 Ⅱ ( 简单😄 )

标签:双指针贪心

附题:验证回文串

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。

示例

示例1

输入: "aba"

输出: True

示例2

输入: "abca"

输出: True

解释: 你可以删除 c 字符。

分析

  • 暴力法:首先验证原串是否为回文串,如果是则返回True,否则,枚举每一个位置空缺时候,是否为回文串。

    时间O(N^2),空间O(1)

  • 贪心法:依旧利用双指针,如果当前两个指针指向值相同,那么继续下一步,如果不相同,则认为(左指针+1)和右指针指向相同 或者 左指针和(右指针+1)指向相同,否则返回False

    时间O(N),空间O(1)

附:贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。 也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解 。

题解

暴力法

function validPalindrome(s: string): boolean {
const len = s.length
let i = 0
let j = len - 1
let res = true

while (i < j) {
if (s[i] != s[j]) {
res = false
break
}

++i
--j
}

if (res) {
console.log('[删除位置]:', '无需删除')
return true
}

for (let k = 0; k < len; ++k) {
i = 0
j = len - 1
res = true

while (i <= j) {
if (i == k) ++i
if (j == k) --j

if (s[i] != s[j]) {
res = false
break
}

++i
--j
}

if (res) {
console.log('[删除位置]:', k)
return true
}
}

return false
}

贪心法

function validPalindrome111(s: string): boolean {
const len = s.length
let low = 0
let high = len - 1

while (low < high) {
if (s[low] == s[high]) {
++low;
--high
} else {
return validPalindromeAssist(s, low + 1, high) || validPalindromeAssist(s, low, high - 1)
}
}

return true


function validPalindromeAssist(s: string, low: number, high: number): boolean {
let i = low
let j = high
while (i < j) {
if (s[i] != s[j]) {
return false
}

++i;
--j
}

return true
}
}

基础题1:验证回文串 ( 简单😄 )

给定一个非空字符串 s,判断是否能成为回文字符串。

解法有:

  • 反转序列法:将字符串反转,如果与原串相等,则认为true

    时间O(N),空间O(N),空间是因为需要通过额外的数组去转化(即字符串转数组,数组再转字符串)

  • 双指针法:定义两个指针,分别指向头和尾,并分别向中间必进,其过程中,如果不相等,则认为false (O(N))

    时间O(N),空间O(1)

双指针法

function validPalindrome(s: string): boolean {
const len = s.length
let i = 0
let j = len - 1
let res = true

while (i < j) {
if (s[i] != s[j]) {
res = false
break
}

++i;
--j
}

return res
}

基础题2:反转数组

给定一个数组,将其元素反转

示例:

输入 arr: [1,2,3,4]

输出 arr: [4,3,2,1]

分析

  • 额外数组法:声明一个数组,从后向前写入即可
  • 双指针法:定义两个指针 i 和 j ,分别指向头和尾,两头逼近中间,分别交换,直至 i==j

13 实现Promise

题目:实现Promise

请利用Ts | Js,实现一个Promise,尽量根据PromiseA+规范来实现

分析

总体而言,有四个方面:

  • 1.承诺有三种状态
  • 2.承诺一定要有 then 方法
  • 3.在调用then后,返回一个promise
  • 4.promise结果解析函数

题解

代码详见 https://github.com/ginlink/js-demo/blob/main/src/02_Leetcode/033_Promise%E5%AE%9E%E7%8E%B0.ts

测试代码 https://github.com/ginlink/js-demo/blob/main/src/02_Leetcode/033_Promise%E5%AE%9E%E7%8E%B0.test.ts

14 算法3.4

题目:1371. 每个元音包含偶数次的最长子字符串 Ⅱ ( 中等😕 )

分析

  • 暴力法:

    • 时间复杂度O(n^3)
  • 前缀和

    前缀和特点

    • 对于一个区间,我们可以用两个前缀和的差值,得到其中某个字母的出现次数

    关于前缀和的解法,可以参考这篇文章:参考官方题解,步步引导,代码实现

    • 时间复杂度O(n),空间复杂度O(n)
  • 前缀和+状态压缩

题解

暴力法

function main() {
const s = 'eleetminicoworoep'
// const s = 'leetcodeisgreat'
// const s = 'bcbcbc'

console.log('[]:', findTheLongestSubstring(s))
}

main()

function findTheLongestSubstring(s: string): number {
const len = s.length

let max = ''
let evenMap: { [key: string]: number } = {}

for (let i = 1; i < len; ++i) {
let current = ''
resetMap()

for (let j = i; j >= 0; --j) {
current = s[j] + current

if (evenMap[s[j]] != undefined) {
++evenMap[s[j]]
}

if (isEven() && current.length >= max.length) {
max = current
}
}
}

return max.length

function isEven(): boolean {
return Object.values(evenMap).every((x) => x % 2 == 0)
}

function resetMap() {
evenMap = {
a: 0,
e: 0,
i: 0,
o: 0,
u: 0,
}
}
}

前缀和

enum NumType {
EVEN = '1',
ODD = '2',
}

class Status {
private status: {
[key: string]: NumType
} = {
a: NumType.EVEN,
e: NumType.EVEN,
i: NumType.EVEN,
o: NumType.EVEN,
u: NumType.EVEN,
}

invert(who: 'a' | 'e' | 'i' | 'o' | 'u') {
this.status[who] = this.status[who] == NumType.EVEN ? NumType.ODD : NumType.EVEN
}

toString() {
return Object.keys(this.status)
.map((key) => key + this.status[key])
.join('')
}

isEqual(newState: { [key: string]: NumType }) {
const state = this.status
const keys = Object.keys(state)

for (let i = 0; i < keys.length; ++i) {
if (state[i] != newState[i]) return false
}

return true
}
}


function main() {
// const s = 'eleetminicoworoep'
// const s = 'leetcodeisgreat'
const s = 'bcbcbc'

console.log('[]:', findTheLongestSubstring222(s))
}

main()

function findTheLongestSubstring222(s: string): number {
const len = s.length

let res = 0
const map = new Map<string, number>()
const currStatus = new Status()

map.set(currStatus.toString(), -1)

for (let i = 0; i < len; ++i) {
const char = s.charAt(i)

if (char == 'a') {
currStatus.invert('a')
} else if (char == 'e') {
currStatus.invert('e')
} else if (char == 'i') {
currStatus.invert('i')
} else if (char == 'o') {
currStatus.invert('o')
} else if (char == 'u') {
currStatus.invert('u')
}

// use status's string as key
const currKey = currStatus.toString()

if (map.has(currKey)) {
res = Math.max(res, i - map.get(currKey))
} else {
map.set(currKey, i)
}
}

return res
}

15 算法3.5

题目:5. 最长回文子串 ( 中等😕 )

给你一个字符串 s,找到 s 中最长的回文子串。

示例

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

示例 2:

输入:s = "cbbd"
输出:"bb"

分析

  • 暴力法:枚举所有的子串,判断是否为回文串

    耗时:枚举需要O(n^2),判断需要O(n),所以时间复杂度为O(n^3)

  • 动态规划,边缘扩散

    呈现的感觉就是,子串从中心向两边扩散

    image-20220311124025313

    详细见:最长回文子串

题解

动态规划

function longestPalindrome(s: string): string {
const len = s.length
// error:
// const dep: boolean[][] = new Array(len).fill([])
const dep: boolean[][] = new Array(len)
let begin = 0,
maxLen = 1

// the substring's length is 1, so it is a palindrome
for (let i = 0; i < len; ++i) {
const newArr = new Array(len)
newArr[i] = true
dep[i] = newArr
}

for (let size = 2; size <= len; ++size) {
for (let i = 0; i < len; ++i) {
// size is [i, j]'s length
// size = j - i + 1 ==> j = size + i -1
const j = size + i - 1

if (j >= len) {
break
}

if (s[i] !== s[j]) {
dep[i][j] = false
} else {
if (j - i + 1 <= 3) {
dep[i][j] = true
} else {
dep[i][j] = dep[i + 1][j - 1]
}
}

const newMaxLen = j - i + 1
if (dep[i][j] && newMaxLen > maxLen) {
maxLen = newMaxLen
begin = i
}
}
}

return s.substring(begin, begin + maxLen)
}

使用

function main() {
// const s = 'babbd'
const s = 'cbbd'

console.log('[]:', longestPalindrome(s))
}

main()

export {}

扩展0:根据下标计算数组的长度

const len = j - i + 1

const j = len + i - 1
const i = j - len + 1

扩展1:Js中初始化二维数组?

我想初始化一个3x3的存放boolean值的二维数组,怎么操作呢?

可能会这样写:

const len = 3

const dp: boolean[][] = new Array(len).fill(new Array(len).fill(true))

// =>> 输出
// [
// [true, true, true],
// [true, true, true],
// [true, true, true],
// ]

看起来,符合预期,我尝试修改第0行第1个元素为false

dp[0][1] = false
console.log('[dp]:', dp)

// =>> 输出
// [
// [true, false, true],
// [true, false, true],
// [true, false, true],
// ]

嗯?为什么第2行,第3行也变了

猜测,Array.fill 填充的是同一个数组对象,即三个数组引用地址是一样的

正确写法

const len = 3
const dp: boolean[][] = new Array(len)

for (let i = 0; i < len; ++i) {
dp[i] = new Array(len).fill(true)
}

输出

[
[true, true, true],
[true, true, true],
[true, true, true],
]

16 Vercel和Netlify的比较

VercelNetlify 都是为了简化前端代码部署的流程的一个平台

它们的差异可以参考 1

17 防抖和节流

项目内容
防抖调用函数触发冷却时间,如果在delay时间内再次被调用,则重新计时冷却时间
节流固定时间被调用
区别防抖是贪婪的,如果在delay时间内反复调用,那么该函数永远不会执行
而节流是开发的,固定时间调用,不管是否在delay时间内调用

题解

以下实现是伊拉克战损版,实际生产中建议用 lodash 库,lodash的防抖函数写得很复杂,边界情况处理到位,还具有 cancel、flush和pending方法,控制函数的执行,详见 debounce

防抖

function debounce(fn: () => void, wait = 50, immediate = true) {
let timer = null

return (...args) => {
if (timer) {
clearTimeout(timer)
}
if (!timer && immediate) {
fn.apply(null, args)
}

timer = setTimeout(() => {
// !immediate && fn.apply(null, args)
fn.apply(null, args)
}, wait)
}
}

function main() {
const fn = debounce(() => {
console.log('[13232323233]:')
})

let counter = 0
const timer = setInterval(() => {
if (counter > 10) {
clearInterval(timer)
}

++counter
fn()
}, 100)
}

main()

节流

function throttle(fn: () => void, wait = 50) {
// let lastTime: number = -Infinity
let lastTime: number = 0

return function (...args) {
const curr = new Date().getTime()

if (curr - lastTime >= wait) {
// apply null said that it is window object
// fn.apply(null, args)

// It's better to pass `this` here
// If you use the arrow function outside, it doesn't work here
fn.apply(this, args)
lastTime = curr
}
}
const that = this

// const throttled = throttle(() => {
// console.log('[equal]:', that === this)
// console.log('[1秒打印一次]:')
// }, 1000)
const throttled = throttle(function () {
console.log('[this]:', this)
console.log('[equal]:', that === this)
console.log('[1秒打印一次]:')
}, 1000)

setInterval(() => {
throttled()
}, 100)
}

main()

18 深拷贝

深拷贝实现程度不一,实现方式也有很多种

实现程度

  • 能够处理对象
  • 能够处理对象和数组
  • 能够解决对象循环引用问题

方式:

  • JSON方式

    • 只适用于拷贝基础类型,例如:string、boolean、null、number、undefined
    • 无法处理引用数据类型,例如:function, RegExp, function等
    • 应用场景:拷贝接口数据
  • 递归

    一层一层判断元素类型,并进行拷贝

注意

生产中,建议使用 lodashcloneDeep

题解

JSON方式

function cloneDeep1(value) {
return JSON.parse(JSON.stringify(value))
}

递归

实现并不完整,无法拷贝函数,没有处理循环引用问题

详细请参考 deepClone

const arrayTag = '[object Array]'
const boolTag = '[object Boolean]'
const dateTag = '[object Date]'
const errorTag = '[object Error]'
const mapTag = '[object Map]'
const numberTag = '[object Number]'
const objectTag = '[object Object]'
const regexpTag = '[object RegExp]'
const setTag = '[object Set]'
const stringTag = '[object String]'
const symbolTag = '[object Symbol]'
const weakMapTag = '[object WeakMap]'

function getTag(value) {
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]'
}
return Object.prototype.toString.call(value)
}

function isObject(value) {
const type = typeof value
return value != null && (type === 'object' || type === 'function')
}

function isArray(value) {
return Array.isArray(value)
}

function cloneDeep2(value) {
// regard null as not object
if (!isObject(value)) {
return value
}

if (isArray(value)) {
return value.map((x) => {
return cloneDeep2(x)
})
} else {
const tag = getTag(value)

if (tag == regexpTag) {
return new RegExp(value)
} else if (tag == dateTag) {
return new Date(value)
} else if (tag == errorTag) {
return new Error(value)
} else if (tag == mapTag) {
const newMap = new Map()
value.forEach((subValue, key) => {
newMap.set(key, cloneDeep2(subValue))
})

return newMap
} else if (tag == setTag) {
const newSet = new Set()
value.forEach((subValue) => {
newSet.add(cloneDeep2(subValue))
})

return newSet
} else {
return Object.keys(value).reduce((memo, key) => {
memo[key] = cloneDeep2(value[key])

return memo
}, {})
}
}
}

19 算法3.6

题目:105.从前序与中序遍历序列构造二叉树 ( 中等😕 )

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例

示例1

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例2

输入: preorder = [-1], inorder = [-1]
输出: [-1]

分析

image-20220306160049312

疑问:

  • 前序遍历的左子树右边界如何计算?

    前序遍历和中序遍历的两个左子树的长度是一致的,所以可得:

    pIndex-1 - inLeft = x - (preLeft + 1),即:

    x = pIndex - inLeft + preLeft

  • 通过代码来看,遍历左子树时,其中序遍历左边界永远为inLeft

    相反,遍历右子树时,其中序遍历有边界永远为inRight,为什么?

题解

class TreeNode {
val: number
left: TreeNode | null
right: TreeNode | null
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = val === undefined ? 0 : val
this.left = left === undefined ? null : left
this.right = right === undefined ? null : right
}
}

function buildTree(preorder: number[], inorder: number[]): TreeNode | null {
const pLen = preorder.length
const iLen = inorder.length
const inorderMap = new Map<number, number>()
for (let i = 0; i < iLen; ++i) {
inorderMap.set(inorder[i], i)
}

return buildTreeRec(preorder, 0, pLen - 1, inorderMap, 0, iLen - 1)

function buildTreeRec(
preorder: number[],
preLeft: number,
preRight: number,
inorderMap: Map<number, number>,
inLeft: number,
inRight: number
) {
if (preLeft > preRight || inLeft > inRight) {
return null
}

const val = preorder[preLeft]
const root = new TreeNode(val)
const pIndex = inorderMap.get(val)

root.left = buildTreeRec(preorder, preLeft + 1, pIndex - inLeft + preLeft, inorderMap, inLeft, pIndex - 1)
root.right = buildTreeRec(preorder, pIndex - inLeft + preLeft + 1, preRight, inorderMap, pIndex + 1, inRight)

return root
}
}

测试

function main() {
const preorder = [3, 9, 20, 15, 7]
const inorder = [9, 3, 15, 20, 7]

console.log('[]:', buildTree(preorder, inorder))
}

main()

20 MobX是什么

mobx与vuex十分类似,设计理念为:任何可以从应用程序状态派生的内容都应该派生

mobx文档: MobX

redux、mobx比较:redux、mobx、concent特性大比拼, 看后生如何对局前辈