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$ 个元素的额外空间。
 | 
				
			||||||
- 桶排序是否稳定取决于排序桶内元素的算法是否稳定。
 | 
					- 桶排序是否稳定取决于排序桶内元素的算法是否稳定。
 | 
				
			||||||
 | 
				
			|||||||