Skip to content

Commit f19caad

Browse files
feat: 更新blog
1 parent 9bca014 commit f19caad

File tree

3 files changed

+150
-0
lines changed

3 files changed

+150
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
## 题目描述
3+
4+
<br />给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?<br />
5+
<br />示例:<br />
6+
7+
```javascript
8+
输入: 3
9+
输出: 5
10+
解释:
11+
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
12+
13+
1 3 3 2 1
14+
\ / / / \ \
15+
3 2 1 1 3 2
16+
/ / \ \
17+
2 1 2 3
18+
```
19+
20+
## 解题方法
21+
22+
- 每个树的构成都可以分为左子树和右子树,所以以 i 为根节点的二叉搜索树种类的个数可以分解为子问题:左子树的种类和右子树的种类
23+
- 左子树的节点范围:1...i-1
24+
- 右子树的节点范围:`i+1...n`
25+
- 那么就可以把同样的问题转接到左子树和右子树上。
26+
27+
```javascript
28+
/**
29+
* @param {number} n
30+
* @return {number}
31+
*/
32+
var numTrees = function (n) {
33+
// dp[i]表示以1...i为节点组成的二叉搜索树的种类
34+
let dp = new Array(n + 1).fill(0);
35+
dp[0] = 1;
36+
dp[1] = 1;
37+
// dp[1]已经确定,因此从2开始
38+
for (let i = 2; i <= n; i++) {
39+
// j表示分别从1为根节点至i为根节点
40+
for (let j = 1; j <= i ; j++) {
41+
dp[i] += dp[j - 1] * dp[i - j];
42+
}
43+
}
44+
return dp[n];
45+
};
46+
```
47+
48+
- 时间复杂度:O(n<sup>2</sup>)
49+
- 空间复杂度:O(n)

docs/algorithm/动态规划/最长公共子序列.md

Whitespace-only changes.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# 最长重复子数组
2+
3+
<a name="273a27cc"></a>
4+
## 题目描述
5+
6+
<br />给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。<br />
7+
<br />示例 1:<br />
8+
<br />输入:<br />
9+
10+
```javascript
11+
A: [1,2,3,2,1]
12+
B: [3,2,1,4,7]
13+
输出: 3
14+
解释:
15+
长度最长的公共子数组是 [3, 2, 1]。
16+
```
17+
18+
19+
<a name="d04d69d9"></a>
20+
## 解题方法
21+
22+
23+
<a name="818f9b73"></a>
24+
### 方法一: 动态规划
25+
26+
<br />暴力解法:<br />枚举数组 A 中的起始位置 i 与数组 B 中的起始位置 j , 然后计算 A[i:] 与 B[i:]的最长公共子数组长度 k。最终答案即为所有的重复子数组的长度的最大值。<br />
27+
28+
> 这里借用 python 表示数组的方法, A[i:] 表示 数组 A 中索引 i 到数组末尾的范围对应的子数组。
29+
30+
31+
32+
```javascript
33+
/**
34+
* @param {number[]} A
35+
* @param {number[]} B
36+
* @return {number}
37+
*/
38+
var findLength = function (A, B) {
39+
const m = A.length;
40+
const n = B.length;
41+
let res = 0;
42+
for (let i = 0; i < m; i++) {
43+
for (let j = 0; j < n; j++) {
44+
if (A[i] == B[j]) {
45+
// 遇到相同项
46+
let subLen = 1; // 公共子序列长度至少为1
47+
while (
48+
A[i + subLen] == B[j + subLen] && // 它们下一项也相同
49+
i + subLen < m && // 并且没有越界
50+
j + subLen < n // 并且没有越界
51+
) {
52+
subLen++; // 公共子序列长度每次增加 1,考察新的一项
53+
}
54+
res = Math.max(subLen, res);
55+
}
56+
}
57+
}
58+
return res;
59+
};
60+
```
61+
62+
<br />暴力解法中,最快的情况就是 对于任意 i 与 j, A[i] 与 B[i] 比较了 min(i + 1, j + 1)次,这也是导致了该暴力解法时间复杂度过高的根本原因。<br />
63+
<br />举个简单的例子:数组A 是[1, 2, 3], 数组B 是[1, 2, 4],那么在暴力解法中 A[2]与B[2]被比较了3次<br />
64+
65+
1. 起始位置 i = 0,j = 0的时候,分别比较 A[0]和 B[0], A[1]和B[1], A[2]和B[2]
66+
1. 起始位置 i = 1, j = 1 的时候,分别比较  A[1]和B[1], A[2]和B[2]
67+
1. 起始位置 i = 2, j = 2 的时候,分别比较  A[2]和B[2]
68+
69+
70+
<br />我们希望优化这过程,使得任意一对 A[i] 和 B[j]值被比较一次。我们很容易想到利用这一次的比较成果,如果 A[i] = B[j], 那么我们知道 A[i:]与 B[j:]的最长公共子数组长度为 A[i-1:]与 B[j-1:]的最长公共子数组长度 加 1,<br />
71+
72+
```javascript
73+
/**
74+
* @param {number[]} A
75+
* @param {number[]} B
76+
* @return {number}
77+
*/
78+
var findLength = function(A, B) {
79+
// 初始化一个二维数组,每一项都是 0
80+
let m = A.length;
81+
let n = B.length;
82+
let dp = new Array(m + 1);
83+
for(let i = 0; i <= m; i++) {
84+
dp[i] = new Array(n + 1).fill(0);
85+
}
86+
let res = 0;
87+
for(let i = 1; i <= m; i++) {
88+
for(let j = 1; j <= n; j++) {
89+
if(A[i - 1] === B[j - 1]) {
90+
dp[i][j] = dp[i -1][j-1] + 1;
91+
}
92+
res = Math.max(dp[i][j], res)
93+
}
94+
}
95+
return res;
96+
};
97+
```
98+
99+
100+
<a name="9d1d8581"></a>
101+
## 方法二:滑动窗口

0 commit comments

Comments
 (0)