题目说明

1
2
3
4
5
6
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,
满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。


说明:

所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。

题目说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符


示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

题目说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。
s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。

示例 1:
给定的树 s:

3
/ \
4 5
/ \
1 2
给定的树 t:

4
/ \
1 2
返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。

示例 2:
给定的树 s:

3
/ \
4 5
/ \
1 2
/
0
给定的树 t:

4
/ \
1 2
返回 false。

题目说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。

火车票有三种不同的销售方式:

一张为期一天的通行证售价为 costs[0] 美元;
一张为期七天的通行证售价为 costs[1] 美元;
一张为期三十天的通行证售价为 costs[2] 美元。
通行证允许数天无限制的旅行。 例如,如果我们在第 2 天获得一张为期 7 天的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。

返回你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费。



示例 1:

输入:days = [1,4,6,7,8,20], costs = [2,7,15]
输出:11
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。
在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, ..., 9 天生效。
在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。
你总共花了 $11,并完成了你计划的每一天旅行。
示例 2:

输入:days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]
输出:17
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[2] = $15 买了一张为期 30 天的通行证,它将在第 1, 2, ..., 30 天生效。
在第 31 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 31 天生效。
你总共花了 $17,并完成了你计划的每一天旅行。


提示:

1 <= days.length <= 365
1 <= days[i] <= 365
days 按顺序严格递增
costs.length == 3
1 <= costs[i] <= 1000

说明

vue实现双向绑定原理,主要是利用Object.defineProperty 来给实例data的属性添加 setter和getter.
并通过发布订阅模式(一对多的依赖关系,当状态发生改变,它的所有依赖都将被通知)来实现响应。

这个环节中包含了三个部分

  • Observer 用来监听拦截data的属性为监察者。

  • Dep用来添加订阅者,为订阅器

  • Watcher 就是订阅者

监察者通过 Dep 向 Watcher发布更新消息

简单实现

那么首先

  1. 通过对set和get的拦截,在get阶段进行依赖收集,在set阶段对通知该属性上所啊绑定的依赖。

如下我们就已经实现了一个简单的双向绑定了。

我们将data的value属性绑定上set和get,通过 _value 来进行操作。

1
2
3
4
<!-- HTML部分 -->

<input type="text" id="inp" oninput="inputFn(this.value)">
<div id='div'></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- JS部分 -->
var inp = document.getElementById('inp');
var div = document.getElementById('div');
var data = {
value:''
}
Object.defineProperty(data, 'value', {
enumerable: true,
configurable: true,
set: function (newValue) {
this._value = newValue;
div.innerText = data._value = value; //watcher
},
get: function () {
return this._value;
}
})
function inputFn(value) {
data._value = value;
}

如果只是实现一个简单的双向绑定那么上面的代码就已经实现了。

进一步完善模拟vue实现

首先我们将watcher抽出来 备用

1
2
3
function watcher(params) {
div.innerText = inp.value = params; // 派发watcher
}

声明一个vm来模拟vue的实例,并初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var vm = {

//类似vue实例上的data
data: {
value: ''
},

// vue私有, _data的所有属性为data中的所有属性被改造为 getter/setter 之后的。
_data: {
value: ''
},

// 代理到vm对象上,可以实现vm.value
value: '',

//value的订阅器用来收集订阅者
valueWatchers:[]
}

遍历其data上的属性 进行改造 这里我们还是只举一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 利用 Object.defineProperty 定义一个属性 (eg:value) 描述符为存取描述符的属性
Object.defineProperty(vm._data, 'value', {
enumerable: true, //是否可枚举
configurable: true, //是否可配置
set: function (newValue) { //set 派发watchers
vm.data.value = newValue;
vm.valueWatchers.map(fn => fn(newValue));
},
get: function () {

// 收集wachter vue中会在compile解析器中通过 显示调用 (this.xxx) 来触发get进行收集
vm.valueWatchers.length = 0;
vm.valueWatchers.push(watcher);
return vm.data.value;
}
})

<!--直接通过显示调用来触发get进行绑定 vue中是在compile解析器中来进行这一步-->
vm._data.value

进行到这儿也已经实现了绑定,但是我们平时使用vue ,都是可以直接通过 this.xxx来获取和定义数据

那么我们还需要进行一步Proxy 代理

1
2
3
4
5
6
7
8
9
10
11

Object.defineProperty(vm, 'value', {
enumerable: true,
configurable: true,
set: function (newValue) {
this._data.value = newValue; //借助
},
get: function () {
return this._data.value;
}
})

这样我们就把vm._data.value 代理到vm.value上了,可以通过其直接操作了。

那么按照官方的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

function proxy (target, sourceKey, key) {
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get() {
return this[sourceKey][key];
},
set(val) {
this[sourceKey][key] = val;
}
});
}

proxy(vm, '_data', 'value');

完善后的完整代码

以下为整个页面,可以直接运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>双向绑定简单实现</title>
</head>
<body>
<input type="text" id="inp" oninput="inputFn(this.value)">
<br>
<input type="text" id="inp2" oninput="inputFn(this.value)">
<div id='div'></div>
<script>
var inp = document.getElementById('inp');
var inp2 = document.getElementById('inp2');
var div = document.getElementById('div');


function inputFn(value) {
div.innerText = vm.value = value;
}

function watcher(params) {
console.log(1)
div.innerText = inp.value = params; // 派发watcher
}

function watcher2(params) {
console.log(2)

div.innerText = inp2.value = params; // 派发watcher
}

function proxy (target, sourceKey, key) {
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get() {
return this[sourceKey][key];
},
set(val) {
this[sourceKey][key] = val;
}
});
}

let handler = {
enumerable: true,
configurable: true,
set: function (newValue) {
vm.data.value = newValue;
vm.valueWatchers.map(fn => fn(newValue));
},
get: function () {
vm.valueWatchers = []; //防止重复添加,
vm.valueWatchers.push(watcher);
vm.valueWatchers.push(watcher2);
return vm.data.value;
}
}

var vm = {
data: {},
_data: {},
value: '',
valueWatchers: []
}

Object.defineProperty(vm._data, 'value', handler)

proxy(vm, '_data', 'value');

vm.value; //显示调用绑定

</script>
</body>
</html>

解释

再多讲一点。实际上vue在初始化的时候是用解析器解析过程中将wathcer进行绑定的。

它会利用一个全局的Dep.target = watcher

然后在get收集中,只收集全局上Dep.target, 添加完毕后会重新初始化全局Dep.target = null;

类似如下操作

1
2
3
4

Dep.target = watcher;
vm.value; // 触发get => Dep.target && valueWatchers.push(Dep.target);
Dep.target = null;

这样也会防止我们在调用时触发get重复去添加watcher。

而我们的例子中只是每次都初始化为[]. 实际订阅器也不只是一个watcher数组。

此例跟官方实现还是有很多差距,只是简单模拟。

vue3.0 使用 Proxy

在vue3.0中,使用proxy这个功能更加强大的函数,它可以定义对象的基本操作的自定义行为。对比defineProperty只能拦截对象的某一属性,proxy的功能更方便。所提供的可自定义的操作也更多。

上面,我用defineProperty实现了vue的双向绑定,接下来我们用proxy来实现。

首先我们可以先了解一下proxy的作用和用法

首先 defineProperty 的用法是Object.defineProperty(obj, prop, descriptor)

proxy的用法如下:

1
const p = new Proxy(target, handler)

我们用proxy来实现一下双向绑定:

核心代码就像这样,在我们这个需求下分析

  1. set函数中
    1. target 为所拦截的对象
    2. key 为属性名
    3. newValue为所赋予的值
    4. set中需要return true代表设置成功,返回flase在严格模式下报TypeError (代表该值与期望值类型不同)
  2. get函数中
    1. target 为所拦截的对象
    2. key 为属性名
    3. get可返回任意值
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      let data = {value: 0}
      const vm = new Proxy({value: 0 }, {
      set: function(target, key, newValue){
      console.log(key + '被赋值为' + newValue)
      target[key] = newValue
      return true
      },
      get: function(target, key) {
      console.log(target[key])
      return target[key]
      }
      })

      vm.value = 1 // 0; value被赋值为1

proxy双向绑定具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1 id="content"></h1>
<p><input type="text" id="enter" value=""></p>
</body>
<script>
let content = document.getElementById("content")
let enter_input = document.getElementById('enter')

let data = {
enter_input: '',
enter_input_watchers: []
}
let watcher = function watcherFn(value) {
content.innerText = value
}
let watcher2 = function watcher2Fn(value) {
enter_input.value = value
}
let handler = {
set: function(target, key, value) {
if (key === 'enter_input') {
target[key] = value;
target[key + "_watchers"].map(function (watcher) {
watcher(value)
})
}
},
get: function(target, key) {
target[key + "_watchers"] = [watcher, watcher2];
return target[key]
}
}

let db = new Proxy(data, handler);
db.enter_input; //收集监听
enter_input.addEventListener('input', function(e){
db.enter_input = e.target.value;
})
</script>
</html>

错误类型扩展

平时我们常见的错误类型分为ReferenceErrorTypeErrorSyntaxError 这三种。

一、 ReferenceError 代表我们的作用域查找错误。
1
2
3
let b = 1;
console.log(b)
console.log(a) //ReferenceError
  1. 我们在全局定义了b,所以console.log(b)为1,但是我们没有定义a,所以我们在全局作用域下找不到a,就会报ReferenceError

  2. 如果是在函数中定义,则在函数中查找不到时,会去父作用域查找,一直到全局,都找不到,才会报ReferenceError

二、 TypeError代表数据类型与预期不符。
1
2
let b = 1;
b() //TypeError
  1. 我们在全局定义了b,其类型为Number,但是我们用()来执行它,把它当作了函数用,所以就会报TypeError
三、 SyntaxError代表语法错误。
1
2
3
let b > 1;//SyntaxError
//or
let let b//SyntaxError
  1. 很明显,我们不可以这么使用let,语法就错误了,所以就会报SyntaxError
Vue

题目说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
示例 1:

输入:
2
/ \
1 3
输出: true
示例 2:

输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。

题目说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

示例:

输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明:

假设你总是可以到达数组的最后一个位置。

题目说明

1
2
3
4
5
6
7
8
9
10
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

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

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

题目说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。