From 48adefba25e92d716ad80e3a9ce4086a0d138f02 Mon Sep 17 00:00:00 2001 From: krahets Date: Sun, 6 Aug 2023 23:19:07 +0800 Subject: [PATCH] build --- chapter_array_and_linkedlist/array.md | 4 +- chapter_array_and_linkedlist/linked_list.md | 20 +++--- chapter_array_and_linkedlist/list.md | 6 +- chapter_computational_complexity/summary.md | 13 ++-- chapter_heap/heap.md | 2 +- chapter_searching/binary_search_edge.md | 54 ++++++++++++++-- chapter_searching/binary_search_insertion.md | 68 ++++++++++++++++++-- chapter_stack_and_queue/stack.md | 2 +- 8 files changed, 136 insertions(+), 33 deletions(-) diff --git a/chapter_array_and_linkedlist/array.md b/chapter_array_and_linkedlist/array.md index ee870f75f..cf3a2c33e 100755 --- a/chapter_array_and_linkedlist/array.md +++ b/chapter_array_and_linkedlist/array.md @@ -36,8 +36,8 @@ comments: true ```python title="array.py" # 初始化数组 - arr: List[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] - nums: List[int] = [1, 3, 2, 5, 4] + arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] + nums: list[int] = [1, 3, 2, 5, 4] ``` === "Go" diff --git a/chapter_array_and_linkedlist/linked_list.md b/chapter_array_and_linkedlist/linked_list.md index 9b0349dc0..10e3173c8 100755 --- a/chapter_array_and_linkedlist/linked_list.md +++ b/chapter_array_and_linkedlist/linked_list.md @@ -4,11 +4,11 @@ comments: true # 4.2.   链表 -内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当我们需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。与数组相比,链表更具灵活性,它可以被存储在非连续的内存空间中。 +内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。与数组相比,链表更具灵活性,它可以被存储在非连续的内存空间中。 「链表 Linked List」是一种线性数据结构,其每个元素都是一个节点对象,各个节点之间通过指针连接,从当前节点通过指针可以访问到下一个节点。**由于指针记录了下个节点的内存地址,因此无需保证内存地址的连续性**,从而可以将各个节点分散存储在内存各处。 -链表「节点 Node」包含两项数据,一是节点「值 Value」,二是指向下一节点的「指针 Pointer」,或称「引用 Reference」。 +链表中的「节点 Node」包含两项数据,一是节点「值 Value」,二是指向下一节点的「指针 Pointer」,或称「引用 Reference」。 ![链表定义与存储方式](linked_list.assets/linkedlist_definition.png) @@ -175,13 +175,7 @@ comments: true ``` -!!! question "尾节点指向什么?" - - 我们将链表的最后一个节点称为「尾节点」,其指向的是“空”,在 Java, C++, Python 中分别记为 $\text{null}$ , $\text{nullptr}$ , $\text{None}$ 。在不引起歧义的前提下,本书都使用 $\text{None}$ 来表示空。 - -!!! question "如何称呼链表?" - - 在编程语言中,数组整体就是一个变量,例如数组 `nums` ,包含各个元素 `nums[0]` , `nums[1]` 等等。而链表是由多个节点对象组成,我们通常将头节点当作链表的代称,例如头节点 `head` 和链表 `head` 实际上是同义的。 +我们将链表的首个节点称为「头节点」,最后一个节点称为「尾节点」。尾节点指向的是“空”,在 Java, C++, Python 中分别记为 $\text{null}$ , $\text{nullptr}$ , $\text{None}$ 。在不引起歧义的前提下,本书都使用 $\text{None}$ 来表示空。 **链表初始化方法**。建立链表分为两步,第一步是初始化各个节点对象,第二步是构建引用指向关系。完成后,即可以从链表的头节点(即首个节点)出发,通过指针 `next` 依次访问所有节点。 @@ -378,9 +372,11 @@ comments: true ``` +在编程语言中,数组整体是一个变量,比如数组 `nums` 包含元素 `nums[0]` , `nums[1]` 等。而链表是由多个分散的节点对象组成,**我们通常将头节点当作链表的代称**,比如以上代码中的链表可被记做链表 `n0` 。 + ## 4.2.1.   链表优点 -**链表中插入与删除节点的操作效率高**。例如,如果我们想在链表中间的两个节点 `A` , `B` 之间插入一个新节点 `P` ,我们只需要改变两个节点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。 +**链表中插入与删除节点的操作效率高**。如果我们想在链表中间的两个节点 `A` , `B` 之间插入一个新节点 `P` ,我们只需要改变两个节点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。 ![链表插入节点](linked_list.assets/linkedlist_insert_node.png) @@ -701,7 +697,7 @@ comments: true ## 4.2.2.   链表缺点 -**链表访问节点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而,链表无法直接访问任意节点,这是因为系统需要从头节点出发,逐个向后遍历直至找到目标节点。例如,若要访问链表索引为 `index`(即第 `index + 1` 个)的节点,则需要向后遍历 `index` 轮。 +**链表访问节点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而链表无法直接访问任意节点,因为程序需要从头节点出发,逐个向后遍历,直至找到目标节点。也就是说,如果想要访问链表中第 $i$ 个节点,则需要向后遍历 $i - 1$ 轮。 === "Java" @@ -872,7 +868,7 @@ comments: true } ``` -**链表的内存占用较大**。链表以节点为单位,每个节点除了保存值之外,还需额外保存指针(引用)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。 +**链表的内存占用较大**。链表以节点为单位,每个节点除了包含值,还需额外保存下一节点的引用(指针)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。 ## 4.2.3.   链表常用操作 diff --git a/chapter_array_and_linkedlist/list.md b/chapter_array_and_linkedlist/list.md index e995fb16f..b04ac4195 100755 --- a/chapter_array_and_linkedlist/list.md +++ b/chapter_array_and_linkedlist/list.md @@ -39,9 +39,9 @@ comments: true ```python title="list.py" # 初始化列表 # 无初始值 - list1: List[int] = [] + list1: list[int] = [] # 有初始值 - list: List[int] = [1, 3, 2, 5, 4] + list: list[int] = [1, 3, 2, 5, 4] ``` === "Go" @@ -650,7 +650,7 @@ comments: true ```python title="list.py" # 拼接两个列表 - list1: List[int] = [6, 8, 7, 10, 9] + list1: list[int] = [6, 8, 7, 10, 9] list += list1 # 将列表 list1 拼接到 list 之后 ``` diff --git a/chapter_computational_complexity/summary.md b/chapter_computational_complexity/summary.md index 28860736a..2534e606f 100644 --- a/chapter_computational_complexity/summary.md +++ b/chapter_computational_complexity/summary.md @@ -30,17 +30,20 @@ comments: true !!! question "尾递归的空间复杂度是 $O(1)$ 吗?" - 理论上,尾递归函数的空间复杂度可以被优化至 $O(1)$ 。不过绝大多数编程语言(例如 Java, Python, C++, Go, C# 等) - 都不支持自动优化尾递归,因此一般来说空间复杂度是 $O(n)$ 。 + 理论上,尾递归函数的空间复杂度可以被优化至 $O(1)$ 。不过绝大多数编程语言(例如 Java, Python, C++, Go, C# 等)都不支持自动优化尾递归,因此通常认为空间复杂度是 $O(n)$ 。 !!! question "函数和方法这两个术语的区别是什么?" - 函数(function)可以独立被执行,所有参数都以显式传递。 - 方法(method)与一个对象关联,方法被隐式传递给调用它的对象,方法能够对类的实例中包含的数据进行操作。 + 函数(function)可以独立被执行,所有参数都以显式传递。方法(method)与一个对象关联,方法被隐式传递给调用它的对象,方法能够对类的实例中包含的数据进行操作。 - 因此,C 和 Go 只有函数,Java 和 C# 只有方法,在 C++, Python 中取决于它是否属于一个类。 + 以几个常见的编程语言为例: + + - C 语言是过程式编程语言,没有面向对象的概念,所以只有函数。但我们可以通过创建结构(struct)来模拟面向对象编程,与结构体相关联的函数就相当于其他语言中的方法。 + - Java, C# 是面向对象的编程语言,代码块(方法)通常都是作为某个类的一部分。静态方法的行为类似于函数,因为它被绑定在类上,不能访问特定的实例变量。 + - C++, Python 既支持过程式编程(函数)也支持面向对象编程(方法)。 !!! question "图片“空间复杂度的常见类型”反映的是否是占用空间的绝对大小?" 不是,该图片展示的是空间复杂度(即增长趋势),而不是占用空间的绝对大小。每个曲线都包含一个常数项,用来把所有曲线的取值范围压缩到一个视觉舒适的范围内。 + 实际中,因为我们通常不知道每个方法的“常数项”复杂度是多少,所以一般无法仅凭复杂度来选择 $n = 8$ 之下的最优解法;但相对地 $n = 8^5$ 就很好选了,这是复杂度占主导的情况。 diff --git a/chapter_heap/heap.md b/chapter_heap/heap.md index 9a2509083..2c7eb2445 100644 --- a/chapter_heap/heap.md +++ b/chapter_heap/heap.md @@ -157,7 +157,7 @@ comments: true is_empty: bool = not max_heap # 输入列表并建堆 - min_heap: List[int] = [1, 3, 2, 5, 4] + min_heap: list[int] = [1, 3, 2, 5, 4] heapq.heapify(min_heap) ``` diff --git a/chapter_searching/binary_search_edge.md b/chapter_searching/binary_search_edge.md index 92eddd71a..379cc5a73 100644 --- a/chapter_searching/binary_search_edge.md +++ b/chapter_searching/binary_search_edge.md @@ -11,7 +11,7 @@ status: new 给定一个长度为 $n$ 的有序数组 `nums` ,数组可能包含重复元素。请返回数组中最左一个元素 `target` 的索引。若数组中不包含该元素,则返回 $-1$ 。 -回忆二分查找插入点的方法,搜索完成后,$i$ 指向最左一个 `target` ,**因此查找插入点本质上是在查找最左一个 `target` 的索引**。 +回忆二分查找插入点的方法,搜索完成后 $i$ 指向最左一个 `target` ,**因此查找插入点本质上是在查找最左一个 `target` 的索引**。 考虑通过查找插入点的函数实现查找左边界。请注意,数组中可能不包含 `target` ,此时有两种可能: @@ -93,13 +93,33 @@ status: new === "C#" ```csharp title="binary_search_edge.cs" - [class]{binary_search_edge}-[func]{binarySearchLeftEdge} + /* 二分查找最左一个 target */ + int binarySearchLeftEdge(int[] nums, int target) { + // 等价于查找 target 的插入点 + int i = binary_search_insertion.binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.Length || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; + } ``` === "Swift" ```swift title="binary_search_edge.swift" - [class]{}-[func]{binarySearchLeftEdge} + /* 二分查找最左一个 target */ + func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { + // 等价于查找 target 的插入点 + let i = binarySearchInsertion(nums: nums, target: target) + // 未找到 target ,返回 -1 + if i == nums.count || nums[i] != target { + return -1 + } + // 找到 target ,返回索引 i + return i + } ``` === "Zig" @@ -217,13 +237,37 @@ status: new === "C#" ```csharp title="binary_search_edge.cs" - [class]{binary_search_edge}-[func]{binarySearchRightEdge} + /* 二分查找最右一个 target */ + int binarySearchRightEdge(int[] nums, int target) { + // 转化为查找最左一个 target + 1 + int i = binary_search_insertion.binarySearchInsertion(nums, target + 1); + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; + } ``` === "Swift" ```swift title="binary_search_edge.swift" - [class]{}-[func]{binarySearchRightEdge} + /* 二分查找最右一个 target */ + func binarySearchRightEdge(nums: [Int], target: Int) -> Int { + // 转化为查找最左一个 target + 1 + let i = binarySearchInsertion(nums: nums, target: target + 1) + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + let j = i - 1 + // 未找到 target ,返回 -1 + if j == -1 || nums[j] != target { + return -1 + } + // 找到 target ,返回索引 j + return j + } ``` === "Zig" diff --git a/chapter_searching/binary_search_insertion.md b/chapter_searching/binary_search_insertion.md index 8f32d4d44..07d0a52ab 100644 --- a/chapter_searching/binary_search_insertion.md +++ b/chapter_searching/binary_search_insertion.md @@ -116,13 +116,43 @@ status: new === "C#" ```csharp title="binary_search_insertion.cs" - [class]{binary_search_insertion}-[func]{binarySearchInsertionSimple} + /* 二分查找插入点(无重复元素) */ + int binarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.Length - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) { + i = m + 1; // target 在区间 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在区间 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入点 m + } + } + // 未找到 target ,返回插入点 i + return i; + } ``` === "Swift" ```swift title="binary_search_insertion.swift" - [class]{}-[func]{binarySearchInsertionSimple} + /* 二分查找插入点(无重复元素) */ + func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { + var i = 0, j = nums.count - 1 // 初始化双闭区间 [0, n-1] + while i <= j { + let m = i + (j - i) / 2 // 计算中点索引 m + if nums[m] < target { + i = m + 1 // target 在区间 [m+1, j] 中 + } else if nums[m] > target { + j = m - 1 // target 在区间 [i, m-1] 中 + } else { + return m // 找到 target ,返回插入点 m + } + } + // 未找到 target ,返回插入点 i + return i + } ``` === "Zig" @@ -284,13 +314,43 @@ status: new === "C#" ```csharp title="binary_search_insertion.cs" - [class]{binary_search_insertion}-[func]{binarySearchInsertion} + /* 二分查找插入点(存在重复元素) */ + int binarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.Length - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) { + i = m + 1; // target 在区间 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在区间 [i, m-1] 中 + } else { + j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 + } + } + // 返回插入点 i + return i; + } ``` === "Swift" ```swift title="binary_search_insertion.swift" - [class]{}-[func]{binarySearchInsertion} + /* 二分查找插入点(存在重复元素) */ + func binarySearchInsertion(nums: [Int], target: Int) -> Int { + var i = 0, j = nums.count - 1 // 初始化双闭区间 [0, n-1] + while i <= j { + let m = i + (j - i) / 2 // 计算中点索引 m + if nums[m] < target { + i = m + 1 // target 在区间 [m+1, j] 中 + } else if nums[m] > target { + j = m - 1 // target 在区间 [i, m-1] 中 + } else { + j = m - 1 // 首个小于 target 的元素在区间 [i, m-1] 中 + } + } + // 返回插入点 i + return i + } ``` === "Zig" diff --git a/chapter_stack_and_queue/stack.md b/chapter_stack_and_queue/stack.md index 4cd2cccf5..15345dd6e 100755 --- a/chapter_stack_and_queue/stack.md +++ b/chapter_stack_and_queue/stack.md @@ -87,7 +87,7 @@ comments: true ```python title="stack.py" # 初始化栈 # Python 没有内置的栈类,可以把 List 当作栈来使用 - stack: List[int] = [] + stack: list[int] = [] # 元素入栈 stack.append(1)