build
This commit is contained in:
parent
fafdebb75c
commit
48adefba25
@ -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"
|
||||
|
@ -4,11 +4,11 @@ comments: true
|
||||
|
||||
# 4.2. 链表
|
||||
|
||||
内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当我们需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。与数组相比,链表更具灵活性,它可以被存储在非连续的内存空间中。
|
||||
内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。与数组相比,链表更具灵活性,它可以被存储在非连续的内存空间中。
|
||||
|
||||
「链表 Linked List」是一种线性数据结构,其每个元素都是一个节点对象,各个节点之间通过指针连接,从当前节点通过指针可以访问到下一个节点。**由于指针记录了下个节点的内存地址,因此无需保证内存地址的连续性**,从而可以将各个节点分散存储在内存各处。
|
||||
|
||||
链表「节点 Node」包含两项数据,一是节点「值 Value」,二是指向下一节点的「指针 Pointer」,或称「引用 Reference」。
|
||||
链表中的「节点 Node」包含两项数据,一是节点「值 Value」,二是指向下一节点的「指针 Pointer」,或称「引用 Reference」。
|
||||
|
||||

|
||||
|
||||
@ -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)$ ;相比之下,数组的插入操作效率要低得多。
|
||||
|
||||

|
||||
|
||||
@ -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. 链表常用操作
|
||||
|
||||
|
@ -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 之后
|
||||
```
|
||||
|
||||
|
@ -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$ 就很好选了,这是复杂度占主导的情况。
|
||||
|
@ -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)
|
||||
```
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -87,7 +87,7 @@ comments: true
|
||||
```python title="stack.py"
|
||||
# 初始化栈
|
||||
# Python 没有内置的栈类,可以把 List 当作栈来使用
|
||||
stack: List[int] = []
|
||||
stack: list[int] = []
|
||||
|
||||
# 元素入栈
|
||||
stack.append(1)
|
||||
|
Loading…
Reference in New Issue
Block a user