@@ -961,7 +961,7 @@ The following table illustrates examples of different operation counts and their
-## 2.3.4 Common Types of Time Complexity
+## 2.3.4 Common types of time complexity
Let's consider the input data size as $n$. The common types of time complexities are illustrated below, arranged from lowest to highest:
@@ -972,11 +972,11 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline
\end{aligned}
$$
-{ class="animation-figure" }
+{ class="animation-figure" }
-
diff --git a/en/docs/chapter_data_structure/character_encoding.md b/en/docs/chapter_data_structure/character_encoding.md
index 37cc0610a..4f770baac 100644
--- a/en/docs/chapter_data_structure/character_encoding.md
+++ b/en/docs/chapter_data_structure/character_encoding.md
@@ -2,29 +2,29 @@
comments: true
---
-# 3.4 Character Encoding *
+# 3.4 Character encoding *
In the computer system, all data is stored in binary form, and characters (represented by char) are no exception. To represent characters, we need to develop a "character set" that defines a one-to-one mapping between each character and binary numbers. With the character set, computers can convert binary numbers to characters by looking up the table.
-## 3.4.1 ASCII Character Set
+## 3.4.1 ASCII character set
The "ASCII code" is one of the earliest character sets, officially known as the American Standard Code for Information Interchange. It uses 7 binary digits (the lower 7 bits of a byte) to represent a character, allowing for a maximum of 128 different characters. As shown in the Figure 3-6 , ASCII includes uppercase and lowercase English letters, numbers 0 ~ 9, various punctuation marks, and certain control characters (such as newline and tab).
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 3-6 ASCII Code
+
Figure 3-6 ASCII code
However, **ASCII can only represent English characters**. With the globalization of computers, a character set called "EASCII" was developed to represent more languages. It expands from the 7-bit structure of ASCII to 8 bits, enabling the representation of 256 characters.
Globally, various region-specific EASCII character sets have been introduced. The first 128 characters of these sets are consistent with the ASCII, while the remaining 128 characters are defined differently to accommodate the requirements of different languages.
-## 3.4.2 GBK Character Set
+## 3.4.2 GBK character set
Later, it was found that **EASCII still could not meet the character requirements of many languages**. For instance, there are nearly a hundred thousand Chinese characters, with several thousand used regularly. In 1980, the Standardization Administration of China released the "GB2312" character set, which included 6763 Chinese characters, essentially fulfilling the computer processing needs for the Chinese language.
However, GB2312 could not handle some rare and traditional characters. The "GBK" character set expands GB2312 and includes 21886 Chinese characters. In the GBK encoding scheme, ASCII characters are represented with one byte, while Chinese characters use two bytes.
-## 3.4.3 Unicode Character Set
+## 3.4.3 Unicode character set
With the rapid evolution of computer technology and a plethora of character sets and encoding standards, numerous problems arose. On the one hand, these character sets generally only defined characters for specific languages and could not function properly in multilingual environments. On the other hand, the existence of multiple character set standards for the same language caused garbled text when information was exchanged between computers using different encoding standards.
@@ -38,13 +38,13 @@ Unicode is a universal character set that assigns a number (called a "code point
A straightforward solution to this problem is to store all characters as equal-length encodings. As shown in the Figure 3-7 , each character in "Hello" occupies 1 byte, while each character in "算法" (algorithm) occupies 2 bytes. We could encode all characters in "Hello 算法" as 2 bytes by padding the higher bits with zeros. This method would enable the system to interpret a character every 2 bytes, recovering the content of the phrase.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 3-7 Unicode Encoding Example
+
Figure 3-7 Unicode encoding example
However, as ASCII has shown us, encoding English only requires 1 byte. Using the above approach would double the space occupied by English text compared to ASCII encoding, which is a waste of memory space. Therefore, a more efficient Unicode encoding method is needed.
-## 3.4.4 UTF-8 Encoding
+## 3.4.4 UTF-8 encoding
Currently, UTF-8 has become the most widely used Unicode encoding method internationally. **It is a variable-length encoding**, using 1 to 4 bytes to represent a character, depending on the complexity of the character. ASCII characters need only 1 byte, Latin and Greek letters require 2 bytes, commonly used Chinese characters need 3 bytes, and some other rare characters need 4 bytes.
@@ -59,26 +59,26 @@ But why set the highest 2 bits of the remaining bytes to $10$? Actually, this $1
The reason for using $10$ as a checksum is that, under UTF-8 encoding rules, it's impossible for the highest two bits of a character to be $10$. This can be proven by contradiction: If the highest two bits of a character are $10$, it indicates that the character's length is $1$, corresponding to ASCII. However, the highest bit of an ASCII character should be $0$, which contradicts the assumption.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 3-8 UTF-8 Encoding Example
+
Figure 3-8 UTF-8 encoding example
Apart from UTF-8, other common encoding methods include:
-- **UTF-16 Encoding**: Uses 2 or 4 bytes to represent a character. All ASCII characters and commonly used non-English characters are represented with 2 bytes; a few characters require 4 bytes. For 2-byte characters, the UTF-16 encoding equals the Unicode code point.
-- **UTF-32 Encoding**: Every character uses 4 bytes. This means UTF-32 occupies more space than UTF-8 and UTF-16, especially for texts with a high proportion of ASCII characters.
+- **UTF-16 encoding**: Uses 2 or 4 bytes to represent a character. All ASCII characters and commonly used non-English characters are represented with 2 bytes; a few characters require 4 bytes. For 2-byte characters, the UTF-16 encoding equals the Unicode code point.
+- **UTF-32 encoding**: Every character uses 4 bytes. This means UTF-32 occupies more space than UTF-8 and UTF-16, especially for texts with a high proportion of ASCII characters.
From the perspective of storage space, using UTF-8 to represent English characters is very efficient because it only requires 1 byte; using UTF-16 to encode some non-English characters (such as Chinese) can be more efficient because it only requires 2 bytes, while UTF-8 might need 3 bytes.
From a compatibility perspective, UTF-8 is the most versatile, with many tools and libraries supporting UTF-8 as a priority.
-## 3.4.5 Character Encoding in Programming Languages
+## 3.4.5 Character encoding in programming languages
Historically, many programming languages utilized fixed-length encodings such as UTF-16 or UTF-32 for processing strings during program execution. This allows strings to be handled as arrays, offering several advantages:
-- **Random Access**: Strings encoded in UTF-16 can be accessed randomly with ease. For UTF-8, which is a variable-length encoding, locating the $i^{th}$ character requires traversing the string from the start to the $i^{th}$ position, taking $O(n)$ time.
-- **Character Counting**: Similar to random access, counting the number of characters in a UTF-16 encoded string is an $O(1)$ operation. However, counting characters in a UTF-8 encoded string requires traversing the entire string.
-- **String Operations**: Many string operations like splitting, concatenating, inserting, and deleting are easier on UTF-16 encoded strings. These operations generally require additional computation on UTF-8 encoded strings to ensure the validity of the UTF-8 encoding.
+- **Random access**: Strings encoded in UTF-16 can be accessed randomly with ease. For UTF-8, which is a variable-length encoding, locating the $i^{th}$ character requires traversing the string from the start to the $i^{th}$ position, taking $O(n)$ time.
+- **Character counting**: Similar to random access, counting the number of characters in a UTF-16 encoded string is an $O(1)$ operation. However, counting characters in a UTF-8 encoded string requires traversing the entire string.
+- **String operations**: Many string operations like splitting, concatenating, inserting, and deleting are easier on UTF-16 encoded strings. These operations generally require additional computation on UTF-8 encoded strings to ensure the validity of the UTF-8 encoding.
The design of character encoding schemes in programming languages is an interesting topic involving various factors:
diff --git a/en/docs/chapter_data_structure/classification_of_data_structure.md b/en/docs/chapter_data_structure/classification_of_data_structure.md
index dd5c3fa19..7d6b26ceb 100644
--- a/en/docs/chapter_data_structure/classification_of_data_structure.md
+++ b/en/docs/chapter_data_structure/classification_of_data_structure.md
@@ -2,38 +2,38 @@
comments: true
---
-# 3.1 Classification of Data Structures
+# 3.1 Classification of data structures
Common data structures include arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. They can be classified into "logical structure" and "physical structure".
-## 3.1.1 Logical Structure: Linear and Non-Linear
+## 3.1.1 Logical structure: linear and non-linear
**The logical structures reveal the logical relationships between data elements**. In arrays and linked lists, data are arranged in a specific sequence, demonstrating the linear relationship between data; while in trees, data are arranged hierarchically from the top down, showing the derived relationship between "ancestors" and "descendants"; and graphs are composed of nodes and edges, reflecting the intricate network relationship.
As shown in the Figure 3-1 , logical structures can be divided into two major categories: "linear" and "non-linear". Linear structures are more intuitive, indicating data is arranged linearly in logical relationships; non-linear structures, conversely, are arranged non-linearly.
-- **Linear Data Structures**: Arrays, Linked Lists, Stacks, Queues, Hash Tables.
-- **Non-Linear Data Structures**: Trees, Heaps, Graphs, Hash Tables.
+- **Linear data structures**: Arrays, Linked Lists, Stacks, Queues, Hash Tables.
+- **Non-linear data structures**: Trees, Heaps, Graphs, Hash Tables.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 3-1 Linear and Non-Linear Data Structures
+
Figure 3-1 Linear and non-linear data structures
Non-linear data structures can be further divided into tree structures and network structures.
-- **Linear Structures**: Arrays, linked lists, queues, stacks, and hash tables, where elements have a one-to-one sequential relationship.
-- **Tree Structures**: Trees, Heaps, Hash Tables, where elements have a one-to-many relationship.
-- **Network Structures**: Graphs, where elements have a many-to-many relationships.
+- **Linear structures**: Arrays, linked lists, queues, stacks, and hash tables, where elements have a one-to-one sequential relationship.
+- **Tree structures**: Trees, Heaps, Hash Tables, where elements have a one-to-many relationship.
+- **Network structures**: Graphs, where elements have a many-to-many relationships.
-## 3.1.2 Physical Structure: Contiguous and Dispersed
+## 3.1.2 Physical structure: contiguous and dispersed
**During the execution of an algorithm, the data being processed is stored in memory**. The Figure 3-2 shows a computer memory stick where each black square is a physical memory space. We can think of memory as a vast Excel spreadsheet, with each cell capable of storing a certain amount of data.
**The system accesses the data at the target location by means of a memory address**. As shown in the Figure 3-2 , the computer assigns a unique identifier to each cell in the table according to specific rules, ensuring that each memory space has a unique memory address. With these addresses, the program can access the data stored in memory.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 3-2 Memory Stick, Memory Spaces, Memory Addresses
+
Figure 3-2 Memory stick, memory spaces, memory addresses
!!! tip
@@ -43,9 +43,9 @@ Memory is a shared resource for all programs. When a block of memory is occupied
As illustrated in the Figure 3-3 , **the physical structure reflects the way data is stored in computer memory** and it can be divided into contiguous space storage (arrays) and non-contiguous space storage (linked lists). The two types of physical structures exhibit complementary characteristics in terms of time efficiency and space efficiency.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 3-3 Contiguous Space Storage and Dispersed Space Storage
+
Figure 3-3 Contiguous space storage and dispersed space storage
**It is worth noting that all data structures are implemented based on arrays, linked lists, or a combination of both**. For example, stacks and queues can be implemented using either arrays or linked lists; while implementations of hash tables may involve both arrays and linked lists.
- **Array-based implementations**: Stacks, Queues, Hash Tables, Trees, Heaps, Graphs, Matrices, Tensors (arrays with dimensions $\geq 3$).
diff --git a/en/docs/chapter_data_structure/index.md b/en/docs/chapter_data_structure/index.md
index 94c454dd6..b3a84f25e 100644
--- a/en/docs/chapter_data_structure/index.md
+++ b/en/docs/chapter_data_structure/index.md
@@ -3,9 +3,9 @@ comments: true
icon: material/shape-outline
---
-# Chapter 3. Data Structures
+# Chapter 3. Data structures
-{ class="cover-image" }
+{ class="cover-image" }
!!! abstract
@@ -15,8 +15,8 @@ icon: material/shape-outline
## Chapter Contents
-- [3.1 Classification of Data Structures](https://www.hello-algo.com/en/chapter_data_structure/classification_of_data_structure/)
-- [3.2 Fundamental Data Types](https://www.hello-algo.com/en/chapter_data_structure/basic_data_types/)
-- [3.3 Number Encoding *](https://www.hello-algo.com/en/chapter_data_structure/number_encoding/)
-- [3.4 Character Encoding *](https://www.hello-algo.com/en/chapter_data_structure/character_encoding/)
+- [3.1 Classification of data structures](https://www.hello-algo.com/en/chapter_data_structure/classification_of_data_structure/)
+- [3.2 Fundamental data types](https://www.hello-algo.com/en/chapter_data_structure/basic_data_types/)
+- [3.3 Number encoding *](https://www.hello-algo.com/en/chapter_data_structure/number_encoding/)
+- [3.4 Character encoding *](https://www.hello-algo.com/en/chapter_data_structure/character_encoding/)
- [3.5 Summary](https://www.hello-algo.com/en/chapter_data_structure/summary/)
diff --git a/en/docs/chapter_data_structure/number_encoding.md b/en/docs/chapter_data_structure/number_encoding.md
index f4336a745..de7fe0dd2 100644
--- a/en/docs/chapter_data_structure/number_encoding.md
+++ b/en/docs/chapter_data_structure/number_encoding.md
@@ -2,13 +2,13 @@
comments: true
---
-# 3.3 Number Encoding *
+# 3.3 Number encoding *
!!! note
In this book, chapters marked with an asterisk '*' are optional readings. If you are short on time or find them challenging, you may skip these initially and return to them after completing the essential chapters.
-## 3.3.1 Integer Encoding
+## 3.3.1 Integer encoding
In the table from the previous section, we observed that all integer types can represent one more negative number than positive numbers, such as the `byte` range of $[-128, 127]$. This phenomenon seems counterintuitive, and its underlying reason involves knowledge of sign-magnitude, one's complement, and two's complement encoding.
@@ -20,9 +20,9 @@ Firstly, it's important to note that **numbers are stored in computers using the
The following diagram illustrates the conversions among sign-magnitude, one's complement, and two's complement:
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 3-4 Conversions between Sign-Magnitude, One's Complement, and Two's Complement
+
Figure 3-4 Conversions between sign-magnitude, one's complement, and two's complement
Although sign-magnitude is the most intuitive, it has limitations. For one, **negative numbers in sign-magnitude cannot be directly used in calculations**. For example, in sign-magnitude, calculating $1 + (-2)$ results in $-3$, which is incorrect.
@@ -92,7 +92,7 @@ We can now summarize the reason for using two's complement in computers: with tw
The design of two's complement is quite ingenious, and due to space constraints, we'll stop here. Interested readers are encouraged to explore further.
-## 3.3.2 Floating-Point Number Encoding
+## 3.3.2 Floating-point number encoding
You might have noticed something intriguing: despite having the same length of 4 bytes, why does a `float` have a much larger range of values compared to an `int`? This seems counterintuitive, as one would expect the range to shrink for `float` since it needs to represent fractions.
@@ -129,9 +129,9 @@ $$
\end{aligned}
$$
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 3-5 Example Calculation of a float in IEEE 754 Standard
+
Figure 3-5 Example calculation of a float in IEEE 754 standard
Observing the diagram, given an example data $\mathrm{S} = 0$, $\mathrm{E} = 124$, $\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$, we have:
@@ -145,7 +145,7 @@ Now we can answer the initial question: **The representation of `float` includes
As shown in the Table 3-2 , exponent bits $E = 0$ and $E = 255$ have special meanings, **used to represent zero, infinity, $\mathrm{NaN}$, etc.**
-
Table 3-2 Meaning of Exponent Bits
+
Table 3-2 Meaning of exponent bits
diff --git a/en/docs/chapter_data_structure/summary.md b/en/docs/chapter_data_structure/summary.md
index 58f1b338b..0da453092 100644
--- a/en/docs/chapter_data_structure/summary.md
+++ b/en/docs/chapter_data_structure/summary.md
@@ -4,7 +4,7 @@ comments: true
# 3.5 Summary
-### 1. Key Review
+### 1. Key review
- Data structures can be categorized from two perspectives: logical structure and physical structure. Logical structure describes the logical relationships between data elements, while physical structure describes how data is stored in computer memory.
- Common logical structures include linear, tree-like, and network structures. We generally classify data structures into linear (arrays, linked lists, stacks, queues) and non-linear (trees, graphs, heaps) based on their logical structure. The implementation of hash tables may involve both linear and non-linear data structures.
diff --git a/en/docs/chapter_hashing/hash_algorithm.md b/en/docs/chapter_hashing/hash_algorithm.md
index 8cb0f8a9e..ff685a9d1 100644
--- a/en/docs/chapter_hashing/hash_algorithm.md
+++ b/en/docs/chapter_hashing/hash_algorithm.md
@@ -2,15 +2,15 @@
comments: true
---
-# 6.3 Hash Algorithms
+# 6.3 Hash algorithms
The previous two sections introduced the working principle of hash tables and the methods to handle hash collisions. However, both open addressing and chaining can **only ensure that the hash table functions normally when collisions occur, but cannot reduce the frequency of hash collisions**.
If hash collisions occur too frequently, the performance of the hash table will deteriorate drastically. As shown in the Figure 6-8 , for a chaining hash table, in the ideal case, the key-value pairs are evenly distributed across the buckets, achieving optimal query efficiency; in the worst case, all key-value pairs are stored in the same bucket, degrading the time complexity to $O(n)$.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 6-8 Ideal and Worst Cases of Hash Collisions
+
Figure 6-8 Ideal and worst cases of hash collisions
**The distribution of key-value pairs is determined by the hash function**. Recalling the steps of calculating a hash function, first compute the hash value, then modulo it by the array length:
@@ -22,35 +22,35 @@ Observing the above formula, when the hash table capacity `capacity` is fixed, *
This means that, to reduce the probability of hash collisions, we should focus on the design of the hash algorithm `hash()`.
-## 6.3.1 Goals of Hash Algorithms
+## 6.3.1 Goals of hash algorithms
To achieve a "fast and stable" hash table data structure, hash algorithms should have the following characteristics:
- **Determinism**: For the same input, the hash algorithm should always produce the same output. Only then can the hash table be reliable.
-- **High Efficiency**: The process of computing the hash value should be fast enough. The smaller the computational overhead, the more practical the hash table.
-- **Uniform Distribution**: The hash algorithm should ensure that key-value pairs are evenly distributed in the hash table. The more uniform the distribution, the lower the probability of hash collisions.
+- **High efficiency**: The process of computing the hash value should be fast enough. The smaller the computational overhead, the more practical the hash table.
+- **Uniform distribution**: The hash algorithm should ensure that key-value pairs are evenly distributed in the hash table. The more uniform the distribution, the lower the probability of hash collisions.
In fact, hash algorithms are not only used to implement hash tables but are also widely applied in other fields.
-- **Password Storage**: To protect the security of user passwords, systems usually do not store the plaintext passwords but rather the hash values of the passwords. When a user enters a password, the system calculates the hash value of the input and compares it with the stored hash value. If they match, the password is considered correct.
-- **Data Integrity Check**: The data sender can calculate the hash value of the data and send it along; the receiver can recalculate the hash value of the received data and compare it with the received hash value. If they match, the data is considered intact.
+- **Password storage**: To protect the security of user passwords, systems usually do not store the plaintext passwords but rather the hash values of the passwords. When a user enters a password, the system calculates the hash value of the input and compares it with the stored hash value. If they match, the password is considered correct.
+- **Data integrity check**: The data sender can calculate the hash value of the data and send it along; the receiver can recalculate the hash value of the received data and compare it with the received hash value. If they match, the data is considered intact.
For cryptographic applications, to prevent reverse engineering such as deducing the original password from the hash value, hash algorithms need higher-level security features.
- **Unidirectionality**: It should be impossible to deduce any information about the input data from the hash value.
-- **Collision Resistance**: It should be extremely difficult to find two different inputs that produce the same hash value.
-- **Avalanche Effect**: Minor changes in the input should lead to significant and unpredictable changes in the output.
+- **Collision resistance**: It should be extremely difficult to find two different inputs that produce the same hash value.
+- **Avalanche effect**: Minor changes in the input should lead to significant and unpredictable changes in the output.
Note that **"Uniform Distribution" and "Collision Resistance" are two separate concepts**. Satisfying uniform distribution does not necessarily mean collision resistance. For example, under random input `key`, the hash function `key % 100` can produce a uniformly distributed output. However, this hash algorithm is too simple, and all `key` with the same last two digits will have the same output, making it easy to deduce a usable `key` from the hash value, thereby cracking the password.
-## 6.3.2 Design of Hash Algorithms
+## 6.3.2 Design of hash algorithms
The design of hash algorithms is a complex issue that requires consideration of many factors. However, for some less demanding scenarios, we can also design some simple hash algorithms.
-- **Additive Hash**: Add up the ASCII codes of each character in the input and use the total sum as the hash value.
-- **Multiplicative Hash**: Utilize the non-correlation of multiplication, multiplying each round by a constant, accumulating the ASCII codes of each character into the hash value.
-- **XOR Hash**: Accumulate the hash value by XORing each element of the input data.
-- **Rotating Hash**: Accumulate the ASCII code of each character into a hash value, performing a rotation operation on the hash value before each accumulation.
+- **Additive hash**: Add up the ASCII codes of each character in the input and use the total sum as the hash value.
+- **Multiplicative hash**: Utilize the non-correlation of multiplication, multiplying each round by a constant, accumulating the ASCII codes of each character into the hash value.
+- **XOR hash**: Accumulate the hash value by XORing each element of the input data.
+- **Rotating hash**: Accumulate the ASCII code of each character into a hash value, performing a rotation operation on the hash value before each accumulation.
=== "Python"
@@ -651,7 +651,7 @@ It is worth noting that if the `key` is guaranteed to be randomly and uniformly
In summary, we usually choose a prime number as the modulus, and this prime number should be large enough to eliminate periodic patterns as much as possible, enhancing the robustness of the hash algorithm.
-## 6.3.3 Common Hash Algorithms
+## 6.3.3 Common hash algorithms
It is not hard to see that the simple hash algorithms mentioned above are quite "fragile" and far from reaching the design goals of hash algorithms. For example, since addition and XOR obey the commutative law, additive hash and XOR hash cannot distinguish strings with the same content but in different order, which may exacerbate hash collisions and cause security issues.
@@ -663,7 +663,7 @@ Over the past century, hash algorithms have been in a continuous process of upgr
- SHA-2 series, especially SHA-256, is one of the most secure hash algorithms to date, with no successful attacks reported, hence commonly used in various security applications and protocols.
- SHA-3 has lower implementation costs and higher computational efficiency compared to SHA-2, but its current usage coverage is not as extensive as the SHA-2 series.
-
Table 6-2 Common Hash Algorithms
+
Table 6-2 Common hash algorithms
@@ -677,7 +677,7 @@ Over the past century, hash algorithms have been in a continuous process of upgr
-# Hash Values in Data Structures
+# Hash values in data structures
We know that the keys in a hash table can be of various data types such as integers, decimals, or strings. Programming languages usually provide built-in hash algorithms for these data types to calculate the bucket indices in the hash table. Taking Python as an example, we can use the `hash()` function to compute the hash values for various data types.
diff --git a/en/docs/chapter_hashing/hash_collision.md b/en/docs/chapter_hashing/hash_collision.md
index a9a9eaa60..0924e0023 100644
--- a/en/docs/chapter_hashing/hash_collision.md
+++ b/en/docs/chapter_hashing/hash_collision.md
@@ -2,7 +2,7 @@
comments: true
---
-# 6.2 Hash Collision
+# 6.2 Hash collision
As mentioned in the previous section, **usually the input space of a hash function is much larger than its output space**, making hash collisions theoretically inevitable. For example, if the input space consists of all integers and the output space is the size of the array capacity, multiple integers will inevitably map to the same bucket index.
@@ -13,24 +13,24 @@ Hash collisions can lead to incorrect query results, severely affecting the usab
There are mainly two methods for improving the structure of hash tables: "Separate Chaining" and "Open Addressing".
-## 6.2.1 Separate Chaining
+## 6.2.1 Separate chaining
In the original hash table, each bucket can store only one key-value pair. "Separate chaining" transforms individual elements into a linked list, with key-value pairs as list nodes, storing all colliding key-value pairs in the same list. The Figure 6-5 shows an example of a hash table with separate chaining.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 6-5 Separate Chaining Hash Table
+
Figure 6-5 Separate chaining hash table
The operations of a hash table implemented with separate chaining have changed as follows:
-- **Querying Elements**: Input `key`, pass through the hash function to obtain the bucket index, access the head node of the list, then traverse the list and compare `key` to find the target key-value pair.
-- **Adding Elements**: First access the list head node via the hash function, then add the node (key-value pair) to the list.
-- **Deleting Elements**: Access the list head based on the hash function's result, then traverse the list to find and remove the target node.
+- **Querying elements**: Input `key`, pass through the hash function to obtain the bucket index, access the head node of the list, then traverse the list and compare `key` to find the target key-value pair.
+- **Adding elements**: First access the list head node via the hash function, then add the node (key-value pair) to the list.
+- **Deleting elements**: Access the list head based on the hash function's result, then traverse the list to find and remove the target node.
Separate chaining has the following limitations:
-- **Increased Space Usage**: The linked list contains node pointers, which consume more memory space than arrays.
-- **Reduced Query Efficiency**: Due to the need for linear traversal of the list to find the corresponding element.
+- **Increased space usage**: The linked list contains node pointers, which consume more memory space than arrays.
+- **Reduced query efficiency**: Due to the need for linear traversal of the list to find the corresponding element.
The code below provides a simple implementation of a separate chaining hash table, with two things to note:
@@ -1445,32 +1445,32 @@ The code below provides a simple implementation of a separate chaining hash tabl
It's worth noting that when the list is very long, the query efficiency $O(n)$ is poor. **At this point, the list can be converted to an "AVL tree" or "Red-Black tree"** to optimize the time complexity of the query operation to $O(\log n)$.
-## 6.2.2 Open Addressing
+## 6.2.2 Open addressing
"Open addressing" does not introduce additional data structures but uses "multiple probes" to handle hash collisions. The probing methods mainly include linear probing, quadratic probing, and double hashing.
Let's use linear probing as an example to introduce the mechanism of open addressing hash tables.
-### 1. Linear Probing
+### 1. Linear probing
Linear probing uses a fixed-step linear search for probing, differing from ordinary hash tables.
-- **Inserting Elements**: Calculate the bucket index using the hash function. If the bucket already contains an element, linearly traverse forward from the conflict position (usually with a step size of $1$) until an empty bucket is found, then insert the element.
-- **Searching for Elements**: If a hash collision is found, use the same step size to linearly traverse forward until the corresponding element is found and return `value`; if an empty bucket is encountered, it means the target element is not in the hash table, so return `None`.
+- **Inserting elements**: Calculate the bucket index using the hash function. If the bucket already contains an element, linearly traverse forward from the conflict position (usually with a step size of $1$) until an empty bucket is found, then insert the element.
+- **Searching for elements**: If a hash collision is found, use the same step size to linearly traverse forward until the corresponding element is found and return `value`; if an empty bucket is encountered, it means the target element is not in the hash table, so return `None`.
The Figure 6-6 shows the distribution of key-value pairs in an open addressing (linear probing) hash table. According to this hash function, keys with the same last two digits will be mapped to the same bucket. Through linear probing, they are stored consecutively in that bucket and the buckets below it.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 6-6 Distribution of Key-Value Pairs in Open Addressing (Linear Probing) Hash Table
+
Figure 6-6 Distribution of key-value pairs in open addressing (linear probing) hash table
However, **linear probing tends to create "clustering"**. Specifically, the longer a continuous position in the array is occupied, the more likely these positions are to encounter hash collisions, further promoting the growth of these clusters and eventually leading to deterioration in the efficiency of operations.
It's important to note that **we cannot directly delete elements in an open addressing hash table**. Deleting an element creates an empty bucket `None` in the array. When searching for elements, if linear probing encounters this empty bucket, it will return, making the elements below this bucket inaccessible. The program may incorrectly assume these elements do not exist, as shown in the Figure 6-7 .
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 6-7 Query Issues Caused by Deletion in Open Addressing
+
Figure 6-7 Query issues caused by deletion in open addressing
To solve this problem, we can use a "lazy deletion" mechanism: instead of directly removing elements from the hash table, **use a constant `TOMBSTONE` to mark the bucket**. In this mechanism, both `None` and `TOMBSTONE` represent empty buckets and can hold key-value pairs. However, when linear probing encounters `TOMBSTONE`, it should continue traversing since there may still be key-value pairs below it.
@@ -3090,7 +3090,7 @@ The code below implements an open addressing (linear probing) hash table with la
[class]{HashMapOpenAddressing}-[func]{}
```
-### 2. Quadratic Probing
+### 2. Quadratic probing
Quadratic probing is similar to linear probing and is one of the common strategies of open addressing. When a collision occurs, quadratic probing does not simply skip a fixed number of steps but skips "the square of the number of probes," i.e., $1, 4, 9, \dots$ steps.
@@ -3104,12 +3104,12 @@ However, quadratic probing is not perfect:
- Clustering still exists, i.e., some positions are more likely to be occupied than others.
- Due to the growth of squares, quadratic probing may not probe the entire hash table, meaning it might not access empty buckets even if they exist in the hash table.
-### 3. Double Hashing
+### 3. Double hashing
As the name suggests, the double hashing method uses multiple hash functions $f_1(x)$, $f_2(x)$, $f_3(x)$, $\dots$ for probing.
-- **Inserting Elements**: If hash function $f_1(x)$ encounters a conflict, try $f_2(x)$, and so on, until an empty position is found and the element is inserted.
-- **Searching for Elements**: Search in the same order of hash functions until the target element is found and returned; if an empty position is encountered or all hash functions have been tried, it indicates the element is not in the hash table, then return `None`.
+- **Inserting elements**: If hash function $f_1(x)$ encounters a conflict, try $f_2(x)$, and so on, until an empty position is found and the element is inserted.
+- **Searching for elements**: Search in the same order of hash functions until the target element is found and returned; if an empty position is encountered or all hash functions have been tried, it indicates the element is not in the hash table, then return `None`.
Compared to linear probing, double hashing is less prone to clustering but involves additional computation for multiple hash functions.
@@ -3117,7 +3117,7 @@ Compared to linear probing, double hashing is less prone to clustering but invol
Please note that open addressing (linear probing, quadratic probing, and double hashing) hash tables all have the issue of "not being able to directly delete elements."
-## 6.2.3 Choice of Programming Languages
+## 6.2.3 Choice of programming languages
Various programming languages have adopted different hash table implementation strategies, here are a few examples:
diff --git a/en/docs/chapter_hashing/hash_map.md b/en/docs/chapter_hashing/hash_map.md
index 9c31db5bd..6a0f5a02d 100755
--- a/en/docs/chapter_hashing/hash_map.md
+++ b/en/docs/chapter_hashing/hash_map.md
@@ -2,7 +2,7 @@
comments: true
---
-# 6.1 Hash Table
+# 6.1 Hash table
A "hash table", also known as a "hash map", achieves efficient element querying by establishing a mapping between keys and values. Specifically, when we input a `key` into the hash table, we can retrieve the corresponding `value` in $O(1)$ time.
@@ -14,11 +14,11 @@ As shown in the Figure 6-1 , given $n$ students, each with two pieces of data: "
Apart from hash tables, arrays and linked lists can also be used to implement querying functions. Their efficiency is compared in the Table 6-1 .
-- **Adding Elements**: Simply add the element to the end of the array (or linked list), using $O(1)$ time.
-- **Querying Elements**: Since the array (or linked list) is unordered, it requires traversing all the elements, using $O(n)$ time.
-- **Deleting Elements**: First, locate the element, then delete it from the array (or linked list), using $O(n)$ time.
+- **Adding elements**: Simply add the element to the end of the array (or linked list), using $O(1)$ time.
+- **Querying elements**: Since the array (or linked list) is unordered, it requires traversing all the elements, using $O(n)$ time.
+- **Deleting elements**: First, locate the element, then delete it from the array (or linked list), using $O(n)$ time.
-
Table 6-1 Comparison of Element Query Efficiency
+
Table 6-1 Comparison of element query efficiency
@@ -32,7 +32,7 @@ Apart from hash tables, arrays and linked lists can also be used to implement qu
Observations reveal that **the time complexity for adding, deleting, and querying in a hash table is $O(1)$**, which is highly efficient.
-## 6.1.1 Common Operations of Hash Table
+## 6.1.1 Common operations of hash table
Common operations of a hash table include initialization, querying, adding key-value pairs, and deleting key-value pairs, etc. Example code is as follows:
@@ -496,7 +496,7 @@ There are three common ways to traverse a hash table: traversing key-value pairs
-## 6.1.2 Simple Implementation of Hash Table
+## 6.1.2 Simple implementation of hash table
First, let's consider the simplest case: **implementing a hash table using just an array**. In the hash table, each empty slot in the array is called a "bucket", and each bucket can store one key-value pair. Therefore, the query operation involves finding the bucket corresponding to the `key` and retrieving the `value` from it.
@@ -1819,7 +1819,7 @@ The following code implements a simple hash table. Here, we encapsulate `key` an
-## 6.1.3 Hash Collision and Resizing
+## 6.1.3 Hash collision and resizing
Fundamentally, the role of the hash function is to map the entire input space of all keys to the output space of all array indices. However, the input space is often much larger than the output space. Therefore, **theoretically, there must be situations where "multiple inputs correspond to the same output"**.
diff --git a/en/docs/chapter_hashing/index.md b/en/docs/chapter_hashing/index.md
index 78c1284c5..0e9bd5f36 100644
--- a/en/docs/chapter_hashing/index.md
+++ b/en/docs/chapter_hashing/index.md
@@ -3,9 +3,9 @@ comments: true
icon: material/table-search
---
-# Chapter 6. Hash Table
+# Chapter 6. Hash table
-{ class="cover-image" }
+{ class="cover-image" }
!!! abstract
@@ -15,7 +15,7 @@ icon: material/table-search
## Chapter Contents
-- [6.1 Hash Table](https://www.hello-algo.com/en/chapter_hashing/hash_map/)
-- [6.2 Hash Collision](https://www.hello-algo.com/en/chapter_hashing/hash_collision/)
-- [6.3 Hash Algorithm](https://www.hello-algo.com/en/chapter_hashing/hash_algorithm/)
+- [6.1 Hash table](https://www.hello-algo.com/en/chapter_hashing/hash_map/)
+- [6.2 Hash collision](https://www.hello-algo.com/en/chapter_hashing/hash_collision/)
+- [6.3 Hash algorithm](https://www.hello-algo.com/en/chapter_hashing/hash_algorithm/)
- [6.4 Summary](https://www.hello-algo.com/en/chapter_hashing/summary/)
diff --git a/en/docs/chapter_hashing/summary.md b/en/docs/chapter_hashing/summary.md
index ec68c7859..780892101 100644
--- a/en/docs/chapter_hashing/summary.md
+++ b/en/docs/chapter_hashing/summary.md
@@ -4,7 +4,7 @@ comments: true
# 6.4 Summary
-### 1. Key Review
+### 1. Key review
- Given an input `key`, a hash table can retrieve the corresponding `value` in $O(1)$ time, which is highly efficient.
- Common hash table operations include querying, adding key-value pairs, deleting key-value pairs, and traversing the hash table.
diff --git a/en/docs/chapter_introduction/algorithms_are_everywhere.md b/en/docs/chapter_introduction/algorithms_are_everywhere.md
index 111362b36..53ac82d77 100644
--- a/en/docs/chapter_introduction/algorithms_are_everywhere.md
+++ b/en/docs/chapter_introduction/algorithms_are_everywhere.md
@@ -2,7 +2,7 @@
comments: true
---
-# 1.1 Algorithms are Everywhere
+# 1.1 Algorithms are everywhere
When we hear the word "algorithm," we naturally think of mathematics. However, many algorithms do not involve complex mathematics but rely more on basic logic, which can be seen everywhere in our daily lives.
@@ -39,9 +39,9 @@ This essential skill for elementary students, looking up a dictionary, is actual
2. Take out a card from the unordered section and insert it into the correct position in the ordered section; after this, the leftmost two cards are in order.
3. Continue to repeat step `2.` until all cards are in order.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 1-2 Playing Cards Sorting Process
+
Figure 1-2 Playing cards sorting process
The above method of organizing playing cards is essentially the "Insertion Sort" algorithm, which is very efficient for small datasets. Many programming languages' sorting functions include the insertion sort.
diff --git a/en/docs/chapter_introduction/index.md b/en/docs/chapter_introduction/index.md
index 553205b82..6274b38a0 100644
--- a/en/docs/chapter_introduction/index.md
+++ b/en/docs/chapter_introduction/index.md
@@ -3,9 +3,9 @@ comments: true
icon: material/calculator-variant-outline
---
-# Chapter 1. Introduction to Algorithms
+# Chapter 1. Introduction to algorithms
-{ class="cover-image" }
+{ class="cover-image" }
!!! abstract
@@ -15,6 +15,6 @@ icon: material/calculator-variant-outline
## Chapter Contents
-- [1.1 Algorithms are Everywhere](https://www.hello-algo.com/en/chapter_introduction/algorithms_are_everywhere/)
-- [1.2 What is an Algorithm](https://www.hello-algo.com/en/chapter_introduction/what_is_dsa/)
+- [1.1 Algorithms are everywhere](https://www.hello-algo.com/en/chapter_introduction/algorithms_are_everywhere/)
+- [1.2 What is an algorithm](https://www.hello-algo.com/en/chapter_introduction/what_is_dsa/)
- [1.3 Summary](https://www.hello-algo.com/en/chapter_introduction/summary/)
diff --git a/en/docs/chapter_introduction/what_is_dsa.md b/en/docs/chapter_introduction/what_is_dsa.md
index 2fb032f9c..d4a533ce9 100644
--- a/en/docs/chapter_introduction/what_is_dsa.md
+++ b/en/docs/chapter_introduction/what_is_dsa.md
@@ -2,9 +2,9 @@
comments: true
---
-# 1.2 What is an Algorithm
+# 1.2 What is an algorithm
-## 1.2.1 Definition of an Algorithm
+## 1.2.1 Definition of an algorithm
An "algorithm" is a set of instructions or steps to solve a specific problem within a finite amount of time. It has the following characteristics:
@@ -12,7 +12,7 @@ An "algorithm" is a set of instructions or steps to solve a specific problem wit
- The algorithm is feasible, meaning it can be completed within a finite number of steps, time, and memory space.
- Each step has a definitive meaning. The output is consistently the same under the same inputs and conditions.
-## 1.2.2 Definition of a Data Structure
+## 1.2.2 Definition of a data structure
A "data structure" is a way of organizing and storing data in a computer, with the following design goals:
@@ -25,7 +25,7 @@ A "data structure" is a way of organizing and storing data in a computer, with t
- Compared to arrays, linked lists offer more convenience in data addition and deletion but sacrifice data access speed.
- Graphs, compared to linked lists, provide richer logical information but require more memory space.
-## 1.2.3 Relationship Between Data Structures and Algorithms
+## 1.2.3 Relationship between data structures and algorithms
As shown in the Figure 1-4 , data structures and algorithms are highly related and closely integrated, specifically in the following three aspects:
@@ -45,7 +45,7 @@ Data structures and algorithms can be likened to a set of building blocks, as il
The detailed correspondence between the two is shown in the Table 1-1 .
-
Table 1-1 Comparing Data Structures and Algorithms to Building Blocks
+
Table 1-1 Comparing data structures and algorithms to building blocks
diff --git a/en/docs/chapter_preface/about_the_book.md b/en/docs/chapter_preface/about_the_book.md
index 7b94c3955..e2cba7191 100644
--- a/en/docs/chapter_preface/about_the_book.md
+++ b/en/docs/chapter_preface/about_the_book.md
@@ -2,7 +2,7 @@
comments: true
---
-# 0.1 About This Book
+# 0.1 About this book
This open-source project aims to create a free, and beginner-friendly crash course on data structures and algorithms.
@@ -10,7 +10,7 @@ This open-source project aims to create a free, and beginner-friendly crash cour
- Run code with just one click, supporting Java, C++, Python, Go, JS, TS, C#, Swift, Rust, Dart, Zig and other languages.
- Readers are encouraged to engage with each other in the discussion area for each section, questions and comments are usually answered within two days.
-## 0.1.1 Target Audience
+## 0.1.1 Target audience
If you are new to algorithms with limited exposure, or you have accumulated some experience in algorithms, but you only have a vague understanding of data structures and algorithms, and you are constantly jumping between "yep" and "hmm", then this book is for you!
@@ -22,17 +22,17 @@ If you are an algorithm expert, we look forward to receiving your valuable sugge
You should know how to write and read simple code in at least one programming language.
-## 0.1.2 Content Structure
+## 0.1.2 Content structure
The main content of the book is shown in the following figure.
-- **Complexity Analysis**: explores aspects and methods for evaluating data structures and algorithms. Covers methods of deriving time complexity and space complexity, along with common types and examples.
-- **Data Structures**: focuses on fundamental data types, classification methods, definitions, pros and cons, common operations, types, applications, and implementation methods of data structures such as array, linked list, stack, queue, hash table, tree, heap, graph, etc.
+- **Complexity analysis**: explores aspects and methods for evaluating data structures and algorithms. Covers methods of deriving time complexity and space complexity, along with common types and examples.
+- **Data structures**: focuses on fundamental data types, classification methods, definitions, pros and cons, common operations, types, applications, and implementation methods of data structures such as array, linked list, stack, queue, hash table, tree, heap, graph, etc.
- **Algorithms**: defines algorithms, discusses their pros and cons, efficiency, application scenarios, problem-solving steps, and includes sample questions for various algorithms such as search, sorting, divide and conquer, backtracking, dynamic programming, greedy algorithms, and more.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 0-1 Main Content of the Book
+
Figure 0-1 Main content of the book
## 0.1.3 Acknowledgements
diff --git a/en/docs/chapter_preface/index.md b/en/docs/chapter_preface/index.md
index 34a61d829..5d285a50c 100644
--- a/en/docs/chapter_preface/index.md
+++ b/en/docs/chapter_preface/index.md
@@ -15,6 +15,6 @@ icon: material/book-open-outline
## Chapter Contents
-- [0.1 About This Book](https://www.hello-algo.com/en/chapter_preface/about_the_book/)
-- [0.2 How to Read](https://www.hello-algo.com/en/chapter_preface/suggestions/)
+- [0.1 About this book](https://www.hello-algo.com/en/chapter_preface/about_the_book/)
+- [0.2 How to read](https://www.hello-algo.com/en/chapter_preface/suggestions/)
- [0.3 Summary](https://www.hello-algo.com/en/chapter_preface/summary/)
diff --git a/en/docs/chapter_preface/suggestions.md b/en/docs/chapter_preface/suggestions.md
index 113ca2094..a5886afe4 100644
--- a/en/docs/chapter_preface/suggestions.md
+++ b/en/docs/chapter_preface/suggestions.md
@@ -2,13 +2,13 @@
comments: true
---
-# 0.2 How to Read
+# 0.2 How to read
!!! tip
For the best reading experience, it is recommended that you read through this section.
-## 0.2.1 Writing Conventions
+## 0.2.1 Writing conventions
- Chapters marked with '*' after the title are optional and contain relatively challenging content. If you are short on time, it is advisable to skip them.
- Technical terms will be in boldface (in the print and PDF versions) or underlined (in the web version), for instance,
array. It's advisable to familiarize yourself with these for better comprehension of technical texts.
@@ -20,7 +20,7 @@ comments: true
=== "Python"
```python title=""
- """Header comments for labeling functions, classes, test samples, etc""""
+ """Header comments for labeling functions, classes, test samples, etc"""
# Comments for explaining details
@@ -184,17 +184,17 @@ comments: true
// comments
```
-## 0.2.2 Efficient Learning via Animated Illustrations
+## 0.2.2 Efficient learning via animated illustrations
Compared with text, videos and pictures have a higher density of information and are more structured, making them easier to understand. In this book, **key and difficult concepts are mainly presented through animations and illustrations**, with text serving as explanations and supplements.
When encountering content with animations or illustrations as shown in the Figure 0-2 , **prioritize understanding the figure, with text as supplementary**, integrating both for a comprehensive understanding.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 0-2 Animated Illustration Example
+
Figure 0-2 Animated illustration example
-## 0.2.3 Deepen Understanding through Coding Practice
+## 0.2.3 Deepen understanding through coding practice
The source code of this book is hosted on the [GitHub Repository](https://github.com/krahets/hello-algo). As shown in the Figure 0-3 , **the source code comes with test examples and can be executed with just a single click**.
@@ -202,9 +202,9 @@ If time permits, **it's recommended to type out the code yourself**. If pressed
Compared to just reading code, writing code often yields more learning. **Learning by doing is the real way to learn.**
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 0-3 Running Code Example
+
Figure 0-3 Running code example
Setting up to run the code involves three main steps.
@@ -220,27 +220,27 @@ git clone https://github.com/krahets/hello-algo.git
Alternatively, you can also click the "Download ZIP" button at the location shown in the Figure 0-4 to directly download the code as a compressed ZIP file. Then, you can simply extract it locally.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 0-4 Cloning Repository and Downloading Code
+
Figure 0-4 Cloning repository and downloading code
**Step 3: Run the source code**. As shown in the Figure 0-5 , for the code block labeled with the file name at the top, we can find the corresponding source code file in the `codes` folder of the repository. These files can be executed with a single click, which will help you save unnecessary debugging time and allow you to focus on learning.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 0-5 Code Block and Corresponding Source Code File
+
Figure 0-5 Code block and corresponding source code file
-## 0.2.4 Learning Together in Discussion
+## 0.2.4 Learning together in discussion
While reading this book, please don't skip over the points that you didn't learn. **Feel free to post your questions in the comment section**. We will be happy to answer them and can usually respond within two days.
As illustrated in the Figure 0-6 , each chapter features a comment section at the bottom. I encourage you to pay attention to these comments. They not only expose you to others' encountered problems, aiding in identifying knowledge gaps and sparking deeper contemplation, but also invite you to generously contribute by answering fellow readers' inquiries, sharing insights, and fostering mutual improvement.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 0-6 Comment Section Example
+
Figure 0-6 Comment section example
-## 0.2.5 Algorithm Learning Path
+## 0.2.5 Algorithm learning path
Overall, the journey of mastering data structures and algorithms can be divided into three stages:
@@ -250,6 +250,6 @@ Overall, the journey of mastering data structures and algorithms can be divided
As shown in the Figure 0-7 , this book mainly covers “Stage 1,” aiming to help you more efficiently embark on Stages 2 and 3.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 0-7 Algorithm Learning Path
+
Figure 0-7 Algorithm learning path
diff --git a/en/docs/chapter_stack_and_queue/deque.md b/en/docs/chapter_stack_and_queue/deque.md
index 5238a0b05..c8123ce64 100644
--- a/en/docs/chapter_stack_and_queue/deque.md
+++ b/en/docs/chapter_stack_and_queue/deque.md
@@ -2,19 +2,19 @@
comments: true
---
-# 5.3 Double-Ended Queue
+# 5.3 Double-ended queue
In a queue, we can only delete elements from the head or add elements to the tail. As shown in the following diagram, a "double-ended queue (deque)" offers more flexibility, allowing the addition or removal of elements at both the head and the tail.
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 5-7 Operations in Double-Ended Queue
+
Figure 5-7 Operations in double-ended queue
-## 5.3.1 Common Operations in Double-Ended Queue
+## 5.3.1 Common operations in double-ended queue
The common operations in a double-ended queue are listed below, and the names of specific methods depend on the programming language used.
-
Table 5-3 Efficiency of Double-Ended Queue Operations
+
Table 5-3 Efficiency of double-ended queue operations
@@ -350,11 +350,11 @@ Similarly, we can directly use the double-ended queue classes implemented in pro
https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22,%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22,%20pop_rear%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
-## 5.3.2 Implementing a Double-Ended Queue *
+## 5.3.2 Implementing a double-ended queue *
The implementation of a double-ended queue is similar to that of a regular queue, it can be based on either a linked list or an array as the underlying data structure.
-### 1. Implementation Based on Doubly Linked List
+### 1. Implementation based on doubly linked list
Recall from the previous section that we used a regular singly linked list to implement a queue, as it conveniently allows for deleting from the head (corresponding to the dequeue operation) and adding new elements after the tail (corresponding to the enqueue operation).
@@ -2136,7 +2136,7 @@ The implementation code is as follows:
}
```
-### 2. Implementation Based on Array
+### 2. Implementation based on array
As shown in the Figure 5-9 , similar to implementing a queue with an array, we can also use a circular array to implement a double-ended queue.
@@ -3470,7 +3470,7 @@ The implementation only needs to add methods for "front enqueue" and "rear deque
[class]{ArrayDeque}-[func]{}
```
-## 5.3.3 Applications of Double-Ended Queue
+## 5.3.3 Applications of double-ended queue
The double-ended queue combines the logic of both stacks and queues, **thus, it can implement all their respective use cases while offering greater flexibility**.
diff --git a/en/docs/chapter_stack_and_queue/index.md b/en/docs/chapter_stack_and_queue/index.md
index 34cb5130f..746f3ed54 100644
--- a/en/docs/chapter_stack_and_queue/index.md
+++ b/en/docs/chapter_stack_and_queue/index.md
@@ -3,9 +3,9 @@ comments: true
icon: material/stack-overflow
---
-# Chapter 5. Stack and Queue
+# Chapter 5. Stack and queue
-{ class="cover-image" }
+{ class="cover-image" }
!!! abstract
@@ -17,5 +17,5 @@ icon: material/stack-overflow
- [5.1 Stack](https://www.hello-algo.com/en/chapter_stack_and_queue/stack/)
- [5.2 Queue](https://www.hello-algo.com/en/chapter_stack_and_queue/queue/)
-- [5.3 Double-ended Queue](https://www.hello-algo.com/en/chapter_stack_and_queue/deque/)
+- [5.3 Double-ended queue](https://www.hello-algo.com/en/chapter_stack_and_queue/deque/)
- [5.4 Summary](https://www.hello-algo.com/en/chapter_stack_and_queue/summary/)
diff --git a/en/docs/chapter_stack_and_queue/queue.md b/en/docs/chapter_stack_and_queue/queue.md
index c90075361..31e8f359d 100755
--- a/en/docs/chapter_stack_and_queue/queue.md
+++ b/en/docs/chapter_stack_and_queue/queue.md
@@ -8,15 +8,15 @@ comments: true
As shown in the Figure 5-4 , we call the front of the queue the "head" and the back the "tail." The operation of adding elements to the rear of the queue is termed "enqueue," and the operation of removing elements from the front is termed "dequeue."
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 5-4 Queue's First-In-First-Out Rule
+
Figure 5-4 Queue's first-in-first-out rule
-## 5.2.1 Common Operations on Queue
+## 5.2.1 Common operations on queue
The common operations on a queue are shown in the Table 5-2 . Note that method names may vary across different programming languages. Here, we use the same naming convention as that used for stacks.
-
Table 5-2 Efficiency of Queue Operations
+
Table 5-2 Efficiency of queue operations
@@ -329,11 +329,11 @@ We can directly use the ready-made queue classes in programming languages:
-## 5.2.2 Implementing a Queue
+## 5.2.2 Implementing a queue
To implement a queue, we need a data structure that allows adding elements at one end and removing them at the other. Both linked lists and arrays meet this requirement.
-### 1. Implementation Based on a Linked List
+### 1. Implementation based on a linked list
As shown in the Figure 5-5 , we can consider the "head node" and "tail node" of a linked list as the "front" and "rear" of the queue, respectively. It is stipulated that nodes can only be added at the rear and removed at the front.
@@ -1296,7 +1296,7 @@ Below is the code for implementing a queue using a linked list:
-### 2. Implementation Based on an Array
+### 2. Implementation based on an array
Deleting the first element in an array has a time complexity of $O(n)$, which would make the dequeue operation inefficient. However, this problem can be cleverly avoided as follows.
@@ -2289,7 +2289,7 @@ The above implementation of the queue still has its limitations: its length is f
The comparison of the two implementations is consistent with that of the stack and is not repeated here.
-## 5.2.3 Typical Applications of Queue
+## 5.2.3 Typical applications of queue
-- **Amazon Orders**. After shoppers place orders, these orders join a queue, and the system processes them in order. During events like Singles' Day, a massive number of orders are generated in a short time, making high concurrency a key challenge for engineers.
-- **Various To-Do Lists**. Any scenario requiring a "first-come, first-served" functionality, such as a printer's task queue or a restaurant's food delivery queue, can effectively maintain the order of processing with a queue.
+- **Amazon orders**: After shoppers place orders, these orders join a queue, and the system processes them in order. During events like Singles' Day, a massive number of orders are generated in a short time, making high concurrency a key challenge for engineers.
+- **Various to-do lists**: Any scenario requiring a "first-come, first-served" functionality, such as a printer's task queue or a restaurant's food delivery queue, can effectively maintain the order of processing with a queue.
diff --git a/en/docs/chapter_stack_and_queue/stack.md b/en/docs/chapter_stack_and_queue/stack.md
index 4a95910c9..161015a7a 100755
--- a/en/docs/chapter_stack_and_queue/stack.md
+++ b/en/docs/chapter_stack_and_queue/stack.md
@@ -10,15 +10,15 @@ We can compare a stack to a pile of plates on a table. To access the bottom plat
As shown in the Figure 5-1 , we refer to the top of the pile of elements as the "top of the stack" and the bottom as the "bottom of the stack." The operation of adding elements to the top of the stack is called "push," and the operation of removing the top element is called "pop."
-{ class="animation-figure" }
+{ class="animation-figure" }
-
Figure 5-1 Stack's Last-In-First-Out Rule
+
Figure 5-1 Stack's last-in-first-out rule
-## 5.1.1 Common Operations on Stack
+## 5.1.1 Common operations on stack
The common operations on a stack are shown in the Table 5-1 . The specific method names depend on the programming language used. Here, we use `push()`, `pop()`, and `peek()` as examples.
-
Table 5-1 Efficiency of Stack Operations
+
Table 5-1 Efficiency of stack operations
@@ -323,13 +323,13 @@ Typically, we can directly use the stack class built into the programming langua
-## 5.1.2 Implementing a Stack
+## 5.1.2 Implementing a stack
To gain a deeper understanding of how a stack operates, let's try implementing a stack class ourselves.
A stack follows the principle of Last-In-First-Out, which means we can only add or remove elements at the top of the stack. However, both arrays and linked lists allow adding and removing elements at any position, **therefore a stack can be seen as a restricted array or linked list**. In other words, we can "shield" certain irrelevant operations of an array or linked list, aligning their external behavior with the characteristics of a stack.
-### 1. Implementation Based on Linked List
+### 1. Implementation based on a linked list
When implementing a stack using a linked list, we can consider the head node of the list as the top of the stack and the tail node as the bottom of the stack.
@@ -1157,7 +1157,7 @@ Below is an example code for implementing a stack based on a linked list:
-### 2. Implementation Based on Array
+### 2. Implementation based on an array
When implementing a stack using an array, we can consider the end of the array as the top of the stack. As shown in the Figure 5-3 , push and pop operations correspond to adding and removing elements at the end of the array, respectively, both with a time complexity of $O(1)$.
@@ -1817,7 +1817,7 @@ Since the elements to be pushed onto the stack may continuously increase, we can
-## 5.1.3 Comparison of the Two Implementations
+## 5.1.3 Comparison of the two implementations
**Supported Operations**
@@ -1842,7 +1842,7 @@ However, since linked list nodes require extra space for storing pointers, **the
In summary, we cannot simply determine which implementation is more memory-efficient. It requires analysis based on specific circumstances.
-## 5.1.4 Typical Applications of Stack
+## 5.1.4 Typical applications of stack
- **Back and forward in browsers, undo and redo in software**. Every time we open a new webpage, the browser pushes the previous page onto the stack, allowing us to go back to the previous page through the back operation, which is essentially a pop operation. To support both back and forward, two stacks are needed to work together.
- **Memory management in programs**. Each time a function is called, the system adds a stack frame at the top of the stack to record the function's context information. In recursive functions, the downward recursion phase keeps pushing onto the stack, while the upward backtracking phase keeps popping from the stack.
diff --git a/en/docs/chapter_stack_and_queue/summary.md b/en/docs/chapter_stack_and_queue/summary.md
index 818277bf8..ad4d9c7d1 100644
--- a/en/docs/chapter_stack_and_queue/summary.md
+++ b/en/docs/chapter_stack_and_queue/summary.md
@@ -4,7 +4,7 @@ comments: true
# 5.4 Summary
-### 1. Key Review
+### 1. Key review
- Stack is a data structure that follows the Last-In-First-Out (LIFO) principle and can be implemented using arrays or linked lists.
- In terms of time efficiency, the array implementation of the stack has a higher average efficiency. However, during expansion, the time complexity for a single push operation can degrade to $O(n)$. In contrast, the linked list implementation of a stack offers more stable efficiency.
diff --git a/overrides/partials/comments.html b/overrides/partials/comments.html
index 6f02cdf8c..75caa1ada 100755
--- a/overrides/partials/comments.html
+++ b/overrides/partials/comments.html
@@ -2,7 +2,7 @@
{% if config.theme.language == 'zh' %}
{% set comm = "欢迎在评论区留下你的见解、问题或建议" %}
{% set lang = "zh-CN" %}
- {% elif config.theme.language == 'zh-Hant' %}
+ {% elif config.theme.language == 'zh-hant' %}
{% set comm = "歡迎在評論區留下你的見解、問題或建議" %}
{% set lang = "zh-TW" %}
{% elif config.theme.language == 'en' %}
diff --git a/zh-Hant/docs/chapter_appendix/contribution.md b/zh-Hant/docs/chapter_appendix/contribution.md
new file mode 100644
index 000000000..48df90d30
--- /dev/null
+++ b/zh-Hant/docs/chapter_appendix/contribution.md
@@ -0,0 +1,53 @@
+---
+comments: true
+---
+
+# 16.2 一起參與創作
+
+由於筆者能力有限,書中難免存在一些遺漏和錯誤,請您諒解。如果您發現了筆誤、連結失效、內容缺失、文字歧義、解釋不清晰或行文結構不合理等問題,請協助我們進行修正,以給讀者提供更優質的學習資源。
+
+所有[撰稿人](https://github.com/krahets/hello-algo/graphs/contributors)的 GitHub ID 將在本書倉庫、網頁版和 PDF 版的主頁上進行展示,以感謝他們對開源社群的無私奉獻。
+
+!!! success "開源的魅力"
+
+ 紙質圖書的兩次印刷的間隔時間往往較久,內容更新非常不方便。
+
+ 而在本開源書中,內容更迭的時間被縮短至數日甚至幾個小時。
+
+### 1. 內容微調
+
+如圖 16-3 所示,每個頁面的右上角都有“編輯圖示”。您可以按照以下步驟修改文字或程式碼。
+
+1. 點選“編輯圖示”,如果遇到“需要 Fork 此倉庫”的提示,請同意該操作。
+2. 修改 Markdown 源檔案內容,檢查內容的正確性,並儘量保持排版格式的統一。
+3. 在頁面底部填寫修改說明,然後點選“Propose file change”按鈕。頁面跳轉後,點選“Create pull request”按鈕即可發起拉取請求。
+
+{ class="animation-figure" }
+
+
圖 16-3 頁面編輯按鍵
+
+圖片無法直接修改,需要透過新建 [Issue](https://github.com/krahets/hello-algo/issues) 或評論留言來描述問題,我們會盡快重新繪製並替換圖片。
+
+### 2. 內容創作
+
+如果您有興趣參與此開源專案,包括將程式碼翻譯成其他程式語言、擴展文章內容等,那麼需要實施以下 Pull Request 工作流程。
+
+1. 登入 GitHub ,將本書的[程式碼倉庫](https://github.com/krahets/hello-algo) Fork 到個人帳號下。
+2. 進入您的 Fork 倉庫網頁,使用 `git clone` 命令將倉庫克隆至本地。
+3. 在本地進行內容創作,並進行完整測試,驗證程式碼的正確性。
+4. 將本地所做更改 Commit ,然後 Push 至遠端倉庫。
+5. 重新整理倉庫網頁,點選“Create pull request”按鈕即可發起拉取請求。
+
+### 3. Docker 部署
+
+在 `hello-algo` 根目錄下,執行以下 Docker 指令碼,即可在 `http://localhost:8000` 訪問本專案:
+
+```shell
+docker-compose up -d
+```
+
+使用以下命令即可刪除部署:
+
+```shell
+docker-compose down
+```
diff --git a/zh-Hant/docs/chapter_appendix/index.md b/zh-Hant/docs/chapter_appendix/index.md
new file mode 100644
index 000000000..f5149e85c
--- /dev/null
+++ b/zh-Hant/docs/chapter_appendix/index.md
@@ -0,0 +1,14 @@
+---
+comments: true
+icon: material/help-circle-outline
+---
+
+# 第 16 章 附錄
+
+{ class="cover-image" }
+
+## Chapter Contents
+
+- [16.1 程式設計環境安裝](https://www.hello-algo.com/en/chapter_appendix/installation/)
+- [16.2 一起參與創作](https://www.hello-algo.com/en/chapter_appendix/contribution/)
+- [16.3 術語表](https://www.hello-algo.com/en/chapter_appendix/terminology/)
diff --git a/zh-Hant/docs/chapter_appendix/installation.md b/zh-Hant/docs/chapter_appendix/installation.md
new file mode 100644
index 000000000..1bfd028aa
--- /dev/null
+++ b/zh-Hant/docs/chapter_appendix/installation.md
@@ -0,0 +1,71 @@
+---
+comments: true
+---
+
+# 16.1 程式設計環境安裝
+
+## 16.1.1 安裝 IDE
+
+推薦使用開源、輕量的 VS Code 作為本地整合開發環境(IDE)。訪問 [VS Code 官網](https://code.visualstudio.com/),根據作業系統選擇相應版本的 VS Code 進行下載和安裝。
+
+{ class="animation-figure" }
+
+
圖 16-1 從官網下載 VS Code
+
+VS Code 擁有強大的擴展包生態系統,支持大多數程式語言的執行和除錯。以 Python 為例,安裝“Python Extension Pack”擴展包之後,即可進行 Python 程式碼除錯。安裝步驟如圖 16-2 所示。
+
+{ class="animation-figure" }
+
+
圖 16-2 安裝 VS Code 擴展包
+
+## 16.1.2 安裝語言環境
+
+### 1. Python 環境
+
+1. 下載並安裝 [Miniconda3](https://docs.conda.io/en/latest/miniconda.html) ,需要 Python 3.10 或更新版本。
+2. 在 VS Code 的擴充功能市場中搜索 `python` ,安裝 Python Extension Pack 。
+3. (可選)在命令列輸入 `pip install black` ,安裝程式碼格式化工具。
+
+### 2. C/C++ 環境
+
+1. Windows 系統需要安裝 [MinGW](https://sourceforge.net/projects/mingw-w64/files/)([配置教程](https://blog.csdn.net/qq_33698226/article/details/129031241));MacOS 自帶 Clang ,無須安裝。
+2. 在 VS Code 的擴充功能市場中搜索 `c++` ,安裝 C/C++ Extension Pack 。
+3. (可選)開啟 Settings 頁面,搜尋 `Clang_format_fallback Style` 程式碼格式化選項,設定為 `{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }` 。
+
+### 3. Java 環境
+
+1. 下載並安裝 [OpenJDK](https://jdk.java.net/18/)(版本需滿足 > JDK 9)。
+2. 在 VS Code 的擴充功能市場中搜索 `java` ,安裝 Extension Pack for Java 。
+
+### 4. C# 環境
+
+1. 下載並安裝 [.Net 8.0](https://dotnet.microsoft.com/en-us/download) 。
+2. 在 VS Code 的擴充功能市場中搜索 `C# Dev Kit` ,安裝 C# Dev Kit ([配置教程](https://code.visualstudio.com/docs/csharp/get-started))。
+3. 也可使用 Visual Studio([安裝教程](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022))。
+
+### 5. Go 環境
+
+1. 下載並安裝 [go](https://go.dev/dl/) 。
+2. 在 VS Code 的擴充功能市場中搜索 `go` ,安裝 Go 。
+3. 按快捷鍵 `Ctrl + Shift + P` 撥出命令欄,輸入 go ,選擇 `Go: Install/Update Tools` ,全部勾選並安裝即可。
+
+### 6. Swift 環境
+
+1. 下載並安裝 [Swift](https://www.swift.org/download/) 。
+2. 在 VS Code 的擴充功能市場中搜索 `swift` ,安裝 [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang) 。
+
+### 7. JavaScript 環境
+
+1. 下載並安裝 [node.js](https://nodejs.org/en/) 。
+2. 在 VS Code 的擴充功能市場中搜索 `javascript` ,安裝 JavaScript (ES6) code snippets 。
+3. (可選)在 VS Code 的擴充功能市場中搜索 `Prettier` ,安裝程式碼格式化工具。
+
+### 8. Dart 環境
+
+1. 下載並安裝 [Dart](https://dart.dev/get-dart) 。
+2. 在 VS Code 的擴充功能市場中搜索 `dart` ,安裝 [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code) 。
+
+### 9. Rust 環境
+
+1. 下載並安裝 [Rust](https://www.rust-lang.org/tools/install) 。
+2. 在 VS Code 的擴充功能市場中搜索 `rust` ,安裝 [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 。
diff --git a/zh-Hant/docs/chapter_appendix/terminology.md b/zh-Hant/docs/chapter_appendix/terminology.md
new file mode 100644
index 000000000..2d26c8466
--- /dev/null
+++ b/zh-Hant/docs/chapter_appendix/terminology.md
@@ -0,0 +1,144 @@
+---
+comments: true
+---
+
+# 16.3 術語表
+
+表 16-1 列出了書中出現的重要術語,值得注意以下幾點。
+
+- 建議記住名詞的英文叫法,以便閱讀英文文獻。
+- 部分名詞在簡體中文和繁體中文下的叫法不同。
+
+
表 16-1 資料結構與演算法的重要名詞
+
+
+
+| English | 简体中文 | 繁体中文 |
+| ------------------------------ | -------------- | -------------- |
+| algorithm | 算法 | 演算法 |
+| data structure | 数据结构 | 資料結構 |
+| code | 代码 | 程式碼 |
+| file | 文件 | 檔案 |
+| function | 函数 | 函式 |
+| method | 方法 | 方法 |
+| variable | 变量 | 變數 |
+| asymptotic complexity analysis | 渐近复杂度分析 | 漸近複雜度分析 |
+| time complexity | 时间复杂度 | 時間複雜度 |
+| space complexity | 空间复杂度 | 空間複雜度 |
+| loop | 循环 | 迴圈 |
+| iteration | 迭代 | 迭代 |
+| recursion | 递归 | 遞迴 |
+| tail recursion | 尾递归 | 尾遞迴 |
+| recursion tree | 递归树 | 遞迴樹 |
+| big-$O$ notation | 大 $O$ 记号 | 大 $O$ 記號 |
+| asymptotic upper bound | 渐近上界 | 漸近上界 |
+| sign-magnitude | 原码 | 原碼 |
+| 1’s complement | 反码 | 一補數 |
+| 2’s complement | 补码 | 二補數 |
+| array | 数组 | 陣列 |
+| index | 索引 | 索引 |
+| linked list | 链表 | 鏈結串列 |
+| linked list node, list node | 链表节点 | 鏈結串列節點 |
+| head node | 头节点 | 頭節點 |
+| tail node | 尾节点 | 尾節點 |
+| list | 列表 | 串列 |
+| dynamic array | 动态数组 | 動態陣列 |
+| hard disk | 硬盘 | 硬碟 |
+| random-access memory (RAM) | 内存 | 記憶體 |
+| cache memory | 缓存 | 快取 |
+| cache miss | 缓存未命中 | 快取未命中 |
+| cache hit rate | 缓存命中率 | 快取命中率 |
+| stack | 栈 | 堆疊 |
+| top of the stack | 栈顶 | 堆疊頂 |
+| bottom of the stack | 栈底 | 堆疊底 |
+| queue | 队列 | 佇列 |
+| double-ended queue | 双向队列 | 雙向佇列 |
+| front of the queue | 队首 | 佇列首 |
+| rear of the queue | 队尾 | 佇列尾 |
+| hash table | 哈希表 | 雜湊表 |
+| bucket | 桶 | 桶 |
+| hash function | 哈希函数 | 雜湊函式 |
+| hash collision | 哈希冲突 | 雜湊衝突 |
+| load factor | 负载因子 | 負載因子 |
+| separate chaining | 链式地址 | 鏈結位址 |
+| open addressing | 开放寻址 | 開放定址 |
+| linear probing | 线性探测 | 線性探查 |
+| lazy deletion | 懒删除 | 懶刪除 |
+| binary tree | 二叉树 | 二元樹 |
+| tree node | 树节点 | 樹節點 |
+| left-child node | 左子节点 | 左子節點 |
+| right-child node | 右子节点 | 右子節點 |
+| parent node | 父节点 | 父節點 |
+| left subtree | 左子树 | 左子樹 |
+| right subtree | 右子树 | 右子樹 |
+| root node | 根节点 | 根節點 |
+| leaf node | 叶节点 | 葉節點 |
+| edge | 边 | 邊 |
+| level | 层 | 層 |
+| degree | 度 | 度 |
+| height | 高度 | 高度 |
+| depth | 深度 | 深度 |
+| perfect binary tree | 完美二叉树 | 完美二元樹 |
+| complete binary tree | 完全二叉树 | 完全二元樹 |
+| full binary tree | 完满二叉树 | 完滿二元樹 |
+| balanced binary tree | 平衡二叉树 | 平衡二元樹 |
+| binary search tree | 二叉搜索树 | 二元搜尋樹 |
+| AVL tree | AVL 树 | AVL 樹 |
+| red-black tree | 红黑树 | 紅黑樹 |
+| level-order traversal | 层序遍历 | 層序走訪 |
+| breadth-first traversal | 广度优先遍历 | 廣度優先走訪 |
+| depth-first traversal | 深度优先遍历 | 深度優先走訪 |
+| binary search tree | 二叉搜索树 | 二元搜尋樹 |
+| balanced binary search tree | 平衡二叉搜索树 | 平衡二元搜尋樹 |
+| balance factor | 平衡因子 | 平衡因子 |
+| heap | 堆 | 堆積 |
+| max heap | 大顶堆 | 大頂堆積 |
+| min heap | 小顶堆 | 小頂堆積 |
+| priority queue | 优先队列 | 優先佇列 |
+| heapify | 堆化 | 堆積化 |
+| top-$k$ problem | Top-$k$ 问题 | Top-$k$ 問題 |
+| graph | 图 | 圖 |
+| vertex | 顶点 | 頂點 |
+| undirected graph | 无向图 | 無向圖 |
+| directed graph | 有向图 | 有向圖 |
+| connected graph | 连通图 | 連通圖 |
+| disconnected graph | 非连通图 | 非連通圖 |
+| weighted graph | 有权图 | 有權圖 |
+| adjacency | 邻接 | 鄰接 |
+| path | 路径 | 路徑 |
+| in-degree | 入度 | 入度 |
+| out-degree | 出度 | 出度 |
+| adjacency matrix | 邻接矩阵 | 鄰接矩陣 |
+| adjacency list | 邻接表 | 鄰接表 |
+| breadth-first search | 广度优先搜索 | 廣度優先搜尋 |
+| depth-first search | 深度优先搜索 | 深度優先搜尋 |
+| binary search | 二分查找 | 二分搜尋 |
+| searching algorithm | 搜索算法 | 搜尋演算法 |
+| sorting algorithm | 排序算法 | 排序演算法 |
+| selection sort | 选择排序 | 選擇排序 |
+| bubble sort | 冒泡排序 | 泡沫排序 |
+| insertion sort | 插入排序 | 插入排序 |
+| quick sort | 快速排序 | 快速排序 |
+| merge sort | 归并排序 | 合併排序 |
+| heap sort | 堆排序 | 堆積排序 |
+| bucket sort | 桶排序 | 桶排序 |
+| counting sort | 计数排序 | 計數排序 |
+| radix sort | 基数排序 | 基數排序 |
+| divide and conquer | 分治 | 分治 |
+| hanota problem | 汉诺塔问题 | 河內塔問題 |
+| backtracking algorithm | 回溯算法 | 回溯演算法 |
+| constraint | 约束 | 約束 |
+| solution | 解 | 解 |
+| state | 状态 | 狀態 |
+| pruning | 剪枝 | 剪枝 |
+| permutations problem | 全排列问题 | 全排列問題 |
+| subset-sum problem | 子集和问题 | 子集合問題 |
+| $n$-queens problem | $n$ 皇后问题 | $n$ 皇后問題 |
+| dynamic programming | 动态规划 | 動態規劃 |
+| initial state | 初始状态 | 初始狀態 |
+| state-transition equation | 状态转移方程 | 狀態轉移方程 |
+| knapsack problem | 背包问题 | 背包問題 |
+| edit distance problem | 编辑距离问题 | 編輯距離問題 |
+| greedy algorithm | 贪心算法 | 貪婪演算法 |
+
+
diff --git a/zh-Hant/docs/chapter_array_and_linkedlist/array.md b/zh-Hant/docs/chapter_array_and_linkedlist/array.md
new file mode 100755
index 000000000..5147d4729
--- /dev/null
+++ b/zh-Hant/docs/chapter_array_and_linkedlist/array.md
@@ -0,0 +1,1462 @@
+---
+comments: true
+---
+
+# 4.1 陣列
+
+
陣列(array)是一種線性資料結構,其將相同型別的元素儲存在連續的記憶體空間中。我們將元素在陣列中的位置稱為該元素的
索引(index)。圖 4-1 展示了陣列的主要概念和儲存方式。
+
+{ class="animation-figure" }
+
+
圖 4-1 陣列定義與儲存方式
+
+## 4.1.1 陣列常用操作
+
+### 1. 初始化陣列
+
+我們可以根據需求選用陣列的兩種初始化方式:無初始值、給定初始值。在未指定初始值的情況下,大多數程式語言會將陣列元素初始化為 $0$ :
+
+=== "Python"
+
+ ```python title="array.py"
+ # 初始化陣列
+ arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ]
+ nums: list[int] = [1, 3, 2, 5, 4]
+ ```
+
+=== "C++"
+
+ ```cpp title="array.cpp"
+ /* 初始化陣列 */
+ // 儲存在堆疊上
+ int arr[5];
+ int nums[5] = { 1, 3, 2, 5, 4 };
+ // 儲存在堆積上(需要手動釋放空間)
+ int* arr1 = new int[5];
+ int* nums1 = new int[5] { 1, 3, 2, 5, 4 };
+ ```
+
+=== "Java"
+
+ ```java title="array.java"
+ /* 初始化陣列 */
+ int[] arr = new int[5]; // { 0, 0, 0, 0, 0 }
+ int[] nums = { 1, 3, 2, 5, 4 };
+ ```
+
+=== "C#"
+
+ ```csharp title="array.cs"
+ /* 初始化陣列 */
+ int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ]
+ int[] nums = [1, 3, 2, 5, 4];
+ ```
+
+=== "Go"
+
+ ```go title="array.go"
+ /* 初始化陣列 */
+ var arr [5]int
+ // 在 Go 中,指定長度時([5]int)為陣列,不指定長度時([]int)為切片
+ // 由於 Go 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度
+ // 為了方便實現擴容 extend() 方法,以下將切片(Slice)看作陣列(Array)
+ nums := []int{1, 3, 2, 5, 4}
+ ```
+
+=== "Swift"
+
+ ```swift title="array.swift"
+ /* 初始化陣列 */
+ let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]
+ let nums = [1, 3, 2, 5, 4]
+ ```
+
+=== "JS"
+
+ ```javascript title="array.js"
+ /* 初始化陣列 */
+ var arr = new Array(5).fill(0);
+ var nums = [1, 3, 2, 5, 4];
+ ```
+
+=== "TS"
+
+ ```typescript title="array.ts"
+ /* 初始化陣列 */
+ let arr: number[] = new Array(5).fill(0);
+ let nums: number[] = [1, 3, 2, 5, 4];
+ ```
+
+=== "Dart"
+
+ ```dart title="array.dart"
+ /* 初始化陣列 */
+ List
arr = List.filled(5, 0); // [0, 0, 0, 0, 0]
+ List nums = [1, 3, 2, 5, 4];
+ ```
+
+=== "Rust"
+
+ ```rust title="array.rs"
+ /* 初始化陣列 */
+ let arr: Vec = vec![0; 5]; // [0, 0, 0, 0, 0]
+ let nums: Vec = vec![1, 3, 2, 5, 4];
+ ```
+
+=== "C"
+
+ ```c title="array.c"
+ /* 初始化陣列 */
+ int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 }
+ int nums[5] = { 1, 3, 2, 5, 4 };
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="array.kt"
+ /* 初始化陣列 */
+ var arr = IntArray(5) // { 0, 0, 0, 0, 0 }
+ var nums = intArrayOf(1, 3, 2, 5, 4)
+ ```
+
+=== "Ruby"
+
+ ```ruby title="array.rb"
+ # 初始化陣列
+ arr = Array.new(5, 0)
+ nums = [1, 3, 2, 5, 4]
+ ```
+
+=== "Zig"
+
+ ```zig title="array.zig"
+ // 初始化陣列
+ var arr = [_]i32{0} ** 5; // { 0, 0, 0, 0, 0 }
+ var nums = [_]i32{ 1, 3, 2, 5, 4 };
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+### 2. 訪問元素
+
+陣列元素被儲存在連續的記憶體空間中,這意味著計算陣列元素的記憶體位址非常容易。給定陣列記憶體位址(首元素記憶體位址)和某個元素的索引,我們可以使用圖 4-2 所示的公式計算得到該元素的記憶體位址,從而直接訪問該元素。
+
+{ class="animation-figure" }
+
+ 圖 4-2 陣列元素的記憶體位址計算
+
+觀察圖 4-2 ,我們發現陣列首個元素的索引為 $0$ ,這似乎有些反直覺,因為從 $1$ 開始計數會更自然。但從位址計算公式的角度看,**索引本質上是記憶體位址的偏移量**。首個元素的位址偏移量是 $0$ ,因此它的索引為 $0$ 是合理的。
+
+在陣列中訪問元素非常高效,我們可以在 $O(1)$ 時間內隨機訪問陣列中的任意一個元素。
+
+=== "Python"
+
+ ```python title="array.py"
+ def random_access(nums: list[int]) -> int:
+ """隨機訪問元素"""
+ # 在區間 [0, len(nums)-1] 中隨機抽取一個數字
+ random_index = random.randint(0, len(nums) - 1)
+ # 獲取並返回隨機元素
+ random_num = nums[random_index]
+ return random_num
+ ```
+
+=== "C++"
+
+ ```cpp title="array.cpp"
+ /* 隨機訪問元素 */
+ int randomAccess(int *nums, int size) {
+ // 在區間 [0, size) 中隨機抽取一個數字
+ int randomIndex = rand() % size;
+ // 獲取並返回隨機元素
+ int randomNum = nums[randomIndex];
+ return randomNum;
+ }
+ ```
+
+=== "Java"
+
+ ```java title="array.java"
+ /* 隨機訪問元素 */
+ int randomAccess(int[] nums) {
+ // 在區間 [0, nums.length) 中隨機抽取一個數字
+ int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length);
+ // 獲取並返回隨機元素
+ int randomNum = nums[randomIndex];
+ return randomNum;
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="array.cs"
+ /* 隨機訪問元素 */
+ int RandomAccess(int[] nums) {
+ Random random = new();
+ // 在區間 [0, nums.Length) 中隨機抽取一個數字
+ int randomIndex = random.Next(nums.Length);
+ // 獲取並返回隨機元素
+ int randomNum = nums[randomIndex];
+ return randomNum;
+ }
+ ```
+
+=== "Go"
+
+ ```go title="array.go"
+ /* 隨機訪問元素 */
+ func randomAccess(nums []int) (randomNum int) {
+ // 在區間 [0, nums.length) 中隨機抽取一個數字
+ randomIndex := rand.Intn(len(nums))
+ // 獲取並返回隨機元素
+ randomNum = nums[randomIndex]
+ return
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="array.swift"
+ /* 隨機訪問元素 */
+ func randomAccess(nums: [Int]) -> Int {
+ // 在區間 [0, nums.count) 中隨機抽取一個數字
+ let randomIndex = nums.indices.randomElement()!
+ // 獲取並返回隨機元素
+ let randomNum = nums[randomIndex]
+ return randomNum
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="array.js"
+ /* 隨機訪問元素 */
+ function randomAccess(nums) {
+ // 在區間 [0, nums.length) 中隨機抽取一個數字
+ const random_index = Math.floor(Math.random() * nums.length);
+ // 獲取並返回隨機元素
+ const random_num = nums[random_index];
+ return random_num;
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="array.ts"
+ /* 隨機訪問元素 */
+ function randomAccess(nums: number[]): number {
+ // 在區間 [0, nums.length) 中隨機抽取一個數字
+ const random_index = Math.floor(Math.random() * nums.length);
+ // 獲取並返回隨機元素
+ const random_num = nums[random_index];
+ return random_num;
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="array.dart"
+ /* 隨機訪問元素 */
+ int randomAccess(List nums) {
+ // 在區間 [0, nums.length) 中隨機抽取一個數字
+ int randomIndex = Random().nextInt(nums.length);
+ // 獲取並返回隨機元素
+ int randomNum = nums[randomIndex];
+ return randomNum;
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="array.rs"
+ /* 隨機訪問元素 */
+ fn random_access(nums: &[i32]) -> i32 {
+ // 在區間 [0, nums.len()) 中隨機抽取一個數字
+ let random_index = rand::thread_rng().gen_range(0..nums.len());
+ // 獲取並返回隨機元素
+ let random_num = nums[random_index];
+ random_num
+ }
+ ```
+
+=== "C"
+
+ ```c title="array.c"
+ /* 隨機訪問元素 */
+ int randomAccess(int *nums, int size) {
+ // 在區間 [0, size) 中隨機抽取一個數字
+ int randomIndex = rand() % size;
+ // 獲取並返回隨機元素
+ int randomNum = nums[randomIndex];
+ return randomNum;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="array.kt"
+ /* 隨機訪問元素 */
+ fun randomAccess(nums: IntArray): Int {
+ // 在區間 [0, nums.size) 中隨機抽取一個數字
+ val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size)
+ // 獲取並返回隨機元素
+ val randomNum = nums[randomIndex]
+ return randomNum
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="array.rb"
+ ### 隨機訪問元素 ###
+ def random_access(nums)
+ # 在區間 [0, nums.length) 中隨機抽取一個數字
+ random_index = Random.rand(0...nums.length)
+
+ # 獲取並返回隨機元素
+ nums[random_index]
+ end
+ ```
+
+=== "Zig"
+
+ ```zig title="array.zig"
+ // 隨機訪問元素
+ fn randomAccess(nums: []i32) i32 {
+ // 在區間 [0, nums.len) 中隨機抽取一個整數
+ var randomIndex = std.crypto.random.intRangeLessThan(usize, 0, nums.len);
+ // 獲取並返回隨機元素
+ var randomNum = nums[randomIndex];
+ return randomNum;
+ }
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+### 3. 插入元素
+
+陣列元素在記憶體中是“緊挨著的”,它們之間沒有空間再存放任何資料。如圖 4-3 所示,如果想在陣列中間插入一個元素,則需要將該元素之後的所有元素都向後移動一位,之後再把元素賦值給該索引。
+
+{ class="animation-figure" }
+
+ 圖 4-3 陣列插入元素示例
+
+值得注意的是,由於陣列的長度是固定的,因此插入一個元素必定會導致陣列尾部元素“丟失”。我們將這個問題的解決方案留在“串列”章節中討論。
+
+=== "Python"
+
+ ```python title="array.py"
+ def insert(nums: list[int], num: int, index: int):
+ """在陣列的索引 index 處插入元素 num"""
+ # 把索引 index 以及之後的所有元素向後移動一位
+ for i in range(len(nums) - 1, index, -1):
+ nums[i] = nums[i - 1]
+ # 將 num 賦給 index 處的元素
+ nums[index] = num
+ ```
+
+=== "C++"
+
+ ```cpp title="array.cpp"
+ /* 在陣列的索引 index 處插入元素 num */
+ void insert(int *nums, int size, int num, int index) {
+ // 把索引 index 以及之後的所有元素向後移動一位
+ for (int i = size - 1; i > index; i--) {
+ nums[i] = nums[i - 1];
+ }
+ // 將 num 賦給 index 處的元素
+ nums[index] = num;
+ }
+ ```
+
+=== "Java"
+
+ ```java title="array.java"
+ /* 在陣列的索引 index 處插入元素 num */
+ void insert(int[] nums, int num, int index) {
+ // 把索引 index 以及之後的所有元素向後移動一位
+ for (int i = nums.length - 1; i > index; i--) {
+ nums[i] = nums[i - 1];
+ }
+ // 將 num 賦給 index 處的元素
+ nums[index] = num;
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="array.cs"
+ /* 在陣列的索引 index 處插入元素 num */
+ void Insert(int[] nums, int num, int index) {
+ // 把索引 index 以及之後的所有元素向後移動一位
+ for (int i = nums.Length - 1; i > index; i--) {
+ nums[i] = nums[i - 1];
+ }
+ // 將 num 賦給 index 處的元素
+ nums[index] = num;
+ }
+ ```
+
+=== "Go"
+
+ ```go title="array.go"
+ /* 在陣列的索引 index 處插入元素 num */
+ func insert(nums []int, num int, index int) {
+ // 把索引 index 以及之後的所有元素向後移動一位
+ for i := len(nums) - 1; i > index; i-- {
+ nums[i] = nums[i-1]
+ }
+ // 將 num 賦給 index 處的元素
+ nums[index] = num
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="array.swift"
+ /* 在陣列的索引 index 處插入元素 num */
+ func insert(nums: inout [Int], num: Int, index: Int) {
+ // 把索引 index 以及之後的所有元素向後移動一位
+ for i in nums.indices.dropFirst(index).reversed() {
+ nums[i] = nums[i - 1]
+ }
+ // 將 num 賦給 index 處的元素
+ nums[index] = num
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="array.js"
+ /* 在陣列的索引 index 處插入元素 num */
+ function insert(nums, num, index) {
+ // 把索引 index 以及之後的所有元素向後移動一位
+ for (let i = nums.length - 1; i > index; i--) {
+ nums[i] = nums[i - 1];
+ }
+ // 將 num 賦給 index 處的元素
+ nums[index] = num;
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="array.ts"
+ /* 在陣列的索引 index 處插入元素 num */
+ function insert(nums: number[], num: number, index: number): void {
+ // 把索引 index 以及之後的所有元素向後移動一位
+ for (let i = nums.length - 1; i > index; i--) {
+ nums[i] = nums[i - 1];
+ }
+ // 將 num 賦給 index 處的元素
+ nums[index] = num;
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="array.dart"
+ /* 在陣列的索引 index 處插入元素 _num */
+ void insert(List nums, int _num, int index) {
+ // 把索引 index 以及之後的所有元素向後移動一位
+ for (var i = nums.length - 1; i > index; i--) {
+ nums[i] = nums[i - 1];
+ }
+ // 將 _num 賦給 index 處元素
+ nums[index] = _num;
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="array.rs"
+ /* 在陣列的索引 index 處插入元素 num */
+ fn insert(nums: &mut Vec, num: i32, index: usize) {
+ // 把索引 index 以及之後的所有元素向後移動一位
+ for i in (index + 1..nums.len()).rev() {
+ nums[i] = nums[i - 1];
+ }
+ // 將 num 賦給 index 處的元素
+ nums[index] = num;
+ }
+ ```
+
+=== "C"
+
+ ```c title="array.c"
+ /* 在陣列的索引 index 處插入元素 num */
+ void insert(int *nums, int size, int num, int index) {
+ // 把索引 index 以及之後的所有元素向後移動一位
+ for (int i = size - 1; i > index; i--) {
+ nums[i] = nums[i - 1];
+ }
+ // 將 num 賦給 index 處的元素
+ nums[index] = num;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="array.kt"
+ /* 在陣列的索引 index 處插入元素 num */
+ fun insert(nums: IntArray, num: Int, index: Int) {
+ // 把索引 index 以及之後的所有元素向後移動一位
+ for (i in nums.size - 1 downTo index + 1) {
+ nums[i] = nums[i - 1]
+ }
+ // 將 num 賦給 index 處的元素
+ nums[index] = num
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="array.rb"
+ ### 在陣列的索引 index 處插入元素 num ###
+ def insert(nums, num, index)
+ # 把索引 index 以及之後的所有元素向後移動一位
+ for i in (nums.length - 1).downto(index + 1)
+ nums[i] = nums[i - 1]
+ end
+
+ # 將 num 賦給 index 處的元素
+ nums[index] = num
+ end
+ ```
+
+=== "Zig"
+
+ ```zig title="array.zig"
+ // 在陣列的索引 index 處插入元素 num
+ fn insert(nums: []i32, num: i32, index: usize) void {
+ // 把索引 index 以及之後的所有元素向後移動一位
+ var i = nums.len - 1;
+ while (i > index) : (i -= 1) {
+ nums[i] = nums[i - 1];
+ }
+ // 將 num 賦給 index 處的元素
+ nums[index] = num;
+ }
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+### 4. 刪除元素
+
+同理,如圖 4-4 所示,若想刪除索引 $i$ 處的元素,則需要把索引 $i$ 之後的元素都向前移動一位。
+
+{ class="animation-figure" }
+
+ 圖 4-4 陣列刪除元素示例
+
+請注意,刪除元素完成後,原先末尾的元素變得“無意義”了,所以我們無須特意去修改它。
+
+=== "Python"
+
+ ```python title="array.py"
+ def remove(nums: list[int], index: int):
+ """刪除索引 index 處的元素"""
+ # 把索引 index 之後的所有元素向前移動一位
+ for i in range(index, len(nums) - 1):
+ nums[i] = nums[i + 1]
+ ```
+
+=== "C++"
+
+ ```cpp title="array.cpp"
+ /* 刪除索引 index 處的元素 */
+ void remove(int *nums, int size, int index) {
+ // 把索引 index 之後的所有元素向前移動一位
+ for (int i = index; i < size - 1; i++) {
+ nums[i] = nums[i + 1];
+ }
+ }
+ ```
+
+=== "Java"
+
+ ```java title="array.java"
+ /* 刪除索引 index 處的元素 */
+ void remove(int[] nums, int index) {
+ // 把索引 index 之後的所有元素向前移動一位
+ for (int i = index; i < nums.length - 1; i++) {
+ nums[i] = nums[i + 1];
+ }
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="array.cs"
+ /* 刪除索引 index 處的元素 */
+ void Remove(int[] nums, int index) {
+ // 把索引 index 之後的所有元素向前移動一位
+ for (int i = index; i < nums.Length - 1; i++) {
+ nums[i] = nums[i + 1];
+ }
+ }
+ ```
+
+=== "Go"
+
+ ```go title="array.go"
+ /* 刪除索引 index 處的元素 */
+ func remove(nums []int, index int) {
+ // 把索引 index 之後的所有元素向前移動一位
+ for i := index; i < len(nums)-1; i++ {
+ nums[i] = nums[i+1]
+ }
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="array.swift"
+ /* 刪除索引 index 處的元素 */
+ func remove(nums: inout [Int], index: Int) {
+ // 把索引 index 之後的所有元素向前移動一位
+ for i in nums.indices.dropFirst(index).dropLast() {
+ nums[i] = nums[i + 1]
+ }
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="array.js"
+ /* 刪除索引 index 處的元素 */
+ function remove(nums, index) {
+ // 把索引 index 之後的所有元素向前移動一位
+ for (let i = index; i < nums.length - 1; i++) {
+ nums[i] = nums[i + 1];
+ }
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="array.ts"
+ /* 刪除索引 index 處的元素 */
+ function remove(nums: number[], index: number): void {
+ // 把索引 index 之後的所有元素向前移動一位
+ for (let i = index; i < nums.length - 1; i++) {
+ nums[i] = nums[i + 1];
+ }
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="array.dart"
+ /* 刪除索引 index 處的元素 */
+ void remove(List nums, int index) {
+ // 把索引 index 之後的所有元素向前移動一位
+ for (var i = index; i < nums.length - 1; i++) {
+ nums[i] = nums[i + 1];
+ }
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="array.rs"
+ /* 刪除索引 index 處的元素 */
+ fn remove(nums: &mut Vec, index: usize) {
+ // 把索引 index 之後的所有元素向前移動一位
+ for i in index..nums.len() - 1 {
+ nums[i] = nums[i + 1];
+ }
+ }
+ ```
+
+=== "C"
+
+ ```c title="array.c"
+ /* 刪除索引 index 處的元素 */
+ // 注意:stdio.h 佔用了 remove 關鍵詞
+ void removeItem(int *nums, int size, int index) {
+ // 把索引 index 之後的所有元素向前移動一位
+ for (int i = index; i < size - 1; i++) {
+ nums[i] = nums[i + 1];
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="array.kt"
+ /* 刪除索引 index 處的元素 */
+ fun remove(nums: IntArray, index: Int) {
+ // 把索引 index 之後的所有元素向前移動一位
+ for (i in index..
+
+
+總的來看,陣列的插入與刪除操作有以下缺點。
+
+- **時間複雜度高**:陣列的插入和刪除的平均時間複雜度均為 $O(n)$ ,其中 $n$ 為陣列長度。
+- **丟失元素**:由於陣列的長度不可變,因此在插入元素後,超出陣列長度範圍的元素會丟失。
+- **記憶體浪費**:我們可以初始化一個比較長的陣列,只用前面一部分,這樣在插入資料時,丟失的末尾元素都是“無意義”的,但這樣做會造成部分記憶體空間浪費。
+
+### 5. 走訪陣列
+
+在大多數程式語言中,我們既可以透過索引走訪陣列,也可以直接走訪獲取陣列中的每個元素:
+
+=== "Python"
+
+ ```python title="array.py"
+ def traverse(nums: list[int]):
+ """走訪陣列"""
+ count = 0
+ # 透過索引走訪陣列
+ for i in range(len(nums)):
+ count += nums[i]
+ # 直接走訪陣列元素
+ for num in nums:
+ count += num
+ # 同時走訪資料索引和元素
+ for i, num in enumerate(nums):
+ count += nums[i]
+ count += num
+ ```
+
+=== "C++"
+
+ ```cpp title="array.cpp"
+ /* 走訪陣列 */
+ void traverse(int *nums, int size) {
+ int count = 0;
+ // 透過索引走訪陣列
+ for (int i = 0; i < size; i++) {
+ count += nums[i];
+ }
+ }
+ ```
+
+=== "Java"
+
+ ```java title="array.java"
+ /* 走訪陣列 */
+ void traverse(int[] nums) {
+ int count = 0;
+ // 透過索引走訪陣列
+ for (int i = 0; i < nums.length; i++) {
+ count += nums[i];
+ }
+ // 直接走訪陣列元素
+ for (int num : nums) {
+ count += num;
+ }
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="array.cs"
+ /* 走訪陣列 */
+ void Traverse(int[] nums) {
+ int count = 0;
+ // 透過索引走訪陣列
+ for (int i = 0; i < nums.Length; i++) {
+ count += nums[i];
+ }
+ // 直接走訪陣列元素
+ foreach (int num in nums) {
+ count += num;
+ }
+ }
+ ```
+
+=== "Go"
+
+ ```go title="array.go"
+ /* 走訪陣列 */
+ func traverse(nums []int) {
+ count := 0
+ // 透過索引走訪陣列
+ for i := 0; i < len(nums); i++ {
+ count += nums[i]
+ }
+ count = 0
+ // 直接走訪陣列元素
+ for _, num := range nums {
+ count += num
+ }
+ // 同時走訪資料索引和元素
+ for i, num := range nums {
+ count += nums[i]
+ count += num
+ }
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="array.swift"
+ /* 走訪陣列 */
+ func traverse(nums: [Int]) {
+ var count = 0
+ // 透過索引走訪陣列
+ for i in nums.indices {
+ count += nums[i]
+ }
+ // 直接走訪陣列元素
+ for num in nums {
+ count += num
+ }
+ // 同時走訪資料索引和元素
+ for (i, num) in nums.enumerated() {
+ count += nums[i]
+ count += num
+ }
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="array.js"
+ /* 走訪陣列 */
+ function traverse(nums) {
+ let count = 0;
+ // 透過索引走訪陣列
+ for (let i = 0; i < nums.length; i++) {
+ count += nums[i];
+ }
+ // 直接走訪陣列元素
+ for (const num of nums) {
+ count += num;
+ }
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="array.ts"
+ /* 走訪陣列 */
+ function traverse(nums: number[]): void {
+ let count = 0;
+ // 透過索引走訪陣列
+ for (let i = 0; i < nums.length; i++) {
+ count += nums[i];
+ }
+ // 直接走訪陣列元素
+ for (const num of nums) {
+ count += num;
+ }
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="array.dart"
+ /* 走訪陣列元素 */
+ void traverse(List
nums) {
+ int count = 0;
+ // 透過索引走訪陣列
+ for (var i = 0; i < nums.length; i++) {
+ count += nums[i];
+ }
+ // 直接走訪陣列元素
+ for (int _num in nums) {
+ count += _num;
+ }
+ // 透過 forEach 方法走訪陣列
+ nums.forEach((_num) {
+ count += _num;
+ });
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="array.rs"
+ /* 走訪陣列 */
+ fn traverse(nums: &[i32]) {
+ let mut _count = 0;
+ // 透過索引走訪陣列
+ for i in 0..nums.len() {
+ _count += nums[i];
+ }
+ // 直接走訪陣列元素
+ for num in nums {
+ _count += num;
+ }
+ }
+ ```
+
+=== "C"
+
+ ```c title="array.c"
+ /* 走訪陣列 */
+ void traverse(int *nums, int size) {
+ int count = 0;
+ // 透過索引走訪陣列
+ for (int i = 0; i < size; i++) {
+ count += nums[i];
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="array.kt"
+ /* 走訪陣列 */
+ fun traverse(nums: IntArray) {
+ var count = 0
+ // 透過索引走訪陣列
+ for (i in nums.indices) {
+ count += nums[i]
+ }
+ // 直接走訪陣列元素
+ for (j: Int in nums) {
+ count += j
+ }
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="array.rb"
+ ### 走訪陣列 ###
+ def traverse(nums)
+ count = 0
+
+ # 透過索引走訪陣列
+ for i in 0...nums.length
+ count += nums[i]
+ end
+
+ # 直接走訪陣列元素
+ for num in nums
+ count += num
+ end
+ end
+ ```
+
+=== "Zig"
+
+ ```zig title="array.zig"
+ // 走訪陣列
+ fn traverse(nums: []i32) void {
+ var count: i32 = 0;
+ // 透過索引走訪陣列
+ var i: i32 = 0;
+ while (i < nums.len) : (i += 1) {
+ count += nums[i];
+ }
+ count = 0;
+ // 直接走訪陣列元素
+ for (nums) |num| {
+ count += num;
+ }
+ }
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+### 6. 查詢元素
+
+在陣列中查詢指定元素需要走訪陣列,每輪判斷元素值是否匹配,若匹配則輸出對應索引。
+
+因為陣列是線性資料結構,所以上述查詢操作被稱為“線性查詢”。
+
+=== "Python"
+
+ ```python title="array.py"
+ def find(nums: list[int], target: int) -> int:
+ """在陣列中查詢指定元素"""
+ for i in range(len(nums)):
+ if nums[i] == target:
+ return i
+ return -1
+ ```
+
+=== "C++"
+
+ ```cpp title="array.cpp"
+ /* 在陣列中查詢指定元素 */
+ int find(int *nums, int size, int target) {
+ for (int i = 0; i < size; i++) {
+ if (nums[i] == target)
+ return i;
+ }
+ return -1;
+ }
+ ```
+
+=== "Java"
+
+ ```java title="array.java"
+ /* 在陣列中查詢指定元素 */
+ int find(int[] nums, int target) {
+ for (int i = 0; i < nums.length; i++) {
+ if (nums[i] == target)
+ return i;
+ }
+ return -1;
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="array.cs"
+ /* 在陣列中查詢指定元素 */
+ int Find(int[] nums, int target) {
+ for (int i = 0; i < nums.Length; i++) {
+ if (nums[i] == target)
+ return i;
+ }
+ return -1;
+ }
+ ```
+
+=== "Go"
+
+ ```go title="array.go"
+ /* 在陣列中查詢指定元素 */
+ func find(nums []int, target int) (index int) {
+ index = -1
+ for i := 0; i < len(nums); i++ {
+ if nums[i] == target {
+ index = i
+ break
+ }
+ }
+ return
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="array.swift"
+ /* 在陣列中查詢指定元素 */
+ func find(nums: [Int], target: Int) -> Int {
+ for i in nums.indices {
+ if nums[i] == target {
+ return i
+ }
+ }
+ return -1
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="array.js"
+ /* 在陣列中查詢指定元素 */
+ function find(nums, target) {
+ for (let i = 0; i < nums.length; i++) {
+ if (nums[i] === target) return i;
+ }
+ return -1;
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="array.ts"
+ /* 在陣列中查詢指定元素 */
+ function find(nums: number[], target: number): number {
+ for (let i = 0; i < nums.length; i++) {
+ if (nums[i] === target) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="array.dart"
+ /* 在陣列中查詢指定元素 */
+ int find(List nums, int target) {
+ for (var i = 0; i < nums.length; i++) {
+ if (nums[i] == target) return i;
+ }
+ return -1;
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="array.rs"
+ /* 在陣列中查詢指定元素 */
+ fn find(nums: &[i32], target: i32) -> Option {
+ for i in 0..nums.len() {
+ if nums[i] == target {
+ return Some(i);
+ }
+ }
+ None
+ }
+ ```
+
+=== "C"
+
+ ```c title="array.c"
+ /* 在陣列中查詢指定元素 */
+ int find(int *nums, int size, int target) {
+ for (int i = 0; i < size; i++) {
+ if (nums[i] == target)
+ return i;
+ }
+ return -1;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="array.kt"
+ /* 在陣列中查詢指定元素 */
+ fun find(nums: IntArray, target: Int): Int {
+ for (i in nums.indices) {
+ if (nums[i] == target) return i
+ }
+ return -1
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="array.rb"
+ ### 在陣列中查詢指定元素 ###
+ def find(nums, target)
+ for i in 0...nums.length
+ return i if nums[i] == target
+ end
+
+ -1
+ end
+ ```
+
+=== "Zig"
+
+ ```zig title="array.zig"
+ // 在陣列中查詢指定元素
+ fn find(nums: []i32, target: i32) i32 {
+ for (nums, 0..) |num, i| {
+ if (num == target) return @intCast(i);
+ }
+ return -1;
+ }
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+### 7. 擴容陣列
+
+在複雜的系統環境中,程式難以保證陣列之後的記憶體空間是可用的,從而無法安全地擴展陣列容量。因此在大多數程式語言中,**陣列的長度是不可變的**。
+
+如果我們希望擴容陣列,則需重新建立一個更大的陣列,然後把原陣列元素依次複製到新陣列。這是一個 $O(n)$ 的操作,在陣列很大的情況下非常耗時。程式碼如下所示:
+
+=== "Python"
+
+ ```python title="array.py"
+ def extend(nums: list[int], enlarge: int) -> list[int]:
+ """擴展陣列長度"""
+ # 初始化一個擴展長度後的陣列
+ res = [0] * (len(nums) + enlarge)
+ # 將原陣列中的所有元素複製到新陣列
+ for i in range(len(nums)):
+ res[i] = nums[i]
+ # 返回擴展後的新陣列
+ return res
+ ```
+
+=== "C++"
+
+ ```cpp title="array.cpp"
+ /* 擴展陣列長度 */
+ int *extend(int *nums, int size, int enlarge) {
+ // 初始化一個擴展長度後的陣列
+ int *res = new int[size + enlarge];
+ // 將原陣列中的所有元素複製到新陣列
+ for (int i = 0; i < size; i++) {
+ res[i] = nums[i];
+ }
+ // 釋放記憶體
+ delete[] nums;
+ // 返回擴展後的新陣列
+ return res;
+ }
+ ```
+
+=== "Java"
+
+ ```java title="array.java"
+ /* 擴展陣列長度 */
+ int[] extend(int[] nums, int enlarge) {
+ // 初始化一個擴展長度後的陣列
+ int[] res = new int[nums.length + enlarge];
+ // 將原陣列中的所有元素複製到新陣列
+ for (int i = 0; i < nums.length; i++) {
+ res[i] = nums[i];
+ }
+ // 返回擴展後的新陣列
+ return res;
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="array.cs"
+ /* 擴展陣列長度 */
+ int[] Extend(int[] nums, int enlarge) {
+ // 初始化一個擴展長度後的陣列
+ int[] res = new int[nums.Length + enlarge];
+ // 將原陣列中的所有元素複製到新陣列
+ for (int i = 0; i < nums.Length; i++) {
+ res[i] = nums[i];
+ }
+ // 返回擴展後的新陣列
+ return res;
+ }
+ ```
+
+=== "Go"
+
+ ```go title="array.go"
+ /* 擴展陣列長度 */
+ func extend(nums []int, enlarge int) []int {
+ // 初始化一個擴展長度後的陣列
+ res := make([]int, len(nums)+enlarge)
+ // 將原陣列中的所有元素複製到新陣列
+ for i, num := range nums {
+ res[i] = num
+ }
+ // 返回擴展後的新陣列
+ return res
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="array.swift"
+ /* 擴展陣列長度 */
+ func extend(nums: [Int], enlarge: Int) -> [Int] {
+ // 初始化一個擴展長度後的陣列
+ var res = Array(repeating: 0, count: nums.count + enlarge)
+ // 將原陣列中的所有元素複製到新陣列
+ for i in nums.indices {
+ res[i] = nums[i]
+ }
+ // 返回擴展後的新陣列
+ return res
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="array.js"
+ /* 擴展陣列長度 */
+ // 請注意,JavaScript 的 Array 是動態陣列,可以直接擴展
+ // 為了方便學習,本函式將 Array 看作長度不可變的陣列
+ function extend(nums, enlarge) {
+ // 初始化一個擴展長度後的陣列
+ const res = new Array(nums.length + enlarge).fill(0);
+ // 將原陣列中的所有元素複製到新陣列
+ for (let i = 0; i < nums.length; i++) {
+ res[i] = nums[i];
+ }
+ // 返回擴展後的新陣列
+ return res;
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="array.ts"
+ /* 擴展陣列長度 */
+ // 請注意,TypeScript 的 Array 是動態陣列,可以直接擴展
+ // 為了方便學習,本函式將 Array 看作長度不可變的陣列
+ function extend(nums: number[], enlarge: number): number[] {
+ // 初始化一個擴展長度後的陣列
+ const res = new Array(nums.length + enlarge).fill(0);
+ // 將原陣列中的所有元素複製到新陣列
+ for (let i = 0; i < nums.length; i++) {
+ res[i] = nums[i];
+ }
+ // 返回擴展後的新陣列
+ return res;
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="array.dart"
+ /* 擴展陣列長度 */
+ List extend(List nums, int enlarge) {
+ // 初始化一個擴展長度後的陣列
+ List res = List.filled(nums.length + enlarge, 0);
+ // 將原陣列中的所有元素複製到新陣列
+ for (var i = 0; i < nums.length; i++) {
+ res[i] = nums[i];
+ }
+ // 返回擴展後的新陣列
+ return res;
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="array.rs"
+ /* 擴展陣列長度 */
+ fn extend(nums: Vec, enlarge: usize) -> Vec {
+ // 初始化一個擴展長度後的陣列
+ let mut res: Vec = vec![0; nums.len() + enlarge];
+ // 將原陣列中的所有元素複製到新
+ for i in 0..nums.len() {
+ res[i] = nums[i];
+ }
+ // 返回擴展後的新陣列
+ res
+ }
+ ```
+
+=== "C"
+
+ ```c title="array.c"
+ /* 擴展陣列長度 */
+ int *extend(int *nums, int size, int enlarge) {
+ // 初始化一個擴展長度後的陣列
+ int *res = (int *)malloc(sizeof(int) * (size + enlarge));
+ // 將原陣列中的所有元素複製到新陣列
+ for (int i = 0; i < size; i++) {
+ res[i] = nums[i];
+ }
+ // 初始化擴展後的空間
+ for (int i = size; i < size + enlarge; i++) {
+ res[i] = 0;
+ }
+ // 返回擴展後的新陣列
+ return res;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="array.kt"
+ /* 擴展陣列長度 */
+ fun extend(nums: IntArray, enlarge: Int): IntArray {
+ // 初始化一個擴展長度後的陣列
+ val res = IntArray(nums.size + enlarge)
+ // 將原陣列中的所有元素複製到新陣列
+ for (i in nums.indices) {
+ res[i] = nums[i]
+ }
+ // 返回擴展後的新陣列
+ return res
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="array.rb"
+ ### 擴展陣列長度 ###
+ # 請注意,Ruby 的 Array 是動態陣列,可以直接擴展
+ # 為了方便學習,本函式將 Array 看作長度不可變的陣列
+ def extend(nums, enlarge)
+ # 初始化一個擴展長度後的陣列
+ res = Array.new(nums.length + enlarge, 0)
+
+ # 將原陣列中的所有元素複製到新陣列
+ for i in 0...nums.length
+ res[i] = nums[i]
+ end
+
+ # 返回擴展後的新陣列
+ res
+ end
+ ```
+
+=== "Zig"
+
+ ```zig title="array.zig"
+ // 擴展陣列長度
+ fn extend(mem_allocator: std.mem.Allocator, nums: []i32, enlarge: usize) ![]i32 {
+ // 初始化一個擴展長度後的陣列
+ var res = try mem_allocator.alloc(i32, nums.len + enlarge);
+ @memset(res, 0);
+ // 將原陣列中的所有元素複製到新陣列
+ std.mem.copy(i32, res, nums);
+ // 返回擴展後的新陣列
+ return res;
+ }
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+## 4.1.2 陣列的優點與侷限性
+
+陣列儲存在連續的記憶體空間內,且元素型別相同。這種做法包含豐富的先驗資訊,系統可以利用這些資訊來最佳化資料結構的操作效率。
+
+- **空間效率高**:陣列為資料分配了連續的記憶體塊,無須額外的結構開銷。
+- **支持隨機訪問**:陣列允許在 $O(1)$ 時間內訪問任何元素。
+- **快取區域性**:當訪問陣列元素時,計算機不僅會載入它,還會快取其周圍的其他資料,從而藉助高速快取來提升後續操作的執行速度。
+
+連續空間儲存是一把雙刃劍,其存在以下侷限性。
+
+- **插入與刪除效率低**:當陣列中元素較多時,插入與刪除操作需要移動大量的元素。
+- **長度不可變**:陣列在初始化後長度就固定了,擴容陣列需要將所有資料複製到新陣列,開銷很大。
+- **空間浪費**:如果陣列分配的大小超過實際所需,那麼多餘的空間就被浪費了。
+
+## 4.1.3 陣列典型應用
+
+陣列是一種基礎且常見的資料結構,既頻繁應用在各類演算法之中,也可用於實現各種複雜資料結構。
+
+- **隨機訪問**:如果我們想隨機抽取一些樣本,那麼可以用陣列儲存,並生成一個隨機序列,根據索引實現隨機抽樣。
+- **排序和搜尋**:陣列是排序和搜尋演算法最常用的資料結構。快速排序、合併排序、二分搜尋等都主要在陣列上進行。
+- **查詢表**:當需要快速查詢一個元素或其對應關係時,可以使用陣列作為查詢表。假如我們想實現字元到 ASCII 碼的對映,則可以將字元的 ASCII 碼值作為索引,對應的元素存放在陣列中的對應位置。
+- **機器學習**:神經網路中大量使用了向量、矩陣、張量之間的線性代數運算,這些資料都是以陣列的形式構建的。陣列是神經網路程式設計中最常使用的資料結構。
+- **資料結構實現**:陣列可以用於實現堆疊、佇列、雜湊表、堆積、圖等資料結構。例如,圖的鄰接矩陣表示實際上是一個二維陣列。
diff --git a/zh-Hant/docs/chapter_array_and_linkedlist/index.md b/zh-Hant/docs/chapter_array_and_linkedlist/index.md
new file mode 100644
index 000000000..1b2e6c964
--- /dev/null
+++ b/zh-Hant/docs/chapter_array_and_linkedlist/index.md
@@ -0,0 +1,22 @@
+---
+comments: true
+icon: material/view-list-outline
+---
+
+# 第 4 章 陣列與鏈結串列
+
+{ class="cover-image" }
+
+!!! abstract
+
+ 資料結構的世界如同一堵厚實的磚牆。
+
+ 陣列的磚塊整齊排列,逐個緊貼。鏈結串列的磚塊分散各處,連線的藤蔓自由地穿梭於磚縫之間。
+
+## Chapter Contents
+
+- [4.1 陣列](https://www.hello-algo.com/en/chapter_array_and_linkedlist/array/)
+- [4.2 鏈結串列](https://www.hello-algo.com/en/chapter_array_and_linkedlist/linked_list/)
+- [4.3 串列](https://www.hello-algo.com/en/chapter_array_and_linkedlist/list/)
+- [4.4 記憶體與快取 *](https://www.hello-algo.com/en/chapter_array_and_linkedlist/ram_and_cache/)
+- [4.5 小結](https://www.hello-algo.com/en/chapter_array_and_linkedlist/summary/)
diff --git a/zh-Hant/docs/chapter_array_and_linkedlist/linked_list.md b/zh-Hant/docs/chapter_array_and_linkedlist/linked_list.md
new file mode 100755
index 000000000..aae4bb749
--- /dev/null
+++ b/zh-Hant/docs/chapter_array_and_linkedlist/linked_list.md
@@ -0,0 +1,1565 @@
+---
+comments: true
+---
+
+# 4.2 鏈結串列
+
+記憶體空間是所有程式的公共資源,在一個複雜的系統執行環境下,空閒的記憶體空間可能散落在記憶體各處。我們知道,儲存陣列的記憶體空間必須是連續的,而當陣列非常大時,記憶體可能無法提供如此大的連續空間。此時鏈結串列的靈活性優勢就體現出來了。
+
+鏈結串列(linked list)是一種線性資料結構,其中的每個元素都是一個節點物件,各個節點透過“引用”相連線。引用記錄了下一個節點的記憶體位址,透過它可以從當前節點訪問到下一個節點。
+
+鏈結串列的設計使得各個節點可以分散儲存在記憶體各處,它們的記憶體位址無須連續。
+
+{ class="animation-figure" }
+
+ 圖 4-5 鏈結串列定義與儲存方式
+
+觀察圖 4-5 ,鏈結串列的組成單位是節點(node)物件。每個節點都包含兩項資料:節點的“值”和指向下一節點的“引用”。
+
+- 鏈結串列的首個節點被稱為“頭節點”,最後一個節點被稱為“尾節點”。
+- 尾節點指向的是“空”,它在 Java、C++ 和 Python 中分別被記為 `null`、`nullptr` 和 `None` 。
+- 在 C、C++、Go 和 Rust 等支持指標的語言中,上述“引用”應被替換為“指標”。
+
+如以下程式碼所示,鏈結串列節點 `ListNode` 除了包含值,還需額外儲存一個引用(指標)。因此在相同資料量下,**鏈結串列比陣列佔用更多的記憶體空間**。
+
+=== "Python"
+
+ ```python title=""
+ class ListNode:
+ """鏈結串列節點類別"""
+ def __init__(self, val: int):
+ self.val: int = val # 節點值
+ self.next: ListNode | None = None # 指向下一節點的引用
+ ```
+
+=== "C++"
+
+ ```cpp title=""
+ /* 鏈結串列節點結構體 */
+ struct ListNode {
+ int val; // 節點值
+ ListNode *next; // 指向下一節點的指標
+ ListNode(int x) : val(x), next(nullptr) {} // 建構子
+ };
+ ```
+
+=== "Java"
+
+ ```java title=""
+ /* 鏈結串列節點類別 */
+ class ListNode {
+ int val; // 節點值
+ ListNode next; // 指向下一節點的引用
+ ListNode(int x) { val = x; } // 建構子
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title=""
+ /* 鏈結串列節點類別 */
+ class ListNode(int x) { //建構子
+ int val = x; // 節點值
+ ListNode? next; // 指向下一節點的引用
+ }
+ ```
+
+=== "Go"
+
+ ```go title=""
+ /* 鏈結串列節點結構體 */
+ type ListNode struct {
+ Val int // 節點值
+ Next *ListNode // 指向下一節點的指標
+ }
+
+ // NewListNode 建構子,建立一個新的鏈結串列
+ func NewListNode(val int) *ListNode {
+ return &ListNode{
+ Val: val,
+ Next: nil,
+ }
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title=""
+ /* 鏈結串列節點類別 */
+ class ListNode {
+ var val: Int // 節點值
+ var next: ListNode? // 指向下一節點的引用
+
+ init(x: Int) { // 建構子
+ val = x
+ }
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title=""
+ /* 鏈結串列節點類別 */
+ class ListNode {
+ constructor(val, next) {
+ this.val = (val === undefined ? 0 : val); // 節點值
+ this.next = (next === undefined ? null : next); // 指向下一節點的引用
+ }
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title=""
+ /* 鏈結串列節點類別 */
+ class ListNode {
+ val: number;
+ next: ListNode | null;
+ constructor(val?: number, next?: ListNode | null) {
+ this.val = val === undefined ? 0 : val; // 節點值
+ this.next = next === undefined ? null : next; // 指向下一節點的引用
+ }
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title=""
+ /* 鏈結串列節點類別 */
+ class ListNode {
+ int val; // 節點值
+ ListNode? next; // 指向下一節點的引用
+ ListNode(this.val, [this.next]); // 建構子
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title=""
+ use std::rc::Rc;
+ use std::cell::RefCell;
+ /* 鏈結串列節點類別 */
+ #[derive(Debug)]
+ struct ListNode {
+ val: i32, // 節點值
+ next: Option>>, // 指向下一節點的指標
+ }
+ ```
+
+=== "C"
+
+ ```c title=""
+ /* 鏈結串列節點結構體 */
+ typedef struct ListNode {
+ int val; // 節點值
+ struct ListNode *next; // 指向下一節點的指標
+ } ListNode;
+
+ /* 建構子 */
+ ListNode *newListNode(int val) {
+ ListNode *node;
+ node = (ListNode *) malloc(sizeof(ListNode));
+ node->val = val;
+ node->next = NULL;
+ return node;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title=""
+ /* 鏈結串列節點類別 */
+ // 建構子
+ class ListNode(x: Int) {
+ val _val: Int = x // 節點值
+ val next: ListNode? = null // 指向下一個節點的引用
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title=""
+ # 鏈結串列節點類別
+ class ListNode
+ attr_accessor :val # 節點值
+ attr_accessor :next # 指向下一節點的引用
+
+ def initialize(val=0, next_node=nil)
+ @val = val
+ @next = next_node
+ end
+ end
+ ```
+
+=== "Zig"
+
+ ```zig title=""
+ // 鏈結串列節點類別
+ pub fn ListNode(comptime T: type) type {
+ return struct {
+ const Self = @This();
+
+ val: T = 0, // 節點值
+ next: ?*Self = null, // 指向下一節點的指標
+
+ // 建構子
+ pub fn init(self: *Self, x: i32) void {
+ self.val = x;
+ self.next = null;
+ }
+ };
+ }
+ ```
+
+## 4.2.1 鏈結串列常用操作
+
+### 1. 初始化鏈結串列
+
+建立鏈結串列分為兩步,第一步是初始化各個節點物件,第二步是構建節點之間的引用關係。初始化完成後,我們就可以從鏈結串列的頭節點出發,透過引用指向 `next` 依次訪問所有節點。
+
+=== "Python"
+
+ ```python title="linked_list.py"
+ # 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4
+ # 初始化各個節點
+ n0 = ListNode(1)
+ n1 = ListNode(3)
+ n2 = ListNode(2)
+ n3 = ListNode(5)
+ n4 = ListNode(4)
+ # 構建節點之間的引用
+ n0.next = n1
+ n1.next = n2
+ n2.next = n3
+ n3.next = n4
+ ```
+
+=== "C++"
+
+ ```cpp title="linked_list.cpp"
+ /* 初始化鏈結串列 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;
+ n2->next = n3;
+ n3->next = n4;
+ ```
+
+=== "Java"
+
+ ```java title="linked_list.java"
+ /* 初始化鏈結串列 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;
+ n2.next = n3;
+ n3.next = n4;
+ ```
+
+=== "C#"
+
+ ```csharp title="linked_list.cs"
+ /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */
+ // 初始化各個節點
+ ListNode n0 = new(1);
+ ListNode n1 = new(3);
+ ListNode n2 = new(2);
+ ListNode n3 = new(5);
+ ListNode n4 = new(4);
+ // 構建節點之間的引用
+ n0.next = n1;
+ n1.next = n2;
+ n2.next = n3;
+ n3.next = n4;
+ ```
+
+=== "Go"
+
+ ```go title="linked_list.go"
+ /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */
+ // 初始化各個節點
+ n0 := NewListNode(1)
+ n1 := NewListNode(3)
+ n2 := NewListNode(2)
+ n3 := NewListNode(5)
+ n4 := NewListNode(4)
+ // 構建節點之間的引用
+ n0.Next = n1
+ n1.Next = n2
+ n2.Next = n3
+ n3.Next = n4
+ ```
+
+=== "Swift"
+
+ ```swift title="linked_list.swift"
+ /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */
+ // 初始化各個節點
+ let n0 = ListNode(x: 1)
+ let n1 = ListNode(x: 3)
+ let n2 = ListNode(x: 2)
+ let n3 = ListNode(x: 5)
+ let n4 = ListNode(x: 4)
+ // 構建節點之間的引用
+ n0.next = n1
+ n1.next = n2
+ n2.next = n3
+ n3.next = n4
+ ```
+
+=== "JS"
+
+ ```javascript title="linked_list.js"
+ /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */
+ // 初始化各個節點
+ const n0 = new ListNode(1);
+ const n1 = new ListNode(3);
+ const n2 = new ListNode(2);
+ const n3 = new ListNode(5);
+ const n4 = new ListNode(4);
+ // 構建節點之間的引用
+ n0.next = n1;
+ n1.next = n2;
+ n2.next = n3;
+ n3.next = n4;
+ ```
+
+=== "TS"
+
+ ```typescript title="linked_list.ts"
+ /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */
+ // 初始化各個節點
+ const n0 = new ListNode(1);
+ const n1 = new ListNode(3);
+ const n2 = new ListNode(2);
+ const n3 = new ListNode(5);
+ const n4 = new ListNode(4);
+ // 構建節點之間的引用
+ n0.next = n1;
+ n1.next = n2;
+ n2.next = n3;
+ n3.next = n4;
+ ```
+
+=== "Dart"
+
+ ```dart title="linked_list.dart"
+ /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */\
+ // 初始化各個節點
+ ListNode n0 = ListNode(1);
+ ListNode n1 = ListNode(3);
+ ListNode n2 = ListNode(2);
+ ListNode n3 = ListNode(5);
+ ListNode n4 = ListNode(4);
+ // 構建節點之間的引用
+ n0.next = n1;
+ n1.next = n2;
+ n2.next = n3;
+ n3.next = n4;
+ ```
+
+=== "Rust"
+
+ ```rust title="linked_list.rs"
+ /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */
+ // 初始化各個節點
+ let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None }));
+ let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None }));
+ let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None }));
+ let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None }));
+ let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None }));
+
+ // 構建節點之間的引用
+ n0.borrow_mut().next = Some(n1.clone());
+ n1.borrow_mut().next = Some(n2.clone());
+ n2.borrow_mut().next = Some(n3.clone());
+ n3.borrow_mut().next = Some(n4.clone());
+ ```
+
+=== "C"
+
+ ```c title="linked_list.c"
+ /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */
+ // 初始化各個節點
+ ListNode* n0 = newListNode(1);
+ ListNode* n1 = newListNode(3);
+ ListNode* n2 = newListNode(2);
+ ListNode* n3 = newListNode(5);
+ ListNode* n4 = newListNode(4);
+ // 構建節點之間的引用
+ n0->next = n1;
+ n1->next = n2;
+ n2->next = n3;
+ n3->next = n4;
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="linked_list.kt"
+ /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */
+ // 初始化各個節點
+ val n0 = ListNode(1)
+ val n1 = ListNode(3)
+ val n2 = ListNode(2)
+ val n3 = ListNode(5)
+ val n4 = ListNode(4)
+ // 構建節點之間的引用
+ n0.next = n1;
+ n1.next = n2;
+ n2.next = n3;
+ n3.next = n4;
+ ```
+
+=== "Ruby"
+
+ ```ruby title="linked_list.rb"
+ # 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4
+ # 初始化各個節點
+ n0 = ListNode.new(1)
+ n1 = ListNode.new(3)
+ n2 = ListNode.new(2)
+ n3 = ListNode.new(5)
+ n4 = ListNode.new(4)
+ # 構建節點之間的引用
+ n0.next = n1
+ n1.next = n2
+ n2.next = n3
+ n3.next = n4
+ ```
+
+=== "Zig"
+
+ ```zig title="linked_list.zig"
+ // 初始化鏈結串列
+ // 初始化各個節點
+ var n0 = inc.ListNode(i32){.val = 1};
+ var n1 = inc.ListNode(i32){.val = 3};
+ var n2 = inc.ListNode(i32){.val = 2};
+ var n3 = inc.ListNode(i32){.val = 5};
+ var n4 = inc.ListNode(i32){.val = 4};
+ // 構建節點之間的引用
+ n0.next = &n1;
+ n1.next = &n2;
+ n2.next = &n3;
+ n3.next = &n4;
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+陣列整體是一個變數,比如陣列 `nums` 包含元素 `nums[0]` 和 `nums[1]` 等,而鏈結串列是由多個獨立的節點物件組成的。**我們通常將頭節點當作鏈結串列的代稱**,比如以上程式碼中的鏈結串列可記作鏈結串列 `n0` 。
+
+### 2. 插入節點
+
+在鏈結串列中插入節點非常容易。如圖 4-6 所示,假設我們想在相鄰的兩個節點 `n0` 和 `n1` 之間插入一個新節點 `P` ,**則只需改變兩個節點引用(指標)即可**,時間複雜度為 $O(1)$ 。
+
+相比之下,在陣列中插入元素的時間複雜度為 $O(n)$ ,在大資料量下的效率較低。
+
+{ class="animation-figure" }
+
+ 圖 4-6 鏈結串列插入節點示例
+
+=== "Python"
+
+ ```python title="linked_list.py"
+ def insert(n0: ListNode, P: ListNode):
+ """在鏈結串列的節點 n0 之後插入節點 P"""
+ n1 = n0.next
+ P.next = n1
+ n0.next = P
+ ```
+
+=== "C++"
+
+ ```cpp title="linked_list.cpp"
+ /* 在鏈結串列的節點 n0 之後插入節點 P */
+ void insert(ListNode *n0, ListNode *P) {
+ ListNode *n1 = n0->next;
+ P->next = n1;
+ n0->next = P;
+ }
+ ```
+
+=== "Java"
+
+ ```java title="linked_list.java"
+ /* 在鏈結串列的節點 n0 之後插入節點 P */
+ void insert(ListNode n0, ListNode P) {
+ ListNode n1 = n0.next;
+ P.next = n1;
+ n0.next = P;
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="linked_list.cs"
+ /* 在鏈結串列的節點 n0 之後插入節點 P */
+ void Insert(ListNode n0, ListNode P) {
+ ListNode? n1 = n0.next;
+ P.next = n1;
+ n0.next = P;
+ }
+ ```
+
+=== "Go"
+
+ ```go title="linked_list.go"
+ /* 在鏈結串列的節點 n0 之後插入節點 P */
+ func insertNode(n0 *ListNode, P *ListNode) {
+ n1 := n0.Next
+ P.Next = n1
+ n0.Next = P
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="linked_list.swift"
+ /* 在鏈結串列的節點 n0 之後插入節點 P */
+ func insert(n0: ListNode, P: ListNode) {
+ let n1 = n0.next
+ P.next = n1
+ n0.next = P
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="linked_list.js"
+ /* 在鏈結串列的節點 n0 之後插入節點 P */
+ function insert(n0, P) {
+ const n1 = n0.next;
+ P.next = n1;
+ n0.next = P;
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="linked_list.ts"
+ /* 在鏈結串列的節點 n0 之後插入節點 P */
+ function insert(n0: ListNode, P: ListNode): void {
+ const n1 = n0.next;
+ P.next = n1;
+ n0.next = P;
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="linked_list.dart"
+ /* 在鏈結串列的節點 n0 之後插入節點 P */
+ void insert(ListNode n0, ListNode P) {
+ ListNode? n1 = n0.next;
+ P.next = n1;
+ n0.next = P;
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="linked_list.rs"
+ /* 在鏈結串列的節點 n0 之後插入節點 P */
+ #[allow(non_snake_case)]
+ pub fn insert(n0: &Rc>>, P: Rc>>) {
+ let n1 = n0.borrow_mut().next.take();
+ P.borrow_mut().next = n1;
+ n0.borrow_mut().next = Some(P);
+ }
+ ```
+
+=== "C"
+
+ ```c title="linked_list.c"
+ /* 在鏈結串列的節點 n0 之後插入節點 P */
+ void insert(ListNode *n0, ListNode *P) {
+ ListNode *n1 = n0->next;
+ P->next = n1;
+ n0->next = P;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="linked_list.kt"
+ /* 在鏈結串列的節點 n0 之後插入節點p */
+ fun insert(n0: ListNode?, p: ListNode?) {
+ val n1 = n0?.next
+ p?.next = n1
+ n0?.next = p
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="linked_list.rb"
+ ### 在鏈結串列的節點 n0 之後插入節點 _p ###
+ # Ruby 的 `p` 是一個內建函式, `P` 是一個常數,所以可以使用 `_p` 代替
+ def insert(n0, _p)
+ n1 = n0.next
+ _p.next = n1
+ n0.next = _p
+ end
+ ```
+
+=== "Zig"
+
+ ```zig title="linked_list.zig"
+ // 在鏈結串列的節點 n0 之後插入節點 P
+ fn insert(n0: ?*inc.ListNode(i32), P: ?*inc.ListNode(i32)) void {
+ var n1 = n0.?.next;
+ P.?.next = n1;
+ n0.?.next = P;
+ }
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+### 3. 刪除節點
+
+如圖 4-7 所示,在鏈結串列中刪除節點也非常方便,**只需改變一個節點的引用(指標)即可**。
+
+請注意,儘管在刪除操作完成後節點 `P` 仍然指向 `n1` ,但實際上走訪此鏈結串列已經無法訪問到 `P` ,這意味著 `P` 已經不再屬於該鏈結串列了。
+
+{ class="animation-figure" }
+
+ 圖 4-7 鏈結串列刪除節點
+
+=== "Python"
+
+ ```python title="linked_list.py"
+ def remove(n0: ListNode):
+ """刪除鏈結串列的節點 n0 之後的首個節點"""
+ if not n0.next:
+ return
+ # n0 -> P -> n1
+ P = n0.next
+ n1 = P.next
+ n0.next = n1
+ ```
+
+=== "C++"
+
+ ```cpp title="linked_list.cpp"
+ /* 刪除鏈結串列的節點 n0 之後的首個節點 */
+ void remove(ListNode *n0) {
+ if (n0->next == nullptr)
+ return;
+ // n0 -> P -> n1
+ ListNode *P = n0->next;
+ ListNode *n1 = P->next;
+ n0->next = n1;
+ // 釋放記憶體
+ delete P;
+ }
+ ```
+
+=== "Java"
+
+ ```java title="linked_list.java"
+ /* 刪除鏈結串列的節點 n0 之後的首個節點 */
+ void remove(ListNode n0) {
+ if (n0.next == null)
+ return;
+ // n0 -> P -> n1
+ ListNode P = n0.next;
+ ListNode n1 = P.next;
+ n0.next = n1;
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="linked_list.cs"
+ /* 刪除鏈結串列的節點 n0 之後的首個節點 */
+ void Remove(ListNode n0) {
+ if (n0.next == null)
+ return;
+ // n0 -> P -> n1
+ ListNode P = n0.next;
+ ListNode? n1 = P.next;
+ n0.next = n1;
+ }
+ ```
+
+=== "Go"
+
+ ```go title="linked_list.go"
+ /* 刪除鏈結串列的節點 n0 之後的首個節點 */
+ func removeItem(n0 *ListNode) {
+ if n0.Next == nil {
+ return
+ }
+ // n0 -> P -> n1
+ P := n0.Next
+ n1 := P.Next
+ n0.Next = n1
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="linked_list.swift"
+ /* 刪除鏈結串列的節點 n0 之後的首個節點 */
+ func remove(n0: ListNode) {
+ if n0.next == nil {
+ return
+ }
+ // n0 -> P -> n1
+ let P = n0.next
+ let n1 = P?.next
+ n0.next = n1
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="linked_list.js"
+ /* 刪除鏈結串列的節點 n0 之後的首個節點 */
+ function remove(n0) {
+ if (!n0.next) return;
+ // n0 -> P -> n1
+ const P = n0.next;
+ const n1 = P.next;
+ n0.next = n1;
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="linked_list.ts"
+ /* 刪除鏈結串列的節點 n0 之後的首個節點 */
+ function remove(n0: ListNode): void {
+ if (!n0.next) {
+ return;
+ }
+ // n0 -> P -> n1
+ const P = n0.next;
+ const n1 = P.next;
+ n0.next = n1;
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="linked_list.dart"
+ /* 刪除鏈結串列的節點 n0 之後的首個節點 */
+ void remove(ListNode n0) {
+ if (n0.next == null) return;
+ // n0 -> P -> n1
+ ListNode P = n0.next!;
+ ListNode? n1 = P.next;
+ n0.next = n1;
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="linked_list.rs"
+ /* 刪除鏈結串列的節點 n0 之後的首個節點 */
+ #[allow(non_snake_case)]
+ pub fn remove(n0: &Rc>>) {
+ if n0.borrow().next.is_none() {
+ return;
+ };
+ // n0 -> P -> n1
+ let P = n0.borrow_mut().next.take();
+ if let Some(node) = P {
+ let n1 = node.borrow_mut().next.take();
+ n0.borrow_mut().next = n1;
+ }
+ }
+ ```
+
+=== "C"
+
+ ```c title="linked_list.c"
+ /* 刪除鏈結串列的節點 n0 之後的首個節點 */
+ // 注意:stdio.h 佔用了 remove 關鍵詞
+ void removeItem(ListNode *n0) {
+ if (!n0->next)
+ return;
+ // n0 -> P -> n1
+ ListNode *P = n0->next;
+ ListNode *n1 = P->next;
+ n0->next = n1;
+ // 釋放記憶體
+ free(P);
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="linked_list.kt"
+ /* 刪除鏈結串列的節點 n0 之後的首個節點 */
+ fun remove(n0: ListNode?) {
+ val p = n0?.next
+ val n1 = p?.next
+ n0?.next = n1
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="linked_list.rb"
+ ### 刪除鏈結串列的節點 n0 之後的首個節點 ###
+ def remove(n0)
+ return if n0.next.nil?
+
+ # n0 -> remove_node -> n1
+ remove_node = n0.next
+ n1 = remove_node.next
+ n0.next = n1
+ end
+ ```
+
+=== "Zig"
+
+ ```zig title="linked_list.zig"
+ // 刪除鏈結串列的節點 n0 之後的首個節點
+ fn remove(n0: ?*inc.ListNode(i32)) void {
+ if (n0.?.next == null) return;
+ // n0 -> P -> n1
+ var P = n0.?.next;
+ var n1 = P.?.next;
+ n0.?.next = n1;
+ }
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+### 4. 訪問節點
+
+**在鏈結串列中訪問節點的效率較低**。如上一節所述,我們可以在 $O(1)$ 時間下訪問陣列中的任意元素。鏈結串列則不然,程式需要從頭節點出發,逐個向後走訪,直至找到目標節點。也就是說,訪問鏈結串列的第 $i$ 個節點需要迴圈 $i - 1$ 輪,時間複雜度為 $O(n)$ 。
+
+=== "Python"
+
+ ```python title="linked_list.py"
+ def access(head: ListNode, index: int) -> ListNode | None:
+ """訪問鏈結串列中索引為 index 的節點"""
+ for _ in range(index):
+ if not head:
+ return None
+ head = head.next
+ return head
+ ```
+
+=== "C++"
+
+ ```cpp title="linked_list.cpp"
+ /* 訪問鏈結串列中索引為 index 的節點 */
+ ListNode *access(ListNode *head, int index) {
+ for (int i = 0; i < index; i++) {
+ if (head == nullptr)
+ return nullptr;
+ head = head->next;
+ }
+ return head;
+ }
+ ```
+
+=== "Java"
+
+ ```java title="linked_list.java"
+ /* 訪問鏈結串列中索引為 index 的節點 */
+ ListNode access(ListNode head, int index) {
+ for (int i = 0; i < index; i++) {
+ if (head == null)
+ return null;
+ head = head.next;
+ }
+ return head;
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="linked_list.cs"
+ /* 訪問鏈結串列中索引為 index 的節點 */
+ ListNode? Access(ListNode? head, int index) {
+ for (int i = 0; i < index; i++) {
+ if (head == null)
+ return null;
+ head = head.next;
+ }
+ return head;
+ }
+ ```
+
+=== "Go"
+
+ ```go title="linked_list.go"
+ /* 訪問鏈結串列中索引為 index 的節點 */
+ func access(head *ListNode, index int) *ListNode {
+ for i := 0; i < index; i++ {
+ if head == nil {
+ return nil
+ }
+ head = head.Next
+ }
+ return head
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="linked_list.swift"
+ /* 訪問鏈結串列中索引為 index 的節點 */
+ func access(head: ListNode, index: Int) -> ListNode? {
+ var head: ListNode? = head
+ for _ in 0 ..< index {
+ if head == nil {
+ return nil
+ }
+ head = head?.next
+ }
+ return head
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="linked_list.js"
+ /* 訪問鏈結串列中索引為 index 的節點 */
+ function access(head, index) {
+ for (let i = 0; i < index; i++) {
+ if (!head) {
+ return null;
+ }
+ head = head.next;
+ }
+ return head;
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="linked_list.ts"
+ /* 訪問鏈結串列中索引為 index 的節點 */
+ function access(head: ListNode | null, index: number): ListNode | null {
+ for (let i = 0; i < index; i++) {
+ if (!head) {
+ return null;
+ }
+ head = head.next;
+ }
+ return head;
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="linked_list.dart"
+ /* 訪問鏈結串列中索引為 index 的節點 */
+ ListNode? access(ListNode? head, int index) {
+ for (var i = 0; i < index; i++) {
+ if (head == null) return null;
+ head = head.next;
+ }
+ return head;
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="linked_list.rs"
+ /* 訪問鏈結串列中索引為 index 的節點 */
+ pub fn access(head: Rc>>, index: i32) -> Rc>> {
+ if index <= 0 {
+ return head;
+ };
+ if let Some(node) = &head.borrow().next {
+ return access(node.clone(), index - 1);
+ }
+
+ return head;
+ }
+ ```
+
+=== "C"
+
+ ```c title="linked_list.c"
+ /* 訪問鏈結串列中索引為 index 的節點 */
+ ListNode *access(ListNode *head, int index) {
+ for (int i = 0; i < index; i++) {
+ if (head == NULL)
+ return NULL;
+ head = head->next;
+ }
+ return head;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="linked_list.kt"
+ /* 訪問鏈結串列中索引為 index 的節點 */
+ fun access(head: ListNode?, index: Int): ListNode? {
+ var h = head
+ for (i in 0..
+
+
+### 5. 查詢節點
+
+走訪鏈結串列,查詢其中值為 `target` 的節點,輸出該節點在鏈結串列中的索引。此過程也屬於線性查詢。程式碼如下所示:
+
+=== "Python"
+
+ ```python title="linked_list.py"
+ def find(head: ListNode, target: int) -> int:
+ """在鏈結串列中查詢值為 target 的首個節點"""
+ index = 0
+ while head:
+ if head.val == target:
+ return index
+ head = head.next
+ index += 1
+ return -1
+ ```
+
+=== "C++"
+
+ ```cpp title="linked_list.cpp"
+ /* 在鏈結串列中查詢值為 target 的首個節點 */
+ int find(ListNode *head, int target) {
+ int index = 0;
+ while (head != nullptr) {
+ if (head->val == target)
+ return index;
+ head = head->next;
+ index++;
+ }
+ return -1;
+ }
+ ```
+
+=== "Java"
+
+ ```java title="linked_list.java"
+ /* 在鏈結串列中查詢值為 target 的首個節點 */
+ int find(ListNode head, int target) {
+ int index = 0;
+ while (head != null) {
+ if (head.val == target)
+ return index;
+ head = head.next;
+ index++;
+ }
+ return -1;
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="linked_list.cs"
+ /* 在鏈結串列中查詢值為 target 的首個節點 */
+ int Find(ListNode? head, int target) {
+ int index = 0;
+ while (head != null) {
+ if (head.val == target)
+ return index;
+ head = head.next;
+ index++;
+ }
+ return -1;
+ }
+ ```
+
+=== "Go"
+
+ ```go title="linked_list.go"
+ /* 在鏈結串列中查詢值為 target 的首個節點 */
+ func findNode(head *ListNode, target int) int {
+ index := 0
+ for head != nil {
+ if head.Val == target {
+ return index
+ }
+ head = head.Next
+ index++
+ }
+ return -1
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="linked_list.swift"
+ /* 在鏈結串列中查詢值為 target 的首個節點 */
+ func find(head: ListNode, target: Int) -> Int {
+ var head: ListNode? = head
+ var index = 0
+ while head != nil {
+ if head?.val == target {
+ return index
+ }
+ head = head?.next
+ index += 1
+ }
+ return -1
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="linked_list.js"
+ /* 在鏈結串列中查詢值為 target 的首個節點 */
+ function find(head, target) {
+ let index = 0;
+ while (head !== null) {
+ if (head.val === target) {
+ return index;
+ }
+ head = head.next;
+ index += 1;
+ }
+ return -1;
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="linked_list.ts"
+ /* 在鏈結串列中查詢值為 target 的首個節點 */
+ function find(head: ListNode | null, target: number): number {
+ let index = 0;
+ while (head !== null) {
+ if (head.val === target) {
+ return index;
+ }
+ head = head.next;
+ index += 1;
+ }
+ return -1;
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="linked_list.dart"
+ /* 在鏈結串列中查詢值為 target 的首個節點 */
+ int find(ListNode? head, int target) {
+ int index = 0;
+ while (head != null) {
+ if (head.val == target) {
+ return index;
+ }
+ head = head.next;
+ index++;
+ }
+ return -1;
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="linked_list.rs"
+ /* 在鏈結串列中查詢值為 target 的首個節點 */
+ pub fn find
(head: Rc>>, target: T, index: i32) -> i32 {
+ if head.borrow().val == target {
+ return index;
+ };
+ if let Some(node) = &head.borrow_mut().next {
+ return find(node.clone(), target, index + 1);
+ }
+ return -1;
+ }
+ ```
+
+=== "C"
+
+ ```c title="linked_list.c"
+ /* 在鏈結串列中查詢值為 target 的首個節點 */
+ int find(ListNode *head, int target) {
+ int index = 0;
+ while (head) {
+ if (head->val == target)
+ return index;
+ head = head->next;
+ index++;
+ }
+ return -1;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="linked_list.kt"
+ /* 在鏈結串列中查詢值為 target 的首個節點 */
+ fun find(head: ListNode?, target: Int): Int {
+ var index = 0
+ var h = head
+ while (h != null) {
+ if (h.value == target) return index
+ h = h.next
+ index++
+ }
+ return -1
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="linked_list.rb"
+ ### 在鏈結串列中查詢值為 target 的首個節點 ###
+ def find(head, target)
+ index = 0
+ while head
+ return index if head.val == target
+ head = head.next
+ index += 1
+ end
+
+ -1
+ end
+ ```
+
+=== "Zig"
+
+ ```zig title="linked_list.zig"
+ // 在鏈結串列中查詢值為 target 的首個節點
+ fn find(node: ?*inc.ListNode(i32), target: i32) i32 {
+ var head = node;
+ var index: i32 = 0;
+ while (head != null) {
+ if (head.?.val == target) return index;
+ head = head.?.next;
+ index += 1;
+ }
+ return -1;
+ }
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+## 4.2.2 陣列 vs. 鏈結串列
+
+表 4-1 總結了陣列和鏈結串列的各項特點並對比了操作效率。由於它們採用兩種相反的儲存策略,因此各種性質和操作效率也呈現對立的特點。
+
+ 表 4-1 陣列與鏈結串列的效率對比
+
+
+
+| | 陣列 | 鏈結串列 |
+| -------- | ------------------------------ | -------------- |
+| 儲存方式 | 連續記憶體空間 | 分散記憶體空間 |
+| 容量擴展 | 長度不可變 | 可靈活擴展 |
+| 記憶體效率 | 元素佔用記憶體少、但可能浪費空間 | 元素佔用記憶體多 |
+| 訪問元素 | $O(1)$ | $O(n)$ |
+| 新增元素 | $O(n)$ | $O(1)$ |
+| 刪除元素 | $O(n)$ | $O(1)$ |
+
+
+
+## 4.2.3 常見鏈結串列型別
+
+如圖 4-8 所示,常見的鏈結串列型別包括三種。
+
+- **單向鏈結串列**:即前面介紹的普通鏈結串列。單向鏈結串列的節點包含值和指向下一節點的引用兩項資料。我們將首個節點稱為頭節點,將最後一個節點稱為尾節點,尾節點指向空 `None` 。
+- **環形鏈結串列**:如果我們令單向鏈結串列的尾節點指向頭節點(首尾相接),則得到一個環形鏈結串列。在環形鏈結串列中,任意節點都可以視作頭節點。
+- **雙向鏈結串列**:與單向鏈結串列相比,雙向鏈結串列記錄了兩個方向的引用。雙向鏈結串列的節點定義同時包含指向後繼節點(下一個節點)和前驅節點(上一個節點)的引用(指標)。相較於單向鏈結串列,雙向鏈結串列更具靈活性,可以朝兩個方向走訪鏈結串列,但相應地也需要佔用更多的記憶體空間。
+
+=== "Python"
+
+ ```python title=""
+ class ListNode:
+ """雙向鏈結串列節點類別"""
+ def __init__(self, val: int):
+ self.val: int = val # 節點值
+ self.next: ListNode | None = None # 指向後繼節點的引用
+ self.prev: ListNode | None = None # 指向前驅節點的引用
+ ```
+
+=== "C++"
+
+ ```cpp title=""
+ /* 雙向鏈結串列節點結構體 */
+ struct ListNode {
+ int val; // 節點值
+ ListNode *next; // 指向後繼節點的指標
+ ListNode *prev; // 指向前驅節點的指標
+ ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 建構子
+ };
+ ```
+
+=== "Java"
+
+ ```java title=""
+ /* 雙向鏈結串列節點類別 */
+ class ListNode {
+ int val; // 節點值
+ ListNode next; // 指向後繼節點的引用
+ ListNode prev; // 指向前驅節點的引用
+ ListNode(int x) { val = x; } // 建構子
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title=""
+ /* 雙向鏈結串列節點類別 */
+ class ListNode(int x) { // 建構子
+ int val = x; // 節點值
+ ListNode next; // 指向後繼節點的引用
+ ListNode prev; // 指向前驅節點的引用
+ }
+ ```
+
+=== "Go"
+
+ ```go title=""
+ /* 雙向鏈結串列節點結構體 */
+ type DoublyListNode struct {
+ Val int // 節點值
+ Next *DoublyListNode // 指向後繼節點的指標
+ Prev *DoublyListNode // 指向前驅節點的指標
+ }
+
+ // NewDoublyListNode 初始化
+ func NewDoublyListNode(val int) *DoublyListNode {
+ return &DoublyListNode{
+ Val: val,
+ Next: nil,
+ Prev: nil,
+ }
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title=""
+ /* 雙向鏈結串列節點類別 */
+ class ListNode {
+ var val: Int // 節點值
+ var next: ListNode? // 指向後繼節點的引用
+ var prev: ListNode? // 指向前驅節點的引用
+
+ init(x: Int) { // 建構子
+ val = x
+ }
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title=""
+ /* 雙向鏈結串列節點類別 */
+ class ListNode {
+ constructor(val, next, prev) {
+ this.val = val === undefined ? 0 : val; // 節點值
+ this.next = next === undefined ? null : next; // 指向後繼節點的引用
+ this.prev = prev === undefined ? null : prev; // 指向前驅節點的引用
+ }
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title=""
+ /* 雙向鏈結串列節點類別 */
+ class ListNode {
+ val: number;
+ next: ListNode | null;
+ 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; // 指向前驅節點的引用
+ }
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title=""
+ /* 雙向鏈結串列節點類別 */
+ class ListNode {
+ int val; // 節點值
+ ListNode next; // 指向後繼節點的引用
+ ListNode prev; // 指向前驅節點的引用
+ ListNode(this.val, [this.next, this.prev]); // 建構子
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title=""
+ use std::rc::Rc;
+ use std::cell::RefCell;
+
+ /* 雙向鏈結串列節點型別 */
+ #[derive(Debug)]
+ struct ListNode {
+ val: i32, // 節點值
+ next: Option>>, // 指向後繼節點的指標
+ prev: Option>>, // 指向前驅節點的指標
+ }
+
+ /* 建構子 */
+ impl ListNode {
+ fn new(val: i32) -> Self {
+ ListNode {
+ val,
+ next: None,
+ prev: None,
+ }
+ }
+ }
+ ```
+
+=== "C"
+
+ ```c title=""
+ /* 雙向鏈結串列節點結構體 */
+ typedef struct ListNode {
+ int val; // 節點值
+ struct ListNode *next; // 指向後繼節點的指標
+ struct ListNode *prev; // 指向前驅節點的指標
+ } ListNode;
+
+ /* 建構子 */
+ ListNode *newListNode(int val) {
+ ListNode *node;
+ node = (ListNode *) malloc(sizeof(ListNode));
+ node->val = val;
+ node->next = NULL;
+ node->prev = NULL;
+ return node;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title=""
+ /* 雙向鏈結串列節點類別 */
+ // 建構子
+ class ListNode(x: Int) {
+ val _val: Int = x // 節點值
+ val next: ListNode? = null // 指向後繼節點的引用
+ val prev: ListNode? = null // 指向前驅節點的引用
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title=""
+ # 雙向鏈結串列節點類別
+ class ListNode
+ attr_accessor :val # 節點值
+ attr_accessor :next # 指向後繼節點的引用
+ attr_accessor :prev # 指向前驅節點的引用
+
+ def initialize(val=0, next_node=nil, prev_node=nil)
+ @val = val
+ @next = next_node
+ @prev = prev_node
+ end
+ end
+ ```
+
+=== "Zig"
+
+ ```zig title=""
+ // 雙向鏈結串列節點類別
+ pub fn ListNode(comptime T: type) type {
+ return struct {
+ const Self = @This();
+
+ val: T = 0, // 節點值
+ next: ?*Self = null, // 指向後繼節點的指標
+ prev: ?*Self = null, // 指向前驅節點的指標
+
+ // 建構子
+ pub fn init(self: *Self, x: i32) void {
+ self.val = x;
+ self.next = null;
+ self.prev = null;
+ }
+ };
+ }
+ ```
+
+{ class="animation-figure" }
+
+ 圖 4-8 常見鏈結串列種類
+
+## 4.2.4 鏈結串列典型應用
+
+單向鏈結串列通常用於實現堆疊、佇列、雜湊表和圖等資料結構。
+
+- **堆疊與佇列**:當插入和刪除操作都在鏈結串列的一端進行時,它表現出先進後出的特性,對應堆疊;當插入操作在鏈結串列的一端進行,刪除操作在鏈結串列的另一端進行,它表現出先進先出的特性,對應佇列。
+- **雜湊表**:鏈式位址是解決雜湊衝突的主流方案之一,在該方案中,所有衝突的元素都會被放到一個鏈結串列中。
+- **圖**:鄰接表是表示圖的一種常用方式,其中圖的每個頂點都與一個鏈結串列相關聯,鏈結串列中的每個元素都代表與該頂點相連的其他頂點。
+
+雙向鏈結串列常用於需要快速查詢前一個和後一個元素的場景。
+
+- **高階資料結構**:比如在紅黑樹、B 樹中,我們需要訪問節點的父節點,這可以透過在節點中儲存一個指向父節點的引用來實現,類似於雙向鏈結串列。
+- **瀏覽器歷史**:在網頁瀏覽器中,當用戶點選前進或後退按鈕時,瀏覽器需要知道使用者訪問過的前一個和後一個網頁。雙向鏈結串列的特性使得這種操作變得簡單。
+- **LRU 演算法**:在快取淘汰(LRU)演算法中,我們需要快速找到最近最少使用的資料,以及支持快速新增和刪除節點。這時候使用雙向鏈結串列就非常合適。
+
+環形鏈結串列常用於需要週期性操作的場景,比如作業系統的資源排程。
+
+- **時間片輪轉排程演算法**:在作業系統中,時間片輪轉排程演算法是一種常見的 CPU 排程演算法,它需要對一組程序進行迴圈。每個程序被賦予一個時間片,當時間片用完時,CPU 將切換到下一個程序。這種迴圈操作可以透過環形鏈結串列來實現。
+- **資料緩衝區**:在某些資料緩衝區的實現中,也可能會使用環形鏈結串列。比如在音訊、影片播放器中,資料流可能會被分成多個緩衝塊並放入一個環形鏈結串列,以便實現無縫播放。
diff --git a/zh-Hant/docs/chapter_array_and_linkedlist/list.md b/zh-Hant/docs/chapter_array_and_linkedlist/list.md
new file mode 100755
index 000000000..4bdaa41dc
--- /dev/null
+++ b/zh-Hant/docs/chapter_array_and_linkedlist/list.md
@@ -0,0 +1,2496 @@
+---
+comments: true
+---
+
+# 4.3 串列
+
+串列(list)是一個抽象的資料結構概念,它表示元素的有序集合,支持元素訪問、修改、新增、刪除和走訪等操作,無須使用者考慮容量限制的問題。串列可以基於鏈結串列或陣列實現。
+
+- 鏈結串列天然可以看作一個串列,其支持元素增刪查改操作,並且可以靈活動態擴容。
+- 陣列也支持元素增刪查改,但由於其長度不可變,因此只能看作一個具有長度限制的串列。
+
+當使用陣列實現串列時,**長度不可變的性質會導致串列的實用性降低**。這是因為我們通常無法事先確定需要儲存多少資料,從而難以選擇合適的串列長度。若長度過小,則很可能無法滿足使用需求;若長度過大,則會造成記憶體空間浪費。
+
+為解決此問題,我們可以使用動態陣列(dynamic array)來實現串列。它繼承了陣列的各項優點,並且可以在程式執行過程中進行動態擴容。
+
+實際上,**許多程式語言中的標準庫提供的串列是基於動態陣列實現的**,例如 Python 中的 `list` 、Java 中的 `ArrayList` 、C++ 中的 `vector` 和 C# 中的 `List` 等。在接下來的討論中,我們將把“串列”和“動態陣列”視為等同的概念。
+
+## 4.3.1 串列常用操作
+
+### 1. 初始化串列
+
+我們通常使用“無初始值”和“有初始值”這兩種初始化方法:
+
+=== "Python"
+
+ ```python title="list.py"
+ # 初始化串列
+ # 無初始值
+ nums1: list[int] = []
+ # 有初始值
+ nums: list[int] = [1, 3, 2, 5, 4]
+ ```
+
+=== "C++"
+
+ ```cpp title="list.cpp"
+ /* 初始化串列 */
+ // 需注意,C++ 中 vector 即是本文描述的 nums
+ // 無初始值
+ vector nums1;
+ // 有初始值
+ vector nums = { 1, 3, 2, 5, 4 };
+ ```
+
+=== "Java"
+
+ ```java title="list.java"
+ /* 初始化串列 */
+ // 無初始值
+ List nums1 = new ArrayList<>();
+ // 有初始值(注意陣列的元素型別需為 int[] 的包裝類別 Integer[])
+ Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 };
+ List nums = new ArrayList<>(Arrays.asList(numbers));
+ ```
+
+=== "C#"
+
+ ```csharp title="list.cs"
+ /* 初始化串列 */
+ // 無初始值
+ List nums1 = [];
+ // 有初始值
+ int[] numbers = [1, 3, 2, 5, 4];
+ List nums = [.. numbers];
+ ```
+
+=== "Go"
+
+ ```go title="list_test.go"
+ /* 初始化串列 */
+ // 無初始值
+ nums1 := []int{}
+ // 有初始值
+ nums := []int{1, 3, 2, 5, 4}
+ ```
+
+=== "Swift"
+
+ ```swift title="list.swift"
+ /* 初始化串列 */
+ // 無初始值
+ let nums1: [Int] = []
+ // 有初始值
+ var nums = [1, 3, 2, 5, 4]
+ ```
+
+=== "JS"
+
+ ```javascript title="list.js"
+ /* 初始化串列 */
+ // 無初始值
+ const nums1 = [];
+ // 有初始值
+ const nums = [1, 3, 2, 5, 4];
+ ```
+
+=== "TS"
+
+ ```typescript title="list.ts"
+ /* 初始化串列 */
+ // 無初始值
+ const nums1: number[] = [];
+ // 有初始值
+ const nums: number[] = [1, 3, 2, 5, 4];
+ ```
+
+=== "Dart"
+
+ ```dart title="list.dart"
+ /* 初始化串列 */
+ // 無初始值
+ List nums1 = [];
+ // 有初始值
+ List nums = [1, 3, 2, 5, 4];
+ ```
+
+=== "Rust"
+
+ ```rust title="list.rs"
+ /* 初始化串列 */
+ // 無初始值
+ let nums1: Vec = Vec::new();
+ // 有初始值
+ let nums: Vec = vec![1, 3, 2, 5, 4];
+ ```
+
+=== "C"
+
+ ```c title="list.c"
+ // C 未提供內建動態陣列
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="list.kt"
+ /* 初始化串列 */
+ // 無初始值
+ var nums1 = listOf()
+ // 有初始值
+ var numbers = arrayOf(1, 3, 2, 5, 4)
+ var nums = numbers.toMutableList()
+ ```
+
+=== "Ruby"
+
+ ```ruby title="list.rb"
+ # 初始化串列
+ # 無初始值
+ nums1 = []
+ # 有初始值
+ nums = [1, 3, 2, 5, 4]
+ ```
+
+=== "Zig"
+
+ ```zig title="list.zig"
+ // 初始化串列
+ var nums = std.ArrayList(i32).init(std.heap.page_allocator);
+ defer nums.deinit();
+ try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 });
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+### 2. 訪問元素
+
+串列本質上是陣列,因此可以在 $O(1)$ 時間內訪問和更新元素,效率很高。
+
+=== "Python"
+
+ ```python title="list.py"
+ # 訪問元素
+ num: int = nums[1] # 訪問索引 1 處的元素
+
+ # 更新元素
+ nums[1] = 0 # 將索引 1 處的元素更新為 0
+ ```
+
+=== "C++"
+
+ ```cpp title="list.cpp"
+ /* 訪問元素 */
+ int num = nums[1]; // 訪問索引 1 處的元素
+
+ /* 更新元素 */
+ nums[1] = 0; // 將索引 1 處的元素更新為 0
+ ```
+
+=== "Java"
+
+ ```java title="list.java"
+ /* 訪問元素 */
+ int num = nums.get(1); // 訪問索引 1 處的元素
+
+ /* 更新元素 */
+ nums.set(1, 0); // 將索引 1 處的元素更新為 0
+ ```
+
+=== "C#"
+
+ ```csharp title="list.cs"
+ /* 訪問元素 */
+ int num = nums[1]; // 訪問索引 1 處的元素
+
+ /* 更新元素 */
+ nums[1] = 0; // 將索引 1 處的元素更新為 0
+ ```
+
+=== "Go"
+
+ ```go title="list_test.go"
+ /* 訪問元素 */
+ num := nums[1] // 訪問索引 1 處的元素
+
+ /* 更新元素 */
+ nums[1] = 0 // 將索引 1 處的元素更新為 0
+ ```
+
+=== "Swift"
+
+ ```swift title="list.swift"
+ /* 訪問元素 */
+ let num = nums[1] // 訪問索引 1 處的元素
+
+ /* 更新元素 */
+ nums[1] = 0 // 將索引 1 處的元素更新為 0
+ ```
+
+=== "JS"
+
+ ```javascript title="list.js"
+ /* 訪問元素 */
+ const num = nums[1]; // 訪問索引 1 處的元素
+
+ /* 更新元素 */
+ nums[1] = 0; // 將索引 1 處的元素更新為 0
+ ```
+
+=== "TS"
+
+ ```typescript title="list.ts"
+ /* 訪問元素 */
+ const num: number = nums[1]; // 訪問索引 1 處的元素
+
+ /* 更新元素 */
+ nums[1] = 0; // 將索引 1 處的元素更新為 0
+ ```
+
+=== "Dart"
+
+ ```dart title="list.dart"
+ /* 訪問元素 */
+ int num = nums[1]; // 訪問索引 1 處的元素
+
+ /* 更新元素 */
+ nums[1] = 0; // 將索引 1 處的元素更新為 0
+ ```
+
+=== "Rust"
+
+ ```rust title="list.rs"
+ /* 訪問元素 */
+ let num: i32 = nums[1]; // 訪問索引 1 處的元素
+ /* 更新元素 */
+ nums[1] = 0; // 將索引 1 處的元素更新為 0
+ ```
+
+=== "C"
+
+ ```c title="list.c"
+ // C 未提供內建動態陣列
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="list.kt"
+ /* 訪問元素 */
+ val num = nums[1] // 訪問索引 1 處的元素
+ /* 更新元素 */
+ nums[1] = 0 // 將索引 1 處的元素更新為 0
+ ```
+
+=== "Ruby"
+
+ ```ruby title="list.rb"
+ # 訪問元素
+ num = nums[1] # 訪問索引 1 處的元素
+ # 更新元素
+ nums[1] = 0 # 將索引 1 處的元素更新為 0
+ ```
+
+=== "Zig"
+
+ ```zig title="list.zig"
+ // 訪問元素
+ var num = nums.items[1]; // 訪問索引 1 處的元素
+
+ // 更新元素
+ nums.items[1] = 0; // 將索引 1 處的元素更新為 0
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+### 3. 插入與刪除元素
+
+相較於陣列,串列可以自由地新增與刪除元素。在串列尾部新增元素的時間複雜度為 $O(1)$ ,但插入和刪除元素的效率仍與陣列相同,時間複雜度為 $O(n)$ 。
+
+=== "Python"
+
+ ```python title="list.py"
+ # 清空串列
+ nums.clear()
+
+ # 在尾部新增元素
+ nums.append(1)
+ nums.append(3)
+ nums.append(2)
+ nums.append(5)
+ nums.append(4)
+
+ # 在中間插入元素
+ nums.insert(3, 6) # 在索引 3 處插入數字 6
+
+ # 刪除元素
+ nums.pop(3) # 刪除索引 3 處的元素
+ ```
+
+=== "C++"
+
+ ```cpp title="list.cpp"
+ /* 清空串列 */
+ nums.clear();
+
+ /* 在尾部新增元素 */
+ nums.push_back(1);
+ nums.push_back(3);
+ nums.push_back(2);
+ nums.push_back(5);
+ nums.push_back(4);
+
+ /* 在中間插入元素 */
+ nums.insert(nums.begin() + 3, 6); // 在索引 3 處插入數字 6
+
+ /* 刪除元素 */
+ nums.erase(nums.begin() + 3); // 刪除索引 3 處的元素
+ ```
+
+=== "Java"
+
+ ```java title="list.java"
+ /* 清空串列 */
+ nums.clear();
+
+ /* 在尾部新增元素 */
+ nums.add(1);
+ nums.add(3);
+ nums.add(2);
+ nums.add(5);
+ nums.add(4);
+
+ /* 在中間插入元素 */
+ nums.add(3, 6); // 在索引 3 處插入數字 6
+
+ /* 刪除元素 */
+ nums.remove(3); // 刪除索引 3 處的元素
+ ```
+
+=== "C#"
+
+ ```csharp title="list.cs"
+ /* 清空串列 */
+ nums.Clear();
+
+ /* 在尾部新增元素 */
+ nums.Add(1);
+ nums.Add(3);
+ nums.Add(2);
+ nums.Add(5);
+ nums.Add(4);
+
+ /* 在中間插入元素 */
+ nums.Insert(3, 6);
+
+ /* 刪除元素 */
+ nums.RemoveAt(3);
+ ```
+
+=== "Go"
+
+ ```go title="list_test.go"
+ /* 清空串列 */
+ nums = nil
+
+ /* 在尾部新增元素 */
+ nums = append(nums, 1)
+ nums = append(nums, 3)
+ nums = append(nums, 2)
+ nums = append(nums, 5)
+ nums = append(nums, 4)
+
+ /* 在中間插入元素 */
+ nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 在索引 3 處插入數字 6
+
+ /* 刪除元素 */
+ nums = append(nums[:3], nums[4:]...) // 刪除索引 3 處的元素
+ ```
+
+=== "Swift"
+
+ ```swift title="list.swift"
+ /* 清空串列 */
+ nums.removeAll()
+
+ /* 在尾部新增元素 */
+ nums.append(1)
+ nums.append(3)
+ nums.append(2)
+ nums.append(5)
+ nums.append(4)
+
+ /* 在中間插入元素 */
+ nums.insert(6, at: 3) // 在索引 3 處插入數字 6
+
+ /* 刪除元素 */
+ nums.remove(at: 3) // 刪除索引 3 處的元素
+ ```
+
+=== "JS"
+
+ ```javascript title="list.js"
+ /* 清空串列 */
+ nums.length = 0;
+
+ /* 在尾部新增元素 */
+ nums.push(1);
+ nums.push(3);
+ nums.push(2);
+ nums.push(5);
+ nums.push(4);
+
+ /* 在中間插入元素 */
+ nums.splice(3, 0, 6);
+
+ /* 刪除元素 */
+ nums.splice(3, 1);
+ ```
+
+=== "TS"
+
+ ```typescript title="list.ts"
+ /* 清空串列 */
+ nums.length = 0;
+
+ /* 在尾部新增元素 */
+ nums.push(1);
+ nums.push(3);
+ nums.push(2);
+ nums.push(5);
+ nums.push(4);
+
+ /* 在中間插入元素 */
+ nums.splice(3, 0, 6);
+
+ /* 刪除元素 */
+ nums.splice(3, 1);
+ ```
+
+=== "Dart"
+
+ ```dart title="list.dart"
+ /* 清空串列 */
+ nums.clear();
+
+ /* 在尾部新增元素 */
+ nums.add(1);
+ nums.add(3);
+ nums.add(2);
+ nums.add(5);
+ nums.add(4);
+
+ /* 在中間插入元素 */
+ nums.insert(3, 6); // 在索引 3 處插入數字 6
+
+ /* 刪除元素 */
+ nums.removeAt(3); // 刪除索引 3 處的元素
+ ```
+
+=== "Rust"
+
+ ```rust title="list.rs"
+ /* 清空串列 */
+ nums.clear();
+
+ /* 在尾部新增元素 */
+ nums.push(1);
+ nums.push(3);
+ nums.push(2);
+ nums.push(5);
+ nums.push(4);
+
+ /* 在中間插入元素 */
+ nums.insert(3, 6); // 在索引 3 處插入數字 6
+
+ /* 刪除元素 */
+ nums.remove(3); // 刪除索引 3 處的元素
+ ```
+
+=== "C"
+
+ ```c title="list.c"
+ // C 未提供內建動態陣列
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="list.kt"
+ /* 清空串列 */
+ nums.clear();
+
+ /* 在尾部新增元素 */
+ nums.add(1);
+ nums.add(3);
+ nums.add(2);
+ nums.add(5);
+ nums.add(4);
+
+ /* 在中間插入元素 */
+ nums.add(3, 6); // 在索引 3 處插入數字 6
+
+ /* 刪除元素 */
+ nums.remove(3); // 刪除索引 3 處的元素
+ ```
+
+=== "Ruby"
+
+ ```ruby title="list.rb"
+ # 清空串列
+ nums.clear
+
+ # 在尾部新增元素
+ nums << 1
+ nums << 3
+ nums << 2
+ nums << 5
+ nums << 4
+
+ # 在中間插入元素
+ nums.insert(3, 6) # 在索引 3 處插入數字 6
+
+ # 刪除元素
+ nums.delete_at(3) # 刪除索引 3 處的元素
+ ```
+
+=== "Zig"
+
+ ```zig title="list.zig"
+ // 清空串列
+ nums.clearRetainingCapacity();
+
+ // 在尾部新增元素
+ try nums.append(1);
+ try nums.append(3);
+ try nums.append(2);
+ try nums.append(5);
+ try nums.append(4);
+
+ // 在中間插入元素
+ try nums.insert(3, 6); // 在索引 3 處插入數字 6
+
+ // 刪除元素
+ _ = nums.orderedRemove(3); // 刪除索引 3 處的元素
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+### 4. 走訪串列
+
+與陣列一樣,串列可以根據索引走訪,也可以直接走訪各元素。
+
+=== "Python"
+
+ ```python title="list.py"
+ # 透過索引走訪串列
+ count = 0
+ for i in range(len(nums)):
+ count += nums[i]
+
+ # 直接走訪串列元素
+ for num in nums:
+ count += num
+ ```
+
+=== "C++"
+
+ ```cpp title="list.cpp"
+ /* 透過索引走訪串列 */
+ int count = 0;
+ for (int i = 0; i < nums.size(); i++) {
+ count += nums[i];
+ }
+
+ /* 直接走訪串列元素 */
+ count = 0;
+ for (int num : nums) {
+ count += num;
+ }
+ ```
+
+=== "Java"
+
+ ```java title="list.java"
+ /* 透過索引走訪串列 */
+ int count = 0;
+ for (int i = 0; i < nums.size(); i++) {
+ count += nums.get(i);
+ }
+
+ /* 直接走訪串列元素 */
+ for (int num : nums) {
+ count += num;
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="list.cs"
+ /* 透過索引走訪串列 */
+ int count = 0;
+ for (int i = 0; i < nums.Count; i++) {
+ count += nums[i];
+ }
+
+ /* 直接走訪串列元素 */
+ count = 0;
+ foreach (int num in nums) {
+ count += num;
+ }
+ ```
+
+=== "Go"
+
+ ```go title="list_test.go"
+ /* 透過索引走訪串列 */
+ count := 0
+ for i := 0; i < len(nums); i++ {
+ count += nums[i]
+ }
+
+ /* 直接走訪串列元素 */
+ count = 0
+ for _, num := range nums {
+ count += num
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="list.swift"
+ /* 透過索引走訪串列 */
+ var count = 0
+ for i in nums.indices {
+ count += nums[i]
+ }
+
+ /* 直接走訪串列元素 */
+ count = 0
+ for num in nums {
+ count += num
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="list.js"
+ /* 透過索引走訪串列 */
+ let count = 0;
+ for (let i = 0; i < nums.length; i++) {
+ count += nums[i];
+ }
+
+ /* 直接走訪串列元素 */
+ count = 0;
+ for (const num of nums) {
+ count += num;
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="list.ts"
+ /* 透過索引走訪串列 */
+ let count = 0;
+ for (let i = 0; i < nums.length; i++) {
+ count += nums[i];
+ }
+
+ /* 直接走訪串列元素 */
+ count = 0;
+ for (const num of nums) {
+ count += num;
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="list.dart"
+ /* 透過索引走訪串列 */
+ int count = 0;
+ for (var i = 0; i < nums.length; i++) {
+ count += nums[i];
+ }
+
+ /* 直接走訪串列元素 */
+ count = 0;
+ for (var num in nums) {
+ count += num;
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="list.rs"
+ // 透過索引走訪串列
+ let mut _count = 0;
+ for i in 0..nums.len() {
+ _count += nums[i];
+ }
+
+ // 直接走訪串列元素
+ _count = 0;
+ for num in &nums {
+ _count += num;
+ }
+ ```
+
+=== "C"
+
+ ```c title="list.c"
+ // C 未提供內建動態陣列
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="list.kt"
+ /* 透過索引走訪串列 */
+ var count = 0
+ for (i in nums.indices) {
+ count += nums[i]
+ }
+
+ /* 直接走訪串列元素 */
+ for (num in nums) {
+ count += num
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="list.rb"
+ # 透過索引走訪串列
+ count = 0
+ for i in 0...nums.length
+ count += nums[i]
+ end
+
+ # 直接走訪串列元素
+ count = 0
+ for num in nums
+ count += num
+ end
+ ```
+
+=== "Zig"
+
+ ```zig title="list.zig"
+ // 透過索引走訪串列
+ var count: i32 = 0;
+ var i: i32 = 0;
+ while (i < nums.items.len) : (i += 1) {
+ count += nums[i];
+ }
+
+ // 直接走訪串列元素
+ count = 0;
+ for (nums.items) |num| {
+ count += num;
+ }
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+### 5. 拼接串列
+
+給定一個新串列 `nums1` ,我們可以將其拼接到原串列的尾部。
+
+=== "Python"
+
+ ```python title="list.py"
+ # 拼接兩個串列
+ nums1: list[int] = [6, 8, 7, 10, 9]
+ nums += nums1 # 將串列 nums1 拼接到 nums 之後
+ ```
+
+=== "C++"
+
+ ```cpp title="list.cpp"
+ /* 拼接兩個串列 */
+ vector nums1 = { 6, 8, 7, 10, 9 };
+ // 將串列 nums1 拼接到 nums 之後
+ nums.insert(nums.end(), nums1.begin(), nums1.end());
+ ```
+
+=== "Java"
+
+ ```java title="list.java"
+ /* 拼接兩個串列 */
+ List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 }));
+ nums.addAll(nums1); // 將串列 nums1 拼接到 nums 之後
+ ```
+
+=== "C#"
+
+ ```csharp title="list.cs"
+ /* 拼接兩個串列 */
+ List nums1 = [6, 8, 7, 10, 9];
+ nums.AddRange(nums1); // 將串列 nums1 拼接到 nums 之後
+ ```
+
+=== "Go"
+
+ ```go title="list_test.go"
+ /* 拼接兩個串列 */
+ nums1 := []int{6, 8, 7, 10, 9}
+ nums = append(nums, nums1...) // 將串列 nums1 拼接到 nums 之後
+ ```
+
+=== "Swift"
+
+ ```swift title="list.swift"
+ /* 拼接兩個串列 */
+ let nums1 = [6, 8, 7, 10, 9]
+ nums.append(contentsOf: nums1) // 將串列 nums1 拼接到 nums 之後
+ ```
+
+=== "JS"
+
+ ```javascript title="list.js"
+ /* 拼接兩個串列 */
+ const nums1 = [6, 8, 7, 10, 9];
+ nums.push(...nums1); // 將串列 nums1 拼接到 nums 之後
+ ```
+
+=== "TS"
+
+ ```typescript title="list.ts"
+ /* 拼接兩個串列 */
+ const nums1: number[] = [6, 8, 7, 10, 9];
+ nums.push(...nums1); // 將串列 nums1 拼接到 nums 之後
+ ```
+
+=== "Dart"
+
+ ```dart title="list.dart"
+ /* 拼接兩個串列 */
+ List nums1 = [6, 8, 7, 10, 9];
+ nums.addAll(nums1); // 將串列 nums1 拼接到 nums 之後
+ ```
+
+=== "Rust"
+
+ ```rust title="list.rs"
+ /* 拼接兩個串列 */
+ let nums1: Vec = vec![6, 8, 7, 10, 9];
+ nums.extend(nums1);
+ ```
+
+=== "C"
+
+ ```c title="list.c"
+ // C 未提供內建動態陣列
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="list.kt"
+ /* 拼接兩個串列 */
+ val nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList()
+ nums.addAll(nums1) // 將串列 nums1 拼接到 nums 之後
+ ```
+
+=== "Ruby"
+
+ ```ruby title="list.rb"
+ # 拼接兩個串列
+ nums1 = [6, 8, 7, 10, 9]
+ nums += nums1
+ ```
+
+=== "Zig"
+
+ ```zig title="list.zig"
+ // 拼接兩個串列
+ var nums1 = std.ArrayList(i32).init(std.heap.page_allocator);
+ defer nums1.deinit();
+ try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 });
+ try nums.insertSlice(nums.items.len, nums1.items); // 將串列 nums1 拼接到 nums 之後
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+### 6. 排序串列
+
+完成串列排序後,我們便可以使用在陣列類別演算法題中經常考查的“二分搜尋”和“雙指標”演算法。
+
+=== "Python"
+
+ ```python title="list.py"
+ # 排序串列
+ nums.sort() # 排序後,串列元素從小到大排列
+ ```
+
+=== "C++"
+
+ ```cpp title="list.cpp"
+ /* 排序串列 */
+ sort(nums.begin(), nums.end()); // 排序後,串列元素從小到大排列
+ ```
+
+=== "Java"
+
+ ```java title="list.java"
+ /* 排序串列 */
+ Collections.sort(nums); // 排序後,串列元素從小到大排列
+ ```
+
+=== "C#"
+
+ ```csharp title="list.cs"
+ /* 排序串列 */
+ nums.Sort(); // 排序後,串列元素從小到大排列
+ ```
+
+=== "Go"
+
+ ```go title="list_test.go"
+ /* 排序串列 */
+ sort.Ints(nums) // 排序後,串列元素從小到大排列
+ ```
+
+=== "Swift"
+
+ ```swift title="list.swift"
+ /* 排序串列 */
+ nums.sort() // 排序後,串列元素從小到大排列
+ ```
+
+=== "JS"
+
+ ```javascript title="list.js"
+ /* 排序串列 */
+ nums.sort((a, b) => a - b); // 排序後,串列元素從小到大排列
+ ```
+
+=== "TS"
+
+ ```typescript title="list.ts"
+ /* 排序串列 */
+ nums.sort((a, b) => a - b); // 排序後,串列元素從小到大排列
+ ```
+
+=== "Dart"
+
+ ```dart title="list.dart"
+ /* 排序串列 */
+ nums.sort(); // 排序後,串列元素從小到大排列
+ ```
+
+=== "Rust"
+
+ ```rust title="list.rs"
+ /* 排序串列 */
+ nums.sort(); // 排序後,串列元素從小到大排列
+ ```
+
+=== "C"
+
+ ```c title="list.c"
+ // C 未提供內建動態陣列
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="list.kt"
+ /* 排序串列 */
+ nums.sort() // 排序後,串列元素從小到大排列
+ ```
+
+=== "Ruby"
+
+ ```ruby title="list.rb"
+ # 排序串列
+ nums = nums.sort { |a, b| a <=> b } # 排序後,串列元素從小到大排列
+ ```
+
+=== "Zig"
+
+ ```zig title="list.zig"
+ // 排序串列
+ std.sort.sort(i32, nums.items, {}, comptime std.sort.asc(i32));
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+## 4.3.2 串列實現
+
+許多程式語言內建了串列,例如 Java、C++、Python 等。它們的實現比較複雜,各個參數的設定也非常考究,例如初始容量、擴容倍數等。感興趣的讀者可以查閱原始碼進行學習。
+
+為了加深對串列工作原理的理解,我們嘗試實現一個簡易版串列,包括以下三個重點設計。
+
+- **初始容量**:選取一個合理的陣列初始容量。在本示例中,我們選擇 10 作為初始容量。
+- **數量記錄**:宣告一個變數 `size` ,用於記錄串列當前元素數量,並隨著元素插入和刪除實時更新。根據此變數,我們可以定位串列尾部,以及判斷是否需要擴容。
+- **擴容機制**:若插入元素時串列容量已滿,則需要進行擴容。先根據擴容倍數建立一個更大的陣列,再將當前陣列的所有元素依次移動至新陣列。在本示例中,我們規定每次將陣列擴容至之前的 2 倍。
+
+=== "Python"
+
+ ```python title="my_list.py"
+ class MyList:
+ """串列類別"""
+
+ def __init__(self):
+ """建構子"""
+ self._capacity: int = 10 # 串列容量
+ self._arr: list[int] = [0] * self._capacity # 陣列(儲存串列元素)
+ self._size: int = 0 # 串列長度(當前元素數量)
+ self._extend_ratio: int = 2 # 每次串列擴容的倍數
+
+ def size(self) -> int:
+ """獲取串列長度(當前元素數量)"""
+ return self._size
+
+ def capacity(self) -> int:
+ """獲取串列容量"""
+ return self._capacity
+
+ def get(self, index: int) -> int:
+ """訪問元素"""
+ # 索引如果越界,則丟擲異常,下同
+ if index < 0 or index >= self._size:
+ raise IndexError("索引越界")
+ return self._arr[index]
+
+ def set(self, num: int, index: int):
+ """更新元素"""
+ if index < 0 or index >= self._size:
+ raise IndexError("索引越界")
+ self._arr[index] = num
+
+ def add(self, num: int):
+ """在尾部新增元素"""
+ # 元素數量超出容量時,觸發擴容機制
+ if self.size() == self.capacity():
+ self.extend_capacity()
+ self._arr[self._size] = num
+ self._size += 1
+
+ def insert(self, num: int, index: int):
+ """在中間插入元素"""
+ if index < 0 or index >= self._size:
+ raise IndexError("索引越界")
+ # 元素數量超出容量時,觸發擴容機制
+ if self._size == self.capacity():
+ self.extend_capacity()
+ # 將索引 index 以及之後的元素都向後移動一位
+ for j in range(self._size - 1, index - 1, -1):
+ self._arr[j + 1] = self._arr[j]
+ self._arr[index] = num
+ # 更新元素數量
+ self._size += 1
+
+ def remove(self, index: int) -> int:
+ """刪除元素"""
+ if index < 0 or index >= self._size:
+ raise IndexError("索引越界")
+ num = self._arr[index]
+ # 將索引 index 之後的元素都向前移動一位
+ for j in range(index, self._size - 1):
+ self._arr[j] = self._arr[j + 1]
+ # 更新元素數量
+ self._size -= 1
+ # 返回被刪除的元素
+ return num
+
+ def extend_capacity(self):
+ """串列擴容"""
+ # 新建一個長度為原陣列 _extend_ratio 倍的新陣列,並將原陣列複製到新陣列
+ self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1)
+ # 更新串列容量
+ self._capacity = len(self._arr)
+
+ def to_array(self) -> list[int]:
+ """返回有效長度的串列"""
+ return self._arr[: self._size]
+ ```
+
+=== "C++"
+
+ ```cpp title="my_list.cpp"
+ /* 串列類別 */
+ class MyList {
+ private:
+ int *arr; // 陣列(儲存串列元素)
+ int arrCapacity = 10; // 串列容量
+ int arrSize = 0; // 串列長度(當前元素數量)
+ int extendRatio = 2; // 每次串列擴容的倍數
+
+ public:
+ /* 建構子 */
+ MyList() {
+ arr = new int[arrCapacity];
+ }
+
+ /* 析構方法 */
+ ~MyList() {
+ delete[] arr;
+ }
+
+ /* 獲取串列長度(當前元素數量)*/
+ int size() {
+ return arrSize;
+ }
+
+ /* 獲取串列容量 */
+ int capacity() {
+ return arrCapacity;
+ }
+
+ /* 訪問元素 */
+ int get(int index) {
+ // 索引如果越界,則丟擲異常,下同
+ if (index < 0 || index >= size())
+ throw out_of_range("索引越界");
+ return arr[index];
+ }
+
+ /* 更新元素 */
+ void set(int index, int num) {
+ if (index < 0 || index >= size())
+ throw out_of_range("索引越界");
+ arr[index] = num;
+ }
+
+ /* 在尾部新增元素 */
+ void add(int num) {
+ // 元素數量超出容量時,觸發擴容機制
+ if (size() == capacity())
+ extendCapacity();
+ arr[size()] = num;
+ // 更新元素數量
+ arrSize++;
+ }
+
+ /* 在中間插入元素 */
+ void insert(int index, int num) {
+ if (index < 0 || index >= size())
+ throw out_of_range("索引越界");
+ // 元素數量超出容量時,觸發擴容機制
+ if (size() == capacity())
+ extendCapacity();
+ // 將索引 index 以及之後的元素都向後移動一位
+ for (int j = size() - 1; j >= index; j--) {
+ arr[j + 1] = arr[j];
+ }
+ arr[index] = num;
+ // 更新元素數量
+ arrSize++;
+ }
+
+ /* 刪除元素 */
+ int remove(int index) {
+ if (index < 0 || index >= size())
+ throw out_of_range("索引越界");
+ int num = arr[index];
+ // 將索引 index 之後的元素都向前移動一位
+ for (int j = index; j < size() - 1; j++) {
+ arr[j] = arr[j + 1];
+ }
+ // 更新元素數量
+ arrSize--;
+ // 返回被刪除的元素
+ return num;
+ }
+
+ /* 串列擴容 */
+ void extendCapacity() {
+ // 新建一個長度為原陣列 extendRatio 倍的新陣列
+ int newCapacity = capacity() * extendRatio;
+ int *tmp = arr;
+ arr = new int[newCapacity];
+ // 將原陣列中的所有元素複製到新陣列
+ for (int i = 0; i < size(); i++) {
+ arr[i] = tmp[i];
+ }
+ // 釋放記憶體
+ delete[] tmp;
+ arrCapacity = newCapacity;
+ }
+
+ /* 將串列轉換為 Vector 用於列印 */
+ vector toVector() {
+ // 僅轉換有效長度範圍內的串列元素
+ vector vec(size());
+ for (int i = 0; i < size(); i++) {
+ vec[i] = arr[i];
+ }
+ return vec;
+ }
+ };
+ ```
+
+=== "Java"
+
+ ```java title="my_list.java"
+ /* 串列類別 */
+ class MyList {
+ private int[] arr; // 陣列(儲存串列元素)
+ private int capacity = 10; // 串列容量
+ private int size = 0; // 串列長度(當前元素數量)
+ private int extendRatio = 2; // 每次串列擴容的倍數
+
+ /* 建構子 */
+ public MyList() {
+ arr = new int[capacity];
+ }
+
+ /* 獲取串列長度(當前元素數量) */
+ public int size() {
+ return size;
+ }
+
+ /* 獲取串列容量 */
+ public int capacity() {
+ return capacity;
+ }
+
+ /* 訪問元素 */
+ public int get(int index) {
+ // 索引如果越界,則丟擲異常,下同
+ if (index < 0 || index >= size)
+ throw new IndexOutOfBoundsException("索引越界");
+ return arr[index];
+ }
+
+ /* 更新元素 */
+ public void set(int index, int num) {
+ if (index < 0 || index >= size)
+ throw new IndexOutOfBoundsException("索引越界");
+ arr[index] = num;
+ }
+
+ /* 在尾部新增元素 */
+ public void add(int num) {
+ // 元素數量超出容量時,觸發擴容機制
+ if (size == capacity())
+ extendCapacity();
+ arr[size] = num;
+ // 更新元素數量
+ size++;
+ }
+
+ /* 在中間插入元素 */
+ public void insert(int index, int num) {
+ if (index < 0 || index >= size)
+ throw new IndexOutOfBoundsException("索引越界");
+ // 元素數量超出容量時,觸發擴容機制
+ if (size == capacity())
+ extendCapacity();
+ // 將索引 index 以及之後的元素都向後移動一位
+ for (int j = size - 1; j >= index; j--) {
+ arr[j + 1] = arr[j];
+ }
+ arr[index] = num;
+ // 更新元素數量
+ size++;
+ }
+
+ /* 刪除元素 */
+ public int remove(int index) {
+ if (index < 0 || index >= size)
+ throw new IndexOutOfBoundsException("索引越界");
+ int num = arr[index];
+ // 將將索引 index 之後的元素都向前移動一位
+ for (int j = index; j < size - 1; j++) {
+ arr[j] = arr[j + 1];
+ }
+ // 更新元素數量
+ size--;
+ // 返回被刪除的元素
+ return num;
+ }
+
+ /* 串列擴容 */
+ public void extendCapacity() {
+ // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列
+ arr = Arrays.copyOf(arr, capacity() * extendRatio);
+ // 更新串列容量
+ capacity = arr.length;
+ }
+
+ /* 將串列轉換為陣列 */
+ public int[] toArray() {
+ int size = size();
+ // 僅轉換有效長度範圍內的串列元素
+ int[] arr = new int[size];
+ for (int i = 0; i < size; i++) {
+ arr[i] = get(i);
+ }
+ return arr;
+ }
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="my_list.cs"
+ /* 串列類別 */
+ class MyList {
+ private int[] arr; // 陣列(儲存串列元素)
+ private int arrCapacity = 10; // 串列容量
+ private int arrSize = 0; // 串列長度(當前元素數量)
+ private readonly int extendRatio = 2; // 每次串列擴容的倍數
+
+ /* 建構子 */
+ public MyList() {
+ arr = new int[arrCapacity];
+ }
+
+ /* 獲取串列長度(當前元素數量)*/
+ public int Size() {
+ return arrSize;
+ }
+
+ /* 獲取串列容量 */
+ public int Capacity() {
+ return arrCapacity;
+ }
+
+ /* 訪問元素 */
+ public int Get(int index) {
+ // 索引如果越界,則丟擲異常,下同
+ if (index < 0 || index >= arrSize)
+ throw new IndexOutOfRangeException("索引越界");
+ return arr[index];
+ }
+
+ /* 更新元素 */
+ public void Set(int index, int num) {
+ if (index < 0 || index >= arrSize)
+ throw new IndexOutOfRangeException("索引越界");
+ arr[index] = num;
+ }
+
+ /* 在尾部新增元素 */
+ public void Add(int num) {
+ // 元素數量超出容量時,觸發擴容機制
+ if (arrSize == arrCapacity)
+ ExtendCapacity();
+ arr[arrSize] = num;
+ // 更新元素數量
+ arrSize++;
+ }
+
+ /* 在中間插入元素 */
+ public void Insert(int index, int num) {
+ if (index < 0 || index >= arrSize)
+ throw new IndexOutOfRangeException("索引越界");
+ // 元素數量超出容量時,觸發擴容機制
+ if (arrSize == arrCapacity)
+ ExtendCapacity();
+ // 將索引 index 以及之後的元素都向後移動一位
+ for (int j = arrSize - 1; j >= index; j--) {
+ arr[j + 1] = arr[j];
+ }
+ arr[index] = num;
+ // 更新元素數量
+ arrSize++;
+ }
+
+ /* 刪除元素 */
+ public int Remove(int index) {
+ if (index < 0 || index >= arrSize)
+ throw new IndexOutOfRangeException("索引越界");
+ int num = arr[index];
+ // 將將索引 index 之後的元素都向前移動一位
+ for (int j = index; j < arrSize - 1; j++) {
+ arr[j] = arr[j + 1];
+ }
+ // 更新元素數量
+ arrSize--;
+ // 返回被刪除的元素
+ return num;
+ }
+
+ /* 串列擴容 */
+ public void ExtendCapacity() {
+ // 新建一個長度為 arrCapacity * extendRatio 的陣列,並將原陣列複製到新陣列
+ Array.Resize(ref arr, arrCapacity * extendRatio);
+ // 更新串列容量
+ arrCapacity = arr.Length;
+ }
+
+ /* 將串列轉換為陣列 */
+ public int[] ToArray() {
+ // 僅轉換有效長度範圍內的串列元素
+ int[] arr = new int[arrSize];
+ for (int i = 0; i < arrSize; i++) {
+ arr[i] = Get(i);
+ }
+ return arr;
+ }
+ }
+ ```
+
+=== "Go"
+
+ ```go title="my_list.go"
+ /* 串列類別 */
+ type myList struct {
+ arrCapacity int
+ arr []int
+ arrSize int
+ extendRatio int
+ }
+
+ /* 建構子 */
+ func newMyList() *myList {
+ return &myList{
+ arrCapacity: 10, // 串列容量
+ arr: make([]int, 10), // 陣列(儲存串列元素)
+ arrSize: 0, // 串列長度(當前元素數量)
+ extendRatio: 2, // 每次串列擴容的倍數
+ }
+ }
+
+ /* 獲取串列長度(當前元素數量) */
+ func (l *myList) size() int {
+ return l.arrSize
+ }
+
+ /* 獲取串列容量 */
+ func (l *myList) capacity() int {
+ return l.arrCapacity
+ }
+
+ /* 訪問元素 */
+ func (l *myList) get(index int) int {
+ // 索引如果越界,則丟擲異常,下同
+ if index < 0 || index >= l.arrSize {
+ panic("索引越界")
+ }
+ return l.arr[index]
+ }
+
+ /* 更新元素 */
+ func (l *myList) set(num, index int) {
+ if index < 0 || index >= l.arrSize {
+ panic("索引越界")
+ }
+ l.arr[index] = num
+ }
+
+ /* 在尾部新增元素 */
+ func (l *myList) add(num int) {
+ // 元素數量超出容量時,觸發擴容機制
+ if l.arrSize == l.arrCapacity {
+ l.extendCapacity()
+ }
+ l.arr[l.arrSize] = num
+ // 更新元素數量
+ l.arrSize++
+ }
+
+ /* 在中間插入元素 */
+ func (l *myList) insert(num, index int) {
+ if index < 0 || index >= l.arrSize {
+ panic("索引越界")
+ }
+ // 元素數量超出容量時,觸發擴容機制
+ if l.arrSize == l.arrCapacity {
+ l.extendCapacity()
+ }
+ // 將索引 index 以及之後的元素都向後移動一位
+ for j := l.arrSize - 1; j >= index; j-- {
+ l.arr[j+1] = l.arr[j]
+ }
+ l.arr[index] = num
+ // 更新元素數量
+ l.arrSize++
+ }
+
+ /* 刪除元素 */
+ func (l *myList) remove(index int) int {
+ if index < 0 || index >= l.arrSize {
+ panic("索引越界")
+ }
+ num := l.arr[index]
+ // 將索引 index 之後的元素都向前移動一位
+ for j := index; j < l.arrSize-1; j++ {
+ l.arr[j] = l.arr[j+1]
+ }
+ // 更新元素數量
+ l.arrSize--
+ // 返回被刪除的元素
+ return num
+ }
+
+ /* 串列擴容 */
+ func (l *myList) extendCapacity() {
+ // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列
+ l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...)
+ // 更新串列容量
+ l.arrCapacity = len(l.arr)
+ }
+
+ /* 返回有效長度的串列 */
+ func (l *myList) toArray() []int {
+ // 僅轉換有效長度範圍內的串列元素
+ return l.arr[:l.arrSize]
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="my_list.swift"
+ /* 串列類別 */
+ class MyList {
+ private var arr: [Int] // 陣列(儲存串列元素)
+ private var _capacity: Int // 串列容量
+ private var _size: Int // 串列長度(當前元素數量)
+ private let extendRatio: Int // 每次串列擴容的倍數
+
+ /* 建構子 */
+ init() {
+ _capacity = 10
+ _size = 0
+ extendRatio = 2
+ arr = Array(repeating: 0, count: _capacity)
+ }
+
+ /* 獲取串列長度(當前元素數量)*/
+ func size() -> Int {
+ _size
+ }
+
+ /* 獲取串列容量 */
+ func capacity() -> Int {
+ _capacity
+ }
+
+ /* 訪問元素 */
+ func get(index: Int) -> Int {
+ // 索引如果越界則丟擲錯誤,下同
+ if index < 0 || index >= size() {
+ fatalError("索引越界")
+ }
+ return arr[index]
+ }
+
+ /* 更新元素 */
+ func set(index: Int, num: Int) {
+ if index < 0 || index >= size() {
+ fatalError("索引越界")
+ }
+ arr[index] = num
+ }
+
+ /* 在尾部新增元素 */
+ func add(num: Int) {
+ // 元素數量超出容量時,觸發擴容機制
+ if size() == capacity() {
+ extendCapacity()
+ }
+ arr[size()] = num
+ // 更新元素數量
+ _size += 1
+ }
+
+ /* 在中間插入元素 */
+ func insert(index: Int, num: Int) {
+ if index < 0 || index >= size() {
+ fatalError("索引越界")
+ }
+ // 元素數量超出容量時,觸發擴容機制
+ if size() == capacity() {
+ extendCapacity()
+ }
+ // 將索引 index 以及之後的元素都向後移動一位
+ for j in (index ..< size()).reversed() {
+ arr[j + 1] = arr[j]
+ }
+ arr[index] = num
+ // 更新元素數量
+ _size += 1
+ }
+
+ /* 刪除元素 */
+ @discardableResult
+ func remove(index: Int) -> Int {
+ if index < 0 || index >= size() {
+ fatalError("索引越界")
+ }
+ let num = arr[index]
+ // 將將索引 index 之後的元素都向前移動一位
+ for j in index ..< (size() - 1) {
+ arr[j] = arr[j + 1]
+ }
+ // 更新元素數量
+ _size -= 1
+ // 返回被刪除的元素
+ return num
+ }
+
+ /* 串列擴容 */
+ func extendCapacity() {
+ // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列
+ arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1))
+ // 更新串列容量
+ _capacity = arr.count
+ }
+
+ /* 將串列轉換為陣列 */
+ func toArray() -> [Int] {
+ Array(arr.prefix(size()))
+ }
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="my_list.js"
+ /* 串列類別 */
+ class MyList {
+ #arr = new Array(); // 陣列(儲存串列元素)
+ #capacity = 10; // 串列容量
+ #size = 0; // 串列長度(當前元素數量)
+ #extendRatio = 2; // 每次串列擴容的倍數
+
+ /* 建構子 */
+ constructor() {
+ this.#arr = new Array(this.#capacity);
+ }
+
+ /* 獲取串列長度(當前元素數量)*/
+ size() {
+ return this.#size;
+ }
+
+ /* 獲取串列容量 */
+ capacity() {
+ return this.#capacity;
+ }
+
+ /* 訪問元素 */
+ get(index) {
+ // 索引如果越界,則丟擲異常,下同
+ if (index < 0 || index >= this.#size) throw new Error('索引越界');
+ return this.#arr[index];
+ }
+
+ /* 更新元素 */
+ set(index, num) {
+ if (index < 0 || index >= this.#size) throw new Error('索引越界');
+ this.#arr[index] = num;
+ }
+
+ /* 在尾部新增元素 */
+ add(num) {
+ // 如果長度等於容量,則需要擴容
+ if (this.#size === this.#capacity) {
+ this.extendCapacity();
+ }
+ // 將新元素新增到串列尾部
+ this.#arr[this.#size] = num;
+ this.#size++;
+ }
+
+ /* 在中間插入元素 */
+ insert(index, num) {
+ if (index < 0 || index >= this.#size) throw new Error('索引越界');
+ // 元素數量超出容量時,觸發擴容機制
+ if (this.#size === this.#capacity) {
+ this.extendCapacity();
+ }
+ // 將索引 index 以及之後的元素都向後移動一位
+ for (let j = this.#size - 1; j >= index; j--) {
+ this.#arr[j + 1] = this.#arr[j];
+ }
+ // 更新元素數量
+ this.#arr[index] = num;
+ this.#size++;
+ }
+
+ /* 刪除元素 */
+ remove(index) {
+ if (index < 0 || index >= this.#size) throw new Error('索引越界');
+ let num = this.#arr[index];
+ // 將將索引 index 之後的元素都向前移動一位
+ for (let j = index; j < this.#size - 1; j++) {
+ this.#arr[j] = this.#arr[j + 1];
+ }
+ // 更新元素數量
+ this.#size--;
+ // 返回被刪除的元素
+ return num;
+ }
+
+ /* 串列擴容 */
+ extendCapacity() {
+ // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列
+ this.#arr = this.#arr.concat(
+ new Array(this.capacity() * (this.#extendRatio - 1))
+ );
+ // 更新串列容量
+ this.#capacity = this.#arr.length;
+ }
+
+ /* 將串列轉換為陣列 */
+ toArray() {
+ let size = this.size();
+ // 僅轉換有效長度範圍內的串列元素
+ const arr = new Array(size);
+ for (let i = 0; i < size; i++) {
+ arr[i] = this.get(i);
+ }
+ return arr;
+ }
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="my_list.ts"
+ /* 串列類別 */
+ class MyList {
+ private arr: Array; // 陣列(儲存串列元素)
+ private _capacity: number = 10; // 串列容量
+ private _size: number = 0; // 串列長度(當前元素數量)
+ private extendRatio: number = 2; // 每次串列擴容的倍數
+
+ /* 建構子 */
+ constructor() {
+ this.arr = new Array(this._capacity);
+ }
+
+ /* 獲取串列長度(當前元素數量)*/
+ public size(): number {
+ return this._size;
+ }
+
+ /* 獲取串列容量 */
+ public capacity(): number {
+ return this._capacity;
+ }
+
+ /* 訪問元素 */
+ public get(index: number): number {
+ // 索引如果越界,則丟擲異常,下同
+ if (index < 0 || index >= this._size) throw new Error('索引越界');
+ return this.arr[index];
+ }
+
+ /* 更新元素 */
+ public set(index: number, num: number): void {
+ if (index < 0 || index >= this._size) throw new Error('索引越界');
+ this.arr[index] = num;
+ }
+
+ /* 在尾部新增元素 */
+ public add(num: number): void {
+ // 如果長度等於容量,則需要擴容
+ if (this._size === this._capacity) this.extendCapacity();
+ // 將新元素新增到串列尾部
+ this.arr[this._size] = num;
+ this._size++;
+ }
+
+ /* 在中間插入元素 */
+ public insert(index: number, num: number): void {
+ if (index < 0 || index >= this._size) throw new Error('索引越界');
+ // 元素數量超出容量時,觸發擴容機制
+ if (this._size === this._capacity) {
+ this.extendCapacity();
+ }
+ // 將索引 index 以及之後的元素都向後移動一位
+ for (let j = this._size - 1; j >= index; j--) {
+ this.arr[j + 1] = this.arr[j];
+ }
+ // 更新元素數量
+ this.arr[index] = num;
+ this._size++;
+ }
+
+ /* 刪除元素 */
+ public remove(index: number): number {
+ if (index < 0 || index >= this._size) throw new Error('索引越界');
+ let num = this.arr[index];
+ // 將將索引 index 之後的元素都向前移動一位
+ for (let j = index; j < this._size - 1; j++) {
+ this.arr[j] = this.arr[j + 1];
+ }
+ // 更新元素數量
+ this._size--;
+ // 返回被刪除的元素
+ return num;
+ }
+
+ /* 串列擴容 */
+ public extendCapacity(): void {
+ // 新建一個長度為 size 的陣列,並將原陣列複製到新陣列
+ this.arr = this.arr.concat(
+ new Array(this.capacity() * (this.extendRatio - 1))
+ );
+ // 更新串列容量
+ this._capacity = this.arr.length;
+ }
+
+ /* 將串列轉換為陣列 */
+ public toArray(): number[] {
+ let size = this.size();
+ // 僅轉換有效長度範圍內的串列元素
+ const arr = new Array(size);
+ for (let i = 0; i < size; i++) {
+ arr[i] = this.get(i);
+ }
+ return arr;
+ }
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="my_list.dart"
+ /* 串列類別 */
+ class MyList {
+ late List _arr; // 陣列(儲存串列元素)
+ int _capacity = 10; // 串列容量
+ int _size = 0; // 串列長度(當前元素數量)
+ int _extendRatio = 2; // 每次串列擴容的倍數
+
+ /* 建構子 */
+ MyList() {
+ _arr = List.filled(_capacity, 0);
+ }
+
+ /* 獲取串列長度(當前元素數量)*/
+ int size() => _size;
+
+ /* 獲取串列容量 */
+ int capacity() => _capacity;
+
+ /* 訪問元素 */
+ int get(int index) {
+ if (index >= _size) throw RangeError('索引越界');
+ return _arr[index];
+ }
+
+ /* 更新元素 */
+ void set(int index, int _num) {
+ if (index >= _size) throw RangeError('索引越界');
+ _arr[index] = _num;
+ }
+
+ /* 在尾部新增元素 */
+ void add(int _num) {
+ // 元素數量超出容量時,觸發擴容機制
+ if (_size == _capacity) extendCapacity();
+ _arr[_size] = _num;
+ // 更新元素數量
+ _size++;
+ }
+
+ /* 在中間插入元素 */
+ void insert(int index, int _num) {
+ if (index >= _size) throw RangeError('索引越界');
+ // 元素數量超出容量時,觸發擴容機制
+ if (_size == _capacity) extendCapacity();
+ // 將索引 index 以及之後的元素都向後移動一位
+ for (var j = _size - 1; j >= index; j--) {
+ _arr[j + 1] = _arr[j];
+ }
+ _arr[index] = _num;
+ // 更新元素數量
+ _size++;
+ }
+
+ /* 刪除元素 */
+ int remove(int index) {
+ if (index >= _size) throw RangeError('索引越界');
+ int _num = _arr[index];
+ // 將將索引 index 之後的元素都向前移動一位
+ for (var j = index; j < _size - 1; j++) {
+ _arr[j] = _arr[j + 1];
+ }
+ // 更新元素數量
+ _size--;
+ // 返回被刪除的元素
+ return _num;
+ }
+
+ /* 串列擴容 */
+ void extendCapacity() {
+ // 新建一個長度為原陣列 _extendRatio 倍的新陣列
+ final _newNums = List.filled(_capacity * _extendRatio, 0);
+ // 將原陣列複製到新陣列
+ List.copyRange(_newNums, 0, _arr);
+ // 更新 _arr 的引用
+ _arr = _newNums;
+ // 更新串列容量
+ _capacity = _arr.length;
+ }
+
+ /* 將串列轉換為陣列 */
+ List toArray() {
+ List arr = [];
+ for (var i = 0; i < _size; i++) {
+ arr.add(get(i));
+ }
+ return arr;
+ }
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="my_list.rs"
+ /* 串列類別 */
+ #[allow(dead_code)]
+ struct MyList {
+ arr: Vec, // 陣列(儲存串列元素)
+ capacity: usize, // 串列容量
+ size: usize, // 串列長度(當前元素數量)
+ extend_ratio: usize, // 每次串列擴容的倍數
+ }
+
+ #[allow(unused, unused_comparisons)]
+ impl MyList {
+ /* 建構子 */
+ pub fn new(capacity: usize) -> Self {
+ let mut vec = Vec::new();
+ vec.resize(capacity, 0);
+ Self {
+ arr: vec,
+ capacity,
+ size: 0,
+ extend_ratio: 2,
+ }
+ }
+
+ /* 獲取串列長度(當前元素數量)*/
+ pub fn size(&self) -> usize {
+ return self.size;
+ }
+
+ /* 獲取串列容量 */
+ pub fn capacity(&self) -> usize {
+ return self.capacity;
+ }
+
+ /* 訪問元素 */
+ pub fn get(&self, index: usize) -> i32 {
+ // 索引如果越界,則丟擲異常,下同
+ if index >= self.size {
+ panic!("索引越界")
+ };
+ return self.arr[index];
+ }
+
+ /* 更新元素 */
+ pub fn set(&mut self, index: usize, num: i32) {
+ if index >= self.size {
+ panic!("索引越界")
+ };
+ self.arr[index] = num;
+ }
+
+ /* 在尾部新增元素 */
+ pub fn add(&mut self, num: i32) {
+ // 元素數量超出容量時,觸發擴容機制
+ if self.size == self.capacity() {
+ self.extend_capacity();
+ }
+ self.arr[self.size] = num;
+ // 更新元素數量
+ self.size += 1;
+ }
+
+ /* 在中間插入元素 */
+ pub fn insert(&mut self, index: usize, num: i32) {
+ if index >= self.size() {
+ panic!("索引越界")
+ };
+ // 元素數量超出容量時,觸發擴容機制
+ if self.size == self.capacity() {
+ self.extend_capacity();
+ }
+ // 將索引 index 以及之後的元素都向後移動一位
+ for j in (index..self.size).rev() {
+ self.arr[j + 1] = self.arr[j];
+ }
+ self.arr[index] = num;
+ // 更新元素數量
+ self.size += 1;
+ }
+
+ /* 刪除元素 */
+ pub fn remove(&mut self, index: usize) -> i32 {
+ if index >= self.size() {
+ panic!("索引越界")
+ };
+ let num = self.arr[index];
+ // 將將索引 index 之後的元素都向前移動一位
+ for j in (index..self.size - 1) {
+ self.arr[j] = self.arr[j + 1];
+ }
+ // 更新元素數量
+ self.size -= 1;
+ // 返回被刪除的元素
+ return num;
+ }
+
+ /* 串列擴容 */
+ pub fn extend_capacity(&mut self) {
+ // 新建一個長度為原陣列 extend_ratio 倍的新陣列,並將原陣列複製到新陣列
+ let new_capacity = self.capacity * self.extend_ratio;
+ self.arr.resize(new_capacity, 0);
+ // 更新串列容量
+ self.capacity = new_capacity;
+ }
+
+ /* 將串列轉換為陣列 */
+ pub fn to_array(&mut self) -> Vec {
+ // 僅轉換有效長度範圍內的串列元素
+ let mut arr = Vec::new();
+ for i in 0..self.size {
+ arr.push(self.get(i));
+ }
+ arr
+ }
+ }
+ ```
+
+=== "C"
+
+ ```c title="my_list.c"
+ /* 串列類別 */
+ typedef struct {
+ int *arr; // 陣列(儲存串列元素)
+ int capacity; // 串列容量
+ int size; // 串列大小
+ int extendRatio; // 串列每次擴容的倍數
+ } MyList;
+
+ /* 建構子 */
+ MyList *newMyList() {
+ MyList *nums = malloc(sizeof(MyList));
+ nums->capacity = 10;
+ nums->arr = malloc(sizeof(int) * nums->capacity);
+ nums->size = 0;
+ nums->extendRatio = 2;
+ return nums;
+ }
+
+ /* 析構函式 */
+ void delMyList(MyList *nums) {
+ free(nums->arr);
+ free(nums);
+ }
+
+ /* 獲取串列長度 */
+ int size(MyList *nums) {
+ return nums->size;
+ }
+
+ /* 獲取串列容量 */
+ int capacity(MyList *nums) {
+ return nums->capacity;
+ }
+
+ /* 訪問元素 */
+ int get(MyList *nums, int index) {
+ assert(index >= 0 && index < nums->size);
+ return nums->arr[index];
+ }
+
+ /* 更新元素 */
+ void set(MyList *nums, int index, int num) {
+ assert(index >= 0 && index < nums->size);
+ nums->arr[index] = num;
+ }
+
+ /* 在尾部新增元素 */
+ void add(MyList *nums, int num) {
+ if (size(nums) == capacity(nums)) {
+ extendCapacity(nums); // 擴容
+ }
+ nums->arr[size(nums)] = num;
+ nums->size++;
+ }
+
+ /* 在中間插入元素 */
+ void insert(MyList *nums, int index, int num) {
+ assert(index >= 0 && index < size(nums));
+ // 元素數量超出容量時,觸發擴容機制
+ if (size(nums) == capacity(nums)) {
+ extendCapacity(nums); // 擴容
+ }
+ for (int i = size(nums); i > index; --i) {
+ nums->arr[i] = nums->arr[i - 1];
+ }
+ nums->arr[index] = num;
+ nums->size++;
+ }
+
+ /* 刪除元素 */
+ // 注意:stdio.h 佔用了 remove 關鍵詞
+ int removeItem(MyList *nums, int index) {
+ assert(index >= 0 && index < size(nums));
+ int num = nums->arr[index];
+ for (int i = index; i < size(nums) - 1; i++) {
+ nums->arr[i] = nums->arr[i + 1];
+ }
+ nums->size--;
+ return num;
+ }
+
+ /* 串列擴容 */
+ void extendCapacity(MyList *nums) {
+ // 先分配空間
+ int newCapacity = capacity(nums) * nums->extendRatio;
+ int *extend = (int *)malloc(sizeof(int) * newCapacity);
+ int *temp = nums->arr;
+
+ // 複製舊資料到新資料
+ for (int i = 0; i < size(nums); i++)
+ extend[i] = nums->arr[i];
+
+ // 釋放舊資料
+ free(temp);
+
+ // 更新新資料
+ nums->arr = extend;
+ nums->capacity = newCapacity;
+ }
+
+ /* 將串列轉換為 Array 用於列印 */
+ int *toArray(MyList *nums) {
+ return nums->arr;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="my_list.kt"
+ /* 串列類別 */
+ class MyList {
+ private var arr: IntArray = intArrayOf() // 陣列(儲存串列元素)
+ private var capacity = 10 // 串列容量
+ private var size = 0 // 串列長度(當前元素數量)
+ private var extendRatio = 2 // 每次串列擴容的倍數
+
+ /* 建構子 */
+ init {
+ arr = IntArray(capacity)
+ }
+
+ /* 獲取串列長度(當前元素數量) */
+ fun size(): Int {
+ return size
+ }
+
+ /* 獲取串列容量 */
+ fun capacity(): Int {
+ return capacity
+ }
+
+ /* 訪問元素 */
+ fun get(index: Int): Int {
+ // 索引如果越界,則丟擲異常,下同
+ if (index < 0 || index >= size)
+ throw IndexOutOfBoundsException()
+ return arr[index]
+ }
+
+ /* 更新元素 */
+ fun set(index: Int, num: Int) {
+ if (index < 0 || index >= size)
+ throw IndexOutOfBoundsException("索引越界")
+ arr[index] = num
+ }
+
+ /* 在尾部新增元素 */
+ fun add(num: Int) {
+ // 元素數量超出容量時,觸發擴容機制
+ if (size == capacity())
+ extendCapacity()
+ arr[size] = num
+ // 更新元素數量
+ size++
+ }
+
+ /* 在中間插入元素 */
+ fun insert(index: Int, num: Int) {
+ if (index < 0 || index >= size)
+ throw IndexOutOfBoundsException("索引越界")
+ // 元素數量超出容量時,觸發擴容機制
+ if (size == capacity())
+ extendCapacity()
+ // 將索引 index 以及之後的元素都向後移動一位
+ for (j in size - 1 downTo index)
+ arr[j + 1] = arr[j]
+ arr[index] = num
+ // 更新元素數量
+ size++
+ }
+
+ /* 刪除元素 */
+ fun remove(index: Int): Int {
+ if (index < 0 || index >= size)
+ throw IndexOutOfBoundsException("索引越界")
+ val num: Int = arr[index]
+ // 將將索引 index 之後的元素都向前移動一位
+ for (j in index..= size
+ @arr[index]
+ end
+
+ ### 訪問元素 ###
+ def set(index, num)
+ raise IndexError, "索引越界" if index < 0 || index >= size
+ @arr[index] = num
+ end
+
+ ### 在尾部新增元素 ###
+ def add(num)
+ # 元素數量超出容量時,觸發擴容機制
+ extend_capacity if size == capacity
+ @arr[size] = num
+
+ # 更新元素數量
+ @size += 1
+ end
+
+ ### 在中間插入元素 ###
+ def insert(index, num)
+ raise IndexError, "索引越界" if index < 0 || index >= size
+
+ # 元素數量超出容量時,觸發擴容機制
+ extend_capacity if size == capacity
+
+ # 將索引 index 以及之後的元素都向後移動一位
+ for j in (size - 1).downto(index)
+ @arr[j + 1] = @arr[j]
+ end
+ @arr[index] = num
+
+ # 更新元素數量
+ @size += 1
+ end
+
+ ### 刪除元素 ###
+ def remove(index)
+ raise IndexError, "索引越界" if index < 0 || index >= size
+ num = @arr[index]
+
+ # 將將索引 index 之後的元素都向前移動一位
+ for j in index...size
+ @arr[j] = @arr[j + 1]
+ end
+
+ # 更新元素數量
+ @size -= 1
+
+ # 返回被刪除的元素
+ num
+ end
+
+ ### 串列擴容 ###
+ def extend_capacity
+ # 新建一個長度為原陣列 extend_ratio 倍的新陣列,並將原陣列複製到新陣列
+ arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1))
+ # 更新串列容量
+ @capacity = arr.length
+ end
+
+ ### 將串列轉換為陣列 ###
+ def to_array
+ sz = size
+ # 僅轉換有效長度範圍內的串列元素
+ arr = Array.new(sz)
+ for i in 0...sz
+ arr[i] = get(i)
+ end
+ arr
+ end
+ end
+ ```
+
+=== "Zig"
+
+ ```zig title="my_list.zig"
+ // 串列類別
+ fn MyList(comptime T: type) type {
+ return struct {
+ const Self = @This();
+
+ arr: []T = undefined, // 陣列(儲存串列元素)
+ arrCapacity: usize = 10, // 串列容量
+ numSize: usize = 0, // 串列長度(當前元素數量)
+ extendRatio: usize = 2, // 每次串列擴容的倍數
+ mem_arena: ?std.heap.ArenaAllocator = null,
+ mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器
+
+ // 建構子(分配記憶體+初始化串列)
+ pub fn init(self: *Self, allocator: std.mem.Allocator) !void {
+ if (self.mem_arena == null) {
+ self.mem_arena = std.heap.ArenaAllocator.init(allocator);
+ self.mem_allocator = self.mem_arena.?.allocator();
+ }
+ self.arr = try self.mem_allocator.alloc(T, self.arrCapacity);
+ @memset(self.arr, @as(T, 0));
+ }
+
+ // 析構函式(釋放記憶體)
+ pub fn deinit(self: *Self) void {
+ if (self.mem_arena == null) return;
+ self.mem_arena.?.deinit();
+ }
+
+ // 獲取串列長度(當前元素數量)
+ pub fn size(self: *Self) usize {
+ return self.numSize;
+ }
+
+ // 獲取串列容量
+ pub fn capacity(self: *Self) usize {
+ return self.arrCapacity;
+ }
+
+ // 訪問元素
+ pub fn get(self: *Self, index: usize) T {
+ // 索引如果越界,則丟擲異常,下同
+ if (index < 0 or index >= self.size()) @panic("索引越界");
+ return self.arr[index];
+ }
+
+ // 更新元素
+ pub fn set(self: *Self, index: usize, num: T) void {
+ // 索引如果越界,則丟擲異常,下同
+ if (index < 0 or index >= self.size()) @panic("索引越界");
+ self.arr[index] = num;
+ }
+
+ // 在尾部新增元素
+ pub fn add(self: *Self, num: T) !void {
+ // 元素數量超出容量時,觸發擴容機制
+ if (self.size() == self.capacity()) try self.extendCapacity();
+ self.arr[self.size()] = num;
+ // 更新元素數量
+ self.numSize += 1;
+ }
+
+ // 在中間插入元素
+ pub fn insert(self: *Self, index: usize, num: T) !void {
+ if (index < 0 or index >= self.size()) @panic("索引越界");
+ // 元素數量超出容量時,觸發擴容機制
+ if (self.size() == self.capacity()) try self.extendCapacity();
+ // 將索引 index 以及之後的元素都向後移動一位
+ var j = self.size() - 1;
+ while (j >= index) : (j -= 1) {
+ self.arr[j + 1] = self.arr[j];
+ }
+ self.arr[index] = num;
+ // 更新元素數量
+ self.numSize += 1;
+ }
+
+ // 刪除元素
+ pub fn remove(self: *Self, index: usize) T {
+ if (index < 0 or index >= self.size()) @panic("索引越界");
+ var num = self.arr[index];
+ // 將索引 index 之後的元素都向前移動一位
+ var j = index;
+ while (j < self.size() - 1) : (j += 1) {
+ self.arr[j] = self.arr[j + 1];
+ }
+ // 更新元素數量
+ self.numSize -= 1;
+ // 返回被刪除的元素
+ return num;
+ }
+
+ // 串列擴容
+ pub fn extendCapacity(self: *Self) !void {
+ // 新建一個長度為 size * extendRatio 的陣列,並將原陣列複製到新陣列
+ var newCapacity = self.capacity() * self.extendRatio;
+ var extend = try self.mem_allocator.alloc(T, newCapacity);
+ @memset(extend, @as(T, 0));
+ // 將原陣列中的所有元素複製到新陣列
+ std.mem.copy(T, extend, self.arr);
+ self.arr = extend;
+ // 更新串列容量
+ self.arrCapacity = newCapacity;
+ }
+
+ // 將串列轉換為陣列
+ pub fn toArray(self: *Self) ![]T {
+ // 僅轉換有效長度範圍內的串列元素
+ var arr = try self.mem_allocator.alloc(T, self.size());
+ @memset(arr, @as(T, 0));
+ for (arr, 0..) |*num, i| {
+ num.* = self.get(i);
+ }
+ return arr;
+ }
+ };
+ }
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
diff --git a/zh-Hant/docs/chapter_array_and_linkedlist/ram_and_cache.md b/zh-Hant/docs/chapter_array_and_linkedlist/ram_and_cache.md
new file mode 100644
index 000000000..a577d1220
--- /dev/null
+++ b/zh-Hant/docs/chapter_array_and_linkedlist/ram_and_cache.md
@@ -0,0 +1,84 @@
+---
+comments: true
+status: new
+---
+
+# 4.4 記憶體與快取 *
+
+在本章的前兩節中,我們探討了陣列和鏈結串列這兩種基礎且重要的資料結構,它們分別代表了“連續儲存”和“分散儲存”兩種物理結構。
+
+實際上,**物理結構在很大程度上決定了程式對記憶體和快取的使用效率**,進而影響演算法程式的整體效能。
+
+## 4.4.1 計算機儲存裝置
+
+計算機中包括三種類型的儲存裝置:硬碟(hard disk)、記憶體(random-access memory, RAM)、快取(cache memory)。表 4-2 展示了它們在計算機系統中的不同角色和效能特點。
+
+ 表 4-2 計算機的儲存裝置
+
+
+
+| | 硬碟 | 記憶體 | 快取 |
+| ------ | ---------------------------------------- | -------------------------------------- | ------------------------------------------------- |
+| 用途 | 長期儲存資料,包括作業系統、程式、檔案等 | 臨時儲存當前執行的程式和正在處理的資料 | 儲存經常訪問的資料和指令,減少 CPU 訪問記憶體的次數 |
+| 易失性 | 斷電後資料不會丟失 | 斷電後資料會丟失 | 斷電後資料會丟失 |
+| 容量 | 較大,TB 級別 | 較小,GB 級別 | 非常小,MB 級別 |
+| 速度 | 較慢,幾百到幾千 MB/s | 較快,幾十 GB/s | 非常快,幾十到幾百 GB/s |
+| 價格 | 較便宜,幾毛到幾元 / GB | 較貴,幾十到幾百元 / GB | 非常貴,隨 CPU 打包計價 |
+
+
+
+我們可以將計算機儲存系統想象為圖 4-9 所示的金字塔結構。越靠近金字塔頂端的儲存裝置的速度越快、容量越小、成本越高。這種多層級的設計並非偶然,而是計算機科學家和工程師們經過深思熟慮的結果。
+
+- **硬碟難以被記憶體取代**。首先,記憶體中的資料在斷電後會丟失,因此它不適合長期儲存資料;其次,記憶體的成本是硬碟的幾十倍,這使得它難以在消費者市場普及。
+- **快取的大容量和高速度難以兼得**。隨著 L1、L2、L3 快取的容量逐步增大,其物理尺寸會變大,與 CPU 核心之間的物理距離會變遠,從而導致資料傳輸時間增加,元素訪問延遲變高。在當前技術下,多層級的快取結構是容量、速度和成本之間的最佳平衡點。
+
+{ class="animation-figure" }
+
+ 圖 4-9 計算機儲存系統
+
+!!! note
+
+ 計算機的儲存層次結構體現了速度、容量和成本三者之間的精妙平衡。實際上,這種權衡普遍存在於所有工業領域,它要求我們在不同的優勢和限制之間找到最佳平衡點。
+
+總的來說,**硬碟用於長期儲存大量資料,記憶體用於臨時儲存程式執行中正在處理的資料,而快取則用於儲存經常訪問的資料和指令**,以提高程式執行效率。三者共同協作,確保計算機系統高效執行。
+
+如圖 4-10 所示,在程式執行時,資料會從硬碟中被讀取到記憶體中,供 CPU 計算使用。快取可以看作 CPU 的一部分,**它透過智慧地從記憶體載入資料**,給 CPU 提供高速的資料讀取,從而顯著提升程式的執行效率,減少對較慢的記憶體的依賴。
+
+{ class="animation-figure" }
+
+ 圖 4-10 硬碟、記憶體和快取之間的資料流通
+
+## 4.4.2 資料結構的記憶體效率
+
+在記憶體空間利用方面,陣列和鏈結串列各自具有優勢和侷限性。
+
+一方面,**記憶體是有限的,且同一塊記憶體不能被多個程式共享**,因此我們希望資料結構能夠儘可能高效地利用空間。陣列的元素緊密排列,不需要額外的空間來儲存鏈結串列節點間的引用(指標),因此空間效率更高。然而,陣列需要一次性分配足夠的連續記憶體空間,這可能導致記憶體浪費,陣列擴容也需要額外的時間和空間成本。相比之下,鏈結串列以“節點”為單位進行動態記憶體分配和回收,提供了更大的靈活性。
+
+另一方面,在程式執行時,**隨著反覆申請與釋放記憶體,空閒記憶體的碎片化程度會越來越高**,從而導致記憶體的利用效率降低。陣列由於其連續的儲存方式,相對不容易導致記憶體碎片化。相反,鏈結串列的元素是分散儲存的,在頻繁的插入與刪除操作中,更容易導致記憶體碎片化。
+
+## 4.4.3 資料結構的快取效率
+
+快取雖然在空間容量上遠小於記憶體,但它比記憶體快得多,在程式執行速度上起著至關重要的作用。由於快取的容量有限,只能儲存一小部分頻繁訪問的資料,因此當 CPU 嘗試訪問的資料不在快取中時,就會發生快取未命中(cache miss),此時 CPU 不得不從速度較慢的記憶體中載入所需資料。
+
+顯然,**“快取未命中”越少,CPU 讀寫資料的效率就越高**,程式效能也就越好。我們將 CPU 從快取中成功獲取資料的比例稱為快取命中率(cache hit rate),這個指標通常用來衡量快取效率。
+
+為了儘可能達到更高的效率,快取會採取以下資料載入機制。
+
+- **快取行**:快取不是單個位元組地儲存與載入資料,而是以快取行為單位。相比於單個位元組的傳輸,快取行的傳輸形式更加高效。
+- **預取機制**:處理器會嘗試預測資料訪問模式(例如順序訪問、固定步長跳躍訪問等),並根據特定模式將資料載入至快取之中,從而提升命中率。
+- **空間區域性**:如果一個數據被訪問,那麼它附近的資料可能近期也會被訪問。因此,快取在載入某一資料時,也會載入其附近的資料,以提高命中率。
+- **時間區域性**:如果一個數據被訪問,那麼它在不久的將來很可能再次被訪問。快取利用這一原理,透過保留最近訪問過的資料來提高命中率。
+
+實際上,**陣列和鏈結串列對快取的利用效率是不同的**,主要體現在以下幾個方面。
+
+- **佔用空間**:鏈結串列元素比陣列元素佔用空間更多,導致快取中容納的有效資料量更少。
+- **快取行**:鏈結串列資料分散在記憶體各處,而快取是“按行載入”的,因此載入到無效資料的比例更高。
+- **預取機制**:陣列比鏈結串列的資料訪問模式更具“可預測性”,即系統更容易猜出即將被載入的資料。
+- **空間區域性**:陣列被儲存在集中的記憶體空間中,因此被載入資料附近的資料更有可能即將被訪問。
+
+總體而言,**陣列具有更高的快取命中率,因此它在操作效率上通常優於鏈結串列**。這使得在解決演算法問題時,基於陣列實現的資料結構往往更受歡迎。
+
+需要注意的是,**高快取效率並不意味著陣列在所有情況下都優於鏈結串列**。實際應用中選擇哪種資料結構,應根據具體需求來決定。例如,陣列和鏈結串列都可以實現“堆疊”資料結構(下一章會詳細介紹),但它們適用於不同場景。
+
+- 在做演算法題時,我們會傾向於選擇基於陣列實現的堆疊,因為它提供了更高的操作效率和隨機訪問的能力,代價僅是需要預先為陣列分配一定的記憶體空間。
+- 如果資料量非常大、動態性很高、堆疊的預期大小難以估計,那麼基於鏈結串列實現的堆疊更加合適。鏈結串列能夠將大量資料分散儲存於記憶體的不同部分,並且避免了陣列擴容產生的額外開銷。
diff --git a/zh-Hant/docs/chapter_array_and_linkedlist/summary.md b/zh-Hant/docs/chapter_array_and_linkedlist/summary.md
new file mode 100644
index 000000000..120c088e7
--- /dev/null
+++ b/zh-Hant/docs/chapter_array_and_linkedlist/summary.md
@@ -0,0 +1,80 @@
+---
+comments: true
+---
+
+# 4.5 小結
+
+### 1. 重點回顧
+
+- 陣列和鏈結串列是兩種基本的資料結構,分別代表資料在計算機記憶體中的兩種儲存方式:連續空間儲存和分散空間儲存。兩者的特點呈現出互補的特性。
+- 陣列支持隨機訪問、佔用記憶體較少;但插入和刪除元素效率低,且初始化後長度不可變。
+- 鏈結串列透過更改引用(指標)實現高效的節點插入與刪除,且可以靈活調整長度;但節點訪問效率低、佔用記憶體較多。常見的鏈結串列型別包括單向鏈結串列、環形鏈結串列、雙向鏈結串列。
+- 串列是一種支持增刪查改的元素有序集合,通常基於動態陣列實現。它保留了陣列的優勢,同時可以靈活調整長度。
+- 串列的出現大幅提高了陣列的實用性,但可能導致部分記憶體空間浪費。
+- 程式執行時,資料主要儲存在記憶體中。陣列可提供更高的記憶體空間效率,而鏈結串列則在記憶體使用上更加靈活。
+- 快取透過快取行、預取機制以及空間區域性和時間區域性等資料載入機制,為 CPU 提供快速資料訪問,顯著提升程式的執行效率。
+- 由於陣列具有更高的快取命中率,因此它通常比鏈結串列更高效。在選擇資料結構時,應根據具體需求和場景做出恰當選擇。
+
+### 2. Q & A
+
+**Q**:陣列儲存在堆疊上和儲存在堆積上,對時間效率和空間效率是否有影響?
+
+儲存在堆疊上和堆積上的陣列都被儲存在連續記憶體空間內,資料操作效率基本一致。然而,堆疊和堆積具有各自的特點,從而導致以下不同點。
+
+1. 分配和釋放效率:堆疊是一塊較小的記憶體,分配由編譯器自動完成;而堆積記憶體相對更大,可以在程式碼中動態分配,更容易碎片化。因此,堆積上的分配和釋放操作通常比堆疊上的慢。
+2. 大小限制:堆疊記憶體相對較小,堆積的大小一般受限於可用記憶體。因此堆積更加適合儲存大型陣列。
+3. 靈活性:堆疊上的陣列的大小需要在編譯時確定,而堆積上的陣列的大小可以在執行時動態確定。
+
+**Q**:為什麼陣列要求相同型別的元素,而在鏈結串列中卻沒有強調相同型別呢?
+
+鏈結串列由節點組成,節點之間透過引用(指標)連線,各個節點可以儲存不同型別的資料,例如 `int`、`double`、`string`、`object` 等。
+
+相對地,陣列元素則必須是相同型別的,這樣才能透過計算偏移量來獲取對應元素位置。例如,陣列同時包含 `int` 和 `long` 兩種型別,單個元素分別佔用 4 位元組 和 8 位元組 ,此時就不能用以下公式計算偏移量了,因為陣列中包含了兩種“元素長度”。
+
+```shell
+# 元素記憶體位址 = 陣列記憶體位址(首元素記憶體位址) + 元素長度 * 元素索引
+```
+
+**Q**:刪除節點 `P` 後,是否需要把 `P.next` 設為 `None` 呢?
+
+不修改 `P.next` 也可以。從該鏈結串列的角度看,從頭節點走訪到尾節點已經不會遇到 `P` 了。這意味著節點 `P` 已經從鏈結串列中刪除了,此時節點 `P` 指向哪裡都不會對該鏈結串列產生影響。
+
+從資料結構與演算法(做題)的角度看,不斷開沒有關係,只要保證程式的邏輯是正確的就行。從標準庫的角度看,斷開更加安全、邏輯更加清晰。如果不斷開,假設被刪除節點未被正常回收,那麼它會影響後繼節點的記憶體回收。
+
+**Q**:在鏈結串列中插入和刪除操作的時間複雜度是 $O(1)$ 。但是增刪之前都需要 $O(n)$ 的時間查詢元素,那為什麼時間複雜度不是 $O(n)$ 呢?
+
+如果是先查詢元素、再刪除元素,時間複雜度確實是 $O(n)$ 。然而,鏈結串列的 $O(1)$ 增刪的優勢可以在其他應用上得到體現。例如,雙向佇列適合使用鏈結串列實現,我們維護一個指標變數始終指向頭節點、尾節點,每次插入與刪除操作都是 $O(1)$ 。
+
+**Q**:圖“鏈結串列定義與儲存方式”中,淺藍色的儲存節點指標是佔用一塊記憶體位址嗎?還是和節點值各佔一半呢?
+
+該示意圖只是定性表示,定量表示需要根據具體情況進行分析。
+
+- 不同型別的節點值佔用的空間是不同的,比如 `int`、`long`、`double` 和例項物件等。
+- 指標變數佔用的記憶體空間大小根據所使用的作業系統及編譯環境而定,大多為 8 位元組或 4 位元組。
+
+**Q**:在串列末尾新增元素是否時時刻刻都為 $O(1)$ ?
+
+如果新增元素時超出串列長度,則需要先擴容串列再新增。系統會申請一塊新的記憶體,並將原串列的所有元素搬運過去,這時候時間複雜度就會是 $O(n)$ 。
+
+**Q**:“串列的出現極大地提高了陣列的實用性,但可能導致部分記憶體空間浪費”,這裡的空間浪費是指額外增加的變數如容量、長度、擴容倍數所佔的記憶體嗎?
+
+這裡的空間浪費主要有兩方面含義:一方面,串列都會設定一個初始長度,我們不一定需要用這麼多;另一方面,為了防止頻繁擴容,擴容一般會乘以一個係數,比如 $\times 1.5$ 。這樣一來,也會出現很多空位,我們通常不能完全填滿它們。
+
+**Q**:在 Python 中初始化 `n = [1, 2, 3]` 後,這 3 個元素的位址是相連的,但是初始化 `m = [2, 1, 3]` 會發現它們每個元素的 id 並不是連續的,而是分別跟 `n` 中的相同。這些元素的位址不連續,那麼 `m` 還是陣列嗎?
+
+假如把串列元素換成鏈結串列節點 `n = [n1, n2, n3, n4, n5]` ,通常情況下這 5 個節點物件也分散儲存在記憶體各處。然而,給定一個串列索引,我們仍然可以在 $O(1)$ 時間內獲取節點記憶體位址,從而訪問到對應的節點。這是因為陣列中儲存的是節點的引用,而非節點本身。
+
+與許多語言不同,Python 中的數字也被包裝為物件,串列中儲存的不是數字本身,而是對數字的引用。因此,我們會發現兩個陣列中的相同數字擁有同一個 id ,並且這些數字的記憶體位址無須連續。
+
+**Q**:C++ STL 裡面的 `std::list` 已經實現了雙向鏈結串列,但好像一些演算法書上不怎麼直接使用它,是不是因為有什麼侷限性呢?
+
+一方面,我們往往更青睞使用陣列實現演算法,而只在必要時才使用鏈結串列,主要有兩個原因。
+
+- 空間開銷:由於每個元素需要兩個額外的指標(一個用於前一個元素,一個用於後一個元素),所以 `std::list` 通常比 `std::vector` 更佔用空間。
+- 快取不友好:由於資料不是連續存放的,因此 `std::list` 對快取的利用率較低。一般情況下,`std::vector` 的效能會更好。
+
+另一方面,必要使用鏈結串列的情況主要是二元樹和圖。堆疊和佇列往往會使用程式語言提供的 `stack` 和 `queue` ,而非鏈結串列。
+
+**Q**:初始化串列 `res = [0] * self.size()` 操作,會導致 `res` 的每個元素引用相同的位址嗎?
+
+不會。但二維陣列會有這個問題,例如初始化二維串列 `res = [[0] * self.size()]` ,則多次引用了同一個串列 `[0]` 。
diff --git a/zh-Hant/docs/chapter_backtracking/backtracking_algorithm.md b/zh-Hant/docs/chapter_backtracking/backtracking_algorithm.md
new file mode 100644
index 000000000..5fa8b4850
--- /dev/null
+++ b/zh-Hant/docs/chapter_backtracking/backtracking_algorithm.md
@@ -0,0 +1,1951 @@
+---
+comments: true
+---
+
+# 13.1 回溯演算法
+
+回溯演算法(backtracking algorithm)是一種透過窮舉來解決問題的方法,它的核心思想是從一個初始狀態出發,暴力搜尋所有可能的解決方案,當遇到正確的解則將其記錄,直到找到解或者嘗試了所有可能的選擇都無法找到解為止。
+
+回溯演算法通常採用“深度優先搜尋”來走訪解空間。在“二元樹”章節中,我們提到前序、中序和後序走訪都屬於深度優先搜尋。接下來,我們利用前序走訪構造一個回溯問題,逐步瞭解回溯演算法的工作原理。
+
+!!! question "例題一"
+
+ 給定一棵二元樹,搜尋並記錄所有值為 $7$ 的節點,請返回節點串列。
+
+對於此題,我們前序走訪這棵樹,並判斷當前節點的值是否為 $7$ ,若是,則將該節點的值加入結果串列 `res` 之中。相關過程實現如圖 13-1 和以下程式碼所示:
+
+=== "Python"
+
+ ```python title="preorder_traversal_i_compact.py"
+ def pre_order(root: TreeNode):
+ """前序走訪:例題一"""
+ if root is None:
+ return
+ if root.val == 7:
+ # 記錄解
+ res.append(root)
+ pre_order(root.left)
+ pre_order(root.right)
+ ```
+
+=== "C++"
+
+ ```cpp title="preorder_traversal_i_compact.cpp"
+ /* 前序走訪:例題一 */
+ void preOrder(TreeNode *root) {
+ if (root == nullptr) {
+ return;
+ }
+ if (root->val == 7) {
+ // 記錄解
+ res.push_back(root);
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ }
+ ```
+
+=== "Java"
+
+ ```java title="preorder_traversal_i_compact.java"
+ /* 前序走訪:例題一 */
+ void preOrder(TreeNode root) {
+ if (root == null) {
+ return;
+ }
+ if (root.val == 7) {
+ // 記錄解
+ res.add(root);
+ }
+ preOrder(root.left);
+ preOrder(root.right);
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="preorder_traversal_i_compact.cs"
+ /* 前序走訪:例題一 */
+ void PreOrder(TreeNode? root) {
+ if (root == null) {
+ return;
+ }
+ if (root.val == 7) {
+ // 記錄解
+ res.Add(root);
+ }
+ PreOrder(root.left);
+ PreOrder(root.right);
+ }
+ ```
+
+=== "Go"
+
+ ```go title="preorder_traversal_i_compact.go"
+ /* 前序走訪:例題一 */
+ func preOrderI(root *TreeNode, res *[]*TreeNode) {
+ if root == nil {
+ return
+ }
+ if (root.Val).(int) == 7 {
+ // 記錄解
+ *res = append(*res, root)
+ }
+ preOrderI(root.Left, res)
+ preOrderI(root.Right, res)
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="preorder_traversal_i_compact.swift"
+ /* 前序走訪:例題一 */
+ func preOrder(root: TreeNode?) {
+ guard let root = root else {
+ return
+ }
+ if root.val == 7 {
+ // 記錄解
+ res.append(root)
+ }
+ preOrder(root: root.left)
+ preOrder(root: root.right)
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="preorder_traversal_i_compact.js"
+ /* 前序走訪:例題一 */
+ function preOrder(root, res) {
+ if (root === null) {
+ return;
+ }
+ if (root.val === 7) {
+ // 記錄解
+ res.push(root);
+ }
+ preOrder(root.left, res);
+ preOrder(root.right, res);
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="preorder_traversal_i_compact.ts"
+ /* 前序走訪:例題一 */
+ function preOrder(root: TreeNode | null, res: TreeNode[]): void {
+ if (root === null) {
+ return;
+ }
+ if (root.val === 7) {
+ // 記錄解
+ res.push(root);
+ }
+ preOrder(root.left, res);
+ preOrder(root.right, res);
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="preorder_traversal_i_compact.dart"
+ /* 前序走訪:例題一 */
+ void preOrder(TreeNode? root, List res) {
+ if (root == null) {
+ return;
+ }
+ if (root.val == 7) {
+ // 記錄解
+ res.add(root);
+ }
+ preOrder(root.left, res);
+ preOrder(root.right, res);
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="preorder_traversal_i_compact.rs"
+ /* 前序走訪:例題一 */
+ fn pre_order(res: &mut Vec>>, root: Option>>) {
+ if root.is_none() {
+ return;
+ }
+ if let Some(node) = root {
+ if node.borrow().val == 7 {
+ // 記錄解
+ res.push(node.clone());
+ }
+ pre_order(res, node.borrow().left.clone());
+ pre_order(res, node.borrow().right.clone());
+ }
+ }
+ ```
+
+=== "C"
+
+ ```c title="preorder_traversal_i_compact.c"
+ /* 前序走訪:例題一 */
+ void preOrder(TreeNode *root) {
+ if (root == NULL) {
+ return;
+ }
+ if (root->val == 7) {
+ // 記錄解
+ res[resSize++] = root;
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="preorder_traversal_i_compact.kt"
+ /* 前序走訪:例題一 */
+ fun preOrder(root: TreeNode?) {
+ if (root == null) {
+ return
+ }
+ if (root.value == 7) {
+ // 記錄解
+ res!!.add(root)
+ }
+ preOrder(root.left)
+ preOrder(root.right)
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="preorder_traversal_i_compact.rb"
+ [class]{}-[func]{pre_order}
+ ```
+
+=== "Zig"
+
+ ```zig title="preorder_traversal_i_compact.zig"
+ [class]{}-[func]{preOrder}
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+{ class="animation-figure" }
+
+ 圖 13-1 在前序走訪中搜索節點
+
+## 13.1.1 嘗試與回退
+
+**之所以稱之為回溯演算法,是因為該演算法在搜尋解空間時會採用“嘗試”與“回退”的策略**。當演算法在搜尋過程中遇到某個狀態無法繼續前進或無法得到滿足條件的解時,它會撤銷上一步的選擇,退回到之前的狀態,並嘗試其他可能的選擇。
+
+對於例題一,訪問每個節點都代表一次“嘗試”,而越過葉節點或返回父節點的 `return` 則表示“回退”。
+
+值得說明的是,**回退並不僅僅包括函式返回**。為解釋這一點,我們對例題一稍作拓展。
+
+!!! question "例題二"
+
+ 在二元樹中搜索所有值為 $7$ 的節點,**請返回根節點到這些節點的路徑**。
+
+在例題一程式碼的基礎上,我們需要藉助一個串列 `path` 記錄訪問過的節點路徑。當訪問到值為 $7$ 的節點時,則複製 `path` 並新增進結果串列 `res` 。走訪完成後,`res` 中儲存的就是所有的解。程式碼如下所示:
+
+=== "Python"
+
+ ```python title="preorder_traversal_ii_compact.py"
+ def pre_order(root: TreeNode):
+ """前序走訪:例題二"""
+ if root is None:
+ return
+ # 嘗試
+ path.append(root)
+ if root.val == 7:
+ # 記錄解
+ res.append(list(path))
+ pre_order(root.left)
+ pre_order(root.right)
+ # 回退
+ path.pop()
+ ```
+
+=== "C++"
+
+ ```cpp title="preorder_traversal_ii_compact.cpp"
+ /* 前序走訪:例題二 */
+ void preOrder(TreeNode *root) {
+ if (root == nullptr) {
+ return;
+ }
+ // 嘗試
+ path.push_back(root);
+ if (root->val == 7) {
+ // 記錄解
+ res.push_back(path);
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ // 回退
+ path.pop_back();
+ }
+ ```
+
+=== "Java"
+
+ ```java title="preorder_traversal_ii_compact.java"
+ /* 前序走訪:例題二 */
+ void preOrder(TreeNode root) {
+ if (root == null) {
+ return;
+ }
+ // 嘗試
+ path.add(root);
+ if (root.val == 7) {
+ // 記錄解
+ res.add(new ArrayList<>(path));
+ }
+ preOrder(root.left);
+ preOrder(root.right);
+ // 回退
+ path.remove(path.size() - 1);
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="preorder_traversal_ii_compact.cs"
+ /* 前序走訪:例題二 */
+ void PreOrder(TreeNode? root) {
+ if (root == null) {
+ return;
+ }
+ // 嘗試
+ path.Add(root);
+ if (root.val == 7) {
+ // 記錄解
+ res.Add(new List(path));
+ }
+ PreOrder(root.left);
+ PreOrder(root.right);
+ // 回退
+ path.RemoveAt(path.Count - 1);
+ }
+ ```
+
+=== "Go"
+
+ ```go title="preorder_traversal_ii_compact.go"
+ /* 前序走訪:例題二 */
+ func preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {
+ if root == nil {
+ return
+ }
+ // 嘗試
+ *path = append(*path, root)
+ if root.Val.(int) == 7 {
+ // 記錄解
+ *res = append(*res, append([]*TreeNode{}, *path...))
+ }
+ preOrderII(root.Left, res, path)
+ preOrderII(root.Right, res, path)
+ // 回退
+ *path = (*path)[:len(*path)-1]
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="preorder_traversal_ii_compact.swift"
+ /* 前序走訪:例題二 */
+ func preOrder(root: TreeNode?) {
+ guard let root = root else {
+ return
+ }
+ // 嘗試
+ path.append(root)
+ if root.val == 7 {
+ // 記錄解
+ res.append(path)
+ }
+ preOrder(root: root.left)
+ preOrder(root: root.right)
+ // 回退
+ path.removeLast()
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="preorder_traversal_ii_compact.js"
+ /* 前序走訪:例題二 */
+ function preOrder(root, path, res) {
+ if (root === null) {
+ return;
+ }
+ // 嘗試
+ path.push(root);
+ if (root.val === 7) {
+ // 記錄解
+ res.push([...path]);
+ }
+ preOrder(root.left, path, res);
+ preOrder(root.right, path, res);
+ // 回退
+ path.pop();
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="preorder_traversal_ii_compact.ts"
+ /* 前序走訪:例題二 */
+ function preOrder(
+ root: TreeNode | null,
+ path: TreeNode[],
+ res: TreeNode[][]
+ ): void {
+ if (root === null) {
+ return;
+ }
+ // 嘗試
+ path.push(root);
+ if (root.val === 7) {
+ // 記錄解
+ res.push([...path]);
+ }
+ preOrder(root.left, path, res);
+ preOrder(root.right, path, res);
+ // 回退
+ path.pop();
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="preorder_traversal_ii_compact.dart"
+ /* 前序走訪:例題二 */
+ void preOrder(
+ TreeNode? root,
+ List path,
+ List> res,
+ ) {
+ if (root == null) {
+ return;
+ }
+
+ // 嘗試
+ path.add(root);
+ if (root.val == 7) {
+ // 記錄解
+ res.add(List.from(path));
+ }
+ preOrder(root.left, path, res);
+ preOrder(root.right, path, res);
+ // 回退
+ path.removeLast();
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="preorder_traversal_ii_compact.rs"
+ /* 前序走訪:例題二 */
+ fn pre_order(
+ res: &mut Vec>>>,
+ path: &mut Vec>>,
+ root: Option>>,
+ ) {
+ if root.is_none() {
+ return;
+ }
+ if let Some(node) = root {
+ // 嘗試
+ path.push(node.clone());
+ if node.borrow().val == 7 {
+ // 記錄解
+ res.push(path.clone());
+ }
+ pre_order(res, path, node.borrow().left.clone());
+ pre_order(res, path, node.borrow().right.clone());
+ // 回退
+ path.remove(path.len() - 1);
+ }
+ }
+ ```
+
+=== "C"
+
+ ```c title="preorder_traversal_ii_compact.c"
+ /* 前序走訪:例題二 */
+ void preOrder(TreeNode *root) {
+ if (root == NULL) {
+ return;
+ }
+ // 嘗試
+ path[pathSize++] = root;
+ if (root->val == 7) {
+ // 記錄解
+ for (int i = 0; i < pathSize; ++i) {
+ res[resSize][i] = path[i];
+ }
+ resSize++;
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ // 回退
+ pathSize--;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="preorder_traversal_ii_compact.kt"
+ /* 前序走訪:例題二 */
+ fun preOrder(root: TreeNode?) {
+ if (root == null) {
+ return
+ }
+ // 嘗試
+ path!!.add(root)
+ if (root.value == 7) {
+ // 記錄解
+ res!!.add(ArrayList(path!!))
+ }
+ preOrder(root.left)
+ preOrder(root.right)
+ // 回退
+ path!!.removeAt(path!!.size - 1)
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="preorder_traversal_ii_compact.rb"
+ [class]{}-[func]{pre_order}
+ ```
+
+=== "Zig"
+
+ ```zig title="preorder_traversal_ii_compact.zig"
+ [class]{}-[func]{preOrder}
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+在每次“嘗試”中,我們透過將當前節點新增進 `path` 來記錄路徑;而在“回退”前,我們需要將該節點從 `path` 中彈出,**以恢復本次嘗試之前的狀態**。
+
+觀察圖 13-2 所示的過程,**我們可以將嘗試和回退理解為“前進”與“撤銷”**,兩個操作互為逆向。
+
+=== "<1>"
+ { class="animation-figure" }
+
+=== "<2>"
+ { class="animation-figure" }
+
+=== "<3>"
+ { class="animation-figure" }
+
+=== "<4>"
+ { class="animation-figure" }
+
+=== "<5>"
+ { class="animation-figure" }
+
+=== "<6>"
+ { class="animation-figure" }
+
+=== "<7>"
+ { class="animation-figure" }
+
+=== "<8>"
+ { class="animation-figure" }
+
+=== "<9>"
+ { class="animation-figure" }
+
+=== "<10>"
+ { class="animation-figure" }
+
+=== "<11>"
+ { class="animation-figure" }
+
+ 圖 13-2 嘗試與回退
+
+## 13.1.2 剪枝
+
+複雜的回溯問題通常包含一個或多個約束條件,**約束條件通常可用於“剪枝”**。
+
+!!! question "例題三"
+
+ 在二元樹中搜索所有值為 $7$ 的節點,請返回根節點到這些節點的路徑,**並要求路徑中不包含值為 $3$ 的節點**。
+
+為了滿足以上約束條件,**我們需要新增剪枝操作**:在搜尋過程中,若遇到值為 $3$ 的節點,則提前返回,不再繼續搜尋。程式碼如下所示:
+
+=== "Python"
+
+ ```python title="preorder_traversal_iii_compact.py"
+ def pre_order(root: TreeNode):
+ """前序走訪:例題三"""
+ # 剪枝
+ if root is None or root.val == 3:
+ return
+ # 嘗試
+ path.append(root)
+ if root.val == 7:
+ # 記錄解
+ res.append(list(path))
+ pre_order(root.left)
+ pre_order(root.right)
+ # 回退
+ path.pop()
+ ```
+
+=== "C++"
+
+ ```cpp title="preorder_traversal_iii_compact.cpp"
+ /* 前序走訪:例題三 */
+ void preOrder(TreeNode *root) {
+ // 剪枝
+ if (root == nullptr || root->val == 3) {
+ return;
+ }
+ // 嘗試
+ path.push_back(root);
+ if (root->val == 7) {
+ // 記錄解
+ res.push_back(path);
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ // 回退
+ path.pop_back();
+ }
+ ```
+
+=== "Java"
+
+ ```java title="preorder_traversal_iii_compact.java"
+ /* 前序走訪:例題三 */
+ void preOrder(TreeNode root) {
+ // 剪枝
+ if (root == null || root.val == 3) {
+ return;
+ }
+ // 嘗試
+ path.add(root);
+ if (root.val == 7) {
+ // 記錄解
+ res.add(new ArrayList<>(path));
+ }
+ preOrder(root.left);
+ preOrder(root.right);
+ // 回退
+ path.remove(path.size() - 1);
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="preorder_traversal_iii_compact.cs"
+ /* 前序走訪:例題三 */
+ void PreOrder(TreeNode? root) {
+ // 剪枝
+ if (root == null || root.val == 3) {
+ return;
+ }
+ // 嘗試
+ path.Add(root);
+ if (root.val == 7) {
+ // 記錄解
+ res.Add(new List(path));
+ }
+ PreOrder(root.left);
+ PreOrder(root.right);
+ // 回退
+ path.RemoveAt(path.Count - 1);
+ }
+ ```
+
+=== "Go"
+
+ ```go title="preorder_traversal_iii_compact.go"
+ /* 前序走訪:例題三 */
+ func preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {
+ // 剪枝
+ if root == nil || root.Val == 3 {
+ return
+ }
+ // 嘗試
+ *path = append(*path, root)
+ if root.Val.(int) == 7 {
+ // 記錄解
+ *res = append(*res, append([]*TreeNode{}, *path...))
+ }
+ preOrderIII(root.Left, res, path)
+ preOrderIII(root.Right, res, path)
+ // 回退
+ *path = (*path)[:len(*path)-1]
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="preorder_traversal_iii_compact.swift"
+ /* 前序走訪:例題三 */
+ func preOrder(root: TreeNode?) {
+ // 剪枝
+ guard let root = root, root.val != 3 else {
+ return
+ }
+ // 嘗試
+ path.append(root)
+ if root.val == 7 {
+ // 記錄解
+ res.append(path)
+ }
+ preOrder(root: root.left)
+ preOrder(root: root.right)
+ // 回退
+ path.removeLast()
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="preorder_traversal_iii_compact.js"
+ /* 前序走訪:例題三 */
+ function preOrder(root, path, res) {
+ // 剪枝
+ if (root === null || root.val === 3) {
+ return;
+ }
+ // 嘗試
+ path.push(root);
+ if (root.val === 7) {
+ // 記錄解
+ res.push([...path]);
+ }
+ preOrder(root.left, path, res);
+ preOrder(root.right, path, res);
+ // 回退
+ path.pop();
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="preorder_traversal_iii_compact.ts"
+ /* 前序走訪:例題三 */
+ function preOrder(
+ root: TreeNode | null,
+ path: TreeNode[],
+ res: TreeNode[][]
+ ): void {
+ // 剪枝
+ if (root === null || root.val === 3) {
+ return;
+ }
+ // 嘗試
+ path.push(root);
+ if (root.val === 7) {
+ // 記錄解
+ res.push([...path]);
+ }
+ preOrder(root.left, path, res);
+ preOrder(root.right, path, res);
+ // 回退
+ path.pop();
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="preorder_traversal_iii_compact.dart"
+ /* 前序走訪:例題三 */
+ void preOrder(
+ TreeNode? root,
+ List path,
+ List> res,
+ ) {
+ if (root == null || root.val == 3) {
+ return;
+ }
+
+ // 嘗試
+ path.add(root);
+ if (root.val == 7) {
+ // 記錄解
+ res.add(List.from(path));
+ }
+ preOrder(root.left, path, res);
+ preOrder(root.right, path, res);
+ // 回退
+ path.removeLast();
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="preorder_traversal_iii_compact.rs"
+ /* 前序走訪:例題三 */
+ fn pre_order(
+ res: &mut Vec>>>,
+ path: &mut Vec>>,
+ root: Option>>,
+ ) {
+ // 剪枝
+ if root.is_none() || root.as_ref().unwrap().borrow().val == 3 {
+ return;
+ }
+ if let Some(node) = root {
+ // 嘗試
+ path.push(node.clone());
+ if node.borrow().val == 7 {
+ // 記錄解
+ res.push(path.clone());
+ }
+ pre_order(res, path, node.borrow().left.clone());
+ pre_order(res, path, node.borrow().right.clone());
+ // 回退
+ path.remove(path.len() - 1);
+ }
+ }
+ ```
+
+=== "C"
+
+ ```c title="preorder_traversal_iii_compact.c"
+ /* 前序走訪:例題三 */
+ void preOrder(TreeNode *root) {
+ // 剪枝
+ if (root == NULL || root->val == 3) {
+ return;
+ }
+ // 嘗試
+ path[pathSize++] = root;
+ if (root->val == 7) {
+ // 記錄解
+ for (int i = 0; i < pathSize; i++) {
+ res[resSize][i] = path[i];
+ }
+ resSize++;
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ // 回退
+ pathSize--;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="preorder_traversal_iii_compact.kt"
+ /* 前序走訪:例題三 */
+ fun preOrder(root: TreeNode?) {
+ // 剪枝
+ if (root == null || root.value == 3) {
+ return
+ }
+ // 嘗試
+ path!!.add(root)
+ if (root.value == 7) {
+ // 記錄解
+ res!!.add(ArrayList(path!!))
+ }
+ preOrder(root.left)
+ preOrder(root.right)
+ // 回退
+ path!!.removeAt(path!!.size - 1)
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="preorder_traversal_iii_compact.rb"
+ [class]{}-[func]{pre_order}
+ ```
+
+=== "Zig"
+
+ ```zig title="preorder_traversal_iii_compact.zig"
+ [class]{}-[func]{preOrder}
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+“剪枝”是一個非常形象的名詞。如圖 13-3 所示,在搜尋過程中,**我們“剪掉”了不滿足約束條件的搜尋分支**,避免許多無意義的嘗試,從而提高了搜尋效率。
+
+{ class="animation-figure" }
+
+ 圖 13-3 根據約束條件剪枝
+
+## 13.1.3 框架程式碼
+
+接下來,我們嘗試將回溯的“嘗試、回退、剪枝”的主體框架提煉出來,提升程式碼的通用性。
+
+在以下框架程式碼中,`state` 表示問題的當前狀態,`choices` 表示當前狀態下可以做出的選擇:
+
+=== "Python"
+
+ ```python title=""
+ def backtrack(state: State, choices: list[choice], res: list[state]):
+ """回溯演算法框架"""
+ # 判斷是否為解
+ if is_solution(state):
+ # 記錄解
+ record_solution(state, res)
+ # 不再繼續搜尋
+ return
+ # 走訪所有選擇
+ for choice in choices:
+ # 剪枝:判斷選擇是否合法
+ if is_valid(state, choice):
+ # 嘗試:做出選擇,更新狀態
+ make_choice(state, choice)
+ backtrack(state, choices, res)
+ # 回退:撤銷選擇,恢復到之前的狀態
+ undo_choice(state, choice)
+ ```
+
+=== "C++"
+
+ ```cpp title=""
+ /* 回溯演算法框架 */
+ void backtrack(State *state, vector &choices, vector &res) {
+ // 判斷是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res);
+ // 不再繼續搜尋
+ return;
+ }
+ // 走訪所有選擇
+ for (Choice choice : choices) {
+ // 剪枝:判斷選擇是否合法
+ if (isValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice);
+ backtrack(state, choices, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, choice);
+ }
+ }
+ }
+ ```
+
+=== "Java"
+
+ ```java title=""
+ /* 回溯演算法框架 */
+ void backtrack(State state, List choices, List res) {
+ // 判斷是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res);
+ // 不再繼續搜尋
+ return;
+ }
+ // 走訪所有選擇
+ for (Choice choice : choices) {
+ // 剪枝:判斷選擇是否合法
+ if (isValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice);
+ backtrack(state, choices, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, choice);
+ }
+ }
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title=""
+ /* 回溯演算法框架 */
+ void Backtrack(State state, List choices, List res) {
+ // 判斷是否為解
+ if (IsSolution(state)) {
+ // 記錄解
+ RecordSolution(state, res);
+ // 不再繼續搜尋
+ return;
+ }
+ // 走訪所有選擇
+ foreach (Choice choice in choices) {
+ // 剪枝:判斷選擇是否合法
+ if (IsValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ MakeChoice(state, choice);
+ Backtrack(state, choices, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ UndoChoice(state, choice);
+ }
+ }
+ }
+ ```
+
+=== "Go"
+
+ ```go title=""
+ /* 回溯演算法框架 */
+ func backtrack(state *State, choices []Choice, res *[]State) {
+ // 判斷是否為解
+ if isSolution(state) {
+ // 記錄解
+ recordSolution(state, res)
+ // 不再繼續搜尋
+ return
+ }
+ // 走訪所有選擇
+ for _, choice := range choices {
+ // 剪枝:判斷選擇是否合法
+ if isValid(state, choice) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice)
+ backtrack(state, choices, res)
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, choice)
+ }
+ }
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title=""
+ /* 回溯演算法框架 */
+ func backtrack(state: inout State, choices: [Choice], res: inout [State]) {
+ // 判斷是否為解
+ if isSolution(state: state) {
+ // 記錄解
+ recordSolution(state: state, res: &res)
+ // 不再繼續搜尋
+ return
+ }
+ // 走訪所有選擇
+ for choice in choices {
+ // 剪枝:判斷選擇是否合法
+ if isValid(state: state, choice: choice) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state: &state, choice: choice)
+ backtrack(state: &state, choices: choices, res: &res)
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state: &state, choice: choice)
+ }
+ }
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title=""
+ /* 回溯演算法框架 */
+ function backtrack(state, choices, res) {
+ // 判斷是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res);
+ // 不再繼續搜尋
+ return;
+ }
+ // 走訪所有選擇
+ for (let choice of choices) {
+ // 剪枝:判斷選擇是否合法
+ if (isValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice);
+ backtrack(state, choices, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, choice);
+ }
+ }
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title=""
+ /* 回溯演算法框架 */
+ function backtrack(state: State, choices: Choice[], res: State[]): void {
+ // 判斷是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res);
+ // 不再繼續搜尋
+ return;
+ }
+ // 走訪所有選擇
+ for (let choice of choices) {
+ // 剪枝:判斷選擇是否合法
+ if (isValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice);
+ backtrack(state, choices, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, choice);
+ }
+ }
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title=""
+ /* 回溯演算法框架 */
+ void backtrack(State state, List, List res) {
+ // 判斷是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res);
+ // 不再繼續搜尋
+ return;
+ }
+ // 走訪所有選擇
+ for (Choice choice in choices) {
+ // 剪枝:判斷選擇是否合法
+ if (isValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice);
+ backtrack(state, choices, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, choice);
+ }
+ }
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title=""
+ /* 回溯演算法框架 */
+ fn backtrack(state: &mut State, choices: &Vec, res: &mut Vec) {
+ // 判斷是否為解
+ if is_solution(state) {
+ // 記錄解
+ record_solution(state, res);
+ // 不再繼續搜尋
+ return;
+ }
+ // 走訪所有選擇
+ for choice in choices {
+ // 剪枝:判斷選擇是否合法
+ if is_valid(state, choice) {
+ // 嘗試:做出選擇,更新狀態
+ make_choice(state, choice);
+ backtrack(state, choices, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undo_choice(state, choice);
+ }
+ }
+ }
+ ```
+
+=== "C"
+
+ ```c title=""
+ /* 回溯演算法框架 */
+ void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) {
+ // 判斷是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res, numRes);
+ // 不再繼續搜尋
+ return;
+ }
+ // 走訪所有選擇
+ for (int i = 0; i < numChoices; i++) {
+ // 剪枝:判斷選擇是否合法
+ if (isValid(state, &choices[i])) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, &choices[i]);
+ backtrack(state, choices, numChoices, res, numRes);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, &choices[i]);
+ }
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title=""
+ /* 回溯演算法框架 */
+ fun backtrack(state: State?, choices: List, res: List?) {
+ // 判斷是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res)
+ // 不再繼續搜尋
+ return
+ }
+ // 走訪所有選擇
+ for (choice in choices) {
+ // 剪枝:判斷選擇是否合法
+ if (isValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice)
+ backtrack(state, choices, res)
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, choice)
+ }
+ }
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title=""
+
+ ```
+
+=== "Zig"
+
+ ```zig title=""
+
+ ```
+
+接下來,我們基於框架程式碼來解決例題三。狀態 `state` 為節點走訪路徑,選擇 `choices` 為當前節點的左子節點和右子節點,結果 `res` 是路徑串列:
+
+=== "Python"
+
+ ```python title="preorder_traversal_iii_template.py"
+ def is_solution(state: list[TreeNode]) -> bool:
+ """判斷當前狀態是否為解"""
+ return state and state[-1].val == 7
+
+ def record_solution(state: list[TreeNode], res: list[list[TreeNode]]):
+ """記錄解"""
+ res.append(list(state))
+
+ def is_valid(state: list[TreeNode], choice: TreeNode) -> bool:
+ """判斷在當前狀態下,該選擇是否合法"""
+ return choice is not None and choice.val != 3
+
+ def make_choice(state: list[TreeNode], choice: TreeNode):
+ """更新狀態"""
+ state.append(choice)
+
+ def undo_choice(state: list[TreeNode], choice: TreeNode):
+ """恢復狀態"""
+ state.pop()
+
+ def backtrack(
+ state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]
+ ):
+ """回溯演算法:例題三"""
+ # 檢查是否為解
+ if is_solution(state):
+ # 記錄解
+ record_solution(state, res)
+ # 走訪所有選擇
+ for choice in choices:
+ # 剪枝:檢查選擇是否合法
+ if is_valid(state, choice):
+ # 嘗試:做出選擇,更新狀態
+ make_choice(state, choice)
+ # 進行下一輪選擇
+ backtrack(state, [choice.left, choice.right], res)
+ # 回退:撤銷選擇,恢復到之前的狀態
+ undo_choice(state, choice)
+ ```
+
+=== "C++"
+
+ ```cpp title="preorder_traversal_iii_template.cpp"
+ /* 判斷當前狀態是否為解 */
+ bool isSolution(vector &state) {
+ return !state.empty() && state.back()->val == 7;
+ }
+
+ /* 記錄解 */
+ void recordSolution(vector &state, vector> &res) {
+ res.push_back(state);
+ }
+
+ /* 判斷在當前狀態下,該選擇是否合法 */
+ bool isValid(vector &state, TreeNode *choice) {
+ return choice != nullptr && choice->val != 3;
+ }
+
+ /* 更新狀態 */
+ void makeChoice(vector &state, TreeNode *choice) {
+ state.push_back(choice);
+ }
+
+ /* 恢復狀態 */
+ void undoChoice(vector &state, TreeNode *choice) {
+ state.pop_back();
+ }
+
+ /* 回溯演算法:例題三 */
+ void backtrack(vector &state, vector &choices, vector> &res) {
+ // 檢查是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res);
+ }
+ // 走訪所有選擇
+ for (TreeNode *choice : choices) {
+ // 剪枝:檢查選擇是否合法
+ if (isValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice);
+ // 進行下一輪選擇
+ vector nextChoices{choice->left, choice->right};
+ backtrack(state, nextChoices, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, choice);
+ }
+ }
+ }
+ ```
+
+=== "Java"
+
+ ```java title="preorder_traversal_iii_template.java"
+ /* 判斷當前狀態是否為解 */
+ boolean isSolution(List state) {
+ return !state.isEmpty() && state.get(state.size() - 1).val == 7;
+ }
+
+ /* 記錄解 */
+ void recordSolution(List state, List> res) {
+ res.add(new ArrayList<>(state));
+ }
+
+ /* 判斷在當前狀態下,該選擇是否合法 */
+ boolean isValid(List state, TreeNode choice) {
+ return choice != null && choice.val != 3;
+ }
+
+ /* 更新狀態 */
+ void makeChoice(List state, TreeNode choice) {
+ state.add(choice);
+ }
+
+ /* 恢復狀態 */
+ void undoChoice(List state, TreeNode choice) {
+ state.remove(state.size() - 1);
+ }
+
+ /* 回溯演算法:例題三 */
+ void backtrack(List state, List choices, List> res) {
+ // 檢查是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res);
+ }
+ // 走訪所有選擇
+ for (TreeNode choice : choices) {
+ // 剪枝:檢查選擇是否合法
+ if (isValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice);
+ // 進行下一輪選擇
+ backtrack(state, Arrays.asList(choice.left, choice.right), res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, choice);
+ }
+ }
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="preorder_traversal_iii_template.cs"
+ /* 判斷當前狀態是否為解 */
+ bool IsSolution(List state) {
+ return state.Count != 0 && state[^1].val == 7;
+ }
+
+ /* 記錄解 */
+ void RecordSolution(List state, List> res) {
+ res.Add(new List(state));
+ }
+
+ /* 判斷在當前狀態下,該選擇是否合法 */
+ bool IsValid(List state, TreeNode choice) {
+ return choice != null && choice.val != 3;
+ }
+
+ /* 更新狀態 */
+ void MakeChoice(List state, TreeNode choice) {
+ state.Add(choice);
+ }
+
+ /* 恢復狀態 */
+ void UndoChoice(List state, TreeNode choice) {
+ state.RemoveAt(state.Count - 1);
+ }
+
+ /* 回溯演算法:例題三 */
+ void Backtrack(List state, List choices, List> res) {
+ // 檢查是否為解
+ if (IsSolution(state)) {
+ // 記錄解
+ RecordSolution(state, res);
+ }
+ // 走訪所有選擇
+ foreach (TreeNode choice in choices) {
+ // 剪枝:檢查選擇是否合法
+ if (IsValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ MakeChoice(state, choice);
+ // 進行下一輪選擇
+ Backtrack(state, [choice.left!, choice.right!], res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ UndoChoice(state, choice);
+ }
+ }
+ }
+ ```
+
+=== "Go"
+
+ ```go title="preorder_traversal_iii_template.go"
+ /* 判斷當前狀態是否為解 */
+ func isSolution(state *[]*TreeNode) bool {
+ return len(*state) != 0 && (*state)[len(*state)-1].Val == 7
+ }
+
+ /* 記錄解 */
+ func recordSolution(state *[]*TreeNode, res *[][]*TreeNode) {
+ *res = append(*res, append([]*TreeNode{}, *state...))
+ }
+
+ /* 判斷在當前狀態下,該選擇是否合法 */
+ func isValid(state *[]*TreeNode, choice *TreeNode) bool {
+ return choice != nil && choice.Val != 3
+ }
+
+ /* 更新狀態 */
+ func makeChoice(state *[]*TreeNode, choice *TreeNode) {
+ *state = append(*state, choice)
+ }
+
+ /* 恢復狀態 */
+ func undoChoice(state *[]*TreeNode, choice *TreeNode) {
+ *state = (*state)[:len(*state)-1]
+ }
+
+ /* 回溯演算法:例題三 */
+ func backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) {
+ // 檢查是否為解
+ if isSolution(state) {
+ // 記錄解
+ recordSolution(state, res)
+ }
+ // 走訪所有選擇
+ for _, choice := range *choices {
+ // 剪枝:檢查選擇是否合法
+ if isValid(state, choice) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice)
+ // 進行下一輪選擇
+ temp := make([]*TreeNode, 0)
+ temp = append(temp, choice.Left, choice.Right)
+ backtrackIII(state, &temp, res)
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, choice)
+ }
+ }
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="preorder_traversal_iii_template.swift"
+ /* 判斷當前狀態是否為解 */
+ func isSolution(state: [TreeNode]) -> Bool {
+ !state.isEmpty && state.last!.val == 7
+ }
+
+ /* 記錄解 */
+ func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) {
+ res.append(state)
+ }
+
+ /* 判斷在當前狀態下,該選擇是否合法 */
+ func isValid(state: [TreeNode], choice: TreeNode?) -> Bool {
+ choice != nil && choice!.val != 3
+ }
+
+ /* 更新狀態 */
+ func makeChoice(state: inout [TreeNode], choice: TreeNode) {
+ state.append(choice)
+ }
+
+ /* 恢復狀態 */
+ func undoChoice(state: inout [TreeNode], choice: TreeNode) {
+ state.removeLast()
+ }
+
+ /* 回溯演算法:例題三 */
+ func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) {
+ // 檢查是否為解
+ if isSolution(state: state) {
+ recordSolution(state: state, res: &res)
+ }
+ // 走訪所有選擇
+ for choice in choices {
+ // 剪枝:檢查選擇是否合法
+ if isValid(state: state, choice: choice) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state: &state, choice: choice)
+ // 進行下一輪選擇
+ backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res)
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state: &state, choice: choice)
+ }
+ }
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="preorder_traversal_iii_template.js"
+ /* 判斷當前狀態是否為解 */
+ function isSolution(state) {
+ return state && state[state.length - 1]?.val === 7;
+ }
+
+ /* 記錄解 */
+ function recordSolution(state, res) {
+ res.push([...state]);
+ }
+
+ /* 判斷在當前狀態下,該選擇是否合法 */
+ function isValid(state, choice) {
+ return choice !== null && choice.val !== 3;
+ }
+
+ /* 更新狀態 */
+ function makeChoice(state, choice) {
+ state.push(choice);
+ }
+
+ /* 恢復狀態 */
+ function undoChoice(state) {
+ state.pop();
+ }
+
+ /* 回溯演算法:例題三 */
+ function backtrack(state, choices, res) {
+ // 檢查是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res);
+ }
+ // 走訪所有選擇
+ for (const choice of choices) {
+ // 剪枝:檢查選擇是否合法
+ if (isValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice);
+ // 進行下一輪選擇
+ backtrack(state, [choice.left, choice.right], res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state);
+ }
+ }
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="preorder_traversal_iii_template.ts"
+ /* 判斷當前狀態是否為解 */
+ function isSolution(state: TreeNode[]): boolean {
+ return state && state[state.length - 1]?.val === 7;
+ }
+
+ /* 記錄解 */
+ function recordSolution(state: TreeNode[], res: TreeNode[][]): void {
+ res.push([...state]);
+ }
+
+ /* 判斷在當前狀態下,該選擇是否合法 */
+ function isValid(state: TreeNode[], choice: TreeNode): boolean {
+ return choice !== null && choice.val !== 3;
+ }
+
+ /* 更新狀態 */
+ function makeChoice(state: TreeNode[], choice: TreeNode): void {
+ state.push(choice);
+ }
+
+ /* 恢復狀態 */
+ function undoChoice(state: TreeNode[]): void {
+ state.pop();
+ }
+
+ /* 回溯演算法:例題三 */
+ function backtrack(
+ state: TreeNode[],
+ choices: TreeNode[],
+ res: TreeNode[][]
+ ): void {
+ // 檢查是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res);
+ }
+ // 走訪所有選擇
+ for (const choice of choices) {
+ // 剪枝:檢查選擇是否合法
+ if (isValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice);
+ // 進行下一輪選擇
+ backtrack(state, [choice.left, choice.right], res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state);
+ }
+ }
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="preorder_traversal_iii_template.dart"
+ /* 判斷當前狀態是否為解 */
+ bool isSolution(List state) {
+ return state.isNotEmpty && state.last.val == 7;
+ }
+
+ /* 記錄解 */
+ void recordSolution(List state, List> res) {
+ res.add(List.from(state));
+ }
+
+ /* 判斷在當前狀態下,該選擇是否合法 */
+ bool isValid(List state, TreeNode? choice) {
+ return choice != null && choice.val != 3;
+ }
+
+ /* 更新狀態 */
+ void makeChoice(List state, TreeNode? choice) {
+ state.add(choice!);
+ }
+
+ /* 恢復狀態 */
+ void undoChoice(List state, TreeNode? choice) {
+ state.removeLast();
+ }
+
+ /* 回溯演算法:例題三 */
+ void backtrack(
+ List state,
+ List choices,
+ List> res,
+ ) {
+ // 檢查是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res);
+ }
+ // 走訪所有選擇
+ for (TreeNode? choice in choices) {
+ // 剪枝:檢查選擇是否合法
+ if (isValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice);
+ // 進行下一輪選擇
+ backtrack(state, [choice!.left, choice.right], res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, choice);
+ }
+ }
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="preorder_traversal_iii_template.rs"
+ /* 判斷當前狀態是否為解 */
+ fn is_solution(state: &mut Vec>>) -> bool {
+ return !state.is_empty() && state.get(state.len() - 1).unwrap().borrow().val == 7;
+ }
+
+ /* 記錄解 */
+ fn record_solution(
+ state: &mut Vec>>,
+ res: &mut Vec>>>,
+ ) {
+ res.push(state.clone());
+ }
+
+ /* 判斷在當前狀態下,該選擇是否合法 */
+ fn is_valid(_: &mut Vec>>, choice: Rc>) -> bool {
+ return choice.borrow().val != 3;
+ }
+
+ /* 更新狀態 */
+ fn make_choice(state: &mut Vec>>, choice: Rc>) {
+ state.push(choice);
+ }
+
+ /* 恢復狀態 */
+ fn undo_choice(state: &mut Vec>>, _: Rc>) {
+ state.remove(state.len() - 1);
+ }
+
+ /* 回溯演算法:例題三 */
+ fn backtrack(
+ state: &mut Vec>>,
+ choices: &mut Vec>>,
+ res: &mut Vec>>>,
+ ) {
+ // 檢查是否為解
+ if is_solution(state) {
+ // 記錄解
+ record_solution(state, res);
+ }
+ // 走訪所有選擇
+ for choice in choices {
+ // 剪枝:檢查選擇是否合法
+ if is_valid(state, choice.clone()) {
+ // 嘗試:做出選擇,更新狀態
+ make_choice(state, choice.clone());
+ // 進行下一輪選擇
+ backtrack(
+ state,
+ &mut vec![
+ choice.borrow().left.clone().unwrap(),
+ choice.borrow().right.clone().unwrap(),
+ ],
+ res,
+ );
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undo_choice(state, choice.clone());
+ }
+ }
+ }
+ ```
+
+=== "C"
+
+ ```c title="preorder_traversal_iii_template.c"
+ /* 判斷當前狀態是否為解 */
+ bool isSolution(void) {
+ return pathSize > 0 && path[pathSize - 1]->val == 7;
+ }
+
+ /* 記錄解 */
+ void recordSolution(void) {
+ for (int i = 0; i < pathSize; i++) {
+ res[resSize][i] = path[i];
+ }
+ resSize++;
+ }
+
+ /* 判斷在當前狀態下,該選擇是否合法 */
+ bool isValid(TreeNode *choice) {
+ return choice != NULL && choice->val != 3;
+ }
+
+ /* 更新狀態 */
+ void makeChoice(TreeNode *choice) {
+ path[pathSize++] = choice;
+ }
+
+ /* 恢復狀態 */
+ void undoChoice(void) {
+ pathSize--;
+ }
+
+ /* 回溯演算法:例題三 */
+ void backtrack(TreeNode *choices[2]) {
+ // 檢查是否為解
+ if (isSolution()) {
+ // 記錄解
+ recordSolution();
+ }
+ // 走訪所有選擇
+ for (int i = 0; i < 2; i++) {
+ TreeNode *choice = choices[i];
+ // 剪枝:檢查選擇是否合法
+ if (isValid(choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(choice);
+ // 進行下一輪選擇
+ TreeNode *nextChoices[2] = {choice->left, choice->right};
+ backtrack(nextChoices);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice();
+ }
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="preorder_traversal_iii_template.kt"
+ /* 判斷當前狀態是否為解 */
+ fun isSolution(state: List): Boolean {
+ return state.isNotEmpty() && state[state.size - 1]?.value == 7
+ }
+
+ /* 記錄解 */
+ fun recordSolution(state: MutableList?, res: MutableList?>) {
+ res.add(state?.let { ArrayList(it) })
+ }
+
+ /* 判斷在當前狀態下,該選擇是否合法 */
+ fun isValid(state: List?, choice: TreeNode?): Boolean {
+ return choice != null && choice.value != 3
+ }
+
+ /* 更新狀態 */
+ fun makeChoice(state: MutableList, choice: TreeNode?) {
+ state.add(choice)
+ }
+
+ /* 恢復狀態 */
+ fun undoChoice(state: MutableList, choice: TreeNode?) {
+ state.removeLast()
+ }
+
+ /* 回溯演算法:例題三 */
+ fun backtrack(
+ state: MutableList,
+ choices: List,
+ res: MutableList?>
+ ) {
+ // 檢查是否為解
+ if (isSolution(state)) {
+ // 記錄解
+ recordSolution(state, res)
+ }
+ // 走訪所有選擇
+ for (choice in choices) {
+ // 剪枝:檢查選擇是否合法
+ if (isValid(state, choice)) {
+ // 嘗試:做出選擇,更新狀態
+ makeChoice(state, choice)
+ // 進行下一輪選擇
+ backtrack(state, listOf(choice!!.left, choice.right), res)
+ // 回退:撤銷選擇,恢復到之前的狀態
+ undoChoice(state, choice)
+ }
+ }
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="preorder_traversal_iii_template.rb"
+ [class]{}-[func]{is_solution}
+
+ [class]{}-[func]{record_solution}
+
+ [class]{}-[func]{is_valid}
+
+ [class]{}-[func]{make_choice}
+
+ [class]{}-[func]{undo_choice}
+
+ [class]{}-[func]{backtrack}
+ ```
+
+=== "Zig"
+
+ ```zig title="preorder_traversal_iii_template.zig"
+ [class]{}-[func]{isSolution}
+
+ [class]{}-[func]{recordSolution}
+
+ [class]{}-[func]{isValid}
+
+ [class]{}-[func]{makeChoice}
+
+ [class]{}-[func]{undoChoice}
+
+ [class]{}-[func]{backtrack}
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+根據題意,我們在找到值為 $7$ 的節點後應該繼續搜尋,**因此需要將記錄解之後的 `return` 語句刪除**。圖 13-4 對比了保留或刪除 `return` 語句的搜尋過程。
+
+{ class="animation-figure" }
+
+ 圖 13-4 保留與刪除 return 的搜尋過程對比
+
+相比基於前序走訪的程式碼實現,基於回溯演算法框架的程式碼實現雖然顯得囉唆,但通用性更好。實際上,**許多回溯問題可以在該框架下解決**。我們只需根據具體問題來定義 `state` 和 `choices` ,並實現框架中的各個方法即可。
+
+## 13.1.4 常用術語
+
+為了更清晰地分析演算法問題,我們總結一下回溯演算法中常用術語的含義,並對照例題三給出對應示例,如表 13-1 所示。
+
+ 表 13-1 常見的回溯演算法術語
+
+
+
+| 名詞 | 定義 | 例題三 |
+| ---------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- |
+| 解(solution) | 解是滿足問題特定條件的答案,可能有一個或多個 | 根節點到節點 $7$ 的滿足約束條件的所有路徑 |
+| 約束條件(constraint) | 約束條件是問題中限制解的可行性的條件,通常用於剪枝 | 路徑中不包含節點 $3$ |
+| 狀態(state) | 狀態表示問題在某一時刻的情況,包括已經做出的選擇 | 當前已訪問的節點路徑,即 `path` 節點串列 |
+| 嘗試(attempt) | 嘗試是根據可用選擇來探索解空間的過程,包括做出選擇,更新狀態,檢查是否為解 | 遞迴訪問左(右)子節點,將節點新增進 `path` ,判斷節點的值是否為 $7$ |
+| 回退(backtracking) | 回退指遇到不滿足約束條件的狀態時,撤銷前面做出的選擇,回到上一個狀態 | 當越過葉節點、結束節點訪問、遇到值為 $3$ 的節點時終止搜尋,函式返回 |
+| 剪枝(pruning) | 剪枝是根據問題特性和約束條件避免無意義的搜尋路徑的方法,可提高搜尋效率 | 當遇到值為 $3$ 的節點時,則不再繼續搜尋 |
+
+
+
+!!! tip
+
+ 問題、解、狀態等概念是通用的,在分治、回溯、動態規劃、貪婪等演算法中都有涉及。
+
+## 13.1.5 優點與侷限性
+
+回溯演算法本質上是一種深度優先搜尋演算法,它嘗試所有可能的解決方案直到找到滿足條件的解。這種方法的優點在於能夠找到所有可能的解決方案,而且在合理的剪枝操作下,具有很高的效率。
+
+然而,在處理大規模或者複雜問題時,**回溯演算法的執行效率可能難以接受**。
+
+- **時間**:回溯演算法通常需要走訪狀態空間的所有可能,時間複雜度可以達到指數階或階乘階。
+- **空間**:在遞迴呼叫中需要儲存當前的狀態(例如路徑、用於剪枝的輔助變數等),當深度很大時,空間需求可能會變得很大。
+
+即便如此,**回溯演算法仍然是某些搜尋問題和約束滿足問題的最佳解決方案**。對於這些問題,由於無法預測哪些選擇可生成有效的解,因此我們必須對所有可能的選擇進行走訪。在這種情況下,**關鍵是如何最佳化效率**,常見的效率最佳化方法有兩種。
+
+- **剪枝**:避免搜尋那些肯定不會產生解的路徑,從而節省時間和空間。
+- **啟發式搜尋**:在搜尋過程中引入一些策略或者估計值,從而優先搜尋最有可能產生有效解的路徑。
+
+## 13.1.6 回溯典型例題
+
+回溯演算法可用於解決許多搜尋問題、約束滿足問題和組合最佳化問題。
+
+**搜尋問題**:這類問題的目標是找到滿足特定條件的解決方案。
+
+- 全排列問題:給定一個集合,求出其所有可能的排列組合。
+- 子集和問題:給定一個集合和一個目標和,找到集合中所有和為目標和的子集。
+- 河內塔問題:給定三根柱子和一系列大小不同的圓盤,要求將所有圓盤從一根柱子移動到另一根柱子,每次只能移動一個圓盤,且不能將大圓盤放在小圓盤上。
+
+**約束滿足問題**:這類問題的目標是找到滿足所有約束條件的解。
+
+- $n$ 皇后:在 $n \times n$ 的棋盤上放置 $n$ 個皇后,使得它們互不攻擊。
+- 數獨:在 $9 \times 9$ 的網格中填入數字 $1$ ~ $9$ ,使得每行、每列和每個 $3 \times 3$ 子網格中的數字不重複。
+- 圖著色問題:給定一個無向圖,用最少的顏色給圖的每個頂點著色,使得相鄰頂點顏色不同。
+
+**組合最佳化問題**:這類問題的目標是在一個組合空間中找到滿足某些條件的最優解。
+
+- 0-1 背包問題:給定一組物品和一個背包,每個物品有一定的價值和重量,要求在背包容量限制內,選擇物品使得總價值最大。
+- 旅行商問題:在一個圖中,從一個點出發,訪問所有其他點恰好一次後返回起點,求最短路徑。
+- 最大團問題:給定一個無向圖,找到最大的完全子圖,即子圖中的任意兩個頂點之間都有邊相連。
+
+請注意,對於許多組合最佳化問題,回溯不是最優解決方案。
+
+- 0-1 背包問題通常使用動態規劃解決,以達到更高的時間效率。
+- 旅行商是一個著名的 NP-Hard 問題,常用解法有遺傳演算法和蟻群演算法等。
+- 最大團問題是圖論中的一個經典問題,可用貪婪演算法等啟發式演算法來解決。
diff --git a/zh-Hant/docs/chapter_backtracking/index.md b/zh-Hant/docs/chapter_backtracking/index.md
new file mode 100644
index 000000000..d11076166
--- /dev/null
+++ b/zh-Hant/docs/chapter_backtracking/index.md
@@ -0,0 +1,22 @@
+---
+comments: true
+icon: material/map-marker-path
+---
+
+# 第 13 章 回溯
+
+{ class="cover-image" }
+
+!!! abstract
+
+ 我們如同迷宮中的探索者,在前進的道路上可能會遇到困難。
+
+ 回溯的力量讓我們能夠重新開始,不斷嘗試,最終找到通往光明的出口。
+
+## Chapter Contents
+
+- [13.1 回溯演算法](https://www.hello-algo.com/en/chapter_backtracking/backtracking_algorithm/)
+- [13.2 全排列問題](https://www.hello-algo.com/en/chapter_backtracking/permutations_problem/)
+- [13.3 子集和問題](https://www.hello-algo.com/en/chapter_backtracking/subset_sum_problem/)
+- [13.4 N 皇后問題](https://www.hello-algo.com/en/chapter_backtracking/n_queens_problem/)
+- [13.5 小結](https://www.hello-algo.com/en/chapter_backtracking/summary/)
diff --git a/zh-Hant/docs/chapter_backtracking/n_queens_problem.md b/zh-Hant/docs/chapter_backtracking/n_queens_problem.md
new file mode 100644
index 000000000..4ae1cba5b
--- /dev/null
+++ b/zh-Hant/docs/chapter_backtracking/n_queens_problem.md
@@ -0,0 +1,732 @@
+---
+comments: true
+---
+
+# 13.4 n 皇后問題
+
+!!! question
+
+ 根據國際象棋的規則,皇后可以攻擊與同處一行、一列或一條斜線上的棋子。給定 $n$ 個皇后和一個 $n \times n$ 大小的棋盤,尋找使得所有皇后之間無法相互攻擊的擺放方案。
+
+如圖 13-15 所示,當 $n = 4$ 時,共可以找到兩個解。從回溯演算法的角度看,$n \times n$ 大小的棋盤共有 $n^2$ 個格子,給出了所有的選擇 `choices` 。在逐個放置皇后的過程中,棋盤狀態在不斷地變化,每個時刻的棋盤就是狀態 `state` 。
+
+{ class="animation-figure" }
+
+ 圖 13-15 4 皇后問題的解
+
+圖 13-16 展示了本題的三個約束條件:**多個皇后不能在同一行、同一列、同一條對角線上**。值得注意的是,對角線分為主對角線 `\` 和次對角線 `/` 兩種。
+
+{ class="animation-figure" }
+
+ 圖 13-16 n 皇后問題的約束條件
+
+### 1. 逐行放置策略
+
+皇后的數量和棋盤的行數都為 $n$ ,因此我們容易得到一個推論:**棋盤每行都允許且只允許放置一個皇后**。
+
+也就是說,我們可以採取逐行放置策略:從第一行開始,在每行放置一個皇后,直至最後一行結束。
+
+圖 13-17 所示為 $4$ 皇后問題的逐行放置過程。受畫幅限制,圖 13-17 僅展開了第一行的其中一個搜尋分支,並且將不滿足列約束和對角線約束的方案都進行了剪枝。
+
+{ class="animation-figure" }
+
+ 圖 13-17 逐行放置策略
+
+從本質上看,**逐行放置策略起到了剪枝的作用**,它避免了同一行出現多個皇后的所有搜尋分支。
+
+### 2. 列與對角線剪枝
+
+為了滿足列約束,我們可以利用一個長度為 $n$ 的布林型陣列 `cols` 記錄每一列是否有皇后。在每次決定放置前,我們透過 `cols` 將已有皇后的列進行剪枝,並在回溯中動態更新 `cols` 的狀態。
+
+那麼,如何處理對角線約束呢?設棋盤中某個格子的行列索引為 $(row, col)$ ,選定矩陣中的某條主對角線,我們發現該對角線上所有格子的行索引減列索引都相等,**即對角線上所有格子的 $row - col$ 為恆定值**。
+
+也就是說,如果兩個格子滿足 $row_1 - col_1 = row_2 - col_2$ ,則它們一定處在同一條主對角線上。利用該規律,我們可以藉助圖 13-18 所示的陣列 `diags1` 記錄每條主對角線上是否有皇后。
+
+同理,**次對角線上的所有格子的 $row + col$ 是恆定值**。我們同樣也可以藉助陣列 `diags2` 來處理次對角線約束。
+
+{ class="animation-figure" }
+
+ 圖 13-18 處理列約束和對角線約束
+
+### 3. 程式碼實現
+
+請注意,$n$ 維方陣中 $row - col$ 的範圍是 $[-n + 1, n - 1]$ ,$row + col$ 的範圍是 $[0, 2n - 2]$ ,所以主對角線和次對角線的數量都為 $2n - 1$ ,即陣列 `diags1` 和 `diags2` 的長度都為 $2n - 1$ 。
+
+=== "Python"
+
+ ```python title="n_queens.py"
+ def backtrack(
+ row: int,
+ n: int,
+ state: list[list[str]],
+ res: list[list[list[str]]],
+ cols: list[bool],
+ diags1: list[bool],
+ diags2: list[bool],
+ ):
+ """回溯演算法:n 皇后"""
+ # 當放置完所有行時,記錄解
+ if row == n:
+ res.append([list(row) for row in state])
+ return
+ # 走訪所有列
+ for col in range(n):
+ # 計算該格子對應的主對角線和次對角線
+ diag1 = row - col + n - 1
+ diag2 = row + col
+ # 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后
+ if not cols[col] and not diags1[diag1] and not diags2[diag2]:
+ # 嘗試:將皇后放置在該格子
+ state[row][col] = "Q"
+ cols[col] = diags1[diag1] = diags2[diag2] = True
+ # 放置下一行
+ backtrack(row + 1, n, state, res, cols, diags1, diags2)
+ # 回退:將該格子恢復為空位
+ state[row][col] = "#"
+ cols[col] = diags1[diag1] = diags2[diag2] = False
+
+ def n_queens(n: int) -> list[list[list[str]]]:
+ """求解 n 皇后"""
+ # 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位
+ state = [["#" for _ in range(n)] for _ in range(n)]
+ cols = [False] * n # 記錄列是否有皇后
+ diags1 = [False] * (2 * n - 1) # 記錄主對角線上是否有皇后
+ diags2 = [False] * (2 * n - 1) # 記錄次對角線上是否有皇后
+ res = []
+ backtrack(0, n, state, res, cols, diags1, diags2)
+
+ return res
+ ```
+
+=== "C++"
+
+ ```cpp title="n_queens.cpp"
+ /* 回溯演算法:n 皇后 */
+ void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols,
+ vector &diags1, vector &diags2) {
+ // 當放置完所有行時,記錄解
+ if (row == n) {
+ res.push_back(state);
+ return;
+ }
+ // 走訪所有列
+ for (int col = 0; col < n; col++) {
+ // 計算該格子對應的主對角線和次對角線
+ int diag1 = row - col + n - 1;
+ int diag2 = row + col;
+ // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后
+ if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
+ // 嘗試:將皇后放置在該格子
+ state[row][col] = "Q";
+ cols[col] = diags1[diag1] = diags2[diag2] = true;
+ // 放置下一行
+ backtrack(row + 1, n, state, res, cols, diags1, diags2);
+ // 回退:將該格子恢復為空位
+ state[row][col] = "#";
+ cols[col] = diags1[diag1] = diags2[diag2] = false;
+ }
+ }
+ }
+
+ /* 求解 n 皇后 */
+ vector>> nQueens(int n) {
+ // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位
+ vector> state(n, vector(n, "#"));
+ vector cols(n, false); // 記錄列是否有皇后
+ vector diags1(2 * n - 1, false); // 記錄主對角線上是否有皇后
+ vector diags2(2 * n - 1, false); // 記錄次對角線上是否有皇后
+ vector>> res;
+
+ backtrack(0, n, state, res, cols, diags1, diags2);
+
+ return res;
+ }
+ ```
+
+=== "Java"
+
+ ```java title="n_queens.java"
+ /* 回溯演算法:n 皇后 */
+ void backtrack(int row, int n, List> state, List>> res,
+ boolean[] cols, boolean[] diags1, boolean[] diags2) {
+ // 當放置完所有行時,記錄解
+ if (row == n) {
+ List> copyState = new ArrayList<>();
+ for (List sRow : state) {
+ copyState.add(new ArrayList<>(sRow));
+ }
+ res.add(copyState);
+ return;
+ }
+ // 走訪所有列
+ for (int col = 0; col < n; col++) {
+ // 計算該格子對應的主對角線和次對角線
+ int diag1 = row - col + n - 1;
+ int diag2 = row + col;
+ // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后
+ if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
+ // 嘗試:將皇后放置在該格子
+ state.get(row).set(col, "Q");
+ cols[col] = diags1[diag1] = diags2[diag2] = true;
+ // 放置下一行
+ backtrack(row + 1, n, state, res, cols, diags1, diags2);
+ // 回退:將該格子恢復為空位
+ state.get(row).set(col, "#");
+ cols[col] = diags1[diag1] = diags2[diag2] = false;
+ }
+ }
+ }
+
+ /* 求解 n 皇后 */
+ List>> nQueens(int n) {
+ // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位
+ List> state = new ArrayList<>();
+ for (int i = 0; i < n; i++) {
+ List row = new ArrayList<>();
+ for (int j = 0; j < n; j++) {
+ row.add("#");
+ }
+ state.add(row);
+ }
+ boolean[] cols = new boolean[n]; // 記錄列是否有皇后
+ boolean[] diags1 = new boolean[2 * n - 1]; // 記錄主對角線上是否有皇后
+ boolean[] diags2 = new boolean[2 * n - 1]; // 記錄次對角線上是否有皇后
+ List>> res = new ArrayList<>();
+
+ backtrack(0, n, state, res, cols, diags1, diags2);
+
+ return res;
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="n_queens.cs"
+ /* 回溯演算法:n 皇后 */
+ void Backtrack(int row, int n, List> state, List>> res,
+ bool[] cols, bool[] diags1, bool[] diags2) {
+ // 當放置完所有行時,記錄解
+ if (row == n) {
+ List> copyState = [];
+ foreach (List sRow in state) {
+ copyState.Add(new List(sRow));
+ }
+ res.Add(copyState);
+ return;
+ }
+ // 走訪所有列
+ for (int col = 0; col < n; col++) {
+ // 計算該格子對應的主對角線和次對角線
+ int diag1 = row - col + n - 1;
+ int diag2 = row + col;
+ // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后
+ if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
+ // 嘗試:將皇后放置在該格子
+ state[row][col] = "Q";
+ cols[col] = diags1[diag1] = diags2[diag2] = true;
+ // 放置下一行
+ Backtrack(row + 1, n, state, res, cols, diags1, diags2);
+ // 回退:將該格子恢復為空位
+ state[row][col] = "#";
+ cols[col] = diags1[diag1] = diags2[diag2] = false;
+ }
+ }
+ }
+
+ /* 求解 n 皇后 */
+ List>> NQueens(int n) {
+ // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位
+ List> state = [];
+ for (int i = 0; i < n; i++) {
+ List row = [];
+ for (int j = 0; j < n; j++) {
+ row.Add("#");
+ }
+ state.Add(row);
+ }
+ bool[] cols = new bool[n]; // 記錄列是否有皇后
+ bool[] diags1 = new bool[2 * n - 1]; // 記錄主對角線上是否有皇后
+ bool[] diags2 = new bool[2 * n - 1]; // 記錄次對角線上是否有皇后
+ List>> res = [];
+
+ Backtrack(0, n, state, res, cols, diags1, diags2);
+
+ return res;
+ }
+ ```
+
+=== "Go"
+
+ ```go title="n_queens.go"
+ /* 回溯演算法:n 皇后 */
+ func backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) {
+ // 當放置完所有行時,記錄解
+ if row == n {
+ newState := make([][]string, len(*state))
+ for i, _ := range newState {
+ newState[i] = make([]string, len((*state)[0]))
+ copy(newState[i], (*state)[i])
+
+ }
+ *res = append(*res, newState)
+ }
+ // 走訪所有列
+ for col := 0; col < n; col++ {
+ // 計算該格子對應的主對角線和次對角線
+ diag1 := row - col + n - 1
+ diag2 := row + col
+ // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后
+ if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] {
+ // 嘗試:將皇后放置在該格子
+ (*state)[row][col] = "Q"
+ (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true
+ // 放置下一行
+ backtrack(row+1, n, state, res, cols, diags1, diags2)
+ // 回退:將該格子恢復為空位
+ (*state)[row][col] = "#"
+ (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false
+ }
+ }
+ }
+
+ /* 求解 n 皇后 */
+ func nQueens(n int) [][][]string {
+ // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位
+ state := make([][]string, n)
+ for i := 0; i < n; i++ {
+ row := make([]string, n)
+ for i := 0; i < n; i++ {
+ row[i] = "#"
+ }
+ state[i] = row
+ }
+ // 記錄列是否有皇后
+ cols := make([]bool, n)
+ diags1 := make([]bool, 2*n-1)
+ diags2 := make([]bool, 2*n-1)
+ res := make([][][]string, 0)
+ backtrack(0, n, &state, &res, &cols, &diags1, &diags2)
+ return res
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="n_queens.swift"
+ /* 回溯演算法:n 皇后 */
+ func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) {
+ // 當放置完所有行時,記錄解
+ if row == n {
+ res.append(state)
+ return
+ }
+ // 走訪所有列
+ for col in 0 ..< n {
+ // 計算該格子對應的主對角線和次對角線
+ let diag1 = row - col + n - 1
+ let diag2 = row + col
+ // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后
+ if !cols[col] && !diags1[diag1] && !diags2[diag2] {
+ // 嘗試:將皇后放置在該格子
+ state[row][col] = "Q"
+ cols[col] = true
+ diags1[diag1] = true
+ diags2[diag2] = true
+ // 放置下一行
+ backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2)
+ // 回退:將該格子恢復為空位
+ state[row][col] = "#"
+ cols[col] = false
+ diags1[diag1] = false
+ diags2[diag2] = false
+ }
+ }
+ }
+
+ /* 求解 n 皇后 */
+ func nQueens(n: Int) -> [[[String]]] {
+ // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位
+ var state = Array(repeating: Array(repeating: "#", count: n), count: n)
+ var cols = Array(repeating: false, count: n) // 記錄列是否有皇后
+ var diags1 = Array(repeating: false, count: 2 * n - 1) // 記錄主對角線上是否有皇后
+ var diags2 = Array(repeating: false, count: 2 * n - 1) // 記錄次對角線上是否有皇后
+ var res: [[[String]]] = []
+
+ backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2)
+
+ return res
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="n_queens.js"
+ /* 回溯演算法:n 皇后 */
+ function backtrack(row, n, state, res, cols, diags1, diags2) {
+ // 當放置完所有行時,記錄解
+ if (row === n) {
+ res.push(state.map((row) => row.slice()));
+ return;
+ }
+ // 走訪所有列
+ for (let col = 0; col < n; col++) {
+ // 計算該格子對應的主對角線和次對角線
+ const diag1 = row - col + n - 1;
+ const diag2 = row + col;
+ // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后
+ if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
+ // 嘗試:將皇后放置在該格子
+ state[row][col] = 'Q';
+ cols[col] = diags1[diag1] = diags2[diag2] = true;
+ // 放置下一行
+ backtrack(row + 1, n, state, res, cols, diags1, diags2);
+ // 回退:將該格子恢復為空位
+ state[row][col] = '#';
+ cols[col] = diags1[diag1] = diags2[diag2] = false;
+ }
+ }
+ }
+
+ /* 求解 n 皇后 */
+ function nQueens(n) {
+ // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位
+ const state = Array.from({ length: n }, () => Array(n).fill('#'));
+ const cols = Array(n).fill(false); // 記錄列是否有皇后
+ const diags1 = Array(2 * n - 1).fill(false); // 記錄主對角線上是否有皇后
+ const diags2 = Array(2 * n - 1).fill(false); // 記錄次對角線上是否有皇后
+ const res = [];
+
+ backtrack(0, n, state, res, cols, diags1, diags2);
+ return res;
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="n_queens.ts"
+ /* 回溯演算法:n 皇后 */
+ function backtrack(
+ row: number,
+ n: number,
+ state: string[][],
+ res: string[][][],
+ cols: boolean[],
+ diags1: boolean[],
+ diags2: boolean[]
+ ): void {
+ // 當放置完所有行時,記錄解
+ if (row === n) {
+ res.push(state.map((row) => row.slice()));
+ return;
+ }
+ // 走訪所有列
+ for (let col = 0; col < n; col++) {
+ // 計算該格子對應的主對角線和次對角線
+ const diag1 = row - col + n - 1;
+ const diag2 = row + col;
+ // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后
+ if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
+ // 嘗試:將皇后放置在該格子
+ state[row][col] = 'Q';
+ cols[col] = diags1[diag1] = diags2[diag2] = true;
+ // 放置下一行
+ backtrack(row + 1, n, state, res, cols, diags1, diags2);
+ // 回退:將該格子恢復為空位
+ state[row][col] = '#';
+ cols[col] = diags1[diag1] = diags2[diag2] = false;
+ }
+ }
+ }
+
+ /* 求解 n 皇后 */
+ function nQueens(n: number): string[][][] {
+ // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位
+ const state = Array.from({ length: n }, () => Array(n).fill('#'));
+ const cols = Array(n).fill(false); // 記錄列是否有皇后
+ const diags1 = Array(2 * n - 1).fill(false); // 記錄主對角線上是否有皇后
+ const diags2 = Array(2 * n - 1).fill(false); // 記錄次對角線上是否有皇后
+ const res: string[][][] = [];
+
+ backtrack(0, n, state, res, cols, diags1, diags2);
+ return res;
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="n_queens.dart"
+ /* 回溯演算法:n 皇后 */
+ void backtrack(
+ int row,
+ int n,
+ List> state,
+ List>> res,
+ List cols,
+ List diags1,
+ List diags2,
+ ) {
+ // 當放置完所有行時,記錄解
+ if (row == n) {
+ List> copyState = [];
+ for (List sRow in state) {
+ copyState.add(List.from(sRow));
+ }
+ res.add(copyState);
+ return;
+ }
+ // 走訪所有列
+ for (int col = 0; col < n; col++) {
+ // 計算該格子對應的主對角線和次對角線
+ int diag1 = row - col + n - 1;
+ int diag2 = row + col;
+ // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后
+ if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
+ // 嘗試:將皇后放置在該格子
+ state[row][col] = "Q";
+ cols[col] = true;
+ diags1[diag1] = true;
+ diags2[diag2] = true;
+ // 放置下一行
+ backtrack(row + 1, n, state, res, cols, diags1, diags2);
+ // 回退:將該格子恢復為空位
+ state[row][col] = "#";
+ cols[col] = false;
+ diags1[diag1] = false;
+ diags2[diag2] = false;
+ }
+ }
+ }
+
+ /* 求解 n 皇后 */
+ List>> nQueens(int n) {
+ // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位
+ List> state = List.generate(n, (index) => List.filled(n, "#"));
+ List cols = List.filled(n, false); // 記錄列是否有皇后
+ List diags1 = List.filled(2 * n - 1, false); // 記錄主對角線上是否有皇后
+ List diags2 = List.filled(2 * n - 1, false); // 記錄次對角線上是否有皇后
+ List>> res = [];
+
+ backtrack(0, n, state, res, cols, diags1, diags2);
+
+ return res;
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="n_queens.rs"
+ /* 回溯演算法:n 皇后 */
+ fn backtrack(
+ row: usize,
+ n: usize,
+ state: &mut Vec>,
+ res: &mut Vec>>,
+ cols: &mut [bool],
+ diags1: &mut [bool],
+ diags2: &mut [bool],
+ ) {
+ // 當放置完所有行時,記錄解
+ if row == n {
+ let mut copy_state: Vec> = Vec::new();
+ for s_row in state.clone() {
+ copy_state.push(s_row);
+ }
+ res.push(copy_state);
+ return;
+ }
+ // 走訪所有列
+ for col in 0..n {
+ // 計算該格子對應的主對角線和次對角線
+ let diag1 = row + n - 1 - col;
+ let diag2 = row + col;
+ // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后
+ if !cols[col] && !diags1[diag1] && !diags2[diag2] {
+ // 嘗試:將皇后放置在該格子
+ state.get_mut(row).unwrap()[col] = "Q".into();
+ (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true);
+ // 放置下一行
+ backtrack(row + 1, n, state, res, cols, diags1, diags2);
+ // 回退:將該格子恢復為空位
+ state.get_mut(row).unwrap()[col] = "#".into();
+ (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false);
+ }
+ }
+ }
+
+ /* 求解 n 皇后 */
+ fn n_queens(n: usize) -> Vec>> {
+ // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位
+ let mut state: Vec> = Vec::new();
+ for _ in 0..n {
+ let mut row: Vec = Vec::new();
+ for _ in 0..n {
+ row.push("#".into());
+ }
+ state.push(row);
+ }
+ let mut cols = vec![false; n]; // 記錄列是否有皇后
+ let mut diags1 = vec![false; 2 * n - 1]; // 記錄主對角線上是否有皇后
+ let mut diags2 = vec![false; 2 * n - 1]; // 記錄次對角線上是否有皇后
+ let mut res: Vec>> = Vec::new();
+
+ backtrack(
+ 0,
+ n,
+ &mut state,
+ &mut res,
+ &mut cols,
+ &mut diags1,
+ &mut diags2,
+ );
+
+ res
+ }
+ ```
+
+=== "C"
+
+ ```c title="n_queens.c"
+ /* 回溯演算法:n 皇后 */
+ void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE],
+ bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) {
+ // 當放置完所有行時,記錄解
+ if (row == n) {
+ res[*resSize] = (char **)malloc(sizeof(char *) * n);
+ for (int i = 0; i < n; ++i) {
+ res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1));
+ strcpy(res[*resSize][i], state[i]);
+ }
+ (*resSize)++;
+ return;
+ }
+ // 走訪所有列
+ for (int col = 0; col < n; col++) {
+ // 計算該格子對應的主對角線和次對角線
+ int diag1 = row - col + n - 1;
+ int diag2 = row + col;
+ // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后
+ if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
+ // 嘗試:將皇后放置在該格子
+ state[row][col] = 'Q';
+ cols[col] = diags1[diag1] = diags2[diag2] = true;
+ // 放置下一行
+ backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2);
+ // 回退:將該格子恢復為空位
+ state[row][col] = '#';
+ cols[col] = diags1[diag1] = diags2[diag2] = false;
+ }
+ }
+ }
+
+ /* 求解 n 皇后 */
+ char ***nQueens(int n, int *returnSize) {
+ char state[MAX_SIZE][MAX_SIZE];
+ // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位
+ for (int i = 0; i < n; ++i) {
+ for (int j = 0; j < n; ++j) {
+ state[i][j] = '#';
+ }
+ state[i][n] = '\0';
+ }
+ bool cols[MAX_SIZE] = {false}; // 記錄列是否有皇后
+ bool diags1[2 * MAX_SIZE - 1] = {false}; // 記錄主對角線上是否有皇后
+ bool diags2[2 * MAX_SIZE - 1] = {false}; // 記錄次對角線上是否有皇后
+
+ char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE);
+ *returnSize = 0;
+ backtrack(0, n, state, res, returnSize, cols, diags1, diags2);
+ return res;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="n_queens.kt"
+ /* 回溯演算法:n 皇后 */
+ fun backtrack(
+ row: Int,
+ n: Int,
+ state: List>,
+ res: MutableList>?>,
+ cols: BooleanArray,
+ diags1: BooleanArray,
+ diags2: BooleanArray
+ ) {
+ // 當放置完所有行時,記錄解
+ if (row == n) {
+ val copyState: MutableList> = ArrayList()
+ for (sRow in state) {
+ copyState.add(ArrayList(sRow))
+ }
+ res.add(copyState)
+ return
+ }
+ // 走訪所有列
+ for (col in 0..>?> {
+ // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位
+ val state: MutableList> = ArrayList()
+ for (i in 0.. = ArrayList()
+ for (j in 0..>?> = ArrayList()
+
+ backtrack(0, n, state, res, cols, diags1, diags2)
+
+ return res
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="n_queens.rb"
+ [class]{}-[func]{backtrack}
+
+ [class]{}-[func]{n_queens}
+ ```
+
+=== "Zig"
+
+ ```zig title="n_queens.zig"
+ [class]{}-[func]{backtrack}
+
+ [class]{}-[func]{nQueens}
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+逐行放置 $n$ 次,考慮列約束,則從第一行到最後一行分別有 $n$、$n-1$、$\dots$、$2$、$1$ 個選擇,使用 $O(n!)$ 時間。當記錄解時,需要複製矩陣 `state` 並新增進 `res` ,複製操作使用 $O(n^2)$ 時間。因此,**總體時間複雜度為 $O(n! \cdot n^2)$** 。實際上,根據對角線約束的剪枝也能夠大幅縮小搜尋空間,因而搜尋效率往往優於以上時間複雜度。
+
+陣列 `state` 使用 $O(n^2)$ 空間,陣列 `cols`、`diags1` 和 `diags2` 皆使用 $O(n)$ 空間。最大遞迴深度為 $n$ ,使用 $O(n)$ 堆疊幀空間。因此,**空間複雜度為 $O(n^2)$** 。
diff --git a/zh-Hant/docs/chapter_backtracking/permutations_problem.md b/zh-Hant/docs/chapter_backtracking/permutations_problem.md
new file mode 100644
index 000000000..d3a835003
--- /dev/null
+++ b/zh-Hant/docs/chapter_backtracking/permutations_problem.md
@@ -0,0 +1,1068 @@
+---
+comments: true
+---
+
+# 13.2 全排列問題
+
+全排列問題是回溯演算法的一個典型應用。它的定義是在給定一個集合(如一個陣列或字串)的情況下,找出其中元素的所有可能的排列。
+
+表 13-2 列舉了幾個示例資料,包括輸入陣列和對應的所有排列。
+
+ 表 13-2 全排列示例
+
+
+
+| 輸入陣列 | 所有排列 |
+| :---------- | :----------------------------------------------------------------- |
+| $[1]$ | $[1]$ |
+| $[1, 2]$ | $[1, 2], [2, 1]$ |
+| $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ |
+
+
+
+## 13.2.1 無相等元素的情況
+
+!!! question
+
+ 輸入一個整數陣列,其中不包含重複元素,返回所有可能的排列。
+
+從回溯演算法的角度看,**我們可以把生成排列的過程想象成一系列選擇的結果**。假設輸入陣列為 $[1, 2, 3]$ ,如果我們先選擇 $1$ ,再選擇 $3$ ,最後選擇 $2$ ,則獲得排列 $[1, 3, 2]$ 。回退表示撤銷一個選擇,之後繼續嘗試其他選擇。
+
+從回溯程式碼的角度看,候選集合 `choices` 是輸入陣列中的所有元素,狀態 `state` 是直至目前已被選擇的元素。請注意,每個元素只允許被選擇一次,**因此 `state` 中的所有元素都應該是唯一的**。
+
+如圖 13-5 所示,我們可以將搜尋過程展開成一棵遞迴樹,樹中的每個節點代表當前狀態 `state` 。從根節點開始,經過三輪選擇後到達葉節點,每個葉節點都對應一個排列。
+
+{ class="animation-figure" }
+
+ 圖 13-5 全排列的遞迴樹
+
+### 1. 重複選擇剪枝
+
+為了實現每個元素只被選擇一次,我們考慮引入一個布林型陣列 `selected` ,其中 `selected[i]` 表示 `choices[i]` 是否已被選擇,並基於它實現以下剪枝操作。
+
+- 在做出選擇 `choice[i]` 後,我們就將 `selected[i]` 賦值為 $\text{True}$ ,代表它已被選擇。
+- 走訪選擇串列 `choices` 時,跳過所有已被選擇的節點,即剪枝。
+
+如圖 13-6 所示,假設我們第一輪選擇 1 ,第二輪選擇 3 ,第三輪選擇 2 ,則需要在第二輪剪掉元素 1 的分支,在第三輪剪掉元素 1 和元素 3 的分支。
+
+{ class="animation-figure" }
+
+ 圖 13-6 全排列剪枝示例
+
+觀察圖 13-6 發現,該剪枝操作將搜尋空間大小從 $O(n^n)$ 減小至 $O(n!)$ 。
+
+### 2. 程式碼實現
+
+想清楚以上資訊之後,我們就可以在框架程式碼中做“完形填空”了。為了縮短整體程式碼,我們不單獨實現框架程式碼中的各個函式,而是將它們展開在 `backtrack()` 函式中:
+
+=== "Python"
+
+ ```python title="permutations_i.py"
+ def backtrack(
+ state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]
+ ):
+ """回溯演算法:全排列 I"""
+ # 當狀態長度等於元素數量時,記錄解
+ if len(state) == len(choices):
+ res.append(list(state))
+ return
+ # 走訪所有選擇
+ for i, choice in enumerate(choices):
+ # 剪枝:不允許重複選擇元素
+ if not selected[i]:
+ # 嘗試:做出選擇,更新狀態
+ selected[i] = True
+ state.append(choice)
+ # 進行下一輪選擇
+ backtrack(state, choices, selected, res)
+ # 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = False
+ state.pop()
+
+ def permutations_i(nums: list[int]) -> list[list[int]]:
+ """全排列 I"""
+ res = []
+ backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)
+ return res
+ ```
+
+=== "C++"
+
+ ```cpp title="permutations_i.cpp"
+ /* 回溯演算法:全排列 I */
+ void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.size() == choices.size()) {
+ res.push_back(state);
+ return;
+ }
+ // 走訪所有選擇
+ for (int i = 0; i < choices.size(); i++) {
+ int choice = choices[i];
+ // 剪枝:不允許重複選擇元素
+ if (!selected[i]) {
+ // 嘗試:做出選擇,更新狀態
+ selected[i] = true;
+ state.push_back(choice);
+ // 進行下一輪選擇
+ backtrack(state, choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.pop_back();
+ }
+ }
+ }
+
+ /* 全排列 I */
+ vector> permutationsI(vector nums) {
+ vector state;
+ vector selected(nums.size(), false);
+ vector> res;
+ backtrack(state, nums, selected, res);
+ return res;
+ }
+ ```
+
+=== "Java"
+
+ ```java title="permutations_i.java"
+ /* 回溯演算法:全排列 I */
+ void backtrack(List state, int[] choices, boolean[] selected, List> res) {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.size() == choices.length) {
+ res.add(new ArrayList(state));
+ return;
+ }
+ // 走訪所有選擇
+ for (int i = 0; i < choices.length; i++) {
+ int choice = choices[i];
+ // 剪枝:不允許重複選擇元素
+ if (!selected[i]) {
+ // 嘗試:做出選擇,更新狀態
+ selected[i] = true;
+ state.add(choice);
+ // 進行下一輪選擇
+ backtrack(state, choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.remove(state.size() - 1);
+ }
+ }
+ }
+
+ /* 全排列 I */
+ List> permutationsI(int[] nums) {
+ List> res = new ArrayList>();
+ backtrack(new ArrayList(), nums, new boolean[nums.length], res);
+ return res;
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="permutations_i.cs"
+ /* 回溯演算法:全排列 I */
+ void Backtrack(List state, int[] choices, bool[] selected, List> res) {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.Count == choices.Length) {
+ res.Add(new List(state));
+ return;
+ }
+ // 走訪所有選擇
+ for (int i = 0; i < choices.Length; i++) {
+ int choice = choices[i];
+ // 剪枝:不允許重複選擇元素
+ if (!selected[i]) {
+ // 嘗試:做出選擇,更新狀態
+ selected[i] = true;
+ state.Add(choice);
+ // 進行下一輪選擇
+ Backtrack(state, choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.RemoveAt(state.Count - 1);
+ }
+ }
+ }
+
+ /* 全排列 I */
+ List> PermutationsI(int[] nums) {
+ List> res = [];
+ Backtrack([], nums, new bool[nums.Length], res);
+ return res;
+ }
+ ```
+
+=== "Go"
+
+ ```go title="permutations_i.go"
+ /* 回溯演算法:全排列 I */
+ func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) {
+ // 當狀態長度等於元素數量時,記錄解
+ if len(*state) == len(*choices) {
+ newState := append([]int{}, *state...)
+ *res = append(*res, newState)
+ }
+ // 走訪所有選擇
+ for i := 0; i < len(*choices); i++ {
+ choice := (*choices)[i]
+ // 剪枝:不允許重複選擇元素
+ if !(*selected)[i] {
+ // 嘗試:做出選擇,更新狀態
+ (*selected)[i] = true
+ *state = append(*state, choice)
+ // 進行下一輪選擇
+ backtrackI(state, choices, selected, res)
+ // 回退:撤銷選擇,恢復到之前的狀態
+ (*selected)[i] = false
+ *state = (*state)[:len(*state)-1]
+ }
+ }
+ }
+
+ /* 全排列 I */
+ func permutationsI(nums []int) [][]int {
+ res := make([][]int, 0)
+ state := make([]int, 0)
+ selected := make([]bool, len(nums))
+ backtrackI(&state, &nums, &selected, &res)
+ return res
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="permutations_i.swift"
+ /* 回溯演算法:全排列 I */
+ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) {
+ // 當狀態長度等於元素數量時,記錄解
+ if state.count == choices.count {
+ res.append(state)
+ return
+ }
+ // 走訪所有選擇
+ for (i, choice) in choices.enumerated() {
+ // 剪枝:不允許重複選擇元素
+ if !selected[i] {
+ // 嘗試:做出選擇,更新狀態
+ selected[i] = true
+ state.append(choice)
+ // 進行下一輪選擇
+ backtrack(state: &state, choices: choices, selected: &selected, res: &res)
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false
+ state.removeLast()
+ }
+ }
+ }
+
+ /* 全排列 I */
+ func permutationsI(nums: [Int]) -> [[Int]] {
+ var state: [Int] = []
+ var selected = Array(repeating: false, count: nums.count)
+ var res: [[Int]] = []
+ backtrack(state: &state, choices: nums, selected: &selected, res: &res)
+ return res
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="permutations_i.js"
+ /* 回溯演算法:全排列 I */
+ function backtrack(state, choices, selected, res) {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.length === choices.length) {
+ res.push([...state]);
+ return;
+ }
+ // 走訪所有選擇
+ choices.forEach((choice, i) => {
+ // 剪枝:不允許重複選擇元素
+ if (!selected[i]) {
+ // 嘗試:做出選擇,更新狀態
+ selected[i] = true;
+ state.push(choice);
+ // 進行下一輪選擇
+ backtrack(state, choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.pop();
+ }
+ });
+ }
+
+ /* 全排列 I */
+ function permutationsI(nums) {
+ const res = [];
+ backtrack([], nums, Array(nums.length).fill(false), res);
+ return res;
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="permutations_i.ts"
+ /* 回溯演算法:全排列 I */
+ function backtrack(
+ state: number[],
+ choices: number[],
+ selected: boolean[],
+ res: number[][]
+ ): void {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.length === choices.length) {
+ res.push([...state]);
+ return;
+ }
+ // 走訪所有選擇
+ choices.forEach((choice, i) => {
+ // 剪枝:不允許重複選擇元素
+ if (!selected[i]) {
+ // 嘗試:做出選擇,更新狀態
+ selected[i] = true;
+ state.push(choice);
+ // 進行下一輪選擇
+ backtrack(state, choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.pop();
+ }
+ });
+ }
+
+ /* 全排列 I */
+ function permutationsI(nums: number[]): number[][] {
+ const res: number[][] = [];
+ backtrack([], nums, Array(nums.length).fill(false), res);
+ return res;
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="permutations_i.dart"
+ /* 回溯演算法:全排列 I */
+ void backtrack(
+ List state,
+ List choices,
+ List selected,
+ List> res,
+ ) {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.length == choices.length) {
+ res.add(List.from(state));
+ return;
+ }
+ // 走訪所有選擇
+ for (int i = 0; i < choices.length; i++) {
+ int choice = choices[i];
+ // 剪枝:不允許重複選擇元素
+ if (!selected[i]) {
+ // 嘗試:做出選擇,更新狀態
+ selected[i] = true;
+ state.add(choice);
+ // 進行下一輪選擇
+ backtrack(state, choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.removeLast();
+ }
+ }
+ }
+
+ /* 全排列 I */
+ List> permutationsI(List nums) {
+ List> res = [];
+ backtrack([], nums, List.filled(nums.length, false), res);
+ return res;
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="permutations_i.rs"
+ /* 回溯演算法:全排列 I */
+ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) {
+ // 當狀態長度等於元素數量時,記錄解
+ if state.len() == choices.len() {
+ res.push(state);
+ return;
+ }
+ // 走訪所有選擇
+ for i in 0..choices.len() {
+ let choice = choices[i];
+ // 剪枝:不允許重複選擇元素
+ if !selected[i] {
+ // 嘗試:做出選擇,更新狀態
+ selected[i] = true;
+ state.push(choice);
+ // 進行下一輪選擇
+ backtrack(state.clone(), choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.remove(state.len() - 1);
+ }
+ }
+ }
+
+ /* 全排列 I */
+ fn permutations_i(nums: &mut [i32]) -> Vec> {
+ let mut res = Vec::new(); // 狀態(子集)
+ backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res);
+ res
+ }
+ ```
+
+=== "C"
+
+ ```c title="permutations_i.c"
+ /* 回溯演算法:全排列 I */
+ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {
+ // 當狀態長度等於元素數量時,記錄解
+ if (stateSize == choicesSize) {
+ res[*resSize] = (int *)malloc(choicesSize * sizeof(int));
+ for (int i = 0; i < choicesSize; i++) {
+ res[*resSize][i] = state[i];
+ }
+ (*resSize)++;
+ return;
+ }
+ // 走訪所有選擇
+ for (int i = 0; i < choicesSize; i++) {
+ int choice = choices[i];
+ // 剪枝:不允許重複選擇元素
+ if (!selected[i]) {
+ // 嘗試:做出選擇,更新狀態
+ selected[i] = true;
+ state[stateSize] = choice;
+ // 進行下一輪選擇
+ backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ }
+ }
+ }
+
+ /* 全排列 I */
+ int **permutationsI(int *nums, int numsSize, int *returnSize) {
+ int *state = (int *)malloc(numsSize * sizeof(int));
+ bool *selected = (bool *)malloc(numsSize * sizeof(bool));
+ for (int i = 0; i < numsSize; i++) {
+ selected[i] = false;
+ }
+ int **res = (int **)malloc(MAX_SIZE * sizeof(int *));
+ *returnSize = 0;
+
+ backtrack(state, 0, nums, numsSize, selected, res, returnSize);
+
+ free(state);
+ free(selected);
+
+ return res;
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin title="permutations_i.kt"
+ /* 回溯演算法:全排列 I */
+ fun backtrack(
+ state: MutableList,
+ choices: IntArray,
+ selected: BooleanArray,
+ res: MutableList?>
+ ) {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.size == choices.size) {
+ res.add(ArrayList(state))
+ return
+ }
+ // 走訪所有選擇
+ for (i in choices.indices) {
+ val choice = choices[i]
+ // 剪枝:不允許重複選擇元素
+ if (!selected[i]) {
+ // 嘗試:做出選擇,更新狀態
+ selected[i] = true
+ state.add(choice)
+ // 進行下一輪選擇
+ backtrack(state, choices, selected, res)
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false
+ state.removeAt(state.size - 1)
+ }
+ }
+ }
+
+ /* 全排列 I */
+ fun permutationsI(nums: IntArray): List?> {
+ val res: MutableList?> = ArrayList()
+ backtrack(ArrayList(), nums, BooleanArray(nums.size), res)
+ return res
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="permutations_i.rb"
+ [class]{}-[func]{backtrack}
+
+ [class]{}-[func]{permutations_i}
+ ```
+
+=== "Zig"
+
+ ```zig title="permutations_i.zig"
+ [class]{}-[func]{backtrack}
+
+ [class]{}-[func]{permutationsI}
+ ```
+
+??? pythontutor "視覺化執行"
+
+
+
+
+## 13.2.2 考慮相等元素的情況
+
+!!! question
+
+ 輸入一個整數陣列,**陣列中可能包含重複元素**,返回所有不重複的排列。
+
+假設輸入陣列為 $[1, 1, 2]$ 。為了方便區分兩個重複元素 $1$ ,我們將第二個 $1$ 記為 $\hat{1}$ 。
+
+如圖 13-7 所示,上述方法生成的排列有一半是重複的。
+
+{ class="animation-figure" }
+
+ 圖 13-7 重複排列
+
+那麼如何去除重複的排列呢?最直接地,考慮藉助一個雜湊表,直接對排列結果進行去重。然而這樣做不夠優雅,**因為生成重複排列的搜尋分支沒有必要,應當提前識別並剪枝**,這樣可以進一步提升演算法效率。
+
+### 1. 相等元素剪枝
+
+觀察圖 13-8 ,在第一輪中,選擇 $1$ 或選擇 $\hat{1}$ 是等價的,在這兩個選擇之下生成的所有排列都是重複的。因此應該把 $\hat{1}$ 剪枝。
+
+同理,在第一輪選擇 $2$ 之後,第二輪選擇中的 $1$ 和 $\hat{1}$ 也會產生重複分支,因此也應將第二輪的 $\hat{1}$ 剪枝。
+
+從本質上看,**我們的目標是在某一輪選擇中,保證多個相等的元素僅被選擇一次**。
+
+{ class="animation-figure" }
+
+ 圖 13-8 重複排列剪枝
+
+### 2. 程式碼實現
+
+在上一題的程式碼的基礎上,我們考慮在每一輪選擇中開啟一個雜湊表 `duplicated` ,用於記錄該輪中已經嘗試過的元素,並將重複元素剪枝:
+
+=== "Python"
+
+ ```python title="permutations_ii.py"
+ def backtrack(
+ state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]
+ ):
+ """回溯演算法:全排列 II"""
+ # 當狀態長度等於元素數量時,記錄解
+ if len(state) == len(choices):
+ res.append(list(state))
+ return
+ # 走訪所有選擇
+ duplicated = set[int]()
+ for i, choice in enumerate(choices):
+ # 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素
+ if not selected[i] and choice not in duplicated:
+ # 嘗試:做出選擇,更新狀態
+ duplicated.add(choice) # 記錄選擇過的元素值
+ selected[i] = True
+ state.append(choice)
+ # 進行下一輪選擇
+ backtrack(state, choices, selected, res)
+ # 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = False
+ state.pop()
+
+ def permutations_ii(nums: list[int]) -> list[list[int]]:
+ """全排列 II"""
+ res = []
+ backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)
+ return res
+ ```
+
+=== "C++"
+
+ ```cpp title="permutations_ii.cpp"
+ /* 回溯演算法:全排列 II */
+ void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.size() == choices.size()) {
+ res.push_back(state);
+ return;
+ }
+ // 走訪所有選擇
+ unordered_set duplicated;
+ for (int i = 0; i < choices.size(); i++) {
+ int choice = choices[i];
+ // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素
+ if (!selected[i] && duplicated.find(choice) == duplicated.end()) {
+ // 嘗試:做出選擇,更新狀態
+ duplicated.emplace(choice); // 記錄選擇過的元素值
+ selected[i] = true;
+ state.push_back(choice);
+ // 進行下一輪選擇
+ backtrack(state, choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.pop_back();
+ }
+ }
+ }
+
+ /* 全排列 II */
+ vector> permutationsII(vector nums) {
+ vector state;
+ vector selected(nums.size(), false);
+ vector> res;
+ backtrack(state, nums, selected, res);
+ return res;
+ }
+ ```
+
+=== "Java"
+
+ ```java title="permutations_ii.java"
+ /* 回溯演算法:全排列 II */
+ void backtrack(List state, int[] choices, boolean[] selected, List> res) {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.size() == choices.length) {
+ res.add(new ArrayList(state));
+ return;
+ }
+ // 走訪所有選擇
+ Set duplicated = new HashSet();
+ for (int i = 0; i < choices.length; i++) {
+ int choice = choices[i];
+ // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素
+ if (!selected[i] && !duplicated.contains(choice)) {
+ // 嘗試:做出選擇,更新狀態
+ duplicated.add(choice); // 記錄選擇過的元素值
+ selected[i] = true;
+ state.add(choice);
+ // 進行下一輪選擇
+ backtrack(state, choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.remove(state.size() - 1);
+ }
+ }
+ }
+
+ /* 全排列 II */
+ List> permutationsII(int[] nums) {
+ List> res = new ArrayList>();
+ backtrack(new ArrayList(), nums, new boolean[nums.length], res);
+ return res;
+ }
+ ```
+
+=== "C#"
+
+ ```csharp title="permutations_ii.cs"
+ /* 回溯演算法:全排列 II */
+ void Backtrack(List state, int[] choices, bool[] selected, List> res) {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.Count == choices.Length) {
+ res.Add(new List(state));
+ return;
+ }
+ // 走訪所有選擇
+ HashSet duplicated = [];
+ for (int i = 0; i < choices.Length; i++) {
+ int choice = choices[i];
+ // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素
+ if (!selected[i] && !duplicated.Contains(choice)) {
+ // 嘗試:做出選擇,更新狀態
+ duplicated.Add(choice); // 記錄選擇過的元素值
+ selected[i] = true;
+ state.Add(choice);
+ // 進行下一輪選擇
+ Backtrack(state, choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.RemoveAt(state.Count - 1);
+ }
+ }
+ }
+
+ /* 全排列 II */
+ List> PermutationsII(int[] nums) {
+ List> res = [];
+ Backtrack([], nums, new bool[nums.Length], res);
+ return res;
+ }
+ ```
+
+=== "Go"
+
+ ```go title="permutations_ii.go"
+ /* 回溯演算法:全排列 II */
+ func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) {
+ // 當狀態長度等於元素數量時,記錄解
+ if len(*state) == len(*choices) {
+ newState := append([]int{}, *state...)
+ *res = append(*res, newState)
+ }
+ // 走訪所有選擇
+ duplicated := make(map[int]struct{}, 0)
+ for i := 0; i < len(*choices); i++ {
+ choice := (*choices)[i]
+ // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素
+ if _, ok := duplicated[choice]; !ok && !(*selected)[i] {
+ // 嘗試:做出選擇,更新狀態
+ // 記錄選擇過的元素值
+ duplicated[choice] = struct{}{}
+ (*selected)[i] = true
+ *state = append(*state, choice)
+ // 進行下一輪選擇
+ backtrackI(state, choices, selected, res)
+ // 回退:撤銷選擇,恢復到之前的狀態
+ (*selected)[i] = false
+ *state = (*state)[:len(*state)-1]
+ }
+ }
+ }
+
+ /* 全排列 II */
+ func permutationsII(nums []int) [][]int {
+ res := make([][]int, 0)
+ state := make([]int, 0)
+ selected := make([]bool, len(nums))
+ backtrackII(&state, &nums, &selected, &res)
+ return res
+ }
+ ```
+
+=== "Swift"
+
+ ```swift title="permutations_ii.swift"
+ /* 回溯演算法:全排列 II */
+ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) {
+ // 當狀態長度等於元素數量時,記錄解
+ if state.count == choices.count {
+ res.append(state)
+ return
+ }
+ // 走訪所有選擇
+ var duplicated: Set = []
+ for (i, choice) in choices.enumerated() {
+ // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素
+ if !selected[i], !duplicated.contains(choice) {
+ // 嘗試:做出選擇,更新狀態
+ duplicated.insert(choice) // 記錄選擇過的元素值
+ selected[i] = true
+ state.append(choice)
+ // 進行下一輪選擇
+ backtrack(state: &state, choices: choices, selected: &selected, res: &res)
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false
+ state.removeLast()
+ }
+ }
+ }
+
+ /* 全排列 II */
+ func permutationsII(nums: [Int]) -> [[Int]] {
+ var state: [Int] = []
+ var selected = Array(repeating: false, count: nums.count)
+ var res: [[Int]] = []
+ backtrack(state: &state, choices: nums, selected: &selected, res: &res)
+ return res
+ }
+ ```
+
+=== "JS"
+
+ ```javascript title="permutations_ii.js"
+ /* 回溯演算法:全排列 II */
+ function backtrack(state, choices, selected, res) {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.length === choices.length) {
+ res.push([...state]);
+ return;
+ }
+ // 走訪所有選擇
+ const duplicated = new Set();
+ choices.forEach((choice, i) => {
+ // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素
+ if (!selected[i] && !duplicated.has(choice)) {
+ // 嘗試:做出選擇,更新狀態
+ duplicated.add(choice); // 記錄選擇過的元素值
+ selected[i] = true;
+ state.push(choice);
+ // 進行下一輪選擇
+ backtrack(state, choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.pop();
+ }
+ });
+ }
+
+ /* 全排列 II */
+ function permutationsII(nums) {
+ const res = [];
+ backtrack([], nums, Array(nums.length).fill(false), res);
+ return res;
+ }
+ ```
+
+=== "TS"
+
+ ```typescript title="permutations_ii.ts"
+ /* 回溯演算法:全排列 II */
+ function backtrack(
+ state: number[],
+ choices: number[],
+ selected: boolean[],
+ res: number[][]
+ ): void {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.length === choices.length) {
+ res.push([...state]);
+ return;
+ }
+ // 走訪所有選擇
+ const duplicated = new Set();
+ choices.forEach((choice, i) => {
+ // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素
+ if (!selected[i] && !duplicated.has(choice)) {
+ // 嘗試:做出選擇,更新狀態
+ duplicated.add(choice); // 記錄選擇過的元素值
+ selected[i] = true;
+ state.push(choice);
+ // 進行下一輪選擇
+ backtrack(state, choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.pop();
+ }
+ });
+ }
+
+ /* 全排列 II */
+ function permutationsII(nums: number[]): number[][] {
+ const res: number[][] = [];
+ backtrack([], nums, Array(nums.length).fill(false), res);
+ return res;
+ }
+ ```
+
+=== "Dart"
+
+ ```dart title="permutations_ii.dart"
+ /* 回溯演算法:全排列 II */
+ void backtrack(
+ List state,
+ List choices,
+ List selected,
+ List> res,
+ ) {
+ // 當狀態長度等於元素數量時,記錄解
+ if (state.length == choices.length) {
+ res.add(List.from(state));
+ return;
+ }
+ // 走訪所有選擇
+ Set duplicated = {};
+ for (int i = 0; i < choices.length; i++) {
+ int choice = choices[i];
+ // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素
+ if (!selected[i] && !duplicated.contains(choice)) {
+ // 嘗試:做出選擇,更新狀態
+ duplicated.add(choice); // 記錄選擇過的元素值
+ selected[i] = true;
+ state.add(choice);
+ // 進行下一輪選擇
+ backtrack(state, choices, selected, res);
+ // 回退:撤銷選擇,恢復到之前的狀態
+ selected[i] = false;
+ state.removeLast();
+ }
+ }
+ }
+
+ /* 全排列 II */
+ List> permutationsII(List nums) {
+ List> res = [];
+ backtrack([], nums, List.filled(nums.length, false), res);
+ return res;
+ }
+ ```
+
+=== "Rust"
+
+ ```rust title="permutations_ii.rs"
+ /* 回溯演算法:全排列 II */
+ fn backtrack(mut state: Vec