diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md
index 53ffecbb5..919566491 100644
--- a/docs/chapter_heap/heap.md
+++ b/docs/chapter_heap/heap.md
@@ -1874,6 +1874,6 @@ comments: true
## 8.1.3 堆的常见应用
-- **优先队列**:堆通常作为实现优先队列的首选数据结构,其入队和出队操作的时间复杂度均为 $O(\log n)$ ,而建队操作为 $O(n)$ ,这些操作都非常高效。
+- **优先队列**:堆通常作为实现优先队列的首选数据结构,其入队和出队操作的时间复杂度均为 $O(\log n)$ ,而建堆操作为 $O(n)$ ,这些操作都非常高效。
- **堆排序**:给定一组数据,我们可以用它们建立一个堆,然后不断地执行元素出堆操作,从而得到有序数据。然而,我们通常会使用一种更优雅的方式实现堆排序,详见“堆排序”章节。
- **获取最大的 $k$ 个元素**:这是一个经典的算法问题,同时也是一种典型应用,例如选择热度前 10 的新闻作为微博热搜,选取销量前 10 的商品等。
diff --git a/en/docs/chapter_array_and_linkedlist/array.md b/en/docs/chapter_array_and_linkedlist/array.md
index 398e332d9..a78206906 100755
--- a/en/docs/chapter_array_and_linkedlist/array.md
+++ b/en/docs/chapter_array_and_linkedlist/array.md
@@ -133,7 +133,7 @@ Elements in an array are stored in contiguous memory spaces, making it simpler t
Figure 4-2 Memory address calculation for array elements
-As observed in the above illustration, array indexing conventionally begins at $0$. While this might appear counterintuitive, considering counting usually starts at $1$, within the address calculation formula, **an index is essentially an offset from the memory address**. For the first element's address, this offset is $0$, validating its index as $0$.
+As observed in Figure 4-2, array indexing conventionally begins at $0$. While this might appear counterintuitive, considering counting usually starts at $1$, within the address calculation formula, **an index is essentially an offset from the memory address**. For the first element's address, this offset is $0$, validating its index as $0$.
Accessing elements in an array is highly efficient, allowing us to randomly access any element in $O(1)$ time.
@@ -152,7 +152,14 @@ Accessing elements in an array is highly efficient, allowing us to randomly acce
=== "C++"
```cpp title="array.cpp"
- [class]{}-[func]{randomAccess}
+ /* Random access to elements */
+ int randomAccess(int *nums, int size) {
+ // Randomly select a number in the range [0, size)
+ int randomIndex = rand() % size;
+ // Retrieve and return a random element
+ int randomNum = nums[randomIndex];
+ return randomNum;
+ }
```
=== "Java"
@@ -236,7 +243,7 @@ Accessing elements in an array is highly efficient, allowing us to randomly acce
### 3. Inserting elements
-Array elements are tightly packed in memory, with no space available to accommodate additional data between them. Illustrated in Figure below, inserting an element in the middle of an array requires shifting all subsequent elements back by one position to create room for the new element.
+Array elements are tightly packed in memory, with no space available to accommodate additional data between them. As illustrated in Figure 4-3, inserting an element in the middle of an array requires shifting all subsequent elements back by one position to create room for the new element.
{ class="animation-figure" }
@@ -259,7 +266,15 @@ It's important to note that due to the fixed length of an array, inserting an el
=== "C++"
```cpp title="array.cpp"
- [class]{}-[func]{insert}
+ /* Insert element num at `index` */
+ void insert(int *nums, int size, int num, int index) {
+ // Move all elements after `index` one position backward
+ for (int i = size - 1; i > index; i--) {
+ nums[i] = nums[i - 1];
+ }
+ // Assign num to the element at index
+ nums[index] = num;
+ }
```
=== "Java"
@@ -365,7 +380,13 @@ Please note that after deletion, the former last element becomes "meaningless,"
=== "C++"
```cpp title="array.cpp"
- [class]{}-[func]{remove}
+ /* Remove the element at `index` */
+ void remove(int *nums, int size, int index) {
+ // Move all elements after `index` one position forward
+ for (int i = index; i < size - 1; i++) {
+ nums[i] = nums[i + 1];
+ }
+ }
```
=== "Java"
@@ -477,7 +498,14 @@ In most programming languages, we can traverse an array either by using indices
=== "C++"
```cpp title="array.cpp"
- [class]{}-[func]{traverse}
+ /* Traverse array */
+ void traverse(int *nums, int size) {
+ int count = 0;
+ // Traverse array by index
+ for (int i = 0; i < size; i++) {
+ count += nums[i];
+ }
+ }
```
=== "Java"
@@ -583,7 +611,14 @@ Because arrays are linear data structures, this operation is commonly referred t
=== "C++"
```cpp title="array.cpp"
- [class]{}-[func]{find}
+ /* Search for a specified element in the array */
+ int find(int *nums, int size, int target) {
+ for (int i = 0; i < size; i++) {
+ if (nums[i] == target)
+ return i;
+ }
+ return -1;
+ }
```
=== "Java"
@@ -688,7 +723,19 @@ To expand an array, it's necessary to create a larger array and then copy the e
=== "C++"
```cpp title="array.cpp"
- [class]{}-[func]{extend}
+ /* Extend array length */
+ int *extend(int *nums, int size, int enlarge) {
+ // Initialize an extended length array
+ int *res = new int[size + enlarge];
+ // Copy all elements from the original array to the new array
+ for (int i = 0; i < size; i++) {
+ res[i] = nums[i];
+ }
+ // Free memory
+ delete[] nums;
+ // Return the new array after expansion
+ return res;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_array_and_linkedlist/linked_list.md b/en/docs/chapter_array_and_linkedlist/linked_list.md
index c743455cf..22e0ac6ec 100755
--- a/en/docs/chapter_array_and_linkedlist/linked_list.md
+++ b/en/docs/chapter_array_and_linkedlist/linked_list.md
@@ -14,7 +14,7 @@ The design of linked lists allows for their nodes to be distributed across memor
Figure 4-5 Linked list definition and storage method
-As shown in the figure, we see that the basic building block of a linked list is the node object. Each node comprises two key components: the node's "value" and a "reference" to the next node.
+As shown in Figure 4-5, we see that the basic building block of a linked list is the node object. Each node comprises two key components: the node's "value" and a "reference" to the next node.
- The first node in a linked list is the "head node", and the final one is the "tail node".
- The tail node points to "null", designated as `null` in Java, `nullptr` in C++, and `None` in Python.
@@ -412,7 +412,7 @@ The array as a whole is a variable, for instance, the array `nums` includes elem
### 2. Inserting nodes
-Inserting a node into a linked list is very easy. As shown in the figure, let's assume we aim to insert a new node `P` between two adjacent nodes `n0` and `n1`. **This can be achieved by simply modifying two node references (pointers)**, with a time complexity of $O(1)$.
+Inserting a node into a linked list is very easy. As shown in Figure 4-6, let's assume we aim to insert a new node `P` between two adjacent nodes `n0` and `n1`. **This can be achieved by simply modifying two node references (pointers)**, with a time complexity of $O(1)$.
By comparison, inserting an element into an array has a time complexity of $O(n)$, which becomes less efficient when dealing with large data volumes.
@@ -433,7 +433,12 @@ By comparison, inserting an element into an array has a time complexity of $O(n)
=== "C++"
```cpp title="linked_list.cpp"
- [class]{}-[func]{insert}
+ /* Insert node P after node n0 in the linked list */
+ void insert(ListNode *n0, ListNode *P) {
+ ListNode *n1 = n0->next;
+ P->next = n1;
+ n0->next = P;
+ }
```
=== "Java"
@@ -515,7 +520,7 @@ By comparison, inserting an element into an array has a time complexity of $O(n)
### 3. Deleting nodes
-As shown in the figure, deleting a node from a linked list is also very easy, **involving only the modification of a single node's reference (pointer)**.
+As shown in Figure 4-7, deleting a node from a linked list is also very easy, **involving only the modification of a single node's reference (pointer)**.
It's important to note that even though node `P` continues to point to `n1` after being deleted, it becomes inaccessible during linked list traversal. This effectively means that `P` is no longer a part of the linked list.
@@ -539,7 +544,17 @@ It's important to note that even though node `P` continues to point to `n1` afte
=== "C++"
```cpp title="linked_list.cpp"
- [class]{}-[func]{remove}
+ /* Remove the first node after node n0 in the linked list */
+ void remove(ListNode *n0) {
+ if (n0->next == nullptr)
+ return;
+ // n0 -> P -> n1
+ ListNode *P = n0->next;
+ ListNode *n1 = P->next;
+ n0->next = n1;
+ // Free memory
+ delete P;
+ }
```
=== "Java"
@@ -641,7 +656,15 @@ It's important to note that even though node `P` continues to point to `n1` afte
=== "C++"
```cpp title="linked_list.cpp"
- [class]{}-[func]{access}
+ /* Access the node at `index` in the linked list */
+ ListNode *access(ListNode *head, int index) {
+ for (int i = 0; i < index; i++) {
+ if (head == nullptr)
+ return nullptr;
+ head = head->next;
+ }
+ return head;
+ }
```
=== "Java"
@@ -745,7 +768,17 @@ Traverse the linked list to locate a node whose value matches `target`, and then
=== "C++"
```cpp title="linked_list.cpp"
- [class]{}-[func]{find}
+ /* Search for the first node with value target in the linked list */
+ 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"
@@ -851,7 +884,7 @@ Table 4-1 summarizes the characteristics of arrays and linked lists, and it also
## 4.2.3 Common types of linked lists
-As shown in the figure, there are three common types of linked lists.
+As shown in Figure 4-8, there are three common types of linked lists.
- **Singly linked list**: This is the standard linked list described earlier. Nodes in a singly linked list include a value and a reference to the next node. The first node is known as the head node, and the last node, which points to null (`None`), is the tail node.
- **Circular linked list**: This is formed when the tail node of a singly linked list points back to the head node, creating a loop. In a circular linked list, any node can function as the head node.
diff --git a/en/docs/chapter_array_and_linkedlist/list.md b/en/docs/chapter_array_and_linkedlist/list.md
index 0fb2bc047..7dbc02836 100755
--- a/en/docs/chapter_array_and_linkedlist/list.md
+++ b/en/docs/chapter_array_and_linkedlist/list.md
@@ -989,7 +989,116 @@ To enhance our understanding of how lists work, we will attempt to implement a s
=== "C++"
```cpp title="my_list.cpp"
- [class]{MyList}-[func]{}
+ /* List class */
+ class MyList {
+ private:
+ int *arr; // Array (stores list elements)
+ int arrCapacity = 10; // List capacity
+ int arrSize = 0; // List length (current number of elements)
+ int extendRatio = 2; // Multiple for each list expansion
+
+ public:
+ /* Constructor */
+ MyList() {
+ arr = new int[arrCapacity];
+ }
+
+ /* Destructor */
+ ~MyList() {
+ delete[] arr;
+ }
+
+ /* Get list length (current number of elements)*/
+ int size() {
+ return arrSize;
+ }
+
+ /* Get list capacity */
+ int capacity() {
+ return arrCapacity;
+ }
+
+ /* Access element */
+ int get(int index) {
+ // If the index is out of bounds, throw an exception, as below
+ if (index < 0 || index >= size())
+ throw out_of_range("Index out of bounds");
+ return arr[index];
+ }
+
+ /* Update element */
+ void set(int index, int num) {
+ if (index < 0 || index >= size())
+ throw out_of_range("Index out of bounds");
+ arr[index] = num;
+ }
+
+ /* Add element at the end */
+ void add(int num) {
+ // When the number of elements exceeds capacity, trigger the expansion mechanism
+ if (size() == capacity())
+ extendCapacity();
+ arr[size()] = num;
+ // Update the number of elements
+ arrSize++;
+ }
+
+ /* Insert element in the middle */
+ void insert(int index, int num) {
+ if (index < 0 || index >= size())
+ throw out_of_range("Index out of bounds");
+ // When the number of elements exceeds capacity, trigger the expansion mechanism
+ if (size() == capacity())
+ extendCapacity();
+ // Move all elements after `index` one position backward
+ for (int j = size() - 1; j >= index; j--) {
+ arr[j + 1] = arr[j];
+ }
+ arr[index] = num;
+ // Update the number of elements
+ arrSize++;
+ }
+
+ /* Remove element */
+ int remove(int index) {
+ if (index < 0 || index >= size())
+ throw out_of_range("Index out of bounds");
+ int num = arr[index];
+ // Move all elements after `index` one position forward
+ for (int j = index; j < size() - 1; j++) {
+ arr[j] = arr[j + 1];
+ }
+ // Update the number of elements
+ arrSize--;
+ // Return the removed element
+ return num;
+ }
+
+ /* Extend list */
+ void extendCapacity() {
+ // Create a new array with a length multiple of the original array by extendRatio
+ int newCapacity = capacity() * extendRatio;
+ int *tmp = arr;
+ arr = new int[newCapacity];
+ // Copy all elements from the original array to the new array
+ for (int i = 0; i < size(); i++) {
+ arr[i] = tmp[i];
+ }
+ // Free memory
+ delete[] tmp;
+ arrCapacity = newCapacity;
+ }
+
+ /* Convert the list to a Vector for printing */
+ vector toVector() {
+ // Only convert elements within valid length range
+ vector vec(size());
+ for (int i = 0; i < size(); i++) {
+ vec[i] = arr[i];
+ }
+ return vec;
+ }
+ };
```
=== "Java"
diff --git a/en/docs/chapter_array_and_linkedlist/summary.md b/en/docs/chapter_array_and_linkedlist/summary.md
index 2ba5f02d7..5e1464de0 100644
--- a/en/docs/chapter_array_and_linkedlist/summary.md
+++ b/en/docs/chapter_array_and_linkedlist/summary.md
@@ -48,7 +48,7 @@ If an element is searched first and then deleted, the time complexity is indeed
**Q**: In the figure "Linked List Definition and Storage Method", do the light blue storage nodes occupy a single memory address, or do they share half with the node value?
-The diagram is just a qualitative representation; quantitative analysis depends on specific situations.
+The figure is just a qualitative representation; quantitative analysis depends on specific situations.
- Different types of node values occupy different amounts of space, such as int, long, double, and object instances.
- The memory space occupied by pointer variables depends on the operating system and compilation environment used, usually 8 bytes or 4 bytes.
diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.md b/en/docs/chapter_backtracking/backtracking_algorithm.md
index 18bc54a8f..ca77620aa 100644
--- a/en/docs/chapter_backtracking/backtracking_algorithm.md
+++ b/en/docs/chapter_backtracking/backtracking_algorithm.md
@@ -12,7 +12,7 @@ Backtracking typically employs "depth-first search" to traverse the solution spa
Given a binary tree, search and record all nodes with a value of $7$, please return a list of nodes.
-For this problem, we traverse this tree in preorder and check if the current node's value is $7$. If it is, we add the node's value to the result list `res`. The relevant process is shown in the following diagram and code:
+For this problem, we traverse this tree in preorder and check if the current node's value is $7$. If it is, we add the node's value to the result list `res`. The relevant process is shown in Figure 13-1:
=== "Python"
@@ -31,7 +31,18 @@ For this problem, we traverse this tree in preorder and check if the current nod
=== "C++"
```cpp title="preorder_traversal_i_compact.cpp"
- [class]{}-[func]{preOrder}
+ /* Pre-order traversal: Example one */
+ void preOrder(TreeNode *root) {
+ if (root == nullptr) {
+ return;
+ }
+ if (root->val == 7) {
+ // Record solution
+ res.push_back(root);
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ }
```
=== "Java"
@@ -156,7 +167,22 @@ Based on the code from Example One, we need to use a list `path` to record the v
=== "C++"
```cpp title="preorder_traversal_ii_compact.cpp"
- [class]{}-[func]{preOrder}
+ /* Pre-order traversal: Example two */
+ void preOrder(TreeNode *root) {
+ if (root == nullptr) {
+ return;
+ }
+ // Attempt
+ path.push_back(root);
+ if (root->val == 7) {
+ // Record solution
+ res.push_back(path);
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ // Retract
+ path.pop_back();
+ }
```
=== "Java"
@@ -317,7 +343,23 @@ To meet the above constraints, **we need to add a pruning operation**: during th
=== "C++"
```cpp title="preorder_traversal_iii_compact.cpp"
- [class]{}-[func]{preOrder}
+ /* Pre-order traversal: Example three */
+ void preOrder(TreeNode *root) {
+ // Pruning
+ if (root == nullptr || root->val == 3) {
+ return;
+ }
+ // Attempt
+ path.push_back(root);
+ if (root->val == 7) {
+ // Record solution
+ res.push_back(path);
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ // Retract
+ path.pop_back();
+ }
```
=== "Java"
@@ -408,7 +450,7 @@ To meet the above constraints, **we need to add a pruning operation**: during th
[class]{}-[func]{preOrder}
```
-"Pruning" is a very vivid noun. As shown in the diagram below, in the search process, **we "cut off" the search branches that do not meet the constraints**, avoiding many meaningless attempts, thus enhancing the search efficiency.
+"Pruning" is a very vivid noun. As shown in Figure 13-3, in the search process, **we "cut off" the search branches that do not meet the constraints**, avoiding many meaningless attempts, thus enhancing the search efficiency.
{ class="animation-figure" }
@@ -788,17 +830,52 @@ Next, we solve Example Three based on the framework code. The `state` is the nod
=== "C++"
```cpp title="preorder_traversal_iii_template.cpp"
- [class]{}-[func]{isSolution}
+ /* Determine if the current state is a solution */
+ bool isSolution(vector &state) {
+ return !state.empty() && state.back()->val == 7;
+ }
- [class]{}-[func]{recordSolution}
+ /* Record solution */
+ void recordSolution(vector &state, vector> &res) {
+ res.push_back(state);
+ }
- [class]{}-[func]{isValid}
+ /* Determine if the choice is legal under the current state */
+ bool isValid(vector &state, TreeNode *choice) {
+ return choice != nullptr && choice->val != 3;
+ }
- [class]{}-[func]{makeChoice}
+ /* Update state */
+ void makeChoice(vector &state, TreeNode *choice) {
+ state.push_back(choice);
+ }
- [class]{}-[func]{undoChoice}
+ /* Restore state */
+ void undoChoice(vector &state, TreeNode *choice) {
+ state.pop_back();
+ }
- [class]{}-[func]{backtrack}
+ /* Backtracking algorithm: Example three */
+ void backtrack(vector &state, vector &choices, vector> &res) {
+ // Check if it's a solution
+ if (isSolution(state)) {
+ // Record solution
+ recordSolution(state, res);
+ }
+ // Traverse all choices
+ for (TreeNode *choice : choices) {
+ // Pruning: check if the choice is legal
+ if (isValid(state, choice)) {
+ // Attempt: make a choice, update the state
+ makeChoice(state, choice);
+ // Proceed to the next round of selection
+ vector nextChoices{choice->left, choice->right};
+ backtrack(state, nextChoices, res);
+ // Retract: undo the choice, restore to the previous state
+ undoChoice(state, choice);
+ }
+ }
+ }
```
=== "Java"
@@ -1027,7 +1104,7 @@ Next, we solve Example Three based on the framework code. The `state` is the nod
[class]{}-[func]{backtrack}
```
-As per the requirements, after finding a node with a value of $7$, the search should continue, **thus the `return` statement after recording the solution should be removed**. The following diagram compares the search processes with and without retaining the `return` statement.
+As per the requirements, after finding a node with a value of $7$, the search should continue, **thus the `return` statement after recording the solution should be removed**. Figure 13-4 compares the search processes with and without retaining the `return` statement.
{ class="animation-figure" }
diff --git a/en/docs/chapter_backtracking/n_queens_problem.md b/en/docs/chapter_backtracking/n_queens_problem.md
index 0e68e9120..17afd005e 100644
--- a/en/docs/chapter_backtracking/n_queens_problem.md
+++ b/en/docs/chapter_backtracking/n_queens_problem.md
@@ -101,9 +101,46 @@ Please note, in an $n$-dimensional matrix, the range of $row - col$ is $[-n + 1,
=== "C++"
```cpp title="n_queens.cpp"
- [class]{}-[func]{backtrack}
+ /* Backtracking algorithm: n queens */
+ void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols,
+ vector &diags1, vector &diags2) {
+ // When all rows are placed, record the solution
+ if (row == n) {
+ res.push_back(state);
+ return;
+ }
+ // Traverse all columns
+ for (int col = 0; col < n; col++) {
+ // Calculate the main and minor diagonals corresponding to the cell
+ int diag1 = row - col + n - 1;
+ int diag2 = row + col;
+ // Pruning: do not allow queens on the column, main diagonal, or minor diagonal of the cell
+ if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
+ // Attempt: place the queen in the cell
+ state[row][col] = "Q";
+ cols[col] = diags1[diag1] = diags2[diag2] = true;
+ // Place the next row
+ backtrack(row + 1, n, state, res, cols, diags1, diags2);
+ // Retract: restore the cell to an empty spot
+ state[row][col] = "#";
+ cols[col] = diags1[diag1] = diags2[diag2] = false;
+ }
+ }
+ }
- [class]{}-[func]{nQueens}
+ /* Solve n queens */
+ vector>> nQueens(int n) {
+ // Initialize an n*n size chessboard, where 'Q' represents the queen and '#' represents an empty spot
+ vector> state(n, vector(n, "#"));
+ vector cols(n, false); // Record columns with queens
+ vector diags1(2 * n - 1, false); // Record main diagonals with queens
+ vector diags2(2 * n - 1, false); // Record minor diagonals with queens
+ vector>> res;
+
+ backtrack(0, n, state, res, cols, diags1, diags2);
+
+ return res;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_backtracking/permutations_problem.md b/en/docs/chapter_backtracking/permutations_problem.md
index bc7d10265..d5d61c78a 100644
--- a/en/docs/chapter_backtracking/permutations_problem.md
+++ b/en/docs/chapter_backtracking/permutations_problem.md
@@ -89,9 +89,38 @@ After understanding the above information, we can "fill in the blanks" in the fr
=== "C++"
```cpp title="permutations_i.cpp"
- [class]{}-[func]{backtrack}
+ /* Backtracking algorithm: Permutation I */
+ void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) {
+ // When the state length equals the number of elements, record the solution
+ if (state.size() == choices.size()) {
+ res.push_back(state);
+ return;
+ }
+ // Traverse all choices
+ for (int i = 0; i < choices.size(); i++) {
+ int choice = choices[i];
+ // Pruning: do not allow repeated selection of elements
+ if (!selected[i]) {
+ // Attempt: make a choice, update the state
+ selected[i] = true;
+ state.push_back(choice);
+ // Proceed to the next round of selection
+ backtrack(state, choices, selected, res);
+ // Retract: undo the choice, restore to the previous state
+ selected[i] = false;
+ state.pop_back();
+ }
+ }
+ }
- [class]{}-[func]{permutationsI}
+ /* Permutation I */
+ vector> permutationsI(vector nums) {
+ vector state;
+ vector selected(nums.size(), false);
+ vector> res;
+ backtrack(state, nums, selected, res);
+ return res;
+ }
```
=== "Java"
@@ -285,9 +314,40 @@ Based on the code from the previous problem, we consider initiating a hash set `
=== "C++"
```cpp title="permutations_ii.cpp"
- [class]{}-[func]{backtrack}
+ /* Backtracking algorithm: Permutation II */
+ void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) {
+ // When the state length equals the number of elements, record the solution
+ if (state.size() == choices.size()) {
+ res.push_back(state);
+ return;
+ }
+ // Traverse all choices
+ unordered_set duplicated;
+ for (int i = 0; i < choices.size(); i++) {
+ int choice = choices[i];
+ // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements
+ if (!selected[i] && duplicated.find(choice) == duplicated.end()) {
+ // Attempt: make a choice, update the state
+ duplicated.emplace(choice); // Record selected element values
+ selected[i] = true;
+ state.push_back(choice);
+ // Proceed to the next round of selection
+ backtrack(state, choices, selected, res);
+ // Retract: undo the choice, restore to the previous state
+ selected[i] = false;
+ state.pop_back();
+ }
+ }
+ }
- [class]{}-[func]{permutationsII}
+ /* Permutation II */
+ vector> permutationsII(vector nums) {
+ vector state;
+ vector selected(nums.size(), false);
+ vector> res;
+ backtrack(state, nums, selected, res);
+ return res;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_backtracking/subset_sum_problem.md b/en/docs/chapter_backtracking/subset_sum_problem.md
index e31f5aaaf..05bacefbe 100644
--- a/en/docs/chapter_backtracking/subset_sum_problem.md
+++ b/en/docs/chapter_backtracking/subset_sum_problem.md
@@ -60,9 +60,36 @@ Unlike the permutation problem, **elements in this problem can be chosen an unli
=== "C++"
```cpp title="subset_sum_i_naive.cpp"
- [class]{}-[func]{backtrack}
+ /* Backtracking algorithm: Subset Sum I */
+ void backtrack(vector &state, int target, int total, vector &choices, vector> &res) {
+ // When the subset sum equals target, record the solution
+ if (total == target) {
+ res.push_back(state);
+ return;
+ }
+ // Traverse all choices
+ for (size_t i = 0; i < choices.size(); i++) {
+ // Pruning: if the subset sum exceeds target, skip that choice
+ if (total + choices[i] > target) {
+ continue;
+ }
+ // Attempt: make a choice, update elements and total
+ state.push_back(choices[i]);
+ // Proceed to the next round of selection
+ backtrack(state, target, total + choices[i], choices, res);
+ // Retract: undo the choice, restore to the previous state
+ state.pop_back();
+ }
+ }
- [class]{}-[func]{subsetSumINaive}
+ /* Solve Subset Sum I (including duplicate subsets) */
+ vector> subsetSumINaive(vector &nums, int target) {
+ vector state; // State (subset)
+ int total = 0; // Subset sum
+ vector> res; // Result list (subset list)
+ backtrack(state, target, total, nums, res);
+ return res;
+ }
```
=== "Java"
@@ -267,9 +294,39 @@ Besides, we have made the following two optimizations to the code.
=== "C++"
```cpp title="subset_sum_i.cpp"
- [class]{}-[func]{backtrack}
+ /* Backtracking algorithm: Subset Sum I */
+ void backtrack(vector &state, int target, vector &choices, int start, vector> &res) {
+ // When the subset sum equals target, record the solution
+ if (target == 0) {
+ res.push_back(state);
+ return;
+ }
+ // Traverse all choices
+ // Pruning two: start traversing from start to avoid generating duplicate subsets
+ for (int i = start; i < choices.size(); i++) {
+ // Pruning one: if the subset sum exceeds target, end the loop immediately
+ // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target
+ if (target - choices[i] < 0) {
+ break;
+ }
+ // Attempt: make a choice, update target, start
+ state.push_back(choices[i]);
+ // Proceed to the next round of selection
+ backtrack(state, target - choices[i], choices, i, res);
+ // Retract: undo the choice, restore to the previous state
+ state.pop_back();
+ }
+ }
- [class]{}-[func]{subsetSumI}
+ /* Solve Subset Sum I */
+ vector> subsetSumI(vector &nums, int target) {
+ vector state; // State (subset)
+ sort(nums.begin(), nums.end()); // Sort nums
+ int start = 0; // Start point for traversal
+ vector> res; // Result list (subset list)
+ backtrack(state, target, nums, start, res);
+ return res;
+ }
```
=== "Java"
@@ -468,9 +525,44 @@ At the same time, **this question stipulates that each array element can only be
=== "C++"
```cpp title="subset_sum_ii.cpp"
- [class]{}-[func]{backtrack}
+ /* Backtracking algorithm: Subset Sum II */
+ void backtrack(vector &state, int target, vector &choices, int start, vector> &res) {
+ // When the subset sum equals target, record the solution
+ if (target == 0) {
+ res.push_back(state);
+ return;
+ }
+ // Traverse all choices
+ // Pruning two: start traversing from start to avoid generating duplicate subsets
+ // Pruning three: start traversing from start to avoid repeatedly selecting the same element
+ for (int i = start; i < choices.size(); i++) {
+ // Pruning one: if the subset sum exceeds target, end the loop immediately
+ // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target
+ if (target - choices[i] < 0) {
+ break;
+ }
+ // Pruning four: if the element equals the left element, it indicates that the search branch is repeated, skip it
+ if (i > start && choices[i] == choices[i - 1]) {
+ continue;
+ }
+ // Attempt: make a choice, update target, start
+ state.push_back(choices[i]);
+ // Proceed to the next round of selection
+ backtrack(state, target - choices[i], choices, i + 1, res);
+ // Retract: undo the choice, restore to the previous state
+ state.pop_back();
+ }
+ }
- [class]{}-[func]{subsetSumII}
+ /* Solve Subset Sum II */
+ vector> subsetSumII(vector &nums, int target) {
+ vector state; // State (subset)
+ sort(nums.begin(), nums.end()); // Sort nums
+ int start = 0; // Start point for traversal
+ vector> res; // Result list (subset list)
+ backtrack(state, target, nums, start, res);
+ return res;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_computational_complexity/iteration_and_recursion.md b/en/docs/chapter_computational_complexity/iteration_and_recursion.md
index 5585d3cd7..58795f7da 100644
--- a/en/docs/chapter_computational_complexity/iteration_and_recursion.md
+++ b/en/docs/chapter_computational_complexity/iteration_and_recursion.md
@@ -31,7 +31,15 @@ The following function uses a `for` loop to perform a summation of $1 + 2 + \dot
=== "C++"
```cpp title="iteration.cpp"
- [class]{}-[func]{forLoop}
+ /* for loop */
+ int forLoop(int n) {
+ int res = 0;
+ // Loop sum 1, 2, ..., n-1, n
+ for (int i = 1; i <= n; ++i) {
+ res += i;
+ }
+ return res;
+ }
```
=== "Java"
@@ -114,7 +122,7 @@ The following function uses a `for` loop to perform a summation of $1 + 2 + \dot
[class]{}-[func]{forLoop}
```
-The flowchart below represents this sum function.
+Figure 2-1 represents this sum function.
{ class="animation-figure" }
@@ -145,7 +153,17 @@ Below we use a `while` loop to implement the sum $1 + 2 + \dots + n$.
=== "C++"
```cpp title="iteration.cpp"
- [class]{}-[func]{whileLoop}
+ /* while loop */
+ int whileLoop(int n) {
+ int res = 0;
+ int i = 1; // Initialize condition variable
+ // Loop sum 1, 2, ..., n-1, n
+ while (i <= n) {
+ res += i;
+ i++; // Update condition variable
+ }
+ return res;
+ }
```
=== "Java"
@@ -253,7 +271,19 @@ For example, in the following code, the condition variable $i$ is updated twice
=== "C++"
```cpp title="iteration.cpp"
- [class]{}-[func]{whileLoopII}
+ /* while loop (two updates) */
+ int whileLoopII(int n) {
+ int res = 0;
+ int i = 1; // Initialize condition variable
+ // Loop sum 1, 4, 10, ...
+ while (i <= n) {
+ res += i;
+ // Update condition variable
+ i++;
+ i *= 2;
+ }
+ return res;
+ }
```
=== "Java"
@@ -363,7 +393,18 @@ We can nest one loop structure within another. Below is an example using `for` l
=== "C++"
```cpp title="iteration.cpp"
- [class]{}-[func]{nestedForLoop}
+ /* Double for loop */
+ string nestedForLoop(int n) {
+ ostringstream res;
+ // Loop i = 1, 2, ..., n-1, n
+ for (int i = 1; i <= n; ++i) {
+ // Loop j = 1, 2, ..., n-1, n
+ for (int j = 1; j <= n; ++j) {
+ res << "(" << i << ", " << j << "), ";
+ }
+ }
+ return res.str();
+ }
```
=== "Java"
@@ -449,7 +490,7 @@ We can nest one loop structure within another. Below is an example using `for` l
[class]{}-[func]{nestedForLoop}
```
-The flowchart below represents this nested loop.
+Figure 2-2 represents this nested loop.
{ class="animation-figure" }
@@ -491,7 +532,16 @@ Observe the following code, where simply calling the function `recur(n)` can com
=== "C++"
```cpp title="recursion.cpp"
- [class]{}-[func]{recur}
+ /* Recursion */
+ int recur(int n) {
+ // Termination condition
+ if (n == 1)
+ return 1;
+ // Recursive: recursive call
+ int res = recur(n - 1);
+ // Return: return result
+ return n + res;
+ }
```
=== "Java"
@@ -630,7 +680,14 @@ For example, in calculating $1 + 2 + \dots + n$, we can make the result variable
=== "C++"
```cpp title="recursion.cpp"
- [class]{}-[func]{tailRecur}
+ /* Tail recursion */
+ int tailRecur(int n, int res) {
+ // Termination condition
+ if (n == 0)
+ return res;
+ // Tail recursive call
+ return tailRecur(n - 1, res + n);
+ }
```
=== "Java"
@@ -757,7 +814,16 @@ Using the recursive relation, and considering the first two numbers as terminati
=== "C++"
```cpp title="recursion.cpp"
- [class]{}-[func]{fib}
+ /* Fibonacci sequence: Recursion */
+ int fib(int n) {
+ // Termination condition f(1) = 0, f(2) = 1
+ if (n == 1 || n == 2)
+ return n - 1;
+ // Recursive call f(n) = f(n-1) + f(n-2)
+ int res = fib(n - 1) + fib(n - 2);
+ // Return result f(n)
+ return res;
+ }
```
=== "Java"
@@ -841,7 +907,7 @@ Using the recursive relation, and considering the first two numbers as terminati
[class]{}-[func]{fib}
```
-Observing the above code, we see that it recursively calls two functions within itself, **meaning that one call generates two branching calls**. As illustrated below, this continuous recursive calling eventually creates a recursion tree with a depth of $n$.
+Observing the above code, we see that it recursively calls two functions within itself, **meaning that one call generates two branching calls**. As illustrated in Figure 2-6, this continuous recursive calling eventually creates a recursion tree with a depth of $n$.
{ class="animation-figure" }
@@ -905,7 +971,25 @@ Therefore, **we can use an explicit stack to simulate the behavior of the call s
=== "C++"
```cpp title="recursion.cpp"
- [class]{}-[func]{forLoopRecur}
+ /* Simulate recursion with iteration */
+ int forLoopRecur(int n) {
+ // Use an explicit stack to simulate the system call stack
+ stack stack;
+ int res = 0;
+ // Recursive: recursive call
+ for (int i = n; i > 0; i--) {
+ // Simulate "recursive" by "pushing onto the stack"
+ stack.push(i);
+ }
+ // Return: return result
+ while (!stack.empty()) {
+ // Simulate "return" by "popping from the stack"
+ res += stack.top();
+ stack.pop();
+ }
+ // res = 1+2+3+...+n
+ return res;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_computational_complexity/space_complexity.md b/en/docs/chapter_computational_complexity/space_complexity.md
index c9ba227b0..51cfe9e97 100644
--- a/en/docs/chapter_computational_complexity/space_complexity.md
+++ b/en/docs/chapter_computational_complexity/space_complexity.md
@@ -731,7 +731,7 @@ The time complexity of both `loop()` and `recur()` functions is $O(n)$, but thei
## 2.4.3 Common types
-Let the size of the input data be $n$, the following chart displays common types of space complexities (arranged from low to high).
+Let the size of the input data be $n$, Figure 2-16 displays common types of space complexities (arranged from low to high).
$$
\begin{aligned}
@@ -775,9 +775,28 @@ Note that memory occupied by initializing variables or calling functions in a lo
=== "C++"
```cpp title="space_complexity.cpp"
- [class]{}-[func]{func}
+ /* Function */
+ int func() {
+ // Perform some operations
+ return 0;
+ }
- [class]{}-[func]{constant}
+ /* Constant complexity */
+ void constant(int n) {
+ // Constants, variables, objects occupy O(1) space
+ const int a = 0;
+ int b = 0;
+ vector nums(10000);
+ ListNode node(0);
+ // Variables in a loop occupy O(1) space
+ for (int i = 0; i < n; i++) {
+ int c = 0;
+ }
+ // Functions in a loop occupy O(1) space
+ for (int i = 0; i < n; i++) {
+ func();
+ }
+ }
```
=== "Java"
@@ -915,7 +934,21 @@ Linear order is common in arrays, linked lists, stacks, queues, etc., where the
=== "C++"
```cpp title="space_complexity.cpp"
- [class]{}-[func]{linear}
+ /* Linear complexity */
+ void linear(int n) {
+ // Array of length n occupies O(n) space
+ vector nums(n);
+ // A list of length n occupies O(n) space
+ vector nodes;
+ for (int i = 0; i < n; i++) {
+ nodes.push_back(ListNode(i));
+ }
+ // A hash table of length n occupies O(n) space
+ unordered_map map;
+ for (int i = 0; i < n; i++) {
+ map[i] = to_string(i);
+ }
+ }
```
=== "Java"
@@ -1022,7 +1055,13 @@ As shown in Figure 2-17, this function's recursive depth is $n$, meaning there a
=== "C++"
```cpp title="space_complexity.cpp"
- [class]{}-[func]{linearRecur}
+ /* Linear complexity (recursive implementation) */
+ void linearRecur(int n) {
+ cout << "Recursion n = " << n << endl;
+ if (n == 1)
+ return;
+ linearRecur(n - 1);
+ }
```
=== "Java"
@@ -1123,7 +1162,18 @@ Quadratic order is common in matrices and graphs, where the number of elements i
=== "C++"
```cpp title="space_complexity.cpp"
- [class]{}-[func]{quadratic}
+ /* Quadratic complexity */
+ void quadratic(int n) {
+ // A two-dimensional list occupies O(n^2) space
+ vector> numMatrix;
+ for (int i = 0; i < n; i++) {
+ vector tmp;
+ for (int j = 0; j < n; j++) {
+ tmp.push_back(0);
+ }
+ numMatrix.push_back(tmp);
+ }
+ }
```
=== "Java"
@@ -1228,7 +1278,14 @@ As shown in Figure 2-18, the recursive depth of this function is $n$, and in eac
=== "C++"
```cpp title="space_complexity.cpp"
- [class]{}-[func]{quadraticRecur}
+ /* Quadratic complexity (recursive implementation) */
+ int quadraticRecur(int n) {
+ if (n <= 0)
+ return 0;
+ vector nums(n);
+ cout << "Recursive n = " << n << ", length of nums = " << nums.size() << endl;
+ return quadraticRecur(n - 1);
+ }
```
=== "Java"
@@ -1335,7 +1392,15 @@ Exponential order is common in binary trees. Observe Figure 2-19, a "full binary
=== "C++"
```cpp title="space_complexity.cpp"
- [class]{}-[func]{buildTree}
+ /* Exponential complexity (building a full binary tree) */
+ TreeNode *buildTree(int n) {
+ if (n == 0)
+ return nullptr;
+ TreeNode *root = new TreeNode(0);
+ root->left = buildTree(n - 1);
+ root->right = buildTree(n - 1);
+ return root;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_computational_complexity/time_complexity.md b/en/docs/chapter_computational_complexity/time_complexity.md
index f9ae785ce..8fec19808 100644
--- a/en/docs/chapter_computational_complexity/time_complexity.md
+++ b/en/docs/chapter_computational_complexity/time_complexity.md
@@ -675,7 +675,7 @@ In essence, time complexity analysis is about finding the asymptotic upper bound
If there exist positive real numbers $c$ and $n_0$ such that for all $n > n_0$, $T(n) \leq c \cdot f(n)$, then $f(n)$ is considered an asymptotic upper bound of $T(n)$, denoted as $T(n) = O(f(n))$.
-As illustrated below, calculating the asymptotic upper bound involves finding a function $f(n)$ such that, as $n$ approaches infinity, $T(n)$ and $f(n)$ have the same growth order, differing only by a constant factor $c$.
+As shown in Figure 2-8, calculating the asymptotic upper bound involves finding a function $f(n)$ such that, as $n$ approaches infinity, $T(n)$ and $f(n)$ have the same growth order, differing only by a constant factor $c$.
{ class="animation-figure" }
@@ -963,7 +963,7 @@ The following table illustrates examples of different operation counts and their
## 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:
+Let's consider the input data size as $n$. The common types of time complexities are shown in Figure 2-9, arranged from lowest to highest:
$$
\begin{aligned}
@@ -995,7 +995,14 @@ Constant order means the number of operations is independent of the input data s
=== "C++"
```cpp title="time_complexity.cpp"
- [class]{}-[func]{constant}
+ /* Constant complexity */
+ int constant(int n) {
+ int count = 0;
+ int size = 100000;
+ for (int i = 0; i < size; i++)
+ count++;
+ return count;
+ }
```
=== "Java"
@@ -1095,7 +1102,13 @@ Linear order indicates the number of operations grows linearly with the input da
=== "C++"
```cpp title="time_complexity.cpp"
- [class]{}-[func]{linear}
+ /* Linear complexity */
+ int linear(int n) {
+ int count = 0;
+ for (int i = 0; i < n; i++)
+ count++;
+ return count;
+ }
```
=== "Java"
@@ -1193,7 +1206,15 @@ Operations like array traversal and linked list traversal have a time complexity
=== "C++"
```cpp title="time_complexity.cpp"
- [class]{}-[func]{arrayTraversal}
+ /* Linear complexity (traversing an array) */
+ int arrayTraversal(vector &nums) {
+ int count = 0;
+ // Loop count is proportional to the length of the array
+ for (int num : nums) {
+ count++;
+ }
+ return count;
+ }
```
=== "Java"
@@ -1298,7 +1319,17 @@ Quadratic order means the number of operations grows quadratically with the inpu
=== "C++"
```cpp title="time_complexity.cpp"
- [class]{}-[func]{quadratic}
+ /* Quadratic complexity */
+ int quadratic(int n) {
+ int count = 0;
+ // Loop count is squared in relation to the data size n
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < n; j++) {
+ count++;
+ }
+ }
+ return count;
+ }
```
=== "Java"
@@ -1413,7 +1444,24 @@ For instance, in bubble sort, the outer loop runs $n - 1$ times, and the inner l
=== "C++"
```cpp title="time_complexity.cpp"
- [class]{}-[func]{bubbleSort}
+ /* Quadratic complexity (bubble sort) */
+ int bubbleSort(vector &nums) {
+ int count = 0; // Counter
+ // Outer loop: unsorted range is [0, i]
+ for (int i = nums.size() - 1; i > 0; i--) {
+ // Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range
+ for (int j = 0; j < i; j++) {
+ if (nums[j] > nums[j + 1]) {
+ // Swap nums[j] and nums[j + 1]
+ int tmp = nums[j];
+ nums[j] = nums[j + 1];
+ nums[j + 1] = tmp;
+ count += 3; // Element swap includes 3 individual operations
+ }
+ }
+ }
+ return count;
+ }
```
=== "Java"
@@ -1530,7 +1578,19 @@ Figure 2-11 and code simulate the cell division process, with a time complexity
=== "C++"
```cpp title="time_complexity.cpp"
- [class]{}-[func]{exponential}
+ /* Exponential complexity (loop implementation) */
+ int exponential(int n) {
+ int count = 0, base = 1;
+ // Cells split into two every round, forming the sequence 1, 2, 4, 8, ..., 2^(n-1)
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < base; j++) {
+ count++;
+ }
+ base *= 2;
+ }
+ // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
+ return count;
+ }
```
=== "Java"
@@ -1636,7 +1696,12 @@ In practice, exponential order often appears in recursive functions. For example
=== "C++"
```cpp title="time_complexity.cpp"
- [class]{}-[func]{expRecur}
+ /* Exponential complexity (recursive implementation) */
+ int expRecur(int n) {
+ if (n == 1)
+ return 1;
+ return expRecur(n - 1) + expRecur(n - 1) + 1;
+ }
```
=== "Java"
@@ -1739,7 +1804,15 @@ Figure 2-12 and code simulate the "halving each round" process, with a time comp
=== "C++"
```cpp title="time_complexity.cpp"
- [class]{}-[func]{logarithmic}
+ /* Logarithmic complexity (loop implementation) */
+ int logarithmic(int n) {
+ int count = 0;
+ while (n > 1) {
+ n = n / 2;
+ count++;
+ }
+ return count;
+ }
```
=== "Java"
@@ -1841,7 +1914,12 @@ Like exponential order, logarithmic order also frequently appears in recursive f
=== "C++"
```cpp title="time_complexity.cpp"
- [class]{}-[func]{logRecur}
+ /* Logarithmic complexity (recursive implementation) */
+ int logRecur(int n) {
+ if (n <= 1)
+ return 0;
+ return logRecur(n / 2) + 1;
+ }
```
=== "Java"
@@ -1953,7 +2031,16 @@ Linear-logarithmic order often appears in nested loops, with the complexities of
=== "C++"
```cpp title="time_complexity.cpp"
- [class]{}-[func]{linearLogRecur}
+ /* Linear logarithmic complexity */
+ int linearLogRecur(int n) {
+ if (n <= 1)
+ return 1;
+ int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);
+ for (int i = 0; i < n; i++) {
+ count++;
+ }
+ return count;
+ }
```
=== "Java"
@@ -2072,7 +2159,17 @@ Factorials are typically implemented using recursion. As shown in the code and F
=== "C++"
```cpp title="time_complexity.cpp"
- [class]{}-[func]{factorialRecur}
+ /* Factorial complexity (recursive implementation) */
+ int factorialRecur(int n) {
+ if (n == 0)
+ return 1;
+ int count = 0;
+ // From 1 split into n
+ for (int i = 0; i < n; i++) {
+ count += factorialRecur(n - 1);
+ }
+ return count;
+ }
```
=== "Java"
@@ -2196,9 +2293,30 @@ The "worst-case time complexity" corresponds to the asymptotic upper bound, deno
=== "C++"
```cpp title="worst_best_time_complexity.cpp"
- [class]{}-[func]{randomNumbers}
+ /* Generate an array with elements {1, 2, ..., n} in a randomly shuffled order */
+ vector randomNumbers(int n) {
+ vector nums(n);
+ // Generate array nums = { 1, 2, 3, ..., n }
+ for (int i = 0; i < n; i++) {
+ nums[i] = i + 1;
+ }
+ // Generate a random seed using system time
+ unsigned seed = chrono::system_clock::now().time_since_epoch().count();
+ // Randomly shuffle array elements
+ shuffle(nums.begin(), nums.end(), default_random_engine(seed));
+ return nums;
+ }
- [class]{}-[func]{findOne}
+ /* Find the index of number 1 in array nums */
+ int findOne(vector &nums) {
+ for (int i = 0; i < nums.size(); i++) {
+ // When element 1 is at the start of the array, achieve best time complexity O(1)
+ // When element 1 is at the end of the array, achieve worst time complexity O(n)
+ if (nums[i] == 1)
+ return i;
+ }
+ return -1;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_data_structure/number_encoding.md b/en/docs/chapter_data_structure/number_encoding.md
index 0398837f2..29275072d 100644
--- a/en/docs/chapter_data_structure/number_encoding.md
+++ b/en/docs/chapter_data_structure/number_encoding.md
@@ -18,7 +18,7 @@ Firstly, it's important to note that **numbers are stored in computers using the
- **One's complement**: The one's complement of a positive number is the same as its sign-magnitude. For negative numbers, it's obtained by inverting all bits except the sign bit.
- **Two's complement**: The two's complement of a positive number is the same as its sign-magnitude. For negative numbers, it's obtained by adding $1$ to their one's complement.
-The following diagram illustrates the conversions among sign-magnitude, one's complement, and two's complement:
+Figure 3-4 illustrates the conversions among sign-magnitude, one's complement, and two's complement:
{ class="animation-figure" }
@@ -133,7 +133,7 @@ $$
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:
+Observing Figure 3-5, given an example data $\mathrm{S} = 0$, $\mathrm{E} = 124$, $\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$, we have:
$$
\text{val} = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875
diff --git a/en/docs/chapter_divide_and_conquer/binary_search_recur.md b/en/docs/chapter_divide_and_conquer/binary_search_recur.md
index 1b502be49..9b1a289da 100644
--- a/en/docs/chapter_divide_and_conquer/binary_search_recur.md
+++ b/en/docs/chapter_divide_and_conquer/binary_search_recur.md
@@ -38,7 +38,7 @@ Starting from the original problem $f(0, n-1)$, perform the binary search throug
2. Recursively solve the subproblem reduced by half in size, which could be $f(i, m-1)$ or $f(m+1, j)$.
3. Repeat steps `1.` and `2.`, until `target` is found or the interval is empty and returns.
-The diagram below shows the divide-and-conquer process of binary search for element $6$ in an array.
+Figure 12-4 shows the divide-and-conquer process of binary search for element $6$ in an array.
{ class="animation-figure" }
@@ -76,9 +76,32 @@ In the implementation code, we declare a recursive function `dfs()` to solve the
=== "C++"
```cpp title="binary_search_recur.cpp"
- [class]{}-[func]{dfs}
+ /* Binary search: problem f(i, j) */
+ int dfs(vector &nums, int target, int i, int j) {
+ // If the interval is empty, indicating no target element, return -1
+ if (i > j) {
+ return -1;
+ }
+ // Calculate midpoint index m
+ int m = (i + j) / 2;
+ if (nums[m] < target) {
+ // Recursive subproblem f(m+1, j)
+ return dfs(nums, target, m + 1, j);
+ } else if (nums[m] > target) {
+ // Recursive subproblem f(i, m-1)
+ return dfs(nums, target, i, m - 1);
+ } else {
+ // Found the target element, thus return its index
+ return m;
+ }
+ }
- [class]{}-[func]{binarySearch}
+ /* Binary search */
+ int binarySearch(vector &nums, int target) {
+ int n = nums.size();
+ // Solve problem f(0, n-1)
+ return dfs(nums, target, 0, n - 1);
+ }
```
=== "Java"
diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md
index bfe2cc893..055d0cd3a 100644
--- a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md
+++ b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md
@@ -6,7 +6,7 @@ comments: true
!!! question
- Given the preorder traversal `preorder` and inorder traversal `inorder` of a binary tree, construct the binary tree and return the root node of the binary tree. Assume that there are no duplicate values in the nodes of the binary tree (as shown in the diagram below).
+ Given the preorder traversal `preorder` and inorder traversal `inorder` of a binary tree, construct the binary tree and return the root node of the binary tree. Assume that there are no duplicate values in the nodes of the binary tree (as shown in Figure 12-5).
{ class="animation-figure" }
@@ -26,10 +26,10 @@ Based on the above analysis, this problem can be solved using divide and conquer
By definition, `preorder` and `inorder` can be divided into three parts.
-- Preorder traversal: `[ Root | Left Subtree | Right Subtree ]`, for example, the tree in the diagram corresponds to `[ 3 | 9 | 2 1 7 ]`.
-- Inorder traversal: `[ Left Subtree | Root | Right Subtree ]`, for example, the tree in the diagram corresponds to `[ 9 | 3 | 1 2 7 ]`.
+- Preorder traversal: `[ Root | Left Subtree | Right Subtree ]`, for example, the tree in the figure corresponds to `[ 3 | 9 | 2 1 7 ]`.
+- Inorder traversal: `[ Left Subtree | Root | Right Subtree ]`, for example, the tree in the figure corresponds to `[ 9 | 3 | 1 2 7 ]`.
-Using the data in the diagram above, we can obtain the division results as shown in the steps below.
+Using the data in the figure above, we can obtain the division results as shown in Figure 12-6.
1. The first element 3 in the preorder traversal is the value of the root node.
2. Find the index of the root node 3 in `inorder`, and use this index to divide `inorder` into `[ 9 | 3 | 1 2 7 ]`.
@@ -61,7 +61,7 @@ As shown in Table 12-1, the above variables can represent the index of the root
-Please note, the meaning of $(m-l)$ in the right subtree root index is "the number of nodes in the left subtree", which is suggested to be understood in conjunction with the diagram below.
+Please note, the meaning of $(m-l)$ in the right subtree root index is "the number of nodes in the left subtree", which is suggested to be understood in conjunction with Figure 12-7.
{ class="animation-figure" }
@@ -107,9 +107,33 @@ To improve the efficiency of querying $m$, we use a hash table `hmap` to store t
=== "C++"
```cpp title="build_tree.cpp"
- [class]{}-[func]{dfs}
+ /* Build binary tree: Divide and conquer */
+ TreeNode *dfs(vector &preorder, unordered_map &inorderMap, int i, int l, int r) {
+ // Terminate when subtree interval is empty
+ if (r - l < 0)
+ return NULL;
+ // Initialize root node
+ TreeNode *root = new TreeNode(preorder[i]);
+ // Query m to divide left and right subtrees
+ int m = inorderMap[preorder[i]];
+ // Subproblem: build left subtree
+ root->left = dfs(preorder, inorderMap, i + 1, l, m - 1);
+ // Subproblem: build right subtree
+ root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);
+ // Return root node
+ return root;
+ }
- [class]{}-[func]{buildTree}
+ /* Build binary tree */
+ TreeNode *buildTree(vector &preorder, vector &inorder) {
+ // Initialize hash table, storing in-order elements to indices mapping
+ unordered_map inorderMap;
+ for (int i = 0; i < inorder.size(); i++) {
+ inorderMap[inorder[i]] = i;
+ }
+ TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1);
+ return root;
+ }
```
=== "Java"
@@ -232,7 +256,7 @@ To improve the efficiency of querying $m$, we use a hash table `hmap` to store t
[class]{}-[func]{buildTree}
```
-The diagram below shows the recursive process of building the binary tree, where each node is established during the "descending" process, and each edge (reference) is established during the "ascending" process.
+Figure 12-8 shows the recursive process of building the binary tree, where each node is established during the "descending" process, and each edge (reference) is established during the "ascending" process.
=== "<1>"
{ class="animation-figure" }
@@ -263,7 +287,7 @@ The diagram below shows the recursive process of building the binary tree, where
Figure 12-8 Recursive process of building a binary tree
-Each recursive function's division results of `preorder` and `inorder` are shown in the diagram below.
+Each recursive function's division results of `preorder` and `inorder` are shown in Figure 12-9.
{ class="animation-figure" }
diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.md b/en/docs/chapter_divide_and_conquer/hanota_problem.md
index e4393f31f..9d51ef496 100644
--- a/en/docs/chapter_divide_and_conquer/hanota_problem.md
+++ b/en/docs/chapter_divide_and_conquer/hanota_problem.md
@@ -129,11 +129,36 @@ In the code, we declare a recursive function `dfs(i, src, buf, tar)` whose role
=== "C++"
```cpp title="hanota.cpp"
- [class]{}-[func]{move}
+ /* Move a disc */
+ void move(vector &src, vector &tar) {
+ // Take out a disc from the top of src
+ int pan = src.back();
+ src.pop_back();
+ // Place the disc on top of tar
+ tar.push_back(pan);
+ }
- [class]{}-[func]{dfs}
+ /* Solve the Tower of Hanoi problem f(i) */
+ void dfs(int i, vector &src, vector &buf, vector &tar) {
+ // If only one disc remains on src, move it to tar
+ if (i == 1) {
+ move(src, tar);
+ return;
+ }
+ // Subproblem f(i-1): move the top i-1 discs from src with the help of tar to buf
+ dfs(i - 1, src, tar, buf);
+ // Subproblem f(1): move the remaining one disc from src to tar
+ move(src, tar);
+ // Subproblem f(i-1): move the top i-1 discs from buf with the help of src to tar
+ dfs(i - 1, buf, src, tar);
+ }
- [class]{}-[func]{solveHanota}
+ /* Solve the Tower of Hanoi problem */
+ void solveHanota(vector &A, vector &B, vector &C) {
+ int n = A.size();
+ // Move the top n discs from A with the help of B to C
+ dfs(n, A, B, C);
+ }
```
=== "Java"
diff --git a/en/docs/chapter_dynamic_programming/dp_problem_features.md b/en/docs/chapter_dynamic_programming/dp_problem_features.md
index 369ad6ff9..dcb60fbbf 100644
--- a/en/docs/chapter_dynamic_programming/dp_problem_features.md
+++ b/en/docs/chapter_dynamic_programming/dp_problem_features.md
@@ -61,7 +61,22 @@ According to the state transition equation, and the initial states $dp[1] = cost
=== "C++"
```cpp title="min_cost_climbing_stairs_dp.cpp"
- [class]{}-[func]{minCostClimbingStairsDP}
+ /* Climbing stairs with minimum cost: Dynamic programming */
+ int minCostClimbingStairsDP(vector &cost) {
+ int n = cost.size() - 1;
+ if (n == 1 || n == 2)
+ return cost[n];
+ // Initialize dp table, used to store subproblem solutions
+ vector dp(n + 1);
+ // Initial state: preset the smallest subproblem solution
+ dp[1] = cost[1];
+ dp[2] = cost[2];
+ // State transition: gradually solve larger subproblems from smaller ones
+ for (int i = 3; i <= n; i++) {
+ dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];
+ }
+ return dp[n];
+ }
```
=== "Java"
@@ -176,7 +191,19 @@ This problem can also be space-optimized, compressing one dimension to zero, red
=== "C++"
```cpp title="min_cost_climbing_stairs_dp.cpp"
- [class]{}-[func]{minCostClimbingStairsDPComp}
+ /* Climbing stairs with minimum cost: Space-optimized dynamic programming */
+ int minCostClimbingStairsDPComp(vector &cost) {
+ int n = cost.size() - 1;
+ if (n == 1 || n == 2)
+ return cost[n];
+ int a = cost[1], b = cost[2];
+ for (int i = 3; i <= n; i++) {
+ int tmp = b;
+ b = min(a, tmp) + cost[i];
+ a = tmp;
+ }
+ return b;
+ }
```
=== "Java"
@@ -327,7 +354,25 @@ In the end, returning $dp[n, 1] + dp[n, 2]$ will do, the sum of the two represen
=== "C++"
```cpp title="climbing_stairs_constraint_dp.cpp"
- [class]{}-[func]{climbingStairsConstraintDP}
+ /* Constrained climbing stairs: Dynamic programming */
+ int climbingStairsConstraintDP(int n) {
+ if (n == 1 || n == 2) {
+ return 1;
+ }
+ // Initialize dp table, used to store subproblem solutions
+ vector> dp(n + 1, vector(3, 0));
+ // Initial state: preset the smallest subproblem solution
+ dp[1][1] = 1;
+ dp[1][2] = 0;
+ dp[2][1] = 0;
+ dp[2][2] = 1;
+ // State transition: gradually solve larger subproblems from smaller ones
+ for (int i = 3; i <= n; i++) {
+ dp[i][1] = dp[i - 1][2];
+ dp[i][2] = dp[i - 2][1] + dp[i - 2][2];
+ }
+ return dp[n][1] + dp[n][2];
+ }
```
=== "Java"
diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.md b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.md
index 7b46f8a2f..34f855d9e 100644
--- a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.md
+++ b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.md
@@ -133,7 +133,22 @@ Implementation code as follows:
=== "C++"
```cpp title="min_path_sum.cpp"
- [class]{}-[func]{minPathSumDFS}
+ /* Minimum path sum: Brute force search */
+ int minPathSumDFS(vector> &grid, int i, int j) {
+ // If it's the top-left cell, terminate the search
+ if (i == 0 && j == 0) {
+ return grid[0][0];
+ }
+ // If the row or column index is out of bounds, return a +∞ cost
+ if (i < 0 || j < 0) {
+ return INT_MAX;
+ }
+ // Calculate the minimum path cost from the top-left to (i-1, j) and (i, j-1)
+ int up = minPathSumDFS(grid, i - 1, j);
+ int left = minPathSumDFS(grid, i, j - 1);
+ // Return the minimum path cost from the top-left to (i, j)
+ return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;
+ }
```
=== "Java"
@@ -264,7 +279,27 @@ We introduce a memo list `mem` of the same size as the grid `grid`, used to reco
=== "C++"
```cpp title="min_path_sum.cpp"
- [class]{}-[func]{minPathSumDFSMem}
+ /* Minimum path sum: Memoized search */
+ int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) {
+ // If it's the top-left cell, terminate the search
+ if (i == 0 && j == 0) {
+ return grid[0][0];
+ }
+ // If the row or column index is out of bounds, return a +∞ cost
+ if (i < 0 || j < 0) {
+ return INT_MAX;
+ }
+ // If there is a record, return it
+ if (mem[i][j] != -1) {
+ return mem[i][j];
+ }
+ // The minimum path cost from the left and top cells
+ int up = minPathSumDFSMem(grid, mem, i - 1, j);
+ int left = minPathSumDFSMem(grid, mem, i, j - 1);
+ // Record and return the minimum path cost from the top-left to (i, j)
+ mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;
+ return mem[i][j];
+ }
```
=== "Java"
@@ -394,7 +429,28 @@ Implement the dynamic programming solution iteratively, code as shown below:
=== "C++"
```cpp title="min_path_sum.cpp"
- [class]{}-[func]{minPathSumDP}
+ /* Minimum path sum: Dynamic programming */
+ int minPathSumDP(vector> &grid) {
+ int n = grid.size(), m = grid[0].size();
+ // Initialize dp table
+ vector> dp(n, vector(m));
+ dp[0][0] = grid[0][0];
+ // State transition: first row
+ for (int j = 1; j < m; j++) {
+ dp[0][j] = dp[0][j - 1] + grid[0][j];
+ }
+ // State transition: first column
+ for (int i = 1; i < n; i++) {
+ dp[i][0] = dp[i - 1][0] + grid[i][0];
+ }
+ // State transition: the rest of the rows and columns
+ for (int i = 1; i < n; i++) {
+ for (int j = 1; j < m; j++) {
+ dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];
+ }
+ }
+ return dp[n - 1][m - 1];
+ }
```
=== "Java"
@@ -563,7 +619,27 @@ Please note, since the array `dp` can only represent the state of one row, we ca
=== "C++"
```cpp title="min_path_sum.cpp"
- [class]{}-[func]{minPathSumDPComp}
+ /* Minimum path sum: Space-optimized dynamic programming */
+ int minPathSumDPComp(vector> &grid) {
+ int n = grid.size(), m = grid[0].size();
+ // Initialize dp table
+ vector dp(m);
+ // State transition: first row
+ dp[0] = grid[0][0];
+ for (int j = 1; j < m; j++) {
+ dp[j] = dp[j - 1] + grid[0][j];
+ }
+ // State transition: the rest of the rows
+ for (int i = 1; i < n; i++) {
+ // State transition: first column
+ dp[0] = dp[0] + grid[i][0];
+ // State transition: the rest of the columns
+ for (int j = 1; j < m; j++) {
+ dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];
+ }
+ }
+ return dp[m - 1];
+ }
```
=== "Java"
diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.md b/en/docs/chapter_dynamic_programming/edit_distance_problem.md
index c9efb6536..2442659fc 100644
--- a/en/docs/chapter_dynamic_programming/edit_distance_problem.md
+++ b/en/docs/chapter_dynamic_programming/edit_distance_problem.md
@@ -104,7 +104,31 @@ Observing the state transition equation, solving $dp[i, j]$ depends on the solut
=== "C++"
```cpp title="edit_distance.cpp"
- [class]{}-[func]{editDistanceDP}
+ /* Edit distance: Dynamic programming */
+ int editDistanceDP(string s, string t) {
+ int n = s.length(), m = t.length();
+ vector> dp(n + 1, vector(m + 1, 0));
+ // State transition: first row and first column
+ for (int i = 1; i <= n; i++) {
+ dp[i][0] = i;
+ }
+ for (int j = 1; j <= m; j++) {
+ dp[0][j] = j;
+ }
+ // State transition: the rest of the rows and columns
+ for (int i = 1; i <= n; i++) {
+ for (int j = 1; j <= m; j++) {
+ if (s[i - 1] == t[j - 1]) {
+ // If the two characters are equal, skip these two characters
+ dp[i][j] = dp[i - 1][j - 1];
+ } else {
+ // The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1
+ dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
+ }
+ }
+ }
+ return dp[n][m];
+ }
```
=== "Java"
@@ -289,7 +313,34 @@ For this reason, we can use a variable `leftup` to temporarily store the solutio
=== "C++"
```cpp title="edit_distance.cpp"
- [class]{}-[func]{editDistanceDPComp}
+ /* Edit distance: Space-optimized dynamic programming */
+ int editDistanceDPComp(string s, string t) {
+ int n = s.length(), m = t.length();
+ vector dp(m + 1, 0);
+ // State transition: first row
+ for (int j = 1; j <= m; j++) {
+ dp[j] = j;
+ }
+ // State transition: the rest of the rows
+ for (int i = 1; i <= n; i++) {
+ // State transition: first column
+ int leftup = dp[0]; // Temporarily store dp[i-1, j-1]
+ dp[0] = i;
+ // State transition: the rest of the columns
+ for (int j = 1; j <= m; j++) {
+ int temp = dp[j];
+ if (s[i - 1] == t[j - 1]) {
+ // If the two characters are equal, skip these two characters
+ dp[j] = leftup;
+ } else {
+ // The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1
+ dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;
+ }
+ leftup = temp; // Update for the next round of dp[i-1, j-1]
+ }
+ }
+ return dp[m];
+ }
```
=== "Java"
diff --git a/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md
index 382580a6a..2bab23f9d 100644
--- a/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md
+++ b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md
@@ -49,9 +49,30 @@ The goal of this problem is to determine the number of ways, **considering using
=== "C++"
```cpp title="climbing_stairs_backtrack.cpp"
- [class]{}-[func]{backtrack}
+ /* Backtracking */
+ void backtrack(vector &choices, int state, int n, vector &res) {
+ // When climbing to the nth step, add 1 to the number of solutions
+ if (state == n)
+ res[0]++;
+ // Traverse all choices
+ for (auto &choice : choices) {
+ // Pruning: do not allow climbing beyond the nth step
+ if (state + choice > n)
+ continue;
+ // Attempt: make a choice, update the state
+ backtrack(choices, state + choice, n, res);
+ // Retract
+ }
+ }
- [class]{}-[func]{climbingStairsBacktrack}
+ /* Climbing stairs: Backtracking */
+ int climbingStairsBacktrack(int n) {
+ vector choices = {1, 2}; // Can choose to climb up 1 step or 2 steps
+ int state = 0; // Start climbing from the 0th step
+ vector res = {0}; // Use res[0] to record the number of solutions
+ backtrack(choices, state, n, res);
+ return res[0];
+ }
```
=== "Java"
@@ -220,9 +241,20 @@ Observe the following code, which, like standard backtracking code, belongs to d
=== "C++"
```cpp title="climbing_stairs_dfs.cpp"
- [class]{}-[func]{dfs}
+ /* Search */
+ int dfs(int i) {
+ // Known dp[1] and dp[2], return them
+ if (i == 1 || i == 2)
+ return i;
+ // dp[i] = dp[i-1] + dp[i-2]
+ int count = dfs(i - 1) + dfs(i - 2);
+ return count;
+ }
- [class]{}-[func]{climbingStairsDFS}
+ /* Climbing stairs: Search */
+ int climbingStairsDFS(int n) {
+ return dfs(n);
+ }
```
=== "Java"
@@ -378,9 +410,27 @@ The code is as follows:
=== "C++"
```cpp title="climbing_stairs_dfs_mem.cpp"
- [class]{}-[func]{dfs}
+ /* Memoized search */
+ int dfs(int i, vector &mem) {
+ // Known dp[1] and dp[2], return them
+ if (i == 1 || i == 2)
+ return i;
+ // If there is a record for dp[i], return it
+ if (mem[i] != -1)
+ return mem[i];
+ // dp[i] = dp[i-1] + dp[i-2]
+ int count = dfs(i - 1, mem) + dfs(i - 2, mem);
+ // Record dp[i]
+ mem[i] = count;
+ return count;
+ }
- [class]{}-[func]{climbingStairsDFSMem}
+ /* Climbing stairs: Memoized search */
+ int climbingStairsDFSMem(int n) {
+ // mem[i] records the total number of solutions for climbing to the ith step, -1 means no record
+ vector mem(n + 1, -1);
+ return dfs(n, mem);
+ }
```
=== "Java"
@@ -532,7 +582,21 @@ Since dynamic programming does not include a backtracking process, it only requi
=== "C++"
```cpp title="climbing_stairs_dp.cpp"
- [class]{}-[func]{climbingStairsDP}
+ /* Climbing stairs: Dynamic programming */
+ int climbingStairsDP(int n) {
+ if (n == 1 || n == 2)
+ return n;
+ // Initialize dp table, used to store subproblem solutions
+ vector dp(n + 1);
+ // Initial state: preset the smallest subproblem solution
+ dp[1] = 1;
+ dp[2] = 2;
+ // State transition: gradually solve larger subproblems from smaller ones
+ for (int i = 3; i <= n; i++) {
+ dp[i] = dp[i - 1] + dp[i - 2];
+ }
+ return dp[n];
+ }
```
=== "Java"
@@ -655,7 +719,18 @@ Observant readers may have noticed that **since $dp[i]$ is only related to $dp[i
=== "C++"
```cpp title="climbing_stairs_dp.cpp"
- [class]{}-[func]{climbingStairsDPComp}
+ /* Climbing stairs: Space-optimized dynamic programming */
+ int climbingStairsDPComp(int n) {
+ if (n == 1 || n == 2)
+ return n;
+ int a = 1, b = 2;
+ for (int i = 3; i <= n; i++) {
+ int tmp = b;
+ b = a + b;
+ a = tmp;
+ }
+ return b;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.md b/en/docs/chapter_dynamic_programming/knapsack_problem.md
index 8d80be9b2..ca7f6afca 100644
--- a/en/docs/chapter_dynamic_programming/knapsack_problem.md
+++ b/en/docs/chapter_dynamic_programming/knapsack_problem.md
@@ -83,7 +83,22 @@ The search code includes the following elements.
=== "C++"
```cpp title="knapsack.cpp"
- [class]{}-[func]{knapsackDFS}
+ /* 0-1 Knapsack: Brute force search */
+ int knapsackDFS(vector &wgt, vector &val, int i, int c) {
+ // If all items have been chosen or the knapsack has no remaining capacity, return value 0
+ if (i == 0 || c == 0) {
+ return 0;
+ }
+ // If exceeding the knapsack capacity, can only choose not to put it in the knapsack
+ if (wgt[i - 1] > c) {
+ return knapsackDFS(wgt, val, i - 1, c);
+ }
+ // Calculate the maximum value of not putting in and putting in item i
+ int no = knapsackDFS(wgt, val, i - 1, c);
+ int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];
+ // Return the greater value of the two options
+ return max(no, yes);
+ }
```
=== "Java"
@@ -214,7 +229,27 @@ After introducing memoization, **the time complexity depends on the number of su
=== "C++"
```cpp title="knapsack.cpp"
- [class]{}-[func]{knapsackDFSMem}
+ /* 0-1 Knapsack: Memoized search */
+ int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) {
+ // If all items have been chosen or the knapsack has no remaining capacity, return value 0
+ if (i == 0 || c == 0) {
+ return 0;
+ }
+ // If there is a record, return it
+ if (mem[i][c] != -1) {
+ return mem[i][c];
+ }
+ // If exceeding the knapsack capacity, can only choose not to put it in the knapsack
+ if (wgt[i - 1] > c) {
+ return knapsackDFSMem(wgt, val, mem, i - 1, c);
+ }
+ // Calculate the maximum value of not putting in and putting in item i
+ int no = knapsackDFSMem(wgt, val, mem, i - 1, c);
+ int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];
+ // Record and return the greater value of the two options
+ mem[i][c] = max(no, yes);
+ return mem[i][c];
+ }
```
=== "Java"
@@ -342,7 +377,25 @@ Dynamic programming essentially involves filling the $dp$ table during the state
=== "C++"
```cpp title="knapsack.cpp"
- [class]{}-[func]{knapsackDP}
+ /* 0-1 Knapsack: Dynamic programming */
+ int knapsackDP(vector &wgt, vector &val, int cap) {
+ int n = wgt.size();
+ // Initialize dp table
+ vector> dp(n + 1, vector(cap + 1, 0));
+ // State transition
+ for (int i = 1; i <= n; i++) {
+ for (int c = 1; c <= cap; c++) {
+ if (wgt[i - 1] > c) {
+ // If exceeding the knapsack capacity, do not choose item i
+ dp[i][c] = dp[i - 1][c];
+ } else {
+ // The greater value between not choosing and choosing item i
+ dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);
+ }
+ }
+ }
+ return dp[n][cap];
+ }
```
=== "Java"
@@ -435,7 +488,7 @@ Dynamic programming essentially involves filling the $dp$ table during the state
[class]{}-[func]{knapsackDP}
```
-As shown in the figures below, both the time complexity and space complexity are determined by the size of the array `dp`, i.e., $O(n \times cap)$.
+As shown in Figure 14-20, both the time complexity and space complexity are determined by the size of the array `dp`, i.e., $O(n \times cap)$.
=== "<1>"
{ class="animation-figure" }
@@ -538,7 +591,23 @@ In the code implementation, we only need to delete the first dimension $i$ of th
=== "C++"
```cpp title="knapsack.cpp"
- [class]{}-[func]{knapsackDPComp}
+ /* 0-1 Knapsack: Space-optimized dynamic programming */
+ int knapsackDPComp(vector &wgt, vector &val, int cap) {
+ int n = wgt.size();
+ // Initialize dp table
+ vector dp(cap + 1, 0);
+ // State transition
+ for (int i = 1; i <= n; i++) {
+ // Traverse in reverse order
+ for (int c = cap; c >= 1; c--) {
+ if (wgt[i - 1] <= c) {
+ // The greater value between not choosing and choosing item i
+ dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);
+ }
+ }
+ }
+ return dp[cap];
+ }
```
=== "Java"
diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md
index ea7a400fd..d3f49df4e 100644
--- a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md
+++ b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md
@@ -61,7 +61,25 @@ Comparing the code for the two problems, the state transition changes from $i-1$
=== "C++"
```cpp title="unbounded_knapsack.cpp"
- [class]{}-[func]{unboundedKnapsackDP}
+ /* Complete knapsack: Dynamic programming */
+ int unboundedKnapsackDP(vector &wgt, vector &val, int cap) {
+ int n = wgt.size();
+ // Initialize dp table
+ vector> dp(n + 1, vector(cap + 1, 0));
+ // State transition
+ for (int i = 1; i <= n; i++) {
+ for (int c = 1; c <= cap; c++) {
+ if (wgt[i - 1] > c) {
+ // If exceeding the knapsack capacity, do not choose item i
+ dp[i][c] = dp[i - 1][c];
+ } else {
+ // The greater value between not choosing and choosing item i
+ dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);
+ }
+ }
+ }
+ return dp[n][cap];
+ }
```
=== "Java"
@@ -206,7 +224,25 @@ The code implementation is quite simple, just remove the first dimension of the
=== "C++"
```cpp title="unbounded_knapsack.cpp"
- [class]{}-[func]{unboundedKnapsackDPComp}
+ /* Complete knapsack: Space-optimized dynamic programming */
+ int unboundedKnapsackDPComp(vector &wgt, vector &val, int cap) {
+ int n = wgt.size();
+ // Initialize dp table
+ vector dp(cap + 1, 0);
+ // State transition
+ for (int i = 1; i <= n; i++) {
+ for (int c = 1; c <= cap; c++) {
+ if (wgt[i - 1] > c) {
+ // If exceeding the knapsack capacity, do not choose item i
+ dp[c] = dp[c];
+ } else {
+ // The greater value between not choosing and choosing item i
+ dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);
+ }
+ }
+ }
+ return dp[cap];
+ }
```
=== "Java"
@@ -375,7 +411,30 @@ For this reason, we use the number $amt + 1$ to represent an invalid solution, b
=== "C++"
```cpp title="coin_change.cpp"
- [class]{}-[func]{coinChangeDP}
+ /* Coin change: Dynamic programming */
+ int coinChangeDP(vector &coins, int amt) {
+ int n = coins.size();
+ int MAX = amt + 1;
+ // Initialize dp table
+ vector> dp(n + 1, vector(amt + 1, 0));
+ // State transition: first row and first column
+ for (int a = 1; a <= amt; a++) {
+ dp[0][a] = MAX;
+ }
+ // State transition: the rest of the rows and columns
+ for (int i = 1; i <= n; i++) {
+ for (int a = 1; a <= amt; a++) {
+ if (coins[i - 1] > a) {
+ // If exceeding the target amount, do not choose coin i
+ dp[i][a] = dp[i - 1][a];
+ } else {
+ // The smaller value between not choosing and choosing coin i
+ dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);
+ }
+ }
+ }
+ return dp[n][amt] != MAX ? dp[n][amt] : -1;
+ }
```
=== "Java"
@@ -552,7 +611,27 @@ The space optimization for the coin change problem is handled in the same way as
=== "C++"
```cpp title="coin_change.cpp"
- [class]{}-[func]{coinChangeDPComp}
+ /* Coin change: Space-optimized dynamic programming */
+ int coinChangeDPComp(vector &coins, int amt) {
+ int n = coins.size();
+ int MAX = amt + 1;
+ // Initialize dp table
+ vector dp(amt + 1, MAX);
+ dp[0] = 0;
+ // State transition
+ for (int i = 1; i <= n; i++) {
+ for (int a = 1; a <= amt; a++) {
+ if (coins[i - 1] > a) {
+ // If exceeding the target amount, do not choose coin i
+ dp[a] = dp[a];
+ } else {
+ // The smaller value between not choosing and choosing coin i
+ dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);
+ }
+ }
+ }
+ return dp[amt] != MAX ? dp[amt] : -1;
+ }
```
=== "Java"
@@ -698,7 +777,29 @@ When the target amount is $0$, no coins are needed to make up the target amount,
=== "C++"
```cpp title="coin_change_ii.cpp"
- [class]{}-[func]{coinChangeIIDP}
+ /* Coin change II: Dynamic programming */
+ int coinChangeIIDP(vector &coins, int amt) {
+ int n = coins.size();
+ // Initialize dp table
+ vector> dp(n + 1, vector(amt + 1, 0));
+ // Initialize first column
+ for (int i = 0; i <= n; i++) {
+ dp[i][0] = 1;
+ }
+ // State transition
+ for (int i = 1; i <= n; i++) {
+ for (int a = 1; a <= amt; a++) {
+ if (coins[i - 1] > a) {
+ // If exceeding the target amount, do not choose coin i
+ dp[i][a] = dp[i - 1][a];
+ } else {
+ // The sum of the two options of not choosing and choosing coin i
+ dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];
+ }
+ }
+ }
+ return dp[n][amt];
+ }
```
=== "Java"
@@ -824,7 +925,26 @@ The space optimization approach is the same, just remove the coin dimension:
=== "C++"
```cpp title="coin_change_ii.cpp"
- [class]{}-[func]{coinChangeIIDPComp}
+ /* Coin change II: Space-optimized dynamic programming */
+ int coinChangeIIDPComp(vector &coins, int amt) {
+ int n = coins.size();
+ // Initialize dp table
+ vector dp(amt + 1, 0);
+ dp[0] = 1;
+ // State transition
+ for (int i = 1; i <= n; i++) {
+ for (int a = 1; a <= amt; a++) {
+ if (coins[i - 1] > a) {
+ // If exceeding the target amount, do not choose coin i
+ dp[a] = dp[a];
+ } else {
+ // The sum of the two options of not choosing and choosing coin i
+ dp[a] = dp[a] + dp[a - coins[i - 1]];
+ }
+ }
+ }
+ return dp[amt];
+ }
```
=== "Java"
diff --git a/en/docs/chapter_graph/graph_operations.md b/en/docs/chapter_graph/graph_operations.md
index 266868ece..4684aa580 100644
--- a/en/docs/chapter_graph/graph_operations.md
+++ b/en/docs/chapter_graph/graph_operations.md
@@ -50,7 +50,7 @@ Below is the implementation code for graphs represented using an adjacency matri
for val in vertices:
self.add_vertex(val)
# Add edge
- # Please note, edges elements represent vertex indices, corresponding to vertices elements indices
+ # Edges elements represent vertex indices
for e in edges:
self.add_edge(e[0], e[1])
@@ -111,7 +111,89 @@ Below is the implementation code for graphs represented using an adjacency matri
=== "C++"
```cpp title="graph_adjacency_matrix.cpp"
- [class]{GraphAdjMat}-[func]{}
+ /* Undirected graph class based on adjacency matrix */
+ class GraphAdjMat {
+ vector vertices; // Vertex list, elements represent "vertex value", index represents "vertex index"
+ vector> adjMat; // Adjacency matrix, row and column indices correspond to "vertex index"
+
+ public:
+ /* Constructor */
+ GraphAdjMat(const vector &vertices, const vector> &edges) {
+ // Add vertex
+ for (int val : vertices) {
+ addVertex(val);
+ }
+ // Add edge
+ // Edges elements represent vertex indices
+ for (const vector &edge : edges) {
+ addEdge(edge[0], edge[1]);
+ }
+ }
+
+ /* Get the number of vertices */
+ int size() const {
+ return vertices.size();
+ }
+
+ /* Add vertex */
+ void addVertex(int val) {
+ int n = size();
+ // Add new vertex value to the vertex list
+ vertices.push_back(val);
+ // Add a row to the adjacency matrix
+ adjMat.emplace_back(vector(n, 0));
+ // Add a column to the adjacency matrix
+ for (vector &row : adjMat) {
+ row.push_back(0);
+ }
+ }
+
+ /* Remove vertex */
+ void removeVertex(int index) {
+ if (index >= size()) {
+ throw out_of_range("Vertex does not exist");
+ }
+ // Remove vertex at `index` from the vertex list
+ vertices.erase(vertices.begin() + index);
+ // Remove the row at `index` from the adjacency matrix
+ adjMat.erase(adjMat.begin() + index);
+ // Remove the column at `index` from the adjacency matrix
+ for (vector &row : adjMat) {
+ row.erase(row.begin() + index);
+ }
+ }
+
+ /* Add edge */
+ // Parameters i, j correspond to vertices element indices
+ void addEdge(int i, int j) {
+ // Handle index out of bounds and equality
+ if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {
+ throw out_of_range("Vertex does not exist");
+ }
+ // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., satisfies (i, j) == (j, i)
+ adjMat[i][j] = 1;
+ adjMat[j][i] = 1;
+ }
+
+ /* Remove edge */
+ // Parameters i, j correspond to vertices element indices
+ void removeEdge(int i, int j) {
+ // Handle index out of bounds and equality
+ if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {
+ throw out_of_range("Vertex does not exist");
+ }
+ adjMat[i][j] = 0;
+ adjMat[j][i] = 0;
+ }
+
+ /* Print adjacency matrix */
+ void print() {
+ cout << "Vertex list = ";
+ printVector(vertices);
+ cout << "Adjacency matrix =" << endl;
+ printVectorMatrix(adjMat);
+ }
+ };
```
=== "Java"
@@ -131,7 +213,7 @@ Below is the implementation code for graphs represented using an adjacency matri
addVertex(val);
}
// Add edge
- // Please note, edges elements represent vertex indices, corresponding to vertices elements indices
+ // Edges elements represent vertex indices
for (int[] e : edges) {
addEdge(e[0], e[1]);
}
@@ -297,7 +379,7 @@ Given an undirected graph with a total of $n$ vertices and $m$ edges, the variou
Figure 9-8 Initialization, adding and removing edges, adding and removing vertices in adjacency list
-Below is the adjacency list code implementation. Compared to the above diagram, the actual code has the following differences.
+Below is the adjacency list code implementation. Compared to Figure 9-8, the actual code has the following differences.
- For convenience in adding and removing vertices, and to simplify the code, we use lists (dynamic arrays) instead of linked lists.
- Use a hash table to store the adjacency list, `key` being the vertex instance, `value` being the list (linked list) of adjacent vertices of that vertex.
@@ -369,7 +451,86 @@ Additionally, we use the `Vertex` class to represent vertices in the adjacency l
=== "C++"
```cpp title="graph_adjacency_list.cpp"
- [class]{GraphAdjList}-[func]{}
+ /* Undirected graph class based on adjacency list */
+ class GraphAdjList {
+ public:
+ // Adjacency list, key: vertex, value: all adjacent vertices of that vertex
+ unordered_map> adjList;
+
+ /* Remove a specified node from vector */
+ void remove(vector &vec, Vertex *vet) {
+ for (int i = 0; i < vec.size(); i++) {
+ if (vec[i] == vet) {
+ vec.erase(vec.begin() + i);
+ break;
+ }
+ }
+ }
+
+ /* Constructor */
+ GraphAdjList(const vector> &edges) {
+ // Add all vertices and edges
+ for (const vector &edge : edges) {
+ addVertex(edge[0]);
+ addVertex(edge[1]);
+ addEdge(edge[0], edge[1]);
+ }
+ }
+
+ /* Get the number of vertices */
+ int size() {
+ return adjList.size();
+ }
+
+ /* Add edge */
+ void addEdge(Vertex *vet1, Vertex *vet2) {
+ if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)
+ throw invalid_argument("Vertex does not exist");
+ // Add edge vet1 - vet2
+ adjList[vet1].push_back(vet2);
+ adjList[vet2].push_back(vet1);
+ }
+
+ /* Remove edge */
+ void removeEdge(Vertex *vet1, Vertex *vet2) {
+ if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)
+ throw invalid_argument("Vertex does not exist");
+ // Remove edge vet1 - vet2
+ remove(adjList[vet1], vet2);
+ remove(adjList[vet2], vet1);
+ }
+
+ /* Add vertex */
+ void addVertex(Vertex *vet) {
+ if (adjList.count(vet))
+ return;
+ // Add a new linked list to the adjacency list
+ adjList[vet] = vector();
+ }
+
+ /* Remove vertex */
+ void removeVertex(Vertex *vet) {
+ if (!adjList.count(vet))
+ throw invalid_argument("Vertex does not exist");
+ // Remove the vertex vet's corresponding linked list from the adjacency list
+ adjList.erase(vet);
+ // Traverse other vertices' linked lists, removing all edges containing vet
+ for (auto &adj : adjList) {
+ remove(adj.second, vet);
+ }
+ }
+
+ /* Print the adjacency list */
+ void print() {
+ cout << "Adjacency list =" << endl;
+ for (auto &adj : adjList) {
+ const auto &key = adj.first;
+ const auto &vec = adj.second;
+ cout << key->val << ": ";
+ printVector(vetsToVals(vec));
+ }
+ }
+ };
```
=== "Java"
diff --git a/en/docs/chapter_graph/graph_traversal.md b/en/docs/chapter_graph/graph_traversal.md
index 3e38419f8..34168b41d 100644
--- a/en/docs/chapter_graph/graph_traversal.md
+++ b/en/docs/chapter_graph/graph_traversal.md
@@ -55,7 +55,32 @@ To prevent revisiting vertices, we use a hash set `visited` to record which node
=== "C++"
```cpp title="graph_bfs.cpp"
- [class]{}-[func]{graphBFS}
+ /* Breadth-first traversal */
+ // Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex
+ vector graphBFS(GraphAdjList &graph, Vertex *startVet) {
+ // Vertex traversal sequence
+ vector res;
+ // Hash set, used to record visited vertices
+ unordered_set visited = {startVet};
+ // Queue used to implement BFS
+ queue que;
+ que.push(startVet);
+ // Starting from vertex vet, loop until all vertices are visited
+ while (!que.empty()) {
+ Vertex *vet = que.front();
+ que.pop(); // Dequeue the vertex at the head of the queue
+ res.push_back(vet); // Record visited vertex
+ // Traverse all adjacent vertices of that vertex
+ for (auto adjVet : graph.adjList[vet]) {
+ if (visited.count(adjVet))
+ continue; // Skip already visited vertices
+ que.push(adjVet); // Only enqueue unvisited vertices
+ visited.emplace(adjVet); // Mark the vertex as visited
+ }
+ }
+ // Return the vertex traversal sequence
+ return res;
+ }
```
=== "Java"
@@ -246,9 +271,29 @@ This "go as far as possible and then return" algorithm paradigm is usually imple
=== "C++"
```cpp title="graph_dfs.cpp"
- [class]{}-[func]{dfs}
+ /* Depth-first traversal helper function */
+ void dfs(GraphAdjList &graph, unordered_set &visited, vector &res, Vertex *vet) {
+ res.push_back(vet); // Record visited vertex
+ visited.emplace(vet); // Mark the vertex as visited
+ // Traverse all adjacent vertices of that vertex
+ for (Vertex *adjVet : graph.adjList[vet]) {
+ if (visited.count(adjVet))
+ continue; // Skip already visited vertices
+ // Recursively visit adjacent vertices
+ dfs(graph, visited, res, adjVet);
+ }
+ }
- [class]{}-[func]{graphDFS}
+ /* Depth-first traversal */
+ // Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex
+ vector graphDFS(GraphAdjList &graph, Vertex *startVet) {
+ // Vertex traversal sequence
+ vector res;
+ // Hash set, used to record visited vertices
+ unordered_set visited;
+ dfs(graph, visited, res, startVet);
+ return res;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_greedy/fractional_knapsack_problem.md b/en/docs/chapter_greedy/fractional_knapsack_problem.md
index b950b7870..8815e4446 100644
--- a/en/docs/chapter_greedy/fractional_knapsack_problem.md
+++ b/en/docs/chapter_greedy/fractional_knapsack_problem.md
@@ -73,9 +73,41 @@ We have created an `Item` class in order to sort the items by their unit value.
=== "C++"
```cpp title="fractional_knapsack.cpp"
- [class]{Item}-[func]{}
+ /* Item */
+ class Item {
+ public:
+ int w; // Item weight
+ int v; // Item value
- [class]{}-[func]{fractionalKnapsack}
+ Item(int w, int v) : w(w), v(v) {
+ }
+ };
+
+ /* Fractional knapsack: Greedy */
+ double fractionalKnapsack(vector &wgt, vector &val, int cap) {
+ // Create an item list, containing two properties: weight, value
+ vector- items;
+ for (int i = 0; i < wgt.size(); i++) {
+ items.push_back(Item(wgt[i], val[i]));
+ }
+ // Sort by unit value item.v / item.w from high to low
+ sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; });
+ // Loop for greedy selection
+ double res = 0;
+ for (auto &item : items) {
+ if (item.w <= cap) {
+ // If the remaining capacity is sufficient, put the entire item into the knapsack
+ res += item.v;
+ cap -= item.w;
+ } else {
+ // If the remaining capacity is insufficient, put part of the item into the knapsack
+ res += (double)item.v / item.w * cap;
+ // No remaining capacity left, thus break the loop
+ break;
+ }
+ }
+ return res;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_greedy/greedy_algorithm.md b/en/docs/chapter_greedy/greedy_algorithm.md
index e7ba3e9f3..9ad8343b0 100644
--- a/en/docs/chapter_greedy/greedy_algorithm.md
+++ b/en/docs/chapter_greedy/greedy_algorithm.md
@@ -48,7 +48,24 @@ The implementation code is as follows:
=== "C++"
```cpp title="coin_change_greedy.cpp"
- [class]{}-[func]{coinChangeGreedy}
+ /* Coin change: Greedy */
+ int coinChangeGreedy(vector &coins, int amt) {
+ // Assume coins list is ordered
+ int i = coins.size() - 1;
+ int count = 0;
+ // Loop for greedy selection until no remaining amount
+ while (amt > 0) {
+ // Find the smallest coin close to and less than the remaining amount
+ while (i > 0 && coins[i] > amt) {
+ i--;
+ }
+ // Choose coins[i]
+ amt -= coins[i];
+ count++;
+ }
+ // If no feasible solution is found, return -1
+ return amt == 0 ? count : -1;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_greedy/max_capacity_problem.md b/en/docs/chapter_greedy/max_capacity_problem.md
index 436d23ac1..03e8dd910 100644
--- a/en/docs/chapter_greedy/max_capacity_problem.md
+++ b/en/docs/chapter_greedy/max_capacity_problem.md
@@ -117,7 +117,26 @@ The variables $i$, $j$, and $res$ use a constant amount of extra space, **thus t
=== "C++"
```cpp title="max_capacity.cpp"
- [class]{}-[func]{maxCapacity}
+ /* Maximum capacity: Greedy */
+ int maxCapacity(vector &ht) {
+ // Initialize i, j, making them split the array at both ends
+ int i = 0, j = ht.size() - 1;
+ // Initial maximum capacity is 0
+ int res = 0;
+ // Loop for greedy selection until the two boards meet
+ while (i < j) {
+ // Update maximum capacity
+ int cap = min(ht[i], ht[j]) * (j - i);
+ res = max(res, cap);
+ // Move the shorter board inward
+ if (ht[i] < ht[j]) {
+ i++;
+ } else {
+ j--;
+ }
+ }
+ return res;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_greedy/max_product_cutting_problem.md b/en/docs/chapter_greedy/max_product_cutting_problem.md
index 16ef9dcdd..333448f68 100644
--- a/en/docs/chapter_greedy/max_product_cutting_problem.md
+++ b/en/docs/chapter_greedy/max_product_cutting_problem.md
@@ -6,7 +6,7 @@ comments: true
!!! question
- Given a positive integer $n$, split it into at least two positive integers that sum up to $n$, and find the maximum product of these integers, as illustrated below.
+ Given a positive integer $n$, split it into at least two positive integers that sum up to $n$, and find the maximum product of these integers, as illustrated in Figure 15-13.
{ class="animation-figure" }
@@ -96,7 +96,26 @@ Please note, for the boundary case where $n \leq 3$, a $1$ must be split out, wi
=== "C++"
```cpp title="max_product_cutting.cpp"
- [class]{}-[func]{maxProductCutting}
+ /* Maximum product of cutting: Greedy */
+ int maxProductCutting(int n) {
+ // When n <= 3, must cut out a 1
+ if (n <= 3) {
+ return 1 * (n - 1);
+ }
+ // Greedy cut out 3s, a is the number of 3s, b is the remainder
+ int a = n / 3;
+ int b = n % 3;
+ if (b == 1) {
+ // When the remainder is 1, convert a pair of 1 * 3 into 2 * 2
+ return (int)pow(3, a - 1) * 2 * 2;
+ }
+ if (b == 2) {
+ // When the remainder is 2, do nothing
+ return (int)pow(3, a) * 2;
+ }
+ // When the remainder is 0, do nothing
+ return (int)pow(3, a);
+ }
```
=== "Java"
diff --git a/en/docs/chapter_hashing/hash_algorithm.md b/en/docs/chapter_hashing/hash_algorithm.md
index cda803595..9745527dc 100644
--- a/en/docs/chapter_hashing/hash_algorithm.md
+++ b/en/docs/chapter_hashing/hash_algorithm.md
@@ -91,13 +91,45 @@ The design of hash algorithms is a complex issue that requires consideration of
=== "C++"
```cpp title="simple_hash.cpp"
- [class]{}-[func]{addHash}
+ /* Additive hash */
+ int addHash(string key) {
+ long long hash = 0;
+ const int MODULUS = 1000000007;
+ for (unsigned char c : key) {
+ hash = (hash + (int)c) % MODULUS;
+ }
+ return (int)hash;
+ }
- [class]{}-[func]{mulHash}
+ /* Multiplicative hash */
+ int mulHash(string key) {
+ long long hash = 0;
+ const int MODULUS = 1000000007;
+ for (unsigned char c : key) {
+ hash = (31 * hash + (int)c) % MODULUS;
+ }
+ return (int)hash;
+ }
- [class]{}-[func]{xorHash}
+ /* XOR hash */
+ int xorHash(string key) {
+ int hash = 0;
+ const int MODULUS = 1000000007;
+ for (unsigned char c : key) {
+ hash ^= (int)c;
+ }
+ return hash & MODULUS;
+ }
- [class]{}-[func]{rotHash}
+ /* Rotational hash */
+ int rotHash(string key) {
+ long long hash = 0;
+ const int MODULUS = 1000000007;
+ for (unsigned char c : key) {
+ hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS;
+ }
+ return (int)hash;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_hashing/hash_collision.md b/en/docs/chapter_hashing/hash_collision.md
index 9c12daf86..90d9f4973 100644
--- a/en/docs/chapter_hashing/hash_collision.md
+++ b/en/docs/chapter_hashing/hash_collision.md
@@ -123,7 +123,119 @@ The code below provides a simple implementation of a separate chaining hash tabl
=== "C++"
```cpp title="hash_map_chaining.cpp"
- [class]{HashMapChaining}-[func]{}
+ /* Chained address hash table */
+ class HashMapChaining {
+ private:
+ int size; // Number of key-value pairs
+ int capacity; // Hash table capacity
+ double loadThres; // Load factor threshold for triggering expansion
+ int extendRatio; // Expansion multiplier
+ vector> buckets; // Bucket array
+
+ public:
+ /* Constructor */
+ HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) {
+ buckets.resize(capacity);
+ }
+
+ /* Destructor */
+ ~HashMapChaining() {
+ for (auto &bucket : buckets) {
+ for (Pair *pair : bucket) {
+ // Free memory
+ delete pair;
+ }
+ }
+ }
+
+ /* Hash function */
+ int hashFunc(int key) {
+ return key % capacity;
+ }
+
+ /* Load factor */
+ double loadFactor() {
+ return (double)size / (double)capacity;
+ }
+
+ /* Query operation */
+ string get(int key) {
+ int index = hashFunc(key);
+ // Traverse the bucket, if the key is found, return the corresponding val
+ for (Pair *pair : buckets[index]) {
+ if (pair->key == key) {
+ return pair->val;
+ }
+ }
+ // If key not found, return an empty string
+ return "";
+ }
+
+ /* Add operation */
+ void put(int key, string val) {
+ // When the load factor exceeds the threshold, perform expansion
+ if (loadFactor() > loadThres) {
+ extend();
+ }
+ int index = hashFunc(key);
+ // Traverse the bucket, if the specified key is encountered, update the corresponding val and return
+ for (Pair *pair : buckets[index]) {
+ if (pair->key == key) {
+ pair->val = val;
+ return;
+ }
+ }
+ // If the key is not found, add the key-value pair to the end
+ buckets[index].push_back(new Pair(key, val));
+ size++;
+ }
+
+ /* Remove operation */
+ void remove(int key) {
+ int index = hashFunc(key);
+ auto &bucket = buckets[index];
+ // Traverse the bucket, remove the key-value pair from it
+ for (int i = 0; i < bucket.size(); i++) {
+ if (bucket[i]->key == key) {
+ Pair *tmp = bucket[i];
+ bucket.erase(bucket.begin() + i); // Remove key-value pair
+ delete tmp; // Free memory
+ size--;
+ return;
+ }
+ }
+ }
+
+ /* Extend hash table */
+ void extend() {
+ // Temporarily store the original hash table
+ vector> bucketsTmp = buckets;
+ // Initialize the extended new hash table
+ capacity *= extendRatio;
+ buckets.clear();
+ buckets.resize(capacity);
+ size = 0;
+ // Move key-value pairs from the original hash table to the new hash table
+ for (auto &bucket : bucketsTmp) {
+ for (Pair *pair : bucket) {
+ put(pair->key, pair->val);
+ // Free memory
+ delete pair;
+ }
+ }
+ }
+
+ /* Print hash table */
+ void print() {
+ for (auto &bucket : buckets) {
+ cout << "[";
+ for (Pair *pair : bucket) {
+ cout << pair->key << " -> " << pair->val << ", ";
+ }
+ cout << "]\n";
+ }
+ }
+ };
```
=== "Java"
@@ -451,7 +563,140 @@ The code below implements an open addressing (linear probing) hash table with la
=== "C++"
```cpp title="hash_map_open_addressing.cpp"
- [class]{HashMapOpenAddressing}-[func]{}
+ /* Open addressing hash table */
+ class HashMapOpenAddressing {
+ private:
+ int size; // Number of key-value pairs
+ int capacity = 4; // Hash table capacity
+ const double loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion
+ const int extendRatio = 2; // Expansion multiplier
+ vector buckets; // Bucket array
+ Pair *TOMBSTONE = new Pair(-1, "-1"); // Removal mark
+
+ public:
+ /* Constructor */
+ HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) {
+ }
+
+ /* Destructor */
+ ~HashMapOpenAddressing() {
+ for (Pair *pair : buckets) {
+ if (pair != nullptr && pair != TOMBSTONE) {
+ delete pair;
+ }
+ }
+ delete TOMBSTONE;
+ }
+
+ /* Hash function */
+ int hashFunc(int key) {
+ return key % capacity;
+ }
+
+ /* Load factor */
+ double loadFactor() {
+ return (double)size / capacity;
+ }
+
+ /* Search for the bucket index corresponding to key */
+ int findBucket(int key) {
+ int index = hashFunc(key);
+ int firstTombstone = -1;
+ // Linear probing, break when encountering an empty bucket
+ while (buckets[index] != nullptr) {
+ // If the key is encountered, return the corresponding bucket index
+ if (buckets[index]->key == key) {
+ // If a removal mark was encountered earlier, move the key-value pair to that index
+ if (firstTombstone != -1) {
+ buckets[firstTombstone] = buckets[index];
+ buckets[index] = TOMBSTONE;
+ return firstTombstone; // Return the moved bucket index
+ }
+ return index; // Return bucket index
+ }
+ // Record the first encountered removal mark
+ if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {
+ firstTombstone = index;
+ }
+ // Calculate the bucket index, return to the head if exceeding the tail
+ index = (index + 1) % capacity;
+ }
+ // If the key does not exist, return the index of the insertion point
+ return firstTombstone == -1 ? index : firstTombstone;
+ }
+
+ /* Query operation */
+ string get(int key) {
+ // Search for the bucket index corresponding to key
+ int index = findBucket(key);
+ // If the key-value pair is found, return the corresponding val
+ if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {
+ return buckets[index]->val;
+ }
+ // If key-value pair does not exist, return an empty string
+ return "";
+ }
+
+ /* Add operation */
+ void put(int key, string val) {
+ // When the load factor exceeds the threshold, perform expansion
+ if (loadFactor() > loadThres) {
+ extend();
+ }
+ // Search for the bucket index corresponding to key
+ int index = findBucket(key);
+ // If the key-value pair is found, overwrite val and return
+ if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {
+ buckets[index]->val = val;
+ return;
+ }
+ // If the key-value pair does not exist, add the key-value pair
+ buckets[index] = new Pair(key, val);
+ size++;
+ }
+
+ /* Remove operation */
+ void remove(int key) {
+ // Search for the bucket index corresponding to key
+ int index = findBucket(key);
+ // If the key-value pair is found, cover it with a removal mark
+ if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {
+ delete buckets[index];
+ buckets[index] = TOMBSTONE;
+ size--;
+ }
+ }
+
+ /* Extend hash table */
+ void extend() {
+ // Temporarily store the original hash table
+ vector bucketsTmp = buckets;
+ // Initialize the extended new hash table
+ capacity *= extendRatio;
+ buckets = vector(capacity, nullptr);
+ size = 0;
+ // Move key-value pairs from the original hash table to the new hash table
+ for (Pair *pair : bucketsTmp) {
+ if (pair != nullptr && pair != TOMBSTONE) {
+ put(pair->key, pair->val);
+ delete pair;
+ }
+ }
+ }
+
+ /* Print hash table */
+ void print() {
+ for (Pair *pair : buckets) {
+ if (pair == nullptr) {
+ cout << "nullptr" << endl;
+ } else if (pair == TOMBSTONE) {
+ cout << "TOMBSTONE" << endl;
+ } else {
+ cout << pair->key << " -> " << pair->val << endl;
+ }
+ }
+ }
+ };
```
=== "Java"
diff --git a/en/docs/chapter_hashing/hash_map.md b/en/docs/chapter_hashing/hash_map.md
index 0e247d300..fc8085acf 100755
--- a/en/docs/chapter_hashing/hash_map.md
+++ b/en/docs/chapter_hashing/hash_map.md
@@ -598,9 +598,106 @@ The following code implements a simple hash table. Here, we encapsulate `key` an
=== "C++"
```cpp title="array_hash_map.cpp"
- [class]{Pair}-[func]{}
+ /* Key-value pair */
+ struct Pair {
+ public:
+ int key;
+ string val;
+ Pair(int key, string val) {
+ this->key = key;
+ this->val = val;
+ }
+ };
- [class]{ArrayHashMap}-[func]{}
+ /* Hash table based on array implementation */
+ class ArrayHashMap {
+ private:
+ vector buckets;
+
+ public:
+ ArrayHashMap() {
+ // Initialize an array, containing 100 buckets
+ buckets = vector(100);
+ }
+
+ ~ArrayHashMap() {
+ // Free memory
+ for (const auto &bucket : buckets) {
+ delete bucket;
+ }
+ buckets.clear();
+ }
+
+ /* Hash function */
+ int hashFunc(int key) {
+ int index = key % 100;
+ return index;
+ }
+
+ /* Query operation */
+ string get(int key) {
+ int index = hashFunc(key);
+ Pair *pair = buckets[index];
+ if (pair == nullptr)
+ return "";
+ return pair->val;
+ }
+
+ /* Add operation */
+ void put(int key, string val) {
+ Pair *pair = new Pair(key, val);
+ int index = hashFunc(key);
+ buckets[index] = pair;
+ }
+
+ /* Remove operation */
+ void remove(int key) {
+ int index = hashFunc(key);
+ // Free memory and set to nullptr
+ delete buckets[index];
+ buckets[index] = nullptr;
+ }
+
+ /* Get all key-value pairs */
+ vector pairSet() {
+ vector pairSet;
+ for (Pair *pair : buckets) {
+ if (pair != nullptr) {
+ pairSet.push_back(pair);
+ }
+ }
+ return pairSet;
+ }
+
+ /* Get all keys */
+ vector keySet() {
+ vector keySet;
+ for (Pair *pair : buckets) {
+ if (pair != nullptr) {
+ keySet.push_back(pair->key);
+ }
+ }
+ return keySet;
+ }
+
+ /* Get all values */
+ vector valueSet() {
+ vector valueSet;
+ for (Pair *pair : buckets) {
+ if (pair != nullptr) {
+ valueSet.push_back(pair->val);
+ }
+ }
+ return valueSet;
+ }
+
+ /* Print hash table */
+ void print() {
+ for (Pair *kv : pairSet()) {
+ cout << kv->key << " -> " << kv->val << endl;
+ }
+ }
+ };
```
=== "Java"
diff --git a/en/docs/chapter_heap/build_heap.md b/en/docs/chapter_heap/build_heap.md
index bea3ca370..3cc0fc831 100644
--- a/en/docs/chapter_heap/build_heap.md
+++ b/en/docs/chapter_heap/build_heap.md
@@ -42,7 +42,15 @@ It's worth mentioning that **since leaf nodes have no children, they naturally f
=== "C++"
```cpp title="my_heap.cpp"
- [class]{MaxHeap}-[func]{MaxHeap}
+ /* Constructor, build heap based on input list */
+ MaxHeap(vector nums) {
+ // Add all list elements into the heap
+ maxHeap = nums;
+ // Heapify all nodes except leaves
+ for (int i = parent(size() - 1); i >= 0; i--) {
+ siftDown(i);
+ }
+ }
```
=== "Java"
diff --git a/en/docs/chapter_heap/heap.md b/en/docs/chapter_heap/heap.md
index 62d1da0d9..50baa7552 100644
--- a/en/docs/chapter_heap/heap.md
+++ b/en/docs/chapter_heap/heap.md
@@ -465,11 +465,20 @@ We can encapsulate the index mapping formula into functions for convenient later
=== "C++"
```cpp title="my_heap.cpp"
- [class]{MaxHeap}-[func]{left}
+ /* Get index of left child node */
+ int left(int i) {
+ return 2 * i + 1;
+ }
- [class]{MaxHeap}-[func]{right}
+ /* Get index of right child node */
+ int right(int i) {
+ return 2 * i + 2;
+ }
- [class]{MaxHeap}-[func]{parent}
+ /* Get index of parent node */
+ int parent(int i) {
+ return (i - 1) / 2; // Integer division down
+ }
```
=== "Java"
@@ -616,7 +625,10 @@ The top element of the heap is the root node of the binary tree, which is also t
=== "C++"
```cpp title="my_heap.cpp"
- [class]{MaxHeap}-[func]{peek}
+ /* Access heap top element */
+ int peek() {
+ return maxHeap[0];
+ }
```
=== "Java"
@@ -758,9 +770,28 @@ Given a total of $n$ nodes, the height of the tree is $O(\log n)$. Hence, the lo
=== "C++"
```cpp title="my_heap.cpp"
- [class]{MaxHeap}-[func]{push}
+ /* Push the element into heap */
+ void push(int val) {
+ // Add node
+ maxHeap.push_back(val);
+ // Heapify from bottom to top
+ siftUp(size() - 1);
+ }
- [class]{MaxHeap}-[func]{siftUp}
+ /* Start heapifying node i, from bottom to top */
+ void siftUp(int i) {
+ while (true) {
+ // Get parent node of node i
+ int p = parent(i);
+ // When "crossing the root node" or "node does not need repair", end heapification
+ if (p < 0 || maxHeap[i] <= maxHeap[p])
+ break;
+ // Swap two nodes
+ swap(maxHeap[i], maxHeap[p]);
+ // Loop upwards heapification
+ i = p;
+ }
+ }
```
=== "Java"
@@ -960,9 +991,37 @@ Similar to the element insertion operation, the time complexity of the top eleme
=== "C++"
```cpp title="my_heap.cpp"
- [class]{MaxHeap}-[func]{pop}
+ /* Element exits heap */
+ void pop() {
+ // Empty handling
+ if (isEmpty()) {
+ throw out_of_range("Heap is empty");
+ }
+ // Swap the root node with the rightmost leaf node (swap the first element with the last element)
+ swap(maxHeap[0], maxHeap[size() - 1]);
+ // Remove node
+ maxHeap.pop_back();
+ // Heapify from top to bottom
+ siftDown(0);
+ }
- [class]{MaxHeap}-[func]{siftDown}
+ /* Start heapifying node i, from top to bottom */
+ void siftDown(int i) {
+ while (true) {
+ // Determine the largest node among i, l, r, noted as ma
+ int l = left(i), r = right(i), ma = i;
+ if (l < size() && maxHeap[l] > maxHeap[ma])
+ ma = l;
+ if (r < size() && maxHeap[r] > maxHeap[ma])
+ ma = r;
+ // If node i is the largest or indices l, r are out of bounds, no further heapification needed, break
+ if (ma == i)
+ break;
+ swap(maxHeap[i], maxHeap[ma]);
+ // Loop downwards heapification
+ i = ma;
+ }
+ }
```
=== "Java"
diff --git a/en/docs/chapter_heap/top_k.md b/en/docs/chapter_heap/top_k.md
index 2876ece37..7e635f2a7 100644
--- a/en/docs/chapter_heap/top_k.md
+++ b/en/docs/chapter_heap/top_k.md
@@ -96,7 +96,24 @@ Example code is as follows:
=== "C++"
```cpp title="top_k.cpp"
- [class]{}-[func]{topKHeap}
+ /* Using heap to find the largest k elements in an array */
+ priority_queue, greater> topKHeap(vector &nums, int k) {
+ // Initialize min-heap
+ priority_queue, greater> heap;
+ // Enter the first k elements of the array into the heap
+ for (int i = 0; i < k; i++) {
+ heap.push(nums[i]);
+ }
+ // From the k+1th element, keep the heap length as k
+ for (int i = k; i < nums.size(); i++) {
+ // If the current element is larger than the heap top element, remove the heap top element and enter the current element into the heap
+ if (nums[i] > heap.top()) {
+ heap.pop();
+ heap.push(nums[i]);
+ }
+ }
+ return heap;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_searching/binary_search.md b/en/docs/chapter_searching/binary_search.md
index aa803d1c8..a9df591f1 100644
--- a/en/docs/chapter_searching/binary_search.md
+++ b/en/docs/chapter_searching/binary_search.md
@@ -76,7 +76,23 @@ The code is as follows:
=== "C++"
```cpp title="binary_search.cpp"
- [class]{}-[func]{binarySearch}
+ /* Binary search (double closed interval) */
+ int binarySearch(vector &nums, int target) {
+ // Initialize double closed interval [0, n-1], i.e., i, j point to the first element and last element of the array respectively
+ int i = 0, j = nums.size() - 1;
+ // Loop until the search interval is empty (when i > j, it is empty)
+ while (i <= j) {
+ int m = i + (j - i) / 2; // Calculate midpoint index m
+ if (nums[m] < target) // This situation indicates that target is in the interval [m+1, j]
+ i = m + 1;
+ else if (nums[m] > target) // This situation indicates that target is in the interval [i, m-1]
+ j = m - 1;
+ else // Found the target element, thus return its index
+ return m;
+ }
+ // Did not find the target element, thus return -1
+ return -1;
+ }
```
=== "Java"
@@ -199,7 +215,23 @@ We can implement a binary search algorithm with the same functionality based on
=== "C++"
```cpp title="binary_search.cpp"
- [class]{}-[func]{binarySearchLCRO}
+ /* Binary search (left closed right open interval) */
+ int binarySearchLCRO(vector &nums, int target) {
+ // Initialize left closed right open interval [0, n), i.e., i, j point to the first element and the last element +1 of the array respectively
+ int i = 0, j = nums.size();
+ // Loop until the search interval is empty (when i = j, it is empty)
+ while (i < j) {
+ int m = i + (j - i) / 2; // Calculate midpoint index m
+ if (nums[m] < target) // This situation indicates that target is in the interval [m+1, j)
+ i = m + 1;
+ else if (nums[m] > target) // This situation indicates that target is in the interval [i, m)
+ j = m;
+ else // Found the target element, thus return its index
+ return m;
+ }
+ // Did not find the target element, thus return -1
+ return -1;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_searching/binary_search_edge.md b/en/docs/chapter_searching/binary_search_edge.md
index 16af6fb90..2ab50a365 100644
--- a/en/docs/chapter_searching/binary_search_edge.md
+++ b/en/docs/chapter_searching/binary_search_edge.md
@@ -36,7 +36,17 @@ In these cases, simply return $-1$. The code is as follows:
=== "C++"
```cpp title="binary_search_edge.cpp"
- [class]{}-[func]{binarySearchLeftEdge}
+ /* Binary search for the leftmost target */
+ int binarySearchLeftEdge(vector &nums, int target) {
+ // Equivalent to finding the insertion point of target
+ int i = binarySearchInsertion(nums, target);
+ // Did not find target, thus return -1
+ if (i == nums.size() || nums[i] != target) {
+ return -1;
+ }
+ // Found target, return index i
+ return i;
+ }
```
=== "Java"
@@ -158,7 +168,19 @@ Please note, the insertion point returned is $i$, therefore, it should be subtra
=== "C++"
```cpp title="binary_search_edge.cpp"
- [class]{}-[func]{binarySearchRightEdge}
+ /* Binary search for the rightmost target */
+ int binarySearchRightEdge(vector &nums, int target) {
+ // Convert to finding the leftmost target + 1
+ int i = binarySearchInsertion(nums, target + 1);
+ // j points to the rightmost target, i points to the first element greater than target
+ int j = i - 1;
+ // Did not find target, thus return -1
+ if (j == -1 || nums[j] != target) {
+ return -1;
+ }
+ // Found target, return index j
+ return j;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_searching/binary_search_insertion.md b/en/docs/chapter_searching/binary_search_insertion.md
index ef6f19f0d..c6a39b03d 100644
--- a/en/docs/chapter_searching/binary_search_insertion.md
+++ b/en/docs/chapter_searching/binary_search_insertion.md
@@ -49,7 +49,22 @@ Therefore, at the end of the binary, it is certain that: $i$ points to the first
=== "C++"
```cpp title="binary_search_insertion.cpp"
- [class]{}-[func]{binarySearchInsertionSimple}
+ /* Binary search for insertion point (no duplicate elements) */
+ int binarySearchInsertionSimple(vector &nums, int target) {
+ int i = 0, j = nums.size() - 1; // Initialize double closed interval [0, n-1]
+ while (i <= j) {
+ int m = i + (j - i) / 2; // Calculate midpoint index m
+ if (nums[m] < target) {
+ i = m + 1; // Target is in interval [m+1, j]
+ } else if (nums[m] > target) {
+ j = m - 1; // Target is in interval [i, m-1]
+ } else {
+ return m; // Found target, return insertion point m
+ }
+ }
+ // Did not find target, return insertion point i
+ return i;
+ }
```
=== "Java"
@@ -216,7 +231,22 @@ Even so, we can still keep the conditions expanded, as their logic is clearer an
=== "C++"
```cpp title="binary_search_insertion.cpp"
- [class]{}-[func]{binarySearchInsertion}
+ /* Binary search for insertion point (with duplicate elements) */
+ int binarySearchInsertion(vector &nums, int target) {
+ int i = 0, j = nums.size() - 1; // Initialize double closed interval [0, n-1]
+ while (i <= j) {
+ int m = i + (j - i) / 2; // Calculate midpoint index m
+ if (nums[m] < target) {
+ i = m + 1; // Target is in interval [m+1, j]
+ } else if (nums[m] > target) {
+ j = m - 1; // Target is in interval [i, m-1]
+ } else {
+ j = m - 1; // First element less than target is in interval [i, m-1]
+ }
+ }
+ // Return insertion point i
+ return i;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_searching/replace_linear_by_hashing.md b/en/docs/chapter_searching/replace_linear_by_hashing.md
index 2a37ac94b..94e656a1f 100644
--- a/en/docs/chapter_searching/replace_linear_by_hashing.md
+++ b/en/docs/chapter_searching/replace_linear_by_hashing.md
@@ -36,7 +36,18 @@ The code is shown below:
=== "C++"
```cpp title="two_sum.cpp"
- [class]{}-[func]{twoSumBruteForce}
+ /* Method one: Brute force enumeration */
+ vector twoSumBruteForce(vector &nums, int target) {
+ int size = nums.size();
+ // Two-layer loop, time complexity is O(n^2)
+ for (int i = 0; i < size - 1; i++) {
+ for (int j = i + 1; j < size; j++) {
+ if (nums[i] + nums[j] == target)
+ return {i, j};
+ }
+ }
+ return {};
+ }
```
=== "Java"
@@ -126,7 +137,7 @@ This method has a time complexity of $O(n^2)$ and a space complexity of $O(1)$,
## 10.4.2 Hash search: trading space for time
-Consider using a hash table, with key-value pairs being the array elements and their indices, respectively. Loop through the array, performing the steps shown in the figures below each round.
+Consider using a hash table, with key-value pairs being the array elements and their indices, respectively. Loop through the array, performing the steps shown in Figure 10-10 each round.
1. Check if the number `target - nums[i]` is in the hash table. If so, directly return the indices of these two elements.
2. Add the key-value pair `nums[i]` and index `i` to the hash table.
@@ -162,7 +173,20 @@ The implementation code is shown below, requiring only a single loop:
=== "C++"
```cpp title="two_sum.cpp"
- [class]{}-[func]{twoSumHashTable}
+ /* Method two: Auxiliary hash table */
+ vector twoSumHashTable(vector &nums, int target) {
+ int size = nums.size();
+ // Auxiliary hash table, space complexity is O(n)
+ unordered_map dic;
+ // Single-layer loop, time complexity is O(n)
+ for (int i = 0; i < size; i++) {
+ if (dic.find(target - nums[i]) != dic.end()) {
+ return {dic[target - nums[i]], i};
+ }
+ dic.emplace(nums[i], i);
+ }
+ return {};
+ }
```
=== "Java"
diff --git a/en/docs/chapter_sorting/bubble_sort.md b/en/docs/chapter_sorting/bubble_sort.md
index 4bdde9601..b3e432bf9 100644
--- a/en/docs/chapter_sorting/bubble_sort.md
+++ b/en/docs/chapter_sorting/bubble_sort.md
@@ -64,7 +64,20 @@ Example code is as follows:
=== "C++"
```cpp title="bubble_sort.cpp"
- [class]{}-[func]{bubbleSort}
+ /* Bubble sort */
+ void bubbleSort(vector &nums) {
+ // Outer loop: unsorted range is [0, i]
+ for (int i = nums.size() - 1; i > 0; i--) {
+ // Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range
+ for (int j = 0; j < i; j++) {
+ if (nums[j] > nums[j + 1]) {
+ // Swap nums[j] and nums[j + 1]
+ // Here, the std
+ swap(nums[j], nums[j + 1]);
+ }
+ }
+ }
+ }
```
=== "Java"
@@ -181,7 +194,24 @@ Even after optimization, the worst-case time complexity and average time complex
=== "C++"
```cpp title="bubble_sort.cpp"
- [class]{}-[func]{bubbleSortWithFlag}
+ /* Bubble sort (optimized with flag)*/
+ void bubbleSortWithFlag(vector &nums) {
+ // Outer loop: unsorted range is [0, i]
+ for (int i = nums.size() - 1; i > 0; i--) {
+ bool flag = false; // Initialize flag
+ // Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range
+ for (int j = 0; j < i; j++) {
+ if (nums[j] > nums[j + 1]) {
+ // Swap nums[j] and nums[j + 1]
+ // Here, the std
+ swap(nums[j], nums[j + 1]);
+ flag = true; // Record swapped elements
+ }
+ }
+ if (!flag)
+ break; // If no elements were swapped in this round of "bubbling", exit
+ }
+ }
```
=== "Java"
diff --git a/en/docs/chapter_sorting/bucket_sort.md b/en/docs/chapter_sorting/bucket_sort.md
index deee84690..65eb9e6f1 100644
--- a/en/docs/chapter_sorting/bucket_sort.md
+++ b/en/docs/chapter_sorting/bucket_sort.md
@@ -51,7 +51,31 @@ The code is shown as follows:
=== "C++"
```cpp title="bucket_sort.cpp"
- [class]{}-[func]{bucketSort}
+ /* Bucket sort */
+ void bucketSort(vector &nums) {
+ // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket
+ int k = nums.size() / 2;
+ vector> buckets(k);
+ // 1. Distribute array elements into various buckets
+ for (float num : nums) {
+ // Input data range is [0, 1), use num * k to map to index range [0, k-1]
+ int i = num * k;
+ // Add number to bucket_idx
+ buckets[i].push_back(num);
+ }
+ // 2. Sort each bucket
+ for (vector &bucket : buckets) {
+ // Use built-in sorting function, can also replace with other sorting algorithms
+ sort(bucket.begin(), bucket.end());
+ }
+ // 3. Traverse buckets to merge results
+ int i = 0;
+ for (vector &bucket : buckets) {
+ for (float num : bucket) {
+ nums[i++] = num;
+ }
+ }
+ }
```
=== "Java"
diff --git a/en/docs/chapter_sorting/counting_sort.md b/en/docs/chapter_sorting/counting_sort.md
index 60b4c45e6..d5ea77dde 100644
--- a/en/docs/chapter_sorting/counting_sort.md
+++ b/en/docs/chapter_sorting/counting_sort.md
@@ -8,7 +8,7 @@ comments: true
## 11.9.1 Simple implementation
-Let's start with a simple example. Given an array `nums` of length $n$, where all elements are "non-negative integers", the overall process of counting sort is illustrated in the following diagram.
+Let's start with a simple example. Given an array `nums` of length $n$, where all elements are "non-negative integers", the overall process of counting sort is illustrated in Figure 11-16.
1. Traverse the array to find the maximum number, denoted as $m$, then create an auxiliary array `counter` of length $m + 1$.
2. **Use `counter` to count the occurrence of each number in `nums`**, where `counter[num]` corresponds to the occurrence of the number `num`. The counting method is simple, just traverse `nums` (suppose the current number is `num`), and increase `counter[num]` by $1$ each round.
@@ -46,7 +46,28 @@ The code is shown below:
=== "C++"
```cpp title="counting_sort.cpp"
- [class]{}-[func]{countingSortNaive}
+ /* Counting sort */
+ // Simple implementation, cannot be used for sorting objects
+ void countingSortNaive(vector &nums) {
+ // 1. Count the maximum element m in the array
+ int m = 0;
+ for (int num : nums) {
+ m = max(m, num);
+ }
+ // 2. Count the occurrence of each digit
+ // counter[num] represents the occurrence of num
+ vector counter(m + 1, 0);
+ for (int num : nums) {
+ counter[num]++;
+ }
+ // 3. Traverse counter, filling each element back into the original array nums
+ int i = 0;
+ for (int num = 0; num < m + 1; num++) {
+ for (int j = 0; j < counter[num]; j++, i++) {
+ nums[i] = num;
+ }
+ }
+ }
```
=== "Java"
@@ -161,7 +182,7 @@ $$
1. Fill `num` into the array `res` at the index `prefix[num] - 1`.
2. Reduce the prefix sum `prefix[num]` by $1$, thus obtaining the next index to place `num`.
-After the traversal, the array `res` contains the sorted result, and finally, `res` replaces the original array `nums`. The complete counting sort process is shown in the figures below.
+After the traversal, the array `res` contains the sorted result, and finally, `res` replaces the original array `nums`. The complete counting sort process is shown in Figure 11-17.
=== "<1>"
{ class="animation-figure" }
@@ -224,7 +245,37 @@ The implementation code of counting sort is shown below:
=== "C++"
```cpp title="counting_sort.cpp"
- [class]{}-[func]{countingSort}
+ /* Counting sort */
+ // Complete implementation, can sort objects and is a stable sort
+ void countingSort(vector &nums) {
+ // 1. Count the maximum element m in the array
+ int m = 0;
+ for (int num : nums) {
+ m = max(m, num);
+ }
+ // 2. Count the occurrence of each digit
+ // counter[num] represents the occurrence of num
+ vector counter(m + 1, 0);
+ for (int num : nums) {
+ counter[num]++;
+ }
+ // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index"
+ // counter[num]-1 is the last index where num appears in res
+ for (int i = 0; i < m; i++) {
+ counter[i + 1] += counter[i];
+ }
+ // 4. Traverse nums in reverse order, placing each element into the result array res
+ // Initialize the array res to record results
+ int n = nums.size();
+ vector res(n);
+ for (int i = n - 1; i >= 0; i--) {
+ int num = nums[i];
+ res[counter[num] - 1] = num; // Place num at the corresponding index
+ counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num
+ }
+ // Use result array res to overwrite the original array nums
+ nums = res;
+ }
```
=== "Java"
diff --git a/en/docs/chapter_sorting/heap_sort.md b/en/docs/chapter_sorting/heap_sort.md
index d018a72a0..d69903e1b 100644
--- a/en/docs/chapter_sorting/heap_sort.md
+++ b/en/docs/chapter_sorting/heap_sort.md
@@ -106,9 +106,42 @@ In the code implementation, we used the sift-down function `sift_down()` from th
=== "C++"
```cpp title="heap_sort.cpp"
- [class]{}-[func]{siftDown}
+ /* Heap length is n, start heapifying node i, from top to bottom */
+ void siftDown(vector &nums, int n, int i) {
+ while (true) {
+ // Determine the largest node among i, l, r, noted as ma
+ int l = 2 * i + 1;
+ int r = 2 * i + 2;
+ int ma = i;
+ if (l < n && nums[l] > nums[ma])
+ ma = l;
+ if (r < n && nums[r] > nums[ma])
+ ma = r;
+ // If node i is the largest or indices l, r are out of bounds, no further heapification needed, break
+ if (ma == i) {
+ break;
+ }
+ // Swap two nodes
+ swap(nums[i], nums[ma]);
+ // Loop downwards heapification
+ i = ma;
+ }
+ }
- [class]{}-[func]{heapSort}
+ /* Heap sort */
+ void heapSort(vector &nums) {
+ // Build heap operation: heapify all nodes except leaves
+ for (int i = nums.size() / 2 - 1; i >= 0; --i) {
+ siftDown(nums, nums.size(), i);
+ }
+ // Extract the largest element from the heap and repeat for n-1 rounds
+ for (int i = nums.size() - 1; i > 0; --i) {
+ // Swap the root node with the rightmost leaf node (swap the first element with the last element)
+ swap(nums[0], nums[i]);
+ // Start heapifying the root node, from top to bottom
+ siftDown(nums, i, 0);
+ }
+ }
```
=== "Java"
diff --git a/en/docs/chapter_sorting/insertion_sort.md b/en/docs/chapter_sorting/insertion_sort.md
index ef19a266c..95af3e8cf 100644
--- a/en/docs/chapter_sorting/insertion_sort.md
+++ b/en/docs/chapter_sorting/insertion_sort.md
@@ -48,7 +48,19 @@ Example code is as follows:
=== "C++"
```cpp title="insertion_sort.cpp"
- [class]{}-[func]{insertionSort}
+ /* Insertion sort */
+ void insertionSort(vector &nums) {
+ // Outer loop: sorted range is [0, i-1]
+ for (int i = 1; i < nums.size(); i++) {
+ int base = nums[i], j = i - 1;
+ // Inner loop: insert base into the correct position within the sorted range [0, i-1]
+ while (j >= 0 && nums[j] > base) {
+ nums[j + 1] = nums[j]; // Move nums[j] to the right by one position
+ j--;
+ }
+ nums[j + 1] = base; // Assign base to the correct position
+ }
+ }
```
=== "Java"
diff --git a/en/docs/chapter_sorting/merge_sort.md b/en/docs/chapter_sorting/merge_sort.md
index 876464738..94f1ea90c 100644
--- a/en/docs/chapter_sorting/merge_sort.md
+++ b/en/docs/chapter_sorting/merge_sort.md
@@ -109,9 +109,45 @@ The implementation of merge sort is shown in the following code. Note that the i
=== "C++"
```cpp title="merge_sort.cpp"
- [class]{}-[func]{merge}
+ /* Merge left subarray and right subarray */
+ void merge(vector &nums, int left, int mid, int right) {
+ // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]
+ // Create a temporary array tmp to store the merged results
+ vector tmp(right - left + 1);
+ // Initialize the start indices of the left and right subarrays
+ int i = left, j = mid + 1, k = 0;
+ // While both subarrays still have elements, compare and copy the smaller element into the temporary array
+ while (i <= mid && j <= right) {
+ if (nums[i] <= nums[j])
+ tmp[k++] = nums[i++];
+ else
+ tmp[k++] = nums[j++];
+ }
+ // Copy the remaining elements of the left and right subarrays into the temporary array
+ while (i <= mid) {
+ tmp[k++] = nums[i++];
+ }
+ while (j <= right) {
+ tmp[k++] = nums[j++];
+ }
+ // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval
+ for (k = 0; k < tmp.size(); k++) {
+ nums[left + k] = tmp[k];
+ }
+ }
- [class]{}-[func]{mergeSort}
+ /* Merge sort */
+ void mergeSort(vector &nums, int left, int right) {
+ // Termination condition
+ if (left >= right)
+ return; // Terminate recursion when subarray length is 1
+ // Partition stage
+ int mid = (left + right) / 2; // Calculate midpoint
+ mergeSort(nums, left, mid); // Recursively process the left subarray
+ mergeSort(nums, mid + 1, right); // Recursively process the right subarray
+ // Merge stage
+ merge(nums, left, mid, right);
+ }
```
=== "Java"
diff --git a/en/docs/chapter_sorting/quick_sort.md b/en/docs/chapter_sorting/quick_sort.md
index d8c800c96..b12aa0dd8 100644
--- a/en/docs/chapter_sorting/quick_sort.md
+++ b/en/docs/chapter_sorting/quick_sort.md
@@ -6,7 +6,7 @@ comments: true
Quick sort is a sorting algorithm based on the divide and conquer strategy, known for its efficiency and wide application.
-The core operation of quick sort is "pivot partitioning," aiming to: select an element from the array as the "pivot," move all elements smaller than the pivot to its left, and move elements greater than the pivot to its right. Specifically, the pivot partitioning process is illustrated as follows.
+The core operation of quick sort is "pivot partitioning," aiming to: select an element from the array as the "pivot," move all elements smaller than the pivot to its left, and move elements greater than the pivot to its right. Specifically, the pivot partitioning process is illustrated in Figure 11-8.
1. Select the leftmost element of the array as the pivot, and initialize two pointers `i` and `j` at both ends of the array.
2. Set up a loop where each round uses `i` (`j`) to find the first element larger (smaller) than the pivot, then swap these two elements.
@@ -69,9 +69,27 @@ After the pivot partitioning, the original array is divided into three parts: le
=== "C++"
```cpp title="quick_sort.cpp"
- [class]{QuickSort}-[func]{swap}
+ /* Swap elements */
+ void swap(vector &nums, int i, int j) {
+ int tmp = nums[i];
+ nums[i] = nums[j];
+ nums[j] = tmp;
+ }
- [class]{QuickSort}-[func]{partition}
+ /* Partition */
+ int partition(vector &nums, int left, int right) {
+ // Use nums[left] as the pivot
+ int i = left, j = right;
+ while (i < j) {
+ while (i < j && nums[j] >= nums[left])
+ j--; // Search from right to left for the first element smaller than the pivot
+ while (i < j && nums[i] <= nums[left])
+ i++; // Search from left to right for the first element greater than the pivot
+ swap(nums, i, j); // Swap these two elements
+ }
+ swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays
+ return i; // Return the index of the pivot
+ }
```
=== "Java"
@@ -210,7 +228,17 @@ The overall process of quick sort is shown in Figure 11-9.
=== "C++"
```cpp title="quick_sort.cpp"
- [class]{QuickSort}-[func]{quickSort}
+ /* Quick sort */
+ void quickSort(vector &nums, int left, int right) {
+ // Terminate recursion when subarray length is 1
+ if (left >= right)
+ return;
+ // Partition
+ int pivot = partition(nums, left, right);
+ // Recursively process the left subarray and right subarray
+ quickSort(nums, left, pivot - 1);
+ quickSort(nums, pivot + 1, right);
+ }
```
=== "Java"
@@ -356,9 +384,34 @@ Sample code is as follows:
=== "C++"
```cpp title="quick_sort.cpp"
- [class]{QuickSortMedian}-[func]{medianThree}
+ /* Select the median of three candidate elements */
+ int medianThree(vector &nums, int left, int mid, int right) {
+ int l = nums[left], m = nums[mid], r = nums[right];
+ if ((l <= m && m <= r) || (r <= m && m <= l))
+ return mid; // m is between l and r
+ if ((m <= l && l <= r) || (r <= l && l <= m))
+ return left; // l is between m and r
+ return right;
+ }
- [class]{QuickSortMedian}-[func]{partition}
+ /* Partition (median of three) */
+ int partition(vector &nums, int left, int right) {
+ // Select the median of three candidate elements
+ int med = medianThree(nums, left, (left + right) / 2, right);
+ // Swap the median to the array's leftmost position
+ swap(nums, left, med);
+ // Use nums[left] as the pivot
+ int i = left, j = right;
+ while (i < j) {
+ while (i < j && nums[j] >= nums[left])
+ j--; // Search from right to left for the first element smaller than the pivot
+ while (i < j && nums[i] <= nums[left])
+ i++; // Search from left to right for the first element greater than the pivot
+ swap(nums, i, j); // Swap these two elements
+ }
+ swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays
+ return i; // Return the index of the pivot
+ }
```
=== "Java"
@@ -509,7 +562,22 @@ To prevent the accumulation of stack frame space, we can compare the lengths of
=== "C++"
```cpp title="quick_sort.cpp"
- [class]{QuickSortTailCall}-[func]{quickSort}
+ /* Quick sort (tail recursion optimization) */
+ void quickSort(vector &nums, int left, int right) {
+ // Terminate when subarray length is 1
+ while (left < right) {
+ // Partition operation
+ int pivot = partition(nums, left, right);
+ // Perform quick sort on the shorter of the two subarrays
+ if (pivot - left < right - pivot) {
+ quickSort(nums, left, pivot - 1); // Recursively sort the left subarray
+ left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right]
+ } else {
+ quickSort(nums, pivot + 1, right); // Recursively sort the right subarray
+ right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]
+ }
+ }
+ }
```
=== "Java"
diff --git a/en/docs/chapter_sorting/radix_sort.md b/en/docs/chapter_sorting/radix_sort.md
index d5a27bed1..c4d22121c 100644
--- a/en/docs/chapter_sorting/radix_sort.md
+++ b/en/docs/chapter_sorting/radix_sort.md
@@ -10,7 +10,7 @@ The previous section introduced counting sort, which is suitable for scenarios w
## 11.10.1 Algorithm process
-Taking the student ID data as an example, assuming the least significant digit is the $1^{st}$ and the most significant is the $8^{th}$, the radix sort process is illustrated in the following diagram.
+Taking the student ID data as an example, assuming the least significant digit is the $1^{st}$ and the most significant is the $8^{th}$, the radix sort process is illustrated in Figure 11-18.
1. Initialize digit $k = 1$.
2. Perform "counting sort" on the $k^{th}$ digit of the student IDs. After completion, the data will be sorted from smallest to largest based on the $k^{th}$ digit.
@@ -79,11 +79,51 @@ Additionally, we need to slightly modify the counting sort code to allow sorting
=== "C++"
```cpp title="radix_sort.cpp"
- [class]{}-[func]{digit}
+ /* Get the k-th digit of element num, where exp = 10^(k-1) */
+ int digit(int num, int exp) {
+ // Passing exp instead of k can avoid repeated expensive exponentiation here
+ return (num / exp) % 10;
+ }
- [class]{}-[func]{countingSortDigit}
+ /* Counting sort (based on nums k-th digit) */
+ void countingSortDigit(vector &nums, int exp) {
+ // Decimal digit range is 0~9, therefore need a bucket array of length 10
+ vector counter(10, 0);
+ int n = nums.size();
+ // Count the occurrence of digits 0~9
+ for (int i = 0; i < n; i++) {
+ int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d
+ counter[d]++; // Count the occurrence of digit d
+ }
+ // Calculate prefix sum, converting "occurrence count" into "array index"
+ for (int i = 1; i < 10; i++) {
+ counter[i] += counter[i - 1];
+ }
+ // Traverse in reverse, based on bucket statistics, place each element into res
+ vector res(n, 0);
+ for (int i = n - 1; i >= 0; i--) {
+ int d = digit(nums[i], exp);
+ int j = counter[d] - 1; // Get the index j for d in the array
+ res[j] = nums[i]; // Place the current element at index j
+ counter[d]--; // Decrease the count of d by 1
+ }
+ // Use result to overwrite the original array nums
+ for (int i = 0; i < n; i++)
+ nums[i] = res[i];
+ }
- [class]{}-[func]{radixSort}
+ /* Radix sort */
+ void radixSort(vector &nums) {
+ // Get the maximum element of the array, used to determine the maximum number of digits
+ int m = *max_element(nums.begin(), nums.end());
+ // Traverse from the lowest to the highest digit
+ for (int exp = 1; exp <= m; exp *= 10)
+ // Perform counting sort on the k-th digit of array elements
+ // k = 1 -> exp = 1
+ // k = 2 -> exp = 10
+ // i.e., exp = 10^(k-1)
+ countingSortDigit(nums, exp);
+ }
```
=== "Java"
diff --git a/en/docs/chapter_sorting/selection_sort.md b/en/docs/chapter_sorting/selection_sort.md
index cf2a8237b..f6f61f5af 100644
--- a/en/docs/chapter_sorting/selection_sort.md
+++ b/en/docs/chapter_sorting/selection_sort.md
@@ -71,7 +71,21 @@ In the code, we use $k$ to record the smallest element within the unsorted inter
=== "C++"
```cpp title="selection_sort.cpp"
- [class]{}-[func]{selectionSort}
+ /* Selection sort */
+ void selectionSort(vector &nums) {
+ int n = nums.size();
+ // Outer loop: unsorted range is [i, n-1]
+ for (int i = 0; i < n - 1; i++) {
+ // Inner loop: find the smallest element within the unsorted range
+ int k = i;
+ for (int j = i + 1; j < n; j++) {
+ if (nums[j] < nums[k])
+ k = j; // Record the index of the smallest element
+ }
+ // Swap the smallest element with the first element of the unsorted range
+ swap(nums[i], nums[k]);
+ }
+ }
```
=== "Java"
diff --git a/en/docs/chapter_stack_and_queue/deque.md b/en/docs/chapter_stack_and_queue/deque.md
index 3809c802a..5ae8e86da 100644
--- a/en/docs/chapter_stack_and_queue/deque.md
+++ b/en/docs/chapter_stack_and_queue/deque.md
@@ -4,7 +4,7 @@ comments: true
# 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.
+In a queue, we can only delete elements from the head or add elements to the tail. As shown in Figure 5-7, 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" }
@@ -390,7 +390,7 @@ The implementation code is as follows:
def __init__(self, val: int):
"""Constructor"""
self.val: int = val
- self.next: ListNode | None = None # Reference to the next node
+ self.next: ListNode | None = None # Reference to successor node
self.prev: ListNode | None = None # Reference to predecessor node
class LinkedListDeque:
@@ -496,9 +496,146 @@ The implementation code is as follows:
=== "C++"
```cpp title="linkedlist_deque.cpp"
- [class]{DoublyListNode}-[func]{}
+ /* Double-linked list node */
+ struct DoublyListNode {
+ int val; // Node value
+ DoublyListNode *next; // Pointer to successor node
+ DoublyListNode *prev; // Pointer to predecessor node
+ DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) {
+ }
+ };
- [class]{LinkedListDeque}-[func]{}
+ /* Double-ended queue class based on double-linked list */
+ class LinkedListDeque {
+ private:
+ DoublyListNode *front, *rear; // Front node front, back node rear
+ int queSize = 0; // Length of the double-ended queue
+
+ public:
+ /* Constructor */
+ LinkedListDeque() : front(nullptr), rear(nullptr) {
+ }
+
+ /* Destructor */
+ ~LinkedListDeque() {
+ // Traverse the linked list, remove nodes, free memory
+ DoublyListNode *pre, *cur = front;
+ while (cur != nullptr) {
+ pre = cur;
+ cur = cur->next;
+ delete pre;
+ }
+ }
+
+ /* Get the length of the double-ended queue */
+ int size() {
+ return queSize;
+ }
+
+ /* Determine if the double-ended queue is empty */
+ bool isEmpty() {
+ return size() == 0;
+ }
+
+ /* Enqueue operation */
+ void push(int num, bool isFront) {
+ DoublyListNode *node = new DoublyListNode(num);
+ // If the list is empty, make front and rear both point to node
+ if (isEmpty())
+ front = rear = node;
+ // Front enqueue operation
+ else if (isFront) {
+ // Add node to the head of the list
+ front->prev = node;
+ node->next = front;
+ front = node; // Update head node
+ // Rear enqueue operation
+ } else {
+ // Add node to the tail of the list
+ rear->next = node;
+ node->prev = rear;
+ rear = node; // Update tail node
+ }
+ queSize++; // Update queue length
+ }
+
+ /* Front enqueue */
+ void pushFirst(int num) {
+ push(num, true);
+ }
+
+ /* Rear enqueue */
+ void pushLast(int num) {
+ push(num, false);
+ }
+
+ /* Dequeue operation */
+ int pop(bool isFront) {
+ if (isEmpty())
+ throw out_of_range("Queue is empty");
+ int val;
+ // Front dequeue operation
+ if (isFront) {
+ val = front->val; // Temporarily store the head node value
+ // Remove head node
+ DoublyListNode *fNext = front->next;
+ if (fNext != nullptr) {
+ fNext->prev = nullptr;
+ front->next = nullptr;
+ }
+ delete front;
+ front = fNext; // Update head node
+ // Rear dequeue operation
+ } else {
+ val = rear->val; // Temporarily store the tail node value
+ // Remove tail node
+ DoublyListNode *rPrev = rear->prev;
+ if (rPrev != nullptr) {
+ rPrev->next = nullptr;
+ rear->prev = nullptr;
+ }
+ delete rear;
+ rear = rPrev; // Update tail node
+ }
+ queSize--; // Update queue length
+ return val;
+ }
+
+ /* Front dequeue */
+ int popFirst() {
+ return pop(true);
+ }
+
+ /* Rear dequeue */
+ int popLast() {
+ return pop(false);
+ }
+
+ /* Access front element */
+ int peekFirst() {
+ if (isEmpty())
+ throw out_of_range("Double-ended queue is empty");
+ return front->val;
+ }
+
+ /* Access rear element */
+ int peekLast() {
+ if (isEmpty())
+ throw out_of_range("Double-ended queue is empty");
+ return rear->val;
+ }
+
+ /* Return array for printing */
+ vector toVector() {
+ DoublyListNode *node = front;
+ vector res(size());
+ for (int i = 0; i < res.size(); i++) {
+ res[i] = node->val;
+ node = node->next;
+ }
+ return res;
+ }
+ };
```
=== "Java"
@@ -507,7 +644,7 @@ The implementation code is as follows:
/* Double-linked list node */
class ListNode {
int val; // Node value
- ListNode next; // Reference to the next node
+ ListNode next; // Reference to successor node
ListNode prev; // Reference to predecessor node
ListNode(int val) {
@@ -837,7 +974,112 @@ The implementation only needs to add methods for "front enqueue" and "rear deque
=== "C++"
```cpp title="array_deque.cpp"
- [class]{ArrayDeque}-[func]{}
+ /* Double-ended queue class based on circular array */
+ class ArrayDeque {
+ private:
+ vector nums; // Array used to store elements of the double-ended queue
+ int front; // Front pointer, pointing to the front element
+ int queSize; // Length of the double-ended queue
+
+ public:
+ /* Constructor */
+ ArrayDeque(int capacity) {
+ nums.resize(capacity);
+ front = queSize = 0;
+ }
+
+ /* Get the capacity of the double-ended queue */
+ int capacity() {
+ return nums.size();
+ }
+
+ /* Get the length of the double-ended queue */
+ int size() {
+ return queSize;
+ }
+
+ /* Determine if the double-ended queue is empty */
+ bool isEmpty() {
+ return queSize == 0;
+ }
+
+ /* Calculate circular array index */
+ int index(int i) {
+ // Implement circular array by modulo operation
+ // When i exceeds the tail of the array, return to the head
+ // When i exceeds the head of the array, return to the tail
+ return (i + capacity()) % capacity();
+ }
+
+ /* Front enqueue */
+ void pushFirst(int num) {
+ if (queSize == capacity()) {
+ cout << "Double-ended queue is full" << endl;
+ return;
+ }
+ // Move the front pointer one position to the left
+ // Implement front crossing the head of the array to return to the tail by modulo operation
+ front = index(front - 1);
+ // Add num to the front
+ nums[front] = num;
+ queSize++;
+ }
+
+ /* Rear enqueue */
+ void pushLast(int num) {
+ if (queSize == capacity()) {
+ cout << "Double-ended queue is full" << endl;
+ return;
+ }
+ // Calculate rear pointer, pointing to rear index + 1
+ int rear = index(front + queSize);
+ // Add num to the rear
+ nums[rear] = num;
+ queSize++;
+ }
+
+ /* Front dequeue */
+ int popFirst() {
+ int num = peekFirst();
+ // Move front pointer one position backward
+ front = index(front + 1);
+ queSize--;
+ return num;
+ }
+
+ /* Rear dequeue */
+ int popLast() {
+ int num = peekLast();
+ queSize--;
+ return num;
+ }
+
+ /* Access front element */
+ int peekFirst() {
+ if (isEmpty())
+ throw out_of_range("Double-ended queue is empty");
+ return nums[front];
+ }
+
+ /* Access rear element */
+ int peekLast() {
+ if (isEmpty())
+ throw out_of_range("Double-ended queue is empty");
+ // Calculate rear element index
+ int last = index(front + queSize - 1);
+ return nums[last];
+ }
+
+ /* Return array for printing */
+ vector toVector() {
+ // Only convert elements within valid length range
+ vector res(queSize);
+ for (int i = 0, j = front; i < queSize; i++, j++) {
+ res[i] = nums[index(j)];
+ }
+ return res;
+ }
+ };
```
=== "Java"
diff --git a/en/docs/chapter_stack_and_queue/queue.md b/en/docs/chapter_stack_and_queue/queue.md
index 9e51c167c..9d2ae7607 100755
--- a/en/docs/chapter_stack_and_queue/queue.md
+++ b/en/docs/chapter_stack_and_queue/queue.md
@@ -411,7 +411,81 @@ Below is the code for implementing a queue using a linked list:
=== "C++"
```cpp title="linkedlist_queue.cpp"
- [class]{LinkedListQueue}-[func]{}
+ /* Queue class based on linked list */
+ class LinkedListQueue {
+ private:
+ ListNode *front, *rear; // Front node front, back node rear
+ int queSize;
+
+ public:
+ LinkedListQueue() {
+ front = nullptr;
+ rear = nullptr;
+ queSize = 0;
+ }
+
+ ~LinkedListQueue() {
+ // Traverse the linked list, remove nodes, free memory
+ freeMemoryLinkedList(front);
+ }
+
+ /* Get the length of the queue */
+ int size() {
+ return queSize;
+ }
+
+ /* Determine if the queue is empty */
+ bool isEmpty() {
+ return queSize == 0;
+ }
+
+ /* Enqueue */
+ void push(int num) {
+ // Add num behind the tail node
+ ListNode *node = new ListNode(num);
+ // If the queue is empty, make the head and tail nodes both point to that node
+ if (front == nullptr) {
+ front = node;
+ rear = node;
+ }
+ // If the queue is not empty, add that node behind the tail node
+ else {
+ rear->next = node;
+ rear = node;
+ }
+ queSize++;
+ }
+
+ /* Dequeue */
+ int pop() {
+ int num = peek();
+ // Remove head node
+ ListNode *tmp = front;
+ front = front->next;
+ // Free memory
+ delete tmp;
+ queSize--;
+ return num;
+ }
+
+ /* Access front element */
+ int peek() {
+ if (size() == 0)
+ throw out_of_range("Queue is empty");
+ return front->val;
+ }
+
+ /* Convert the linked list to Vector and return */
+ vector toVector() {
+ ListNode *node = front;
+ vector res(size());
+ for (int i = 0; i < res.size(); i++) {
+ res[i] = node->val;
+ node = node->next;
+ }
+ return res;
+ }
+ };
```
=== "Java"
@@ -638,7 +712,81 @@ In a circular array, `front` or `rear` needs to loop back to the start of the ar
=== "C++"
```cpp title="array_queue.cpp"
- [class]{ArrayQueue}-[func]{}
+ /* Queue class based on circular array */
+ class ArrayQueue {
+ private:
+ int *nums; // Array for storing queue elements
+ int front; // Front pointer, pointing to the front element
+ int queSize; // Queue length
+ int queCapacity; // Queue capacity
+
+ public:
+ ArrayQueue(int capacity) {
+ // Initialize an array
+ nums = new int[capacity];
+ queCapacity = capacity;
+ front = queSize = 0;
+ }
+
+ ~ArrayQueue() {
+ delete[] nums;
+ }
+
+ /* Get the capacity of the queue */
+ int capacity() {
+ return queCapacity;
+ }
+
+ /* Get the length of the queue */
+ int size() {
+ return queSize;
+ }
+
+ /* Determine if the queue is empty */
+ bool isEmpty() {
+ return size() == 0;
+ }
+
+ /* Enqueue */
+ void push(int num) {
+ if (queSize == queCapacity) {
+ cout << "Queue is full" << endl;
+ return;
+ }
+ // Calculate rear pointer, pointing to rear index + 1
+ // Use modulo operation to wrap the rear pointer from the end of the array back to the start
+ int rear = (front + queSize) % queCapacity;
+ // Add num to the rear
+ nums[rear] = num;
+ queSize++;
+ }
+
+ /* Dequeue */
+ int pop() {
+ int num = peek();
+ // Move front pointer one position backward, returning to the head of the array if it exceeds the tail
+ front = (front + 1) % queCapacity;
+ queSize--;
+ return num;
+ }
+
+ /* Access front element */
+ int peek() {
+ if (isEmpty())
+ throw out_of_range("Queue is empty");
+ return nums[front];
+ }
+
+ /* Convert array to Vector and return */
+ vector toVector() {
+ // Only convert elements within valid length range
+ vector arr(queSize);
+ for (int i = 0, j = front; i < queSize; i++, j++) {
+ arr[i] = nums[j % queCapacity];
+ }
+ return arr;
+ }
+ };
```
=== "Java"
diff --git a/en/docs/chapter_stack_and_queue/stack.md b/en/docs/chapter_stack_and_queue/stack.md
index 52cacb48b..166d155b4 100755
--- a/en/docs/chapter_stack_and_queue/stack.md
+++ b/en/docs/chapter_stack_and_queue/stack.md
@@ -401,7 +401,70 @@ Below is an example code for implementing a stack based on a linked list:
=== "C++"
```cpp title="linkedlist_stack.cpp"
- [class]{LinkedListStack}-[func]{}
+ /* Stack class based on linked list */
+ class LinkedListStack {
+ private:
+ ListNode *stackTop; // Use the head node as the top of the stack
+ int stkSize; // Length of the stack
+
+ public:
+ LinkedListStack() {
+ stackTop = nullptr;
+ stkSize = 0;
+ }
+
+ ~LinkedListStack() {
+ // Traverse the linked list, remove nodes, free memory
+ freeMemoryLinkedList(stackTop);
+ }
+
+ /* Get the length of the stack */
+ int size() {
+ return stkSize;
+ }
+
+ /* Determine if the stack is empty */
+ bool isEmpty() {
+ return size() == 0;
+ }
+
+ /* Push */
+ void push(int num) {
+ ListNode *node = new ListNode(num);
+ node->next = stackTop;
+ stackTop = node;
+ stkSize++;
+ }
+
+ /* Pop */
+ int pop() {
+ int num = top();
+ ListNode *tmp = stackTop;
+ stackTop = stackTop->next;
+ // Free memory
+ delete tmp;
+ stkSize--;
+ return num;
+ }
+
+ /* Access stack top element */
+ int top() {
+ if (isEmpty())
+ throw out_of_range("Stack is empty");
+ return stackTop->val;
+ }
+
+ /* Convert the List to Array and return */
+ vector toVector() {
+ ListNode *node = stackTop;
+ vector res(size());
+ for (int i = res.size() - 1; i >= 0; i--) {
+ res[i] = node->val;
+ node = node->next;
+ }
+ return res;
+ }
+ };
```
=== "Java"
@@ -587,7 +650,46 @@ Since the elements to be pushed onto the stack may continuously increase, we can
=== "C++"
```cpp title="array_stack.cpp"
- [class]{ArrayStack}-[func]{}
+ /* Stack class based on array */
+ class ArrayStack {
+ private:
+ vector stack;
+
+ public:
+ /* Get the length of the stack */
+ int size() {
+ return stack.size();
+ }
+
+ /* Determine if the stack is empty */
+ bool isEmpty() {
+ return stack.size() == 0;
+ }
+
+ /* Push */
+ void push(int num) {
+ stack.push_back(num);
+ }
+
+ /* Pop */
+ int pop() {
+ int num = top();
+ stack.pop_back();
+ return num;
+ }
+
+ /* Access stack top element */
+ int top() {
+ if (isEmpty())
+ throw out_of_range("Stack is empty");
+ return stack.back();
+ }
+
+ /* Return Vector */
+ vector toVector() {
+ return stack;
+ }
+ };
```
=== "Java"
diff --git a/en/docs/chapter_tree/array_representation_of_tree.md b/en/docs/chapter_tree/array_representation_of_tree.md
index 8d535eee8..efd484fb2 100644
--- a/en/docs/chapter_tree/array_representation_of_tree.md
+++ b/en/docs/chapter_tree/array_representation_of_tree.md
@@ -237,7 +237,95 @@ The following code implements a binary tree based on array representation, inclu
=== "C++"
```cpp title="array_binary_tree.cpp"
- [class]{ArrayBinaryTree}-[func]{}
+ /* Array-based binary tree class */
+ class ArrayBinaryTree {
+ public:
+ /* Constructor */
+ ArrayBinaryTree(vector arr) {
+ tree = arr;
+ }
+
+ /* List capacity */
+ int size() {
+ return tree.size();
+ }
+
+ /* Get the value of the node at index i */
+ int val(int i) {
+ // If index is out of bounds, return INT_MAX, representing a null
+ if (i < 0 || i >= size())
+ return INT_MAX;
+ return tree[i];
+ }
+
+ /* Get the index of the left child of the node at index i */
+ int left(int i) {
+ return 2 * i + 1;
+ }
+
+ /* Get the index of the right child of the node at index i */
+ int right(int i) {
+ return 2 * i + 2;
+ }
+
+ /* Get the index of the parent of the node at index i */
+ int parent(int i) {
+ return (i - 1) / 2;
+ }
+
+ /* Level-order traversal */
+ vector levelOrder() {
+ vector res;
+ // Traverse array
+ for (int i = 0; i < size(); i++) {
+ if (val(i) != INT_MAX)
+ res.push_back(val(i));
+ }
+ return res;
+ }
+
+ /* Pre-order traversal */
+ vector preOrder() {
+ vector res;
+ dfs(0, "pre", res);
+ return res;
+ }
+
+ /* In-order traversal */
+ vector inOrder() {
+ vector res;
+ dfs(0, "in", res);
+ return res;
+ }
+
+ /* Post-order traversal */
+ vector postOrder() {
+ vector res;
+ dfs(0, "post", res);
+ return res;
+ }
+
+ private:
+ vector tree;
+
+ /* Depth-first traversal */
+ void dfs(int i, string order, vector &res) {
+ // If it is an empty spot, return
+ if (val(i) == INT_MAX)
+ return;
+ // Pre-order traversal
+ if (order == "pre")
+ res.push_back(val(i));
+ dfs(left(i), order, res);
+ // In-order traversal
+ if (order == "in")
+ res.push_back(val(i));
+ dfs(right(i), order, res);
+ // Post-order traversal
+ if (order == "post")
+ res.push_back(val(i));
+ }
+ };
```
=== "Java"
diff --git a/en/docs/chapter_tree/avl_tree.md b/en/docs/chapter_tree/avl_tree.md
index 3268d226b..3eda26eab 100644
--- a/en/docs/chapter_tree/avl_tree.md
+++ b/en/docs/chapter_tree/avl_tree.md
@@ -252,9 +252,17 @@ The "node height" refers to the distance from that node to its farthest leaf nod
=== "C++"
```cpp title="avl_tree.cpp"
- [class]{AVLTree}-[func]{height}
+ /* Get node height */
+ int height(TreeNode *node) {
+ // Empty node height is -1, leaf node height is 0
+ return node == nullptr ? -1 : node->height;
+ }
- [class]{AVLTree}-[func]{updateHeight}
+ /* Update node height */
+ void updateHeight(TreeNode *node) {
+ // Node height equals the height of the tallest subtree + 1
+ node->height = max(height(node->left), height(node->right)) + 1;
+ }
```
=== "Java"
@@ -380,7 +388,14 @@ The