Skip to content

Commit

Permalink
update: dsa 144 ~ 149.md
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnynode committed Nov 28, 2023
1 parent 05636e6 commit feaf287
Show file tree
Hide file tree
Showing 7 changed files with 790 additions and 0 deletions.
6 changes: 6 additions & 0 deletions dsa/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,9 @@ Data Structures and Algorithms (DSA)
- [141、数据结构与算法之LRU: 实现 LRU 缓存算法功能 (Typescript版)](./contents/141.md)
- [142、数据结构与算法之查找: 顺序查找 (Typescript版)](./contents/142.md)
- [143、数据结构与算法之查找: LeetCode 704. 二分查找 (Typescript版)](./contents/143.md)
- [144、数据结构与算法之二叉树: LeetCode 100. 相同的树 (Typescript版)](./contents/144.md)
- [145、数据结构与算法之二叉树: LeetCode 101. 对称二叉树 (Typescript版)](./contents/145.md)
- [146、数据结构与算法之二叉树: LeetCode 226. 翻转二叉树 (Typescript版)](./contents/146.md)
- [147、数据结构与算法之贪心: LeetCode 122. 买卖股票的最佳时机II (Typescript版)](./contents/147.md)
- [148、数据结构与算法之贪心: LeetCode 860. 柠檬水找零 (Typescript版)](./contents/148.md)
- [149、数据结构与算法之贪心: LeetCode 455. 分饼干 (Typescript版)](./contents/149.md)
151 changes: 151 additions & 0 deletions dsa/contents/144.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
### 相同的树

- https://leetcode.cn/problems/same-tree/

### 描述

- 给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。

- 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

### 示例 1

```
1 1
/ \ / \
2 3 2 3
```

```
输入:p = [1,2,3], q = [1,2,3]
输出:true
```

### 示例 2

```
1 1
/ \
2 2
```

```
输入:p = [1,2], q = [1,null,2]
输出:false
```

### 示例 3

```
1 1
/ \ / \
2 1 1 2
```

```
输入:p = [1,2,1], q = [1,1,2]
输出:false
```

### 提示

- 两棵树上的节点数目都在范围 [0, 100]
- -$10^4$ <= Node.val <= $10^4$

### 算法实现

1 )深度优先递归版本

```ts
/**
* Definition for a binary tree node.
* 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 isSameTree(p: TreeNode | null, q: TreeNode | null): boolean {
// 都为空时
if(!p && !q) return true;
// 不相同时
if (p?.val !== q?.val) return false;
// 递归判断
return isSameTree(p?.left, q?.left) && isSameTree(p?.right, q?.right);
};
```
- 解题思路
* 两棵树相同:根节点值相同,左子树相同,右子树相同
* 如此一来,我们把若干大问题,分解成若干个相似小问题
* 符合:分、解、合特性
* 选择分而治之
- 解题步骤
* 分:获取两个树的左子树和右子树
* 解:递归地判断两个树的左子树是否相同,右子树是否相同
* 合:将上述结果合并,若根节点值也相同,树就相同
- 时间复杂度:O(n)
* n是所有节点数
- 空间复杂度:O(n)
* 递归,底部形成堆栈
* n是树的节点数,最坏情况下,树的节点数是树的高度

2 )广度优先迭代版本

```ts
/**
* Definition for a binary tree node.
* 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 isSameTree(p: TreeNode | null, q: TreeNode | null): boolean {
// 都为空时
if(!p && !q) return true;
// 不相同时
if (p?.val !== q?.val) return false;
// 声明两个队列
const queue1 = [p];
const queue2 = [q];
// 迭代对比
while (queue1.length && queue2.length) {
// 拿到队首
const pTop = queue1.shift();
const qTop = queue2.shift();
// 对比判断, 符合则提前终止
if (pTop.val !== qTop.val) return false;
// 拿到下一层
const pLeft = pTop?.left;
const pRight = pTop?.right;
const qLeft = qTop?.left;
const qRight = qTop?.right;
// 进入判断, 符合则提前终止
if ((pLeft && !qLeft) || (!pLeft && qLeft)) return false;
if ((pRight && !qRight) || (!pRight && qRight)) return false;
// 进入队列
if (pLeft) queue1.push(pLeft);
if (pRight) queue1.push(pRight);
if (qLeft) queue2.push(qLeft);
if (qRight) queue2.push(qRight);
}
// 最终结果对比
return !queue1.length && !queue2.length;
}
```

- 这里换一种广度优先遍历来对比两棵树是否相同
- 时间复杂度:O(n)
- 空间复杂度:O(n)
199 changes: 199 additions & 0 deletions dsa/contents/145.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
### 对称二叉树

- https://leetcode.cn/problems/symmetric-tree/

### 描述

- 给你一个二叉树的根节点 root , 检查它是否轴对称。

### 示例 1

```
1
/ | \
2 | 2
/ \ | / \
3 4 | 4 3
```

中间一条线是对称轴

```
输入:root = [1,2,2,3,4,4,3]
输出:true
```


### 示例 2

```
1
/ \
2 2
\ \
3 3
```

```
输入:root = [1,2,2,null,3,null,3]
输出:false
```

### 提示

- 树中节点数目在范围 [1, 1000]
- -100 <= Node.val <= 100

### 算法实现

1 )深度优先递归版本

```ts
/**
* Definition for a binary tree node.
* 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 isSymmetric(root: TreeNode | null): boolean {
if(!root) return true;
const isMirror = (l: TreeNode | null, r: TreeNode | null) => {
if(!l && !r) return true; // 空树是镜像的
return l?.val === r?.val && isMirror(l?.left, r?.right) && isMirror(r?.left, l?.right)
};
return isMirror(root.left, root.right);
}
```

- 解题思路
* 转化为:左右子树是否镜像
* 分解为:树1的左子树和树2的左子树是否镜像;树1的右子树和树2的左子树是否镜像,成立,且根节点值相同,则镜像
* 符合:分、解、合的特性,考虑分而治之
- 解题步骤
* 分:获取两个树的左子树和右子树
* 解:递归地判断树1的左子树和树2的右子树是否镜像,树1的右子树和树2的左子树是否镜像
* 合:如果上述两个都成立,且根节点值也相同,两个树就是镜像
- 时间复杂度:O(n)
* 叶子节点数量
- 空间复杂度: O(n)
* 堆栈的高度,树的高度
* 最坏情况高度==树节点数

2 )广度优先迭代版本

```ts
/**
* Definition for a binary tree node.
* 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 isSymmetric(root: TreeNode | null): boolean {
if(!root || (!root.left && !root.right)) return true;
const queue: (TreeNode | null)[] = [root.left, root.right];
while(queue.length) {
const left = queue.shift();
const right = queue.shift();
// 如果两个节点都为空就继续循环,两者有一个为空就返回false
if (!left && !right) continue;
if (!left || !right) return false;
if (left.val !== right.val) return false;
// 将左节点的左孩子, 右节点的右孩子 存入队列
queue.push(left.left);
queue.push(right.right);
// 将左节点的右孩子 和 右节点的左孩子 存入队列
queue.push(left.right);
queue.push(right.left);
}
return true;
}
```

- 使用广度优先遍历来替代深度优先遍历实现对称校验
- 时间复杂度是 O(n)
- 空间复杂度是 O(n)

### 扩展

二叉树中,从数组中的数据解析成Tree Node 结构的过程如下

1 )二叉树节点和数组索引(下标)之间的关系,如下

```
0层 0 2的0次方 = 1 个
/ \
1层 1 2 2的1次方 = 2 个
/ \ / \
2层 3 4 5 6 2的2次方 = 4 个
```

2 )从数组转换成Tree Node的代码实现过程如下,如下

```ts

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)
}
}

class Tree {
constructor (data) {
// 临时存储所有节点,方便寻找父子节点
const nodeList = [];
for (let i = 0, len = data.length; i < len; i++) {
const node = new TreeNode(data[i]);
nodeList.push(node);
if (i === 0) continue; // 开始层root跳过处理
// 计算当前节点属于那一层
const n = Math.floor(Math.sqrt(i + 1));
// 记录当前层的起始点
const q = Math.pow(2, n) - 1;
// 记录上一层的起始点
const p = Math.pow(2, n - 1) - 1;
// 找到当前节点的父节点
const parent = nodeList[p + Math.floor((i - q) / 2)];
// 将当前节点和上一层的父节点做关联
if (parent.left) {
parent.right = node
} else {
parent.left = node
}
}
const root = nodeList.shift(); // 拿出第一个节点
nodeList.length = 0; // 清空临时节点列表
return root;
}
}
```

- 从上述关系和代码过程可以看到
* 当前节点(的下标i)与层级n的关系: `const n = Math.floor(Math.sqrt(i + 1))`
* 当前层的起点下标: `const q = Math.pow(2, n) - 1`
* 上一层的起点下标: `const p = Math.pow(2, n - 1) - 1`
* 基于当前节点,找到其父节点: `const parent = nodeList[p + Math.floor((i - q) / 2)]`
- 我们如果想到得到从数组转换过来的当前树,只需要执行 `const root = new Tree([1, 2, 2, 3, 4, 5, 3])`
* 第一个数组参数是传入的数据
* 得到的 root 是这棵树的起点,内部已经构建好了节点之间的关系
* 当然,这只是从数组转二叉树的一种广度优先遍历的实现形式
Loading

0 comments on commit feaf287

Please sign in to comment.