Fix naming of the section
build_binary_tree_problem
@ -18,7 +18,7 @@ def dfs(
|
|||||||
l: int,
|
l: int,
|
||||||
r: int,
|
r: int,
|
||||||
) -> TreeNode | None:
|
) -> TreeNode | None:
|
||||||
"""构建二叉树 DFS"""
|
"""构建二叉树:分治"""
|
||||||
# 子树区间为空时终止
|
# 子树区间为空时终止
|
||||||
if r - l < 0:
|
if r - l < 0:
|
||||||
return None
|
return None
|
||||||
@ -26,9 +26,9 @@ def dfs(
|
|||||||
root = TreeNode(preorder[i])
|
root = TreeNode(preorder[i])
|
||||||
# 查询 m ,从而划分左右子树
|
# 查询 m ,从而划分左右子树
|
||||||
m = hmap[preorder[i]]
|
m = hmap[preorder[i]]
|
||||||
# 递归构建左子树
|
# 子问题:构建左子树
|
||||||
root.left = dfs(preorder, inorder, hmap, i + 1, l, m - 1)
|
root.left = dfs(preorder, inorder, hmap, i + 1, l, m - 1)
|
||||||
# 递归构建右子树
|
# 子问题:构建右子树
|
||||||
root.right = dfs(preorder, inorder, hmap, i + 1 + m - l, m + 1, r)
|
root.right = dfs(preorder, inorder, hmap, i + 1 + m - l, m + 1, r)
|
||||||
# 返回根节点
|
# 返回根节点
|
||||||
return root
|
return root
|
||||||
|
|||||||
@ -22,6 +22,6 @@
|
|||||||
|
|
||||||
哈希表底层是数组,而为了解决哈希冲突,我们可能会使用“拉链法”(后续散列表章节会讲)。在拉链法中,数组中每个地址(桶)指向一个链表;当这个链表长度超过一定阈值时,又可能被转化为树(通常为红黑树)。因此,哈希表可能同时包含线性(数组、链表)和非线性(树)数据结构。
|
哈希表底层是数组,而为了解决哈希冲突,我们可能会使用“拉链法”(后续散列表章节会讲)。在拉链法中,数组中每个地址(桶)指向一个链表;当这个链表长度超过一定阈值时,又可能被转化为树(通常为红黑树)。因此,哈希表可能同时包含线性(数组、链表)和非线性(树)数据结构。
|
||||||
|
|
||||||
!!! question "char 类型的长度是 1 bytes 吗?"
|
!!! question "char 类型的长度是 1 byte 吗?"
|
||||||
|
|
||||||
这个与编程语言采用的编码方法有关。例如,Java, JS, TS, C# 都采用 UTF-16 编码(保存 Unicode 码点),因此 char 类型的长度为 2 bytes 。
|
char 类型的长度由编程语言采用的编码方法决定。例如,Java, JS, TS, C# 都采用 UTF-16 编码(保存 Unicode 码点),因此 char 类型的长度为 2 bytes 。
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
给定一个二叉树的前序遍历 `preorder` 和中序遍历 `inorder` ,请从中构建二叉树,返回二叉树的根节点。
|
给定一个二叉树的前序遍历 `preorder` 和中序遍历 `inorder` ,请从中构建二叉树,返回二叉树的根节点。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
原问题定义为从 `preorder` 和 `inorder` 构建二叉树。我们首先从分治的角度分析这道题:
|
原问题定义为从 `preorder` 和 `inorder` 构建二叉树。我们首先从分治的角度分析这道题:
|
||||||
|
|
||||||
@ -25,7 +25,7 @@
|
|||||||
2. 查找根节点在 `inorder` 中的索引,基于该索引可将 `inorder` 划分为 `[ 9 | 3 | 1 2 7 ]` ;
|
2. 查找根节点在 `inorder` 中的索引,基于该索引可将 `inorder` 划分为 `[ 9 | 3 | 1 2 7 ]` ;
|
||||||
3. 根据 `inorder` 划分结果,可得左子树和右子树分别有 1 个和 3 个节点,从而可将 `preorder` 划分为 `[ 3 | 9 | 2 1 7 ]` ;
|
3. 根据 `inorder` 划分结果,可得左子树和右子树分别有 1 个和 3 个节点,从而可将 `preorder` 划分为 `[ 3 | 9 | 2 1 7 ]` ;
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
至此,**我们已经推导出根节点、左子树、右子树在 `preorder` 和 `inorder` 中的索引区间**。而为了描述这些索引区间,我们需要借助几个指针变量:
|
至此,**我们已经推导出根节点、左子树、右子树在 `preorder` 和 `inorder` 中的索引区间**。而为了描述这些索引区间,我们需要借助几个指针变量:
|
||||||
|
|
||||||
@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
请注意,右子树根节点索引中的 $(m-l)$ 的含义是“左子树的节点数量”,建议配合下图理解。
|
请注意,右子树根节点索引中的 $(m-l)$ 的含义是“左子树的节点数量”,建议配合下图理解。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
接下来就可以实现代码了。为了提升查询 $m$ 的效率,我们借助一个哈希表 `hmap` 来存储 `inorder` 列表元素到索引的映射。
|
接下来就可以实现代码了。为了提升查询 $m$ 的效率,我们借助一个哈希表 `hmap` 来存储 `inorder` 列表元素到索引的映射。
|
||||||
|
|
||||||
@ -142,34 +142,34 @@
|
|||||||
下图展示了构建二叉树的递归过程,各个节点是在向下“递”的过程中建立的,而各条边是在向上“归”的过程中建立的。
|
下图展示了构建二叉树的递归过程,各个节点是在向下“递”的过程中建立的,而各条边是在向上“归”的过程中建立的。
|
||||||
|
|
||||||
=== "<1>"
|
=== "<1>"
|
||||||

|

|
||||||
|
|
||||||
=== "<2>"
|
=== "<2>"
|
||||||

|

|
||||||
|
|
||||||
=== "<3>"
|
=== "<3>"
|
||||||

|

|
||||||
|
|
||||||
=== "<4>"
|
=== "<4>"
|
||||||

|

|
||||||
|
|
||||||
=== "<5>"
|
=== "<5>"
|
||||||

|

|
||||||
|
|
||||||
=== "<6>"
|
=== "<6>"
|
||||||

|

|
||||||
|
|
||||||
=== "<7>"
|
=== "<7>"
|
||||||

|

|
||||||
|
|
||||||
=== "<8>"
|
=== "<8>"
|
||||||

|

|
||||||
|
|
||||||
=== "<9>"
|
=== "<9>"
|
||||||

|

|
||||||
|
|
||||||
=== "<10>"
|
=== "<10>"
|
||||||

|

|
||||||
|
|
||||||
设树的节点数量为 $n$ ,初始化每一个节点(执行一个递归函数 `dfs()` )使用 $O(1)$ 时间。**因此总体时间复杂度为 $O(n)$** 。
|
设树的节点数量为 $n$ ,初始化每一个节点(执行一个递归函数 `dfs()` )使用 $O(1)$ 时间。**因此总体时间复杂度为 $O(n)$** 。
|
||||||
|
|
||||||
@ -476,5 +476,5 @@ $$
|
|||||||
总的看来,**子问题分解是一种通用的算法思路,在分治、动态规划、回溯中各有特点**:
|
总的看来,**子问题分解是一种通用的算法思路,在分治、动态规划、回溯中各有特点**:
|
||||||
|
|
||||||
- 分治算法将原问题划分为几个独立的子问题,然后递归解决子问题,最后合并子问题的解得到原问题的解。例如,归并排序将长数组不断划分为两个短子数组,再将排序好的子数组合并为排序好的长数组。
|
- 分治算法将原问题划分为几个独立的子问题,然后递归解决子问题,最后合并子问题的解得到原问题的解。例如,归并排序将长数组不断划分为两个短子数组,再将排序好的子数组合并为排序好的长数组。
|
||||||
- 动态规划也是将原问题分解为多个子问题,但与分治算法的主要区别是,**动态规划中的子问题往往不是相互独立的**,原问题的解依赖于子问题的解,而子问题的解又依赖于更小的子问题的解。因此,动态规划通常会引入记忆化,保存已经解决的子问题的解,避免重复计算。
|
- 动态规划也是将原问题分解为多个子问题,但与分治算法的主要区别是,**动态规划中的子问题往往不是相互独立的**,原问题的解依赖于子问题的解,而子问题的解又依赖于更小的子问题的解。
|
||||||
- 回溯算法在尝试和回退中穷举所有可能的解,并通过剪枝避免不必要的搜索分支。原问题的解由一系列决策步骤构成,我们可以将每个决策步骤之后的剩余问题看作为一个子问题。
|
- 回溯算法在尝试和回退中穷举所有可能的解,并通过剪枝避免不必要的搜索分支。原问题的解由一系列决策步骤构成,我们可以将每个决策步骤之前的子序列看作为一个子问题。
|
||||||
|
|||||||
@ -86,7 +86,7 @@
|
|||||||
|
|
||||||
## 算法特性
|
## 算法特性
|
||||||
|
|
||||||
- **时间复杂度 $O(n + k)$** :假设元素在各个桶内平均分布,那么每个桶内的元素数量为 $\frac{n}{k}$ 。假设排序单个桶使用 $O(\frac{n}{k} \log\frac{n}{k})$ 时间,则排序所有桶使用 $O(n \log\frac{n}{k})$ 时间。**当桶数量 $k$ 比较大时,时间复杂度则趋向于 $O(n)$** 。合并结果时需要遍历 $n$ 个桶,花费 $O(k)$ 时间。
|
- **时间复杂度 $O(n + k)$** :假设元素在各个桶内平均分布,那么每个桶内的元素数量为 $\frac{n}{k}$ 。假设排序单个桶使用 $O(\frac{n}{k} \log\frac{n}{k})$ 时间,则排序所有桶使用 $O(n \log\frac{n}{k})$ 时间。**当桶数量 $k$ 比较大时,时间复杂度则趋向于 $O(n)$** 。合并结果时需要遍历所有桶和元素,花费 $O(n + k)$ 时间。
|
||||||
- **自适应排序**:在最坏情况下,所有数据被分配到一个桶中,且排序该桶使用 $O(n^2)$ 时间。
|
- **自适应排序**:在最坏情况下,所有数据被分配到一个桶中,且排序该桶使用 $O(n^2)$ 时间。
|
||||||
- **空间复杂度 $O(n + k)$ 、非原地排序** :需要借助 $k$ 个桶和总共 $n$ 个元素的额外空间。
|
- **空间复杂度 $O(n + k)$ 、非原地排序** :需要借助 $k$ 个桶和总共 $n$ 个元素的额外空间。
|
||||||
- 桶排序是否稳定取决于排序桶内元素的算法是否稳定。
|
- 桶排序是否稳定取决于排序桶内元素的算法是否稳定。
|
||||||
|
|||||||