-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
05636e6
commit feaf287
Showing
7 changed files
with
790 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 是这棵树的起点,内部已经构建好了节点之间的关系 | ||
* 当然,这只是从数组转二叉树的一种广度优先遍历的实现形式 |
Oops, something went wrong.