mirror of
https://github.com/krahets/hello-algo.git
synced 2025-12-19 07:17:54 +08:00
Merge branch 'krahets:master' into master
This commit is contained in:
@@ -75,9 +75,6 @@ comments: true
|
||||
/* 初始化数组 */
|
||||
int[] arr = new int[5]; // { 0, 0, 0, 0, 0 }
|
||||
int[] nums = { 1, 3, 2, 5, 4 };
|
||||
|
||||
var arr2=new int[5]; // { 0, 0, 0, 0, 0 }
|
||||
var nums2=new int[]{1,2,3,4,5};
|
||||
```
|
||||
|
||||
## 数组优点
|
||||
@@ -314,7 +311,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
|
||||
}
|
||||
```
|
||||
|
||||
**数组中插入或删除元素效率低下。** 假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是 “紧挨着的” ,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点:
|
||||
**数组中插入或删除元素效率低下。** 假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点:
|
||||
|
||||
- **时间复杂度高:** 数组的插入和删除的平均时间复杂度均为 $O(N)$ ,其中 $N$ 为数组长度。
|
||||
- **丢失元素:** 由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会被丢失。
|
||||
@@ -712,6 +709,6 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
|
||||
|
||||
**随机访问。** 如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。
|
||||
|
||||
**二分查找。** 例如前文查字典的例子,我们可以将字典中的所有字按照拼音顺序存储在数组中,然后使用与日常查纸质字典相同的 “翻开中间,排除一半” 的方式,来实现一个查电子字典的算法。
|
||||
**二分查找。** 例如前文查字典的例子,我们可以将字典中的所有字按照拼音顺序存储在数组中,然后使用与日常查纸质字典相同的“翻开中间,排除一半”的方式,来实现一个查电子字典的算法。
|
||||
|
||||
**深度学习。** 神经网络中大量使用了向量、矩阵、张量之间的线性代数运算,这些数据都是以数组的形式构建的。数组是神经网络编程中最常使用的数据结构。
|
||||
|
||||
@@ -91,7 +91,7 @@ comments: true
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
// 链表结点类
|
||||
/* 链表结点类 */
|
||||
class ListNode
|
||||
{
|
||||
int val; // 结点值
|
||||
@@ -208,13 +208,13 @@ comments: true
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
// 初始化链表 1 -> 3 -> 2 -> 5 -> 4
|
||||
// 初始化各结点
|
||||
n0 = new ListNode(1);
|
||||
n1 = new ListNode(3);
|
||||
n2 = new ListNode(2);
|
||||
n3 = new ListNode(5);
|
||||
n4 = new ListNode(4);
|
||||
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
|
||||
// 初始化各个结点
|
||||
ListNode n0 = new ListNode(1);
|
||||
ListNode n1 = new ListNode(3);
|
||||
ListNode n2 = new ListNode(2);
|
||||
ListNode n3 = new ListNode(5);
|
||||
ListNode n4 = new ListNode(4);
|
||||
// 构建引用指向
|
||||
n0.next = n1;
|
||||
n1.next = n2;
|
||||
@@ -613,7 +613,7 @@ comments: true
|
||||
int val; // 结点值
|
||||
ListNode *next; // 指向后继结点的指针(引用)
|
||||
ListNode *prev; // 指向前驱结点的指针(引用)
|
||||
ListNode(int x) : val(x), next(nullptr) {} // 构造函数
|
||||
ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 构造函数
|
||||
};
|
||||
```
|
||||
|
||||
@@ -644,8 +644,8 @@ comments: true
|
||||
prev;
|
||||
constructor(val, next) {
|
||||
this.val = val === undefined ? 0 : val; // 结点值
|
||||
this.next = next === undefined ? null : next; // 指向后继结点的引用
|
||||
this.prev = prev === undefined ? null : prev; // 指向前驱结点的引用
|
||||
this.next = next === undefined ? null : next; // 指向后继结点的指针(引用)
|
||||
this.prev = prev === undefined ? null : prev; // 指向前驱结点的指针(引用)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -660,8 +660,8 @@ comments: true
|
||||
prev: ListNode | null;
|
||||
constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {
|
||||
this.val = val === undefined ? 0 : val; // 结点值
|
||||
this.next = next === undefined ? null : next; // 指向后继结点的引用
|
||||
this.prev = prev === undefined ? null : prev; // 指向前驱结点的引用
|
||||
this.next = next === undefined ? null : next; // 指向后继结点的指针(引用)
|
||||
this.prev = prev === undefined ? null : prev; // 指向前驱结点的指针(引用)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -675,7 +675,7 @@ comments: true
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
// 双向链表结点类
|
||||
/* 双向链表结点类 */
|
||||
class ListNode {
|
||||
int val; // 结点值
|
||||
ListNode next; // 指向后继结点的指针(引用)
|
||||
|
||||
@@ -10,13 +10,15 @@ comments: true
|
||||
|
||||
## 列表常用操作
|
||||
|
||||
**初始化列表。** 我们通常使用 `Integer[]` 包装类和 `Arrays.asList()` 作为中转,来初始化一个带有初始值的列表。
|
||||
**初始化列表。** 我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="list.java"
|
||||
/* 初始化列表 */
|
||||
// 注意数组的元素类型是 int[] 的包装类 Integer[]
|
||||
// 无初始值
|
||||
List<Integer> list1 = new ArrayList<>();
|
||||
// 有初始值(注意数组的元素类型需为 int[] 的包装类 Integer[])
|
||||
Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 };
|
||||
List<Integer> list = new ArrayList<>(Arrays.asList(numbers));
|
||||
```
|
||||
@@ -25,6 +27,10 @@ comments: true
|
||||
|
||||
```cpp title="list.cpp"
|
||||
/* 初始化列表 */
|
||||
// 需注意,C++ 中 vector 即是本文描述的 list
|
||||
// 无初始值
|
||||
vector<int> list1;
|
||||
// 有初始值
|
||||
vector<int> list = { 1, 3, 2, 5, 4 };
|
||||
```
|
||||
|
||||
@@ -32,6 +38,9 @@ comments: true
|
||||
|
||||
```python title="list.py"
|
||||
""" 初始化列表 """
|
||||
# 无初始值
|
||||
list1 = []
|
||||
# 有初始值
|
||||
list = [1, 3, 2, 5, 4]
|
||||
```
|
||||
|
||||
@@ -39,6 +48,9 @@ comments: true
|
||||
|
||||
```go title="list_test.go"
|
||||
/* 初始化列表 */
|
||||
// 无初始值
|
||||
list1 := []int
|
||||
// 有初始值
|
||||
list := []int{1, 3, 2, 5, 4}
|
||||
```
|
||||
|
||||
@@ -46,6 +58,9 @@ comments: true
|
||||
|
||||
```js title="list.js"
|
||||
/* 初始化列表 */
|
||||
// 无初始值
|
||||
const list1 = [];
|
||||
// 有初始值
|
||||
const list = [1, 3, 2, 5, 4];
|
||||
```
|
||||
|
||||
@@ -53,6 +68,9 @@ comments: true
|
||||
|
||||
```typescript title="list.ts"
|
||||
/* 初始化列表 */
|
||||
// 无初始值
|
||||
const list1: number[] = [];
|
||||
// 有初始值
|
||||
const list: number[] = [1, 3, 2, 5, 4];
|
||||
```
|
||||
|
||||
@@ -65,7 +83,12 @@ comments: true
|
||||
=== "C#"
|
||||
|
||||
```csharp title="list.cs"
|
||||
|
||||
/* 初始化列表 */
|
||||
// 无初始值
|
||||
List<int> list1 = new ();
|
||||
// 有初始值(注意数组的元素类型需为 int[] 的包装类 Integer[])
|
||||
int[] numbers = new int[] { 1, 3, 2, 5, 4 };
|
||||
List<int> list = numbers.ToList();
|
||||
```
|
||||
|
||||
**访问与更新元素。** 列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。
|
||||
@@ -114,20 +137,20 @@ comments: true
|
||||
|
||||
```js title="list.js"
|
||||
/* 访问元素 */
|
||||
const num = list[1];
|
||||
const num = list[1]; // 访问索引 1 处的元素
|
||||
|
||||
/* 更新元素 */
|
||||
list[1] = 0;
|
||||
list[1] = 0; // 将索引 1 处的元素更新为 0
|
||||
```
|
||||
|
||||
=== "TypeScript"
|
||||
|
||||
```typescript title="list.ts"
|
||||
/* 访问元素 */
|
||||
const num: number = list[1];
|
||||
const num: number = list[1]; // 访问索引 1 处的元素
|
||||
|
||||
/* 更新元素 */
|
||||
list[1] = 0;
|
||||
list[1] = 0; // 将索引 1 处的元素更新为 0
|
||||
```
|
||||
|
||||
=== "C"
|
||||
@@ -139,7 +162,11 @@ comments: true
|
||||
=== "C#"
|
||||
|
||||
```csharp title="list.cs"
|
||||
/* 访问元素 */
|
||||
int num = list[1]; // 访问索引 1 处的元素
|
||||
|
||||
/* 更新元素 */
|
||||
list[1] = 0; // 将索引 1 处的元素更新为 0
|
||||
```
|
||||
|
||||
**在列表中添加、插入、删除元素。** 相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。
|
||||
@@ -273,7 +300,21 @@ comments: true
|
||||
=== "C#"
|
||||
|
||||
```csharp title="list.cs"
|
||||
/* 清空列表 */
|
||||
list.Clear();
|
||||
|
||||
/* 尾部添加元素 */
|
||||
list.Add(1);
|
||||
list.Add(3);
|
||||
list.Add(2);
|
||||
list.Add(5);
|
||||
list.Add(4);
|
||||
|
||||
/* 中间插入元素 */
|
||||
list.Insert(3, 6);
|
||||
|
||||
/* 删除元素 */
|
||||
list.RemoveAt(3);
|
||||
```
|
||||
|
||||
**遍历列表。** 与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。
|
||||
@@ -335,9 +376,9 @@ comments: true
|
||||
|
||||
/* 直接遍历列表元素 */
|
||||
count = 0
|
||||
for range list {
|
||||
count++
|
||||
}
|
||||
for range list {
|
||||
count++
|
||||
}
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
@@ -381,7 +422,19 @@ comments: true
|
||||
=== "C#"
|
||||
|
||||
```csharp title="list.cs"
|
||||
/* 通过索引遍历列表 */
|
||||
int count = 0;
|
||||
for (int i = 0; i < list.Count(); i++)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
/* 直接遍历列表元素 */
|
||||
count = 0;
|
||||
foreach (int n in list)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
```
|
||||
|
||||
**拼接两个列表。** 再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。
|
||||
@@ -424,7 +477,7 @@ comments: true
|
||||
```js title="list.js"
|
||||
/* 拼接两个列表 */
|
||||
const list1 = [6, 8, 7, 10, 9];
|
||||
list.push(...list1);
|
||||
list.push(...list1); // 将列表 list1 拼接到 list 之后
|
||||
```
|
||||
|
||||
=== "TypeScript"
|
||||
@@ -432,7 +485,7 @@ comments: true
|
||||
```typescript title="list.ts"
|
||||
/* 拼接两个列表 */
|
||||
const list1: number[] = [6, 8, 7, 10, 9];
|
||||
list.push(...list1);
|
||||
list.push(...list1); // 将列表 list1 拼接到 list 之后
|
||||
```
|
||||
|
||||
=== "C"
|
||||
@@ -444,7 +497,9 @@ comments: true
|
||||
=== "C#"
|
||||
|
||||
```csharp title="list.cs"
|
||||
|
||||
/* 拼接两个列表 */
|
||||
List<int> list1 = new() { 6, 8, 7, 10, 9 };
|
||||
list.AddRange(list1); // 将列表 list1 拼接到 list 之后
|
||||
```
|
||||
|
||||
**排序列表。** 排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。
|
||||
@@ -481,7 +536,7 @@ comments: true
|
||||
|
||||
```js title="list.js"
|
||||
/* 排序列表 */
|
||||
list.sort((a, b) => a - b);
|
||||
list.sort((a, b) => a - b); // 排序后,列表元素从小到大排列
|
||||
```
|
||||
|
||||
=== "TypeScript"
|
||||
@@ -500,7 +555,8 @@ comments: true
|
||||
=== "C#"
|
||||
|
||||
```csharp title="list.cs"
|
||||
|
||||
/* 排序列表 */
|
||||
list.Sort(); // 排序后,列表元素从小到大排列
|
||||
```
|
||||
|
||||
## 列表简易实现 *
|
||||
@@ -1066,5 +1122,101 @@ comments: true
|
||||
=== "C#"
|
||||
|
||||
```csharp title="my_list.cs"
|
||||
class MyList
|
||||
{
|
||||
private int[] nums; // 数组(存储列表元素)
|
||||
private int capacity = 10; // 列表容量
|
||||
private int size = 0; // 列表长度(即当前元素数量)
|
||||
private int extendRatio = 2; // 每次列表扩容的倍数
|
||||
|
||||
/* 构造函数 */
|
||||
public MyList()
|
||||
{
|
||||
nums = new int[capacity];
|
||||
}
|
||||
|
||||
/* 获取列表长度(即当前元素数量)*/
|
||||
public int Size()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
/* 获取列表容量 */
|
||||
public int Capacity()
|
||||
{
|
||||
return capacity;
|
||||
}
|
||||
|
||||
/* 访问元素 */
|
||||
public int Get(int index)
|
||||
{
|
||||
// 索引如果越界则抛出异常,下同
|
||||
if (index >= size)
|
||||
throw new IndexOutOfRangeException("索引越界");
|
||||
return nums[index];
|
||||
}
|
||||
|
||||
/* 更新元素 */
|
||||
public void Set(int index, int num)
|
||||
{
|
||||
if (index >= size)
|
||||
throw new IndexOutOfRangeException("索引越界");
|
||||
nums[index] = num;
|
||||
}
|
||||
|
||||
/* 尾部添加元素 */
|
||||
public void Add(int num)
|
||||
{
|
||||
// 元素数量超出容量时,触发扩容机制
|
||||
if (size == Capacity())
|
||||
ExtendCapacity();
|
||||
nums[size] = num;
|
||||
// 更新元素数量
|
||||
size++;
|
||||
}
|
||||
|
||||
/* 中间插入元素 */
|
||||
public void Insert(int index, int num)
|
||||
{
|
||||
if (index >= size)
|
||||
throw new IndexOutOfRangeException("索引越界");
|
||||
// 元素数量超出容量时,触发扩容机制
|
||||
if (size == Capacity())
|
||||
ExtendCapacity();
|
||||
// 将索引 index 以及之后的元素都向后移动一位
|
||||
for (int j = size - 1; j >= index; j--)
|
||||
{
|
||||
nums[j + 1] = nums[j];
|
||||
}
|
||||
nums[index] = num;
|
||||
// 更新元素数量
|
||||
size++;
|
||||
}
|
||||
|
||||
/* 删除元素 */
|
||||
public int Remove(int index)
|
||||
{
|
||||
if (index >= size)
|
||||
throw new IndexOutOfRangeException("索引越界");
|
||||
int num = nums[index];
|
||||
// 将索引 index 之后的元素都向前移动一位
|
||||
for (int j = index; j < size - 1; j++)
|
||||
{
|
||||
nums[j] = nums[j + 1];
|
||||
}
|
||||
// 更新元素数量
|
||||
size--;
|
||||
// 返回被删除元素
|
||||
return num;
|
||||
}
|
||||
|
||||
/* 列表扩容 */
|
||||
public void ExtendCapacity()
|
||||
{
|
||||
// 新建一个长度为 size 的数组,并将原数组拷贝到新数组
|
||||
System.Array.Resize(ref nums, Capacity() * extendRatio);
|
||||
// 更新列表容量
|
||||
capacity = nums.Length;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user