From d246e08cc66c792026a4ee023536a0fb06cc2ca1 Mon Sep 17 00:00:00 2001 From: krahets Date: Wed, 1 May 2024 07:30:15 +0800 Subject: [PATCH] deploy --- en/chapter_appendix/contribution/index.html | 4 ++-- en/chapter_appendix/installation/index.html | 2 +- en/chapter_appendix/terminology/index.html | 2 +- .../array/index.html | 4 ++-- .../linked_list/index.html | 2 +- .../ram_and_cache/index.html | 4 ++-- .../summary/index.html | 2 +- .../backtracking_algorithm/index.html | 4 ++-- .../n_queens_problem/index.html | 8 +++---- .../permutations_problem/index.html | 14 ++++++------ .../subset_sum_problem/index.html | 10 ++++----- .../iteration_and_recursion/index.html | 6 +++--- .../space_complexity/index.html | 8 +++---- .../time_complexity/index.html | 12 +++++------ .../character_encoding/index.html | 6 +++--- .../index.html | 8 +++---- .../number_encoding/index.html | 2 +- .../build_binary_tree_problem/index.html | 2 +- .../divide_and_conquer/index.html | 6 +++--- .../hanota_problem/index.html | 12 +++++------ .../dp_problem_features/index.html | 8 +++---- .../dp_solution_pipeline/index.html | 14 ++++++------ .../edit_distance_problem/index.html | 8 +++---- .../intro_to_dynamic_programming/index.html | 12 +++++------ .../knapsack_problem/index.html | 8 +++---- .../unbounded_knapsack_problem/index.html | 4 ++-- en/chapter_graph/graph/index.html | 20 +++++++++--------- en/chapter_graph/graph_operations/index.html | 8 +++---- en/chapter_graph/graph_traversal/index.html | 12 +++++------ .../fractional_knapsack_problem/index.html | 8 +++---- en/chapter_greedy/greedy_algorithm/index.html | 4 ++-- .../max_capacity_problem/index.html | 12 +++++------ .../max_product_cutting_problem/index.html | 6 +++--- en/chapter_hashing/hash_algorithm/index.html | 4 ++-- en/chapter_hashing/hash_collision/index.html | 6 +++--- en/chapter_hashing/hash_map/index.html | 10 ++++----- en/chapter_heap/build_heap/index.html | 2 +- en/chapter_heap/heap/index.html | 10 ++++----- en/chapter_heap/top_k/index.html | 4 ++-- .../algorithms_are_everywhere/index.html | 2 +- .../what_is_dsa/index.html | 6 +++--- en/chapter_preface/about_the_book/index.html | 2 +- en/chapter_preface/suggestions/index.html | 12 +++++------ en/chapter_searching/binary_search/index.html | 6 +++--- .../binary_search_edge/index.html | 4 ++-- .../binary_search_insertion/index.html | 6 +++--- .../replace_linear_by_hashing/index.html | 2 +- .../searching_algorithm_revisited/index.html | 2 +- en/chapter_sorting/bubble_sort/index.html | 4 ++-- en/chapter_sorting/bucket_sort/index.html | 6 +++--- en/chapter_sorting/insertion_sort/index.html | 4 ++-- en/chapter_sorting/merge_sort/index.html | 4 ++-- en/chapter_sorting/quick_sort/index.html | 2 +- en/chapter_sorting/selection_sort/index.html | 4 ++-- .../sorting_algorithm/index.html | 2 +- en/chapter_sorting/summary/index.html | 2 +- en/chapter_stack_and_queue/deque/index.html | 4 ++-- en/chapter_stack_and_queue/queue/index.html | 8 +++---- en/chapter_stack_and_queue/stack/index.html | 8 +++---- .../array_representation_of_tree/index.html | 8 +++---- en/chapter_tree/avl_tree/index.html | 20 +++++++++--------- en/chapter_tree/binary_search_tree/index.html | 18 ++++++++-------- en/chapter_tree/binary_tree/index.html | 18 ++++++++-------- .../binary_tree_traversal/index.html | 6 +++--- en/search/search_index.json | 2 +- en/sitemap.xml.gz | Bin 1007 -> 1007 bytes sitemap.xml.gz | Bin 1011 -> 1011 bytes zh-hant/sitemap.xml.gz | Bin 1009 -> 1009 bytes 68 files changed, 220 insertions(+), 220 deletions(-) diff --git a/en/chapter_appendix/contribution/index.html b/en/chapter_appendix/contribution/index.html index b71079a6a..a6d6bafaa 100644 --- a/en/chapter_appendix/contribution/index.html +++ b/en/chapter_appendix/contribution/index.html @@ -3617,7 +3617,7 @@

In this open-source book, however, the content update cycle is shortened to just a few days or even hours.

1.   Content fine-tuning

-

As shown in the Figure 16-3 , there is an "edit icon" in the upper right corner of each page. You can follow these steps to modify text or code.

+

As shown in Figure 16-3, there is an "edit icon" in the upper right corner of each page. You can follow these steps to modify text or code.

  1. Click the "edit icon". If prompted to "fork this repository", please agree to do so.
  2. Modify the Markdown source file content, check the accuracy of the content, and try to keep the formatting consistent.
  3. @@ -3626,7 +3626,7 @@

    Edit page button

    Figure 16-3   Edit page button

    -

    Images cannot be directly modified and require the creation of a new Issue or a comment to describe the problem. We will redraw and replace the images as soon as possible.

    +

    Figures cannot be directly modified and require the creation of a new Issue or a comment to describe the problem. We will redraw and replace the figures as soon as possible.

    2.   Content creation

    If you are interested in participating in this open-source project, including translating code into other programming languages or expanding article content, then the following Pull Request workflow needs to be implemented.

      diff --git a/en/chapter_appendix/installation/index.html b/en/chapter_appendix/installation/index.html index b1ada803a..1a40f55e9 100644 --- a/en/chapter_appendix/installation/index.html +++ b/en/chapter_appendix/installation/index.html @@ -3788,7 +3788,7 @@

      Download VS Code from the official website

      Figure 16-1   Download VS Code from the official website

      -

      VS Code has a powerful extension ecosystem, supporting the execution and debugging of most programming languages. For example, after installing the "Python Extension Pack," you can debug Python code. The installation steps are shown in the following figure.

      +

      VS Code has a powerful extension ecosystem, supporting the execution and debugging of most programming languages. For example, after installing the "Python Extension Pack," you can debug Python code. The installation steps are shown in Figure 16-2.

      Install VS Code Extension Pack

      Figure 16-2   Install VS Code Extension Pack

      diff --git a/en/chapter_appendix/terminology/index.html b/en/chapter_appendix/terminology/index.html index 2f420187f..fdfcd465a 100644 --- a/en/chapter_appendix/terminology/index.html +++ b/en/chapter_appendix/terminology/index.html @@ -3517,7 +3517,7 @@

      16.3   Glossary

      -

      The Table 16-1 lists the important terms that appear in the book, and it is worth noting the following points.

      +

      Table 16-1 lists the important terms that appear in the book, and it is worth noting the following points.

      • It is recommended to remember the English names of the terms to facilitate reading English literature.
      • Some terms have different names in Simplified and Traditional Chinese.
      • diff --git a/en/chapter_array_and_linkedlist/array/index.html b/en/chapter_array_and_linkedlist/array/index.html index b7a5d17a0..888f6304b 100644 --- a/en/chapter_array_and_linkedlist/array/index.html +++ b/en/chapter_array_and_linkedlist/array/index.html @@ -3747,7 +3747,7 @@

        4.1   Array

        -

        An "array" is a linear data structure that operates as a lineup of similar items, stored together in a computer's memory in contiguous spaces. It's like a sequence that maintains organized storage. Each item in this lineup has its unique 'spot' known as an "index". Please refer to the Figure 4-1 to observe how arrays work and grasp these key terms.

        +

        An "array" is a linear data structure that operates as a lineup of similar items, stored together in a computer's memory in contiguous spaces. It's like a sequence that maintains organized storage. Each item in this lineup has its unique 'spot' known as an "index". Please refer to Figure 4-1 to observe how arrays work and grasp these key terms.

        Array definition and storage method

        Figure 4-1   Array definition and storage method

        @@ -4195,7 +4195,7 @@
        Full Screen >

        4.   Deleting elements

        -

        Similarly, as depicted in the Figure 4-4 , to delete an element at index \(i\), all elements following index \(i\) must be moved forward by one position.

        +

        Similarly, as depicted in Figure 4-4, to delete an element at index \(i\), all elements following index \(i\) must be moved forward by one position.

        Array element deletion example

        Figure 4-4   Array element deletion example

        diff --git a/en/chapter_array_and_linkedlist/linked_list/index.html b/en/chapter_array_and_linkedlist/linked_list/index.html index 6bc60fa56..91b9bd3ad 100644 --- a/en/chapter_array_and_linkedlist/linked_list/index.html +++ b/en/chapter_array_and_linkedlist/linked_list/index.html @@ -4812,7 +4812,7 @@
        Full Screen >

        4.2.2   Arrays vs. linked lists

        -

        The Table 4-1 summarizes the characteristics of arrays and linked lists, and it also compares their efficiencies in various operations. Because they utilize opposing storage strategies, their respective properties and operational efficiencies exhibit distinct contrasts.

        +

        Table 4-1 summarizes the characteristics of arrays and linked lists, and it also compares their efficiencies in various operations. Because they utilize opposing storage strategies, their respective properties and operational efficiencies exhibit distinct contrasts.

        Table 4-1   Efficiency comparison of arrays and linked lists

        diff --git a/en/chapter_array_and_linkedlist/ram_and_cache/index.html b/en/chapter_array_and_linkedlist/ram_and_cache/index.html index e89d2cfdc..c9ee84d5e 100644 --- a/en/chapter_array_and_linkedlist/ram_and_cache/index.html +++ b/en/chapter_array_and_linkedlist/ram_and_cache/index.html @@ -3659,7 +3659,7 @@
        -

        We can imagine the computer storage system as a pyramid structure shown in the Figure 4-9 . The storage devices closer to the top of the pyramid are faster, have smaller capacity, and are more costly. This multi-level design is not accidental, but the result of careful consideration by computer scientists and engineers.

        +

        We can imagine the computer storage system as a pyramid structure shown in Figure 4-9. The storage devices closer to the top of the pyramid are faster, have smaller capacity, and are more costly. This multi-level design is not accidental, but the result of careful consideration by computer scientists and engineers.

        • Hard disks are difficult to replace with memory. Firstly, data in memory is lost after power off, making it unsuitable for long-term data storage; secondly, the cost of memory is dozens of times that of hard disks, making it difficult to popularize in the consumer market.
        • It is difficult for caches to have both large capacity and high speed. As the capacity of L1, L2, L3 caches gradually increases, their physical size becomes larger, increasing the physical distance from the CPU core, leading to increased data transfer time and higher element access latency. Under current technology, a multi-level cache structure is the best balance between capacity, speed, and cost.
        • @@ -3672,7 +3672,7 @@

          The storage hierarchy of computers reflects a delicate balance between speed, capacity, and cost. In fact, this kind of trade-off is common in all industrial fields, requiring us to find the best balance between different advantages and limitations.

          Overall, hard disks are used for long-term storage of large amounts of data, memory is used for temporary storage of data being processed during program execution, and cache is used to store frequently accessed data and instructions to improve program execution efficiency. Together, they ensure the efficient operation of computer systems.

          -

          As shown in the Figure 4-10 , during program execution, data is read from the hard disk into memory for CPU computation. The cache can be considered a part of the CPU, smartly loading data from memory to provide fast data access to the CPU, significantly enhancing program execution efficiency and reducing reliance on slower memory.

          +

          As shown in Figure 4-10, during program execution, data is read from the hard disk into memory for CPU computation. The cache can be considered a part of the CPU, smartly loading data from memory to provide fast data access to the CPU, significantly enhancing program execution efficiency and reducing reliance on slower memory.

          Data flow between hard disk, memory, and cache

          Figure 4-10   Data flow between hard disk, memory, and cache

          diff --git a/en/chapter_array_and_linkedlist/summary/index.html b/en/chapter_array_and_linkedlist/summary/index.html index 455628ba0..c37057a76 100644 --- a/en/chapter_array_and_linkedlist/summary/index.html +++ b/en/chapter_array_and_linkedlist/summary/index.html @@ -3621,7 +3621,7 @@

          From a garbage collection perspective, for languages with automatic garbage collection mechanisms like Java, Python, and Go, whether node P is collected depends on whether there are still references pointing to it, not on the value of P.next. In languages like C and C++, we need to manually free the node's memory.

          Q: In linked lists, the time complexity for insertion and deletion operations is O(1). But searching for the element before insertion or deletion takes O(n) time, so why isn't the time complexity O(n)?

          If an element is searched first and then deleted, the time complexity is indeed O(n). However, the O(1) advantage of linked lists in insertion and deletion can be realized in other applications. For example, in the implementation of double-ended queues using linked lists, we maintain pointers always pointing to the head and tail nodes, making each insertion and deletion operation O(1).

          -

          Q: In the image "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?

          +

          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.

          • Different types of node values occupy different amounts of space, such as int, long, double, and object instances.
          • diff --git a/en/chapter_backtracking/backtracking_algorithm/index.html b/en/chapter_backtracking/backtracking_algorithm/index.html index 4b78359ac..034a4750e 100644 --- a/en/chapter_backtracking/backtracking_algorithm/index.html +++ b/en/chapter_backtracking/backtracking_algorithm/index.html @@ -4139,7 +4139,7 @@

            In each "try", we record the path by adding the current node to path; before "retreating", we need to pop the node from path to restore the state before this attempt.

            -

            Observe the process shown below, we can understand trying and retreating as "advancing" and "undoing", two operations that are reverse to each other.

            +

            Observe the process shown in Figure 13-2, we can understand trying and retreating as "advancing" and "undoing", two operations that are reverse to each other.

            @@ -5406,7 +5406,7 @@

            Compared to the implementation based on preorder traversal, the code implementation based on the backtracking algorithm framework seems verbose, but it has better universality. In fact, many backtracking problems can be solved within this framework. We just need to define state and choices according to the specific problem and implement the methods in the framework.

            13.1.4   Common terminology

            -

            To analyze algorithmic problems more clearly, we summarize the meanings of commonly used terminology in backtracking algorithms and provide corresponding examples from Example Three as shown in the Table 13-1 .

            +

            To analyze algorithmic problems more clearly, we summarize the meanings of commonly used terminology in backtracking algorithms and provide corresponding examples from Example Three as shown in Table 13-1.

            Table 13-1   Common backtracking algorithm terminology

            diff --git a/en/chapter_backtracking/n_queens_problem/index.html b/en/chapter_backtracking/n_queens_problem/index.html index 479b5773e..6d7f38cb6 100644 --- a/en/chapter_backtracking/n_queens_problem/index.html +++ b/en/chapter_backtracking/n_queens_problem/index.html @@ -3613,18 +3613,18 @@

            Question

            According to the rules of chess, a queen can attack pieces in the same row, column, or on a diagonal line. Given \(n\) queens and an \(n \times n\) chessboard, find arrangements where no two queens can attack each other.

            -

            As shown in the Figure 13-15 , when \(n = 4\), there are two solutions. From the perspective of the backtracking algorithm, an \(n \times n\) chessboard has \(n^2\) squares, presenting all possible choices choices. The state of the chessboard state changes continuously as each queen is placed.

            +

            As shown in Figure 13-15, when \(n = 4\), there are two solutions. From the perspective of the backtracking algorithm, an \(n \times n\) chessboard has \(n^2\) squares, presenting all possible choices choices. The state of the chessboard state changes continuously as each queen is placed.

            Solution to the 4 queens problem

            Figure 13-15   Solution to the 4 queens problem

            -

            The following image shows the three constraints of this problem: multiple queens cannot be on the same row, column, or diagonal. It is important to note that diagonals are divided into the main diagonal \ and the secondary diagonal /.

            +

            Figure 13-16 shows the three constraints of this problem: multiple queens cannot be on the same row, column, or diagonal. It is important to note that diagonals are divided into the main diagonal \ and the secondary diagonal /.

            Constraints of the n queens problem

            Figure 13-16   Constraints of the n queens problem

            1.   Row-by-row placing strategy

            As the number of queens equals the number of rows on the chessboard, both being \(n\), it is easy to conclude: each row on the chessboard allows and only allows one queen to be placed.

            This means that we can adopt a row-by-row placing strategy: starting from the first row, place one queen per row until the last row is reached.

            -

            The image below shows the row-by-row placing process for the 4 queens problem. Due to space limitations, the image only expands one search branch of the first row, and prunes any placements that do not meet the column and diagonal constraints.

            +

            Figure 13-17 shows the row-by-row placing process for the 4 queens problem. Due to space limitations, the figure only expands one search branch of the first row, and prunes any placements that do not meet the column and diagonal constraints.

            Row-by-row placing strategy

            Figure 13-17   Row-by-row placing strategy

            @@ -3632,7 +3632,7 @@

            2.   Column and diagonal pruning

            To satisfy column constraints, we can use a boolean array cols of length \(n\) to track whether a queen occupies each column. Before each placement decision, cols is used to prune the columns that already have queens, and it is dynamically updated during backtracking.

            How about the diagonal constraints? Let the row and column indices of a cell on the chessboard be \((row, col)\). By selecting a specific main diagonal, we notice that the difference \(row - col\) is the same for all cells on that diagonal, meaning that \(row - col\) is a constant value on that diagonal.

            -

            Thus, if two cells satisfy \(row_1 - col_1 = row_2 - col_2\), they are definitely on the same main diagonal. Using this pattern, we can utilize the array diags1 shown below to track whether a queen is on any main diagonal.

            +

            Thus, if two cells satisfy \(row_1 - col_1 = row_2 - col_2\), they are definitely on the same main diagonal. Using this pattern, we can utilize the array diags1 shown in Figure 13-18 to track whether a queen is on any main diagonal.

            Similarly, the sum \(row + col\) is a constant value for all cells on a secondary diagonal. We can also use the array diags2 to handle secondary diagonal constraints.

            Handling column and diagonal constraints

            Figure 13-18   Handling column and diagonal constraints

            diff --git a/en/chapter_backtracking/permutations_problem/index.html b/en/chapter_backtracking/permutations_problem/index.html index 7a3b8fb30..626c33151 100644 --- a/en/chapter_backtracking/permutations_problem/index.html +++ b/en/chapter_backtracking/permutations_problem/index.html @@ -3706,7 +3706,7 @@

            13.2   Permutation problem

            The permutation problem is a typical application of the backtracking algorithm. It is defined as finding all possible arrangements of elements from a given set (such as an array or string).

            -

            The Table 13-2 lists several example data, including the input arrays and their corresponding permutations.

            +

            Table 13-2 lists several example data, including the input arrays and their corresponding permutations.

            Table 13-2   Permutation examples

            @@ -3740,7 +3740,7 @@

            From the perspective of the backtracking algorithm, we can imagine the process of generating permutations as a series of choices. Suppose the input array is \([1, 2, 3]\), if we first choose \(1\), then \(3\), and finally \(2\), we obtain the permutation \([1, 3, 2]\). Backtracking means undoing a choice and then continuing to try other choices.

            From the code perspective, the candidate set choices contains all elements of the input array, and the state state contains elements that have been selected so far. Please note that each element can only be chosen once, thus all elements in state must be unique.

            -

            As shown in the following figure, we can unfold the search process into a recursive tree, where each node represents the current state state. Starting from the root node, after three rounds of choices, we reach the leaf nodes, each corresponding to a permutation.

            +

            As shown in Figure 13-5, we can unfold the search process into a recursive tree, where each node represents the current state state. Starting from the root node, after three rounds of choices, we reach the leaf nodes, each corresponding to a permutation.

            Permutation recursive tree

            Figure 13-5   Permutation recursive tree

            @@ -3750,11 +3750,11 @@
          • After making the choice choice[i], we set selected[i] to \(\text{True}\), indicating it has been chosen.
          • When iterating through the choice list choices, skip all nodes that have already been selected, i.e., prune.
          -

          As shown in the following figure, suppose we choose 1 in the first round, 3 in the second round, and 2 in the third round, we need to prune the branch of element 1 in the second round and elements 1 and 3 in the third round.

          +

          As shown in Figure 13-6, suppose we choose 1 in the first round, 3 in the second round, and 2 in the third round, we need to prune the branch of element 1 in the second round and elements 1 and 3 in the third round.

          Permutation pruning example

          Figure 13-6   Permutation pruning example

          -

          Observing the above figure, this pruning operation reduces the search space size from \(O(n^n)\) to \(O(n!)\).

          +

          Observing Figure 13-6, this pruning operation reduces the search space size from \(O(n^n)\) to \(O(n!)\).

          2.   Code implementation

          After understanding the above information, we can "fill in the blanks" in the framework code. To shorten the overall code, we do not implement individual functions within the framework code separately, but expand them in the backtrack() function:

          @@ -4208,13 +4208,13 @@

          Enter an integer array, which may contain duplicate elements, and return all unique permutations.

          Suppose the input array is \([1, 1, 2]\). To differentiate the two duplicate elements \(1\), we mark the second \(1\) as \(\hat{1}\).

          -

          As shown in the following figure, half of the permutations generated by the above method are duplicates.

          +

          As shown in Figure 13-7, half of the permutations generated by the above method are duplicates.

          Duplicate permutations

          Figure 13-7   Duplicate permutations

          So, how do we eliminate duplicate permutations? Most directly, consider using a hash set to deduplicate permutation results. However, this is not elegant, as branches generating duplicate permutations are unnecessary and should be identified and pruned in advance, which can further improve algorithm efficiency.

          1.   Pruning of equal elements

          -

          Observing the following figure, in the first round, choosing \(1\) or \(\hat{1}\) results in identical permutations under both choices, thus we should prune \(\hat{1}\).

          +

          Observing Figure 13-8, in the first round, choosing \(1\) or \(\hat{1}\) results in identical permutations under both choices, thus we should prune \(\hat{1}\).

          Similarly, after choosing \(2\) in the first round, choosing \(1\) and \(\hat{1}\) in the second round also produces duplicate branches, so we should also prune \(\hat{1}\) in the second round.

          Essentially, our goal is to ensure that multiple equal elements are only selected once in each round of choices.

          Duplicate permutations pruning

          @@ -4700,7 +4700,7 @@
        • Repeated choice pruning: There is only one selected throughout the search process. It records which elements are currently in the state, aiming to prevent an element from appearing repeatedly in state.
        • Equal element pruning: Each round of choices (each call to the backtrack function) contains a duplicated. It records which elements have been chosen in the current traversal (for loop), aiming to ensure equal elements are selected only once.
        -

        The following figure shows the scope of the two pruning conditions. Note, each node in the tree represents a choice, and the nodes from the root to the leaf form a permutation.

        +

        Figure 13-9 shows the scope of the two pruning conditions. Note, each node in the tree represents a choice, and the nodes from the root to the leaf form a permutation.

        Scope of the two pruning conditions

        Figure 13-9   Scope of the two pruning conditions

        diff --git a/en/chapter_backtracking/subset_sum_problem/index.html b/en/chapter_backtracking/subset_sum_problem/index.html index fb7fbfc5a..3f9a1f4f9 100644 --- a/en/chapter_backtracking/subset_sum_problem/index.html +++ b/en/chapter_backtracking/subset_sum_problem/index.html @@ -4162,7 +4162,7 @@
        Full Screen >

        Inputting the array \([3, 4, 5]\) and target element \(9\) into the above code yields the results \([3, 3, 3], [4, 5], [5, 4]\). Although it successfully finds all subsets with a sum of \(9\), it includes the duplicate subset \([4, 5]\) and \([5, 4]\).

        -

        This is because the search process distinguishes the order of choices, however, subsets do not distinguish the choice order. As shown in the following figure, choosing \(4\) before \(5\) and choosing \(5\) before \(4\) are different branches, but correspond to the same subset.

        +

        This is because the search process distinguishes the order of choices, however, subsets do not distinguish the choice order. As shown in Figure 13-10, choosing \(4\) before \(5\) and choosing \(5\) before \(4\) are different branches, but correspond to the same subset.

        Subset search and pruning out of bounds

        Figure 13-10   Subset search and pruning out of bounds

        @@ -4172,7 +4172,7 @@
      • Comparing subsets (arrays) for differences is very time-consuming, requiring arrays to be sorted first, then comparing the differences of each element in the arrays.

      2.   Duplicate subset pruning

      -

      We consider deduplication during the search process through pruning. Observing the following figure, duplicate subsets are generated when choosing array elements in different orders, for example in the following situations.

      +

      We consider deduplication during the search process through pruning. Observing Figure 13-11, duplicate subsets are generated when choosing array elements in different orders, for example in the following situations.

      1. When choosing \(3\) in the first round and \(4\) in the second round, all subsets containing these two elements are generated, denoted as \([3, 4, \dots]\).
      2. Later, when \(4\) is chosen in the first round, the second round should skip \(3\) because the subset \([4, 3, \dots]\) generated by this choice completely duplicates the subset from step 1..
      3. @@ -4670,7 +4670,7 @@

        Full Screen >

        -

        The following figure shows the overall backtracking process after inputting the array \([3, 4, 5]\) and target element \(9\) into the above code.

        +

        Figure 13-12 shows the overall backtracking process after inputting the array \([3, 4, 5]\) and target element \(9\) into the above code.

        Subset sum I backtracking process

        Figure 13-12   Subset sum I backtracking process

        @@ -4680,7 +4680,7 @@

        Given an array of positive integers nums and a target positive integer target, find all possible combinations such that the sum of the elements in the combination equals target. The given array may contain duplicate elements, and each element can only be chosen once. Please return these combinations as a list, which should not contain duplicate combinations.

        Compared to the previous question, this question's input array may contain duplicate elements, introducing new problems. For example, given the array \([4, \hat{4}, 5]\) and target element \(9\), the existing code's output results in \([4, 5], [\hat{4}, 5]\), resulting in duplicate subsets.

        -

        The reason for this duplication is that equal elements are chosen multiple times in a certain round. In the following figure, the first round has three choices, two of which are \(4\), generating two duplicate search branches, thus outputting duplicate subsets; similarly, the two \(4\)s in the second round also produce duplicate subsets.

        +

        The reason for this duplication is that equal elements are chosen multiple times in a certain round. In Figure 13-13, the first round has three choices, two of which are \(4\), generating two duplicate search branches, thus outputting duplicate subsets; similarly, the two \(4\)s in the second round also produce duplicate subsets.

        Duplicate subsets caused by equal elements

        Figure 13-13   Duplicate subsets caused by equal elements

        @@ -5223,7 +5223,7 @@

        Full Screen >

        -

        The following figure shows the backtracking process for the array \([4, 4, 5]\) and target element \(9\), including four types of pruning operations. Please combine the illustration with the code comments to understand the entire search process and how each type of pruning operation works.

        +

        Figure 13-14 shows the backtracking process for the array \([4, 4, 5]\) and target element \(9\), including four types of pruning operations. Please combine the illustration with the code comments to understand the entire search process and how each type of pruning operation works.

        Subset sum II backtracking process

        Figure 13-14   Subset sum II backtracking process

        diff --git a/en/chapter_computational_complexity/iteration_and_recursion/index.html b/en/chapter_computational_complexity/iteration_and_recursion/index.html index 40f725777..dbad6478f 100644 --- a/en/chapter_computational_complexity/iteration_and_recursion/index.html +++ b/en/chapter_computational_complexity/iteration_and_recursion/index.html @@ -4814,7 +4814,7 @@

        Full Screen >

        -

        The Figure 2-3 shows the recursive process of this function.

        +

        Figure 2-3 shows the recursive process of this function.

        Recursive process of the sum function

        Figure 2-3   Recursive process of the sum function

        @@ -4834,7 +4834,7 @@
      4. The function's context data is stored in a memory area called "stack frame space" and is only released after the function returns. Therefore, recursion generally consumes more memory space than iteration.
      5. Recursive calls introduce additional overhead. Hence, recursion is usually less time-efficient than loops.
      6. -

        As shown in the Figure 2-4 , there are \(n\) unreturned recursive functions before triggering the termination condition, indicating a recursion depth of \(n\).

        +

        As shown in Figure 2-4, there are \(n\) unreturned recursive functions before triggering the termination condition, indicating a recursion depth of \(n\).

        Recursion call depth

        Figure 2-4   Recursion call depth

        @@ -5009,7 +5009,7 @@

        Full Screen >

        -

        The execution process of tail recursion is shown in the following figure. Comparing regular recursion and tail recursion, the point of the summation operation is different.

        +

        The execution process of tail recursion is shown in Figure 2-5. Comparing regular recursion and tail recursion, the point of the summation operation is different.

        • Regular recursion: The summation operation occurs during the "returning" phase, requiring another summation after each layer returns.
        • Tail recursion: The summation operation occurs during the "calling" phase, and the "returning" phase only involves returning through each layer.
        • diff --git a/en/chapter_computational_complexity/space_complexity/index.html b/en/chapter_computational_complexity/space_complexity/index.html index e11ba3f1f..e0c5ca07a 100644 --- a/en/chapter_computational_complexity/space_complexity/index.html +++ b/en/chapter_computational_complexity/space_complexity/index.html @@ -3744,7 +3744,7 @@
        • Stack frame space: Used to save the context data of the called function. The system creates a stack frame at the top of the stack each time a function is called, and the stack frame space is released after the function returns.
        • Instruction space: Used to store compiled program instructions, which are usually negligible in actual statistics.
        -

        When analyzing the space complexity of a program, we typically count the Temporary Data, Stack Frame Space, and Output Data, as shown in the Figure 2-15 .

        +

        When analyzing the space complexity of a program, we typically count the Temporary Data, Stack Frame Space, and Output Data, as shown in Figure 2-15.

        Space types used in algorithms

        Figure 2-15   Space types used in algorithms

        @@ -5036,7 +5036,7 @@ O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline

        Full Screen >

        -

        As shown below, this function's recursive depth is \(n\), meaning there are \(n\) instances of unreturned linear_recur() function, using \(O(n)\) size of stack frame space:

        +

        As shown in Figure 2-17, this function's recursive depth is \(n\), meaning there are \(n\) instances of unreturned linear_recur() function, using \(O(n)\) size of stack frame space:

        @@ -5409,7 +5409,7 @@ O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline

        -

        As shown below, the recursive depth of this function is \(n\), and in each recursive call, an array is initialized with lengths \(n\), \(n-1\), \(\dots\), \(2\), \(1\), averaging \(n/2\), thus overall occupying \(O(n^2)\) space:

        +

        As shown in Figure 2-18, the recursive depth of this function is \(n\), and in each recursive call, an array is initialized with lengths \(n\), \(n-1\), \(\dots\), \(2\), \(1\), averaging \(n/2\), thus overall occupying \(O(n^2)\) space:

        @@ -5581,7 +5581,7 @@ O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline

        Figure 2-18   Recursive function generating quadratic order space complexity

        4.   Exponential order \(O(2^n)\)

        -

        Exponential order is common in binary trees. Observe the below image, a "full binary tree" with \(n\) levels has \(2^n - 1\) nodes, occupying \(O(2^n)\) space:

        +

        Exponential order is common in binary trees. Observe Figure 2-19, a "full binary tree" with \(n\) levels has \(2^n - 1\) nodes, occupying \(O(2^n)\) space:

        diff --git a/en/chapter_computational_complexity/time_complexity/index.html b/en/chapter_computational_complexity/time_complexity/index.html index fcf6840cb..02299b6df 100644 --- a/en/chapter_computational_complexity/time_complexity/index.html +++ b/en/chapter_computational_complexity/time_complexity/index.html @@ -4243,7 +4243,7 @@
        -

        The following figure shows the time complexities of these three algorithms.

        +

        Figure 2-7 shows the time complexities of these three algorithms.

        • Algorithm A has just one print operation, and its run time does not grow with \(n\). Its time complexity is considered "constant order."
        • Algorithm B involves a print operation looping \(n\) times, and its run time grows linearly with \(n\). Its time complexity is "linear order."
        • @@ -5418,7 +5418,7 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!

          -

          The following image compares constant order, linear order, and quadratic order time complexities.

          +

          Figure 2-10 compares constant order, linear order, and quadratic order time complexities.

          Constant, linear, and quadratic order time complexities

          Figure 2-10   Constant, linear, and quadratic order time complexities

          @@ -5727,7 +5727,7 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!

          4.   Exponential order \(O(2^n)\)

          Biological "cell division" is a classic example of exponential order growth: starting with one cell, it becomes two after one division, four after two divisions, and so on, resulting in \(2^n\) cells after \(n\) divisions.

          -

          The following image and code simulate the cell division process, with a time complexity of \(O(2^n)\):

          +

          Figure 2-11 and code simulate the cell division process, with a time complexity of \(O(2^n)\):

          @@ -6107,7 +6107,7 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!

          Exponential order growth is extremely rapid and is commonly seen in exhaustive search methods (brute force, backtracking, etc.). For large-scale problems, exponential order is unacceptable, often requiring dynamic programming or greedy algorithms as solutions.

          5.   Logarithmic order \(O(\log n)\)

          In contrast to exponential order, logarithmic order reflects situations where "the size is halved each round." Given an input data size \(n\), since the size is halved each round, the number of iterations is \(\log_2 n\), the inverse function of \(2^n\).

          -

          The following image and code simulate the "halving each round" process, with a time complexity of \(O(\log_2 n)\), commonly abbreviated as \(O(\log n)\):

          +

          Figure 2-12 and code simulate the "halving each round" process, with a time complexity of \(O(\log_2 n)\), commonly abbreviated as \(O(\log n)\):

          @@ -6622,7 +6622,7 @@ O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n)

          -

          The image below demonstrates how linear-logarithmic order is generated. Each level of a binary tree has \(n\) operations, and the tree has \(\log_2 n + 1\) levels, resulting in a time complexity of \(O(n \log n)\).

          +

          Figure 2-13 demonstrates how linear-logarithmic order is generated. Each level of a binary tree has \(n\) operations, and the tree has \(\log_2 n + 1\) levels, resulting in a time complexity of \(O(n \log n)\).

          Linear-logarithmic order time complexity

          Figure 2-13   Linear-logarithmic order time complexity

          @@ -6632,7 +6632,7 @@ O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n)
          \[ n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1 \]
          -

          Factorials are typically implemented using recursion. As shown in the image and code below, the first level splits into \(n\) branches, the second level into \(n - 1\) branches, and so on, stopping after the \(n\)th level:

          +

          Factorials are typically implemented using recursion. As shown in the code and Figure 2-14, the first level splits into \(n\) branches, the second level into \(n - 1\) branches, and so on, stopping after the \(n\)th level:

          diff --git a/en/chapter_data_structure/character_encoding/index.html b/en/chapter_data_structure/character_encoding/index.html index bf77f44fa..9e4a35e65 100644 --- a/en/chapter_data_structure/character_encoding/index.html +++ b/en/chapter_data_structure/character_encoding/index.html @@ -3647,7 +3647,7 @@

          3.4   Character encoding *

          In the computer system, all data is stored in binary form, and characters (represented by char) are no exception. To represent characters, we need to develop a "character set" that defines a one-to-one mapping between each character and binary numbers. With the character set, computers can convert binary numbers to characters by looking up the table.

          3.4.1   ASCII character set

          -

          The "ASCII code" is one of the earliest character sets, officially known as the American Standard Code for Information Interchange. It uses 7 binary digits (the lower 7 bits of a byte) to represent a character, allowing for a maximum of 128 different characters. As shown in the Figure 3-6 , ASCII includes uppercase and lowercase English letters, numbers 0 ~ 9, various punctuation marks, and certain control characters (such as newline and tab).

          +

          The "ASCII code" is one of the earliest character sets, officially known as the American Standard Code for Information Interchange. It uses 7 binary digits (the lower 7 bits of a byte) to represent a character, allowing for a maximum of 128 different characters. As shown in Figure 3-6, ASCII includes uppercase and lowercase English letters, numbers 0 ~ 9, various punctuation marks, and certain control characters (such as newline and tab).

          ASCII code

          Figure 3-6   ASCII code

          @@ -3662,7 +3662,7 @@

          "Unicode" is referred to as "统一码" (Unified Code) in Chinese, theoretically capable of accommodating over a million characters. It aims to incorporate characters from all over the world into a single set, providing a universal character set for processing and displaying various languages and reducing the issues of garbled text due to different encoding standards.

          Since its release in 1991, Unicode has continually expanded to include new languages and characters. As of September 2022, Unicode contains 149,186 characters, including characters, symbols, and even emojis from various languages. In the vast Unicode character set, commonly used characters occupy 2 bytes, while some rare characters may occupy 3 or even 4 bytes.

          Unicode is a universal character set that assigns a number (called a "code point") to each character, but it does not specify how these character code points should be stored in a computer system. One might ask: How does a system interpret Unicode code points of varying lengths within a text? For example, given a 2-byte code, how does the system determine if it represents a single 2-byte character or two 1-byte characters?

          -

          A straightforward solution to this problem is to store all characters as equal-length encodings. As shown in the Figure 3-7 , each character in "Hello" occupies 1 byte, while each character in "算法" (algorithm) occupies 2 bytes. We could encode all characters in "Hello 算法" as 2 bytes by padding the higher bits with zeros. This method would enable the system to interpret a character every 2 bytes, recovering the content of the phrase.

          +

          A straightforward solution to this problem is to store all characters as equal-length encodings. As shown in Figure 3-7, each character in "Hello" occupies 1 byte, while each character in "算法" (algorithm) occupies 2 bytes. We could encode all characters in "Hello 算法" as 2 bytes by padding the higher bits with zeros. This method would enable the system to interpret a character every 2 bytes, recovering the content of the phrase.

          Unicode encoding example

          Figure 3-7   Unicode encoding example

          @@ -3674,7 +3674,7 @@
        • For 1-byte characters, set the highest bit to \(0\), and the remaining 7 bits to the Unicode code point. Notably, ASCII characters occupy the first 128 code points in the Unicode set. This means that UTF-8 encoding is backward compatible with ASCII. This implies that UTF-8 can be used to parse ancient ASCII text.
        • For characters of length \(n\) bytes (where \(n > 1\)), set the highest \(n\) bits of the first byte to \(1\), and the \((n + 1)^{\text{th}}\) bit to \(0\); starting from the second byte, set the highest 2 bits of each byte to \(10\); the rest of the bits are used to fill the Unicode code point.
        -

        The Figure 3-8 shows the UTF-8 encoding for "Hello算法". It can be observed that since the highest \(n\) bits are set to \(1\), the system can determine the length of the character as \(n\) by counting the number of highest bits set to \(1\).

        +

        Figure 3-8 shows the UTF-8 encoding for "Hello算法". It can be observed that since the highest \(n\) bits are set to \(1\), the system can determine the length of the character as \(n\) by counting the number of highest bits set to \(1\).

        But why set the highest 2 bits of the remaining bytes to \(10\)? Actually, this \(10\) serves as a kind of checksum. If the system starts parsing text from an incorrect byte, the \(10\) at the beginning of the byte can help the system quickly detect anomalies.

        The reason for using \(10\) as a checksum is that, under UTF-8 encoding rules, it's impossible for the highest two bits of a character to be \(10\). This can be proven by contradiction: If the highest two bits of a character are \(10\), it indicates that the character's length is \(1\), corresponding to ASCII. However, the highest bit of an ASCII character should be \(0\), which contradicts the assumption.

        UTF-8 encoding example

        diff --git a/en/chapter_data_structure/classification_of_data_structure/index.html b/en/chapter_data_structure/classification_of_data_structure/index.html index f26f17e1b..94167238b 100644 --- a/en/chapter_data_structure/classification_of_data_structure/index.html +++ b/en/chapter_data_structure/classification_of_data_structure/index.html @@ -3594,7 +3594,7 @@

        Common data structures include arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. They can be classified into "logical structure" and "physical structure".

        3.1.1   Logical structure: linear and non-linear

        The logical structures reveal the logical relationships between data elements. In arrays and linked lists, data are arranged in a specific sequence, demonstrating the linear relationship between data; while in trees, data are arranged hierarchically from the top down, showing the derived relationship between "ancestors" and "descendants"; and graphs are composed of nodes and edges, reflecting the intricate network relationship.

        -

        As shown in the Figure 3-1 , logical structures can be divided into two major categories: "linear" and "non-linear". Linear structures are more intuitive, indicating data is arranged linearly in logical relationships; non-linear structures, conversely, are arranged non-linearly.

        +

        As shown in Figure 3-1, logical structures can be divided into two major categories: "linear" and "non-linear". Linear structures are more intuitive, indicating data is arranged linearly in logical relationships; non-linear structures, conversely, are arranged non-linearly.

        • Linear data structures: Arrays, Linked Lists, Stacks, Queues, Hash Tables.
        • Non-linear data structures: Trees, Heaps, Graphs, Hash Tables.
        • @@ -3609,8 +3609,8 @@
        • Network structures: Graphs, where elements have a many-to-many relationships.

        3.1.2   Physical structure: contiguous and dispersed

        -

        During the execution of an algorithm, the data being processed is stored in memory. The Figure 3-2 shows a computer memory stick where each black square is a physical memory space. We can think of memory as a vast Excel spreadsheet, with each cell capable of storing a certain amount of data.

        -

        The system accesses the data at the target location by means of a memory address. As shown in the Figure 3-2 , the computer assigns a unique identifier to each cell in the table according to specific rules, ensuring that each memory space has a unique memory address. With these addresses, the program can access the data stored in memory.

        +

        During the execution of an algorithm, the data being processed is stored in memory. Figure 3-2 shows a computer memory stick where each black square is a physical memory space. We can think of memory as a vast Excel spreadsheet, with each cell capable of storing a certain amount of data.

        +

        The system accesses the data at the target location by means of a memory address. As shown in Figure 3-2, the computer assigns a unique identifier to each cell in the table according to specific rules, ensuring that each memory space has a unique memory address. With these addresses, the program can access the data stored in memory.

        Memory stick, memory spaces, memory addresses

        Figure 3-2   Memory stick, memory spaces, memory addresses

        @@ -3619,7 +3619,7 @@

        It's worth noting that comparing memory to an Excel spreadsheet is a simplified analogy. The actual working mechanism of memory is more complex, involving concepts like address space, memory management, cache mechanisms, virtual memory, and physical memory.

        Memory is a shared resource for all programs. When a block of memory is occupied by one program, it cannot be simultaneously used by other programs. Therefore, considering memory resources is crucial in designing data structures and algorithms. For instance, the algorithm's peak memory usage should not exceed the remaining free memory of the system; if there is a lack of contiguous memory blocks, then the data structure chosen must be able to be stored in non-contiguous memory blocks.

        -

        As illustrated in the Figure 3-3 , the physical structure reflects the way data is stored in computer memory and it can be divided into contiguous space storage (arrays) and non-contiguous space storage (linked lists). The two types of physical structures exhibit complementary characteristics in terms of time efficiency and space efficiency.

        +

        As illustrated in Figure 3-3, the physical structure reflects the way data is stored in computer memory and it can be divided into contiguous space storage (arrays) and non-contiguous space storage (linked lists). The two types of physical structures exhibit complementary characteristics in terms of time efficiency and space efficiency.

        Contiguous space storage and dispersed space storage

        Figure 3-3   Contiguous space storage and dispersed space storage

        diff --git a/en/chapter_data_structure/number_encoding/index.html b/en/chapter_data_structure/number_encoding/index.html index e3d16c835..409346e1e 100644 --- a/en/chapter_data_structure/number_encoding/index.html +++ b/en/chapter_data_structure/number_encoding/index.html @@ -3695,7 +3695,7 @@ b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0 \]

        Now we can answer the initial question: The representation of float includes an exponent bit, leading to a much larger range than int. Based on the above calculation, the maximum positive number representable by float is approximately \(2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}\), and the minimum negative number is obtained by switching the sign bit.

        However, the trade-off for float's expanded range is a sacrifice in precision. The integer type int uses all 32 bits to represent the number, with values evenly distributed; but due to the exponent bit, the larger the value of a float, the greater the difference between adjacent numbers.

        -

        As shown in the Table 3-2 , exponent bits \(\mathrm{E} = 0\) and \(\mathrm{E} = 255\) have special meanings, used to represent zero, infinity, \(\mathrm{NaN}\), etc.

        +

        As shown in Table 3-2, exponent bits \(\mathrm{E} = 0\) and \(\mathrm{E} = 255\) have special meanings, used to represent zero, infinity, \(\mathrm{NaN}\), etc.

        Table 3-2   Meaning of exponent bits

        diff --git a/en/chapter_divide_and_conquer/build_binary_tree_problem/index.html b/en/chapter_divide_and_conquer/build_binary_tree_problem/index.html index 0726ef1c2..e7db9b422 100644 --- a/en/chapter_divide_and_conquer/build_binary_tree_problem/index.html +++ b/en/chapter_divide_and_conquer/build_binary_tree_problem/index.html @@ -3664,7 +3664,7 @@
      7. Let the index of the current tree's root node in inorder be denoted as \(m\).
      8. Let the index interval of the current tree in inorder be denoted as \([l, r]\).
      9. -

        As shown in the Table 12-1 , the above variables can represent the index of the root node in preorder as well as the index intervals of the subtrees in inorder.

        +

        As shown in Table 12-1, the above variables can represent the index of the root node in preorder as well as the index intervals of the subtrees in inorder.

        Table 12-1   Indexes of the root node and subtrees in preorder and inorder traversals

        diff --git a/en/chapter_divide_and_conquer/divide_and_conquer/index.html b/en/chapter_divide_and_conquer/divide_and_conquer/index.html index c3a966046..6cc1d8720 100644 --- a/en/chapter_divide_and_conquer/divide_and_conquer/index.html +++ b/en/chapter_divide_and_conquer/divide_and_conquer/index.html @@ -3662,7 +3662,7 @@
      10. Divide (partition phase): Recursively decompose the original problem into two or more sub-problems until the smallest sub-problem is reached and the process terminates.
      11. Conquer (merge phase): Starting from the smallest sub-problem with a known solution, merge the solutions of the sub-problems from bottom to top to construct the solution to the original problem.
      -

      As shown in the Figure 12-1 , "merge sort" is one of the typical applications of the divide and conquer strategy.

      +

      As shown in Figure 12-1, "merge sort" is one of the typical applications of the divide and conquer strategy.

      1. Divide: Recursively divide the original array (original problem) into two sub-arrays (sub-problems), until the sub-array has only one element (smallest sub-problem).
      2. Conquer: Merge the ordered sub-arrays (solutions to the sub-problems) from bottom to top to obtain an ordered original array (solution to the original problem).
      3. @@ -3687,7 +3687,7 @@

        Divide and conquer can not only effectively solve algorithm problems but often also improve algorithm efficiency. In sorting algorithms, quicksort, merge sort, and heap sort are faster than selection, bubble, and insertion sorts because they apply the divide and conquer strategy.

        Then, we may ask: Why can divide and conquer improve algorithm efficiency, and what is the underlying logic? In other words, why are the steps of decomposing a large problem into multiple sub-problems, solving the sub-problems, and merging the solutions of the sub-problems into the solution of the original problem more efficient than directly solving the original problem? This question can be discussed from the aspects of the number of operations and parallel computation.

        1.   Optimization of operation count

        -

        Taking "bubble sort" as an example, it requires \(O(n^2)\) time to process an array of length \(n\). Suppose we divide the array from the midpoint into two sub-arrays as shown in the Figure 12-2 , then the division requires \(O(n)\) time, sorting each sub-array requires \(O((n / 2)^2)\) time, and merging the two sub-arrays requires \(O(n)\) time, with the total time complexity being:

        +

        Taking "bubble sort" as an example, it requires \(O(n^2)\) time to process an array of length \(n\). Suppose we divide the array from the midpoint into two sub-arrays as shown in Figure 12-2, then the division requires \(O(n)\) time, sorting each sub-array requires \(O((n / 2)^2)\) time, and merging the two sub-arrays requires \(O(n)\) time, with the total time complexity being:

        \[ O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) \]
        @@ -3708,7 +3708,7 @@ n(n - 4) & > 0

        2.   Optimization through parallel computation

        We know that the sub-problems generated by divide and conquer are independent of each other, thus they can usually be solved in parallel. This means that divide and conquer can not only reduce the algorithm's time complexity, but also facilitate parallel optimization by the operating system.

        Parallel optimization is especially effective in environments with multiple cores or processors, as the system can process multiple sub-problems simultaneously, making fuller use of computing resources and significantly reducing the overall runtime.

        -

        For example, in the "bucket sort" shown in the Figure 12-3 , we distribute massive data evenly across various buckets, then the sorting tasks of all buckets can be distributed to different computing units, and the results are merged after completion.

        +

        For example, in the "bucket sort" shown in Figure 12-3, we distribute massive data evenly across various buckets, then the sorting tasks of all buckets can be distributed to different computing units, and the results are merged after completion.

        Bucket sort's parallel computation

        Figure 12-3   Bucket sort's parallel computation

        diff --git a/en/chapter_divide_and_conquer/hanota_problem/index.html b/en/chapter_divide_and_conquer/hanota_problem/index.html index 7dde6e8dd..2742b9cf0 100644 --- a/en/chapter_divide_and_conquer/hanota_problem/index.html +++ b/en/chapter_divide_and_conquer/hanota_problem/index.html @@ -3612,7 +3612,7 @@

        In both merge sorting and building binary trees, we decompose the original problem into two subproblems, each half the size of the original problem. However, for the Tower of Hanoi, we adopt a different decomposition strategy.

        Question

        -

        Given three pillars, denoted as A, B, and C. Initially, pillar A is stacked with \(n\) discs, arranged in order from top to bottom from smallest to largest. Our task is to move these \(n\) discs to pillar C, maintaining their original order (as shown below). The following rules must be followed during the disc movement process:

        +

        Given three pillars, denoted as A, B, and C. Initially, pillar A is stacked with \(n\) discs, arranged in order from top to bottom from smallest to largest. Our task is to move these \(n\) discs to pillar C, maintaining their original order (as shown in Figure 12-10). The following rules must be followed during the disc movement process:

        1. A disc can only be picked up from the top of a pillar and placed on top of another pillar.
        2. Only one disc can be moved at a time.
        3. @@ -3624,7 +3624,7 @@

          We denote the Tower of Hanoi of size \(i\) as \(f(i)\). For example, \(f(3)\) represents the Tower of Hanoi of moving \(3\) discs from A to C.

          1.   Consider the base case

          -

          As shown below, for the problem \(f(1)\), i.e., when there is only one disc, we can directly move it from A to C.

          +

          As shown in Figure 12-11, for the problem \(f(1)\), i.e., when there is only one disc, we can directly move it from A to C.

          @@ -3637,7 +3637,7 @@

          Figure 12-11   Solution for a problem of size 1

          -

          As shown below, for the problem \(f(2)\), i.e., when there are two discs, since the smaller disc must always be above the larger disc, B is needed to assist in the movement.

          +

          As shown in Figure 12-12, for the problem \(f(2)\), i.e., when there are two discs, since the smaller disc must always be above the larger disc, B is needed to assist in the movement.

          1. First, move the smaller disc from A to B.
          2. Then move the larger disc from A to C.
          3. @@ -3664,7 +3664,7 @@

            The process of solving the problem \(f(2)\) can be summarized as: moving two discs from A to C with the help of B. Here, C is called the target pillar, and B is called the buffer pillar.

            2.   Decomposition of subproblems

            For the problem \(f(3)\), i.e., when there are three discs, the situation becomes slightly more complicated.

            -

            Since we already know the solutions to \(f(1)\) and \(f(2)\), we can think from a divide-and-conquer perspective and consider the two top discs on A as a unit, performing the steps shown below. This way, the three discs are successfully moved from A to C.

            +

            Since we already know the solutions to \(f(1)\) and \(f(2)\), we can think from a divide-and-conquer perspective and consider the two top discs on A as a unit, performing the steps shown in Figure 12-13. This way, the three discs are successfully moved from A to C.

            1. Let B be the target pillar and C the buffer pillar, and move the two discs from A to B.
            2. Move the remaining disc from A directly to C.
            3. @@ -3689,7 +3689,7 @@

              Figure 12-13   Solution for a problem of size 3

              Essentially, we divide the problem \(f(3)\) into two subproblems \(f(2)\) and one subproblem \(f(1)\). By solving these three subproblems in order, the original problem is resolved. This indicates that the subproblems are independent, and their solutions can be merged.

              -

              From this, we can summarize the divide-and-conquer strategy for solving the Tower of Hanoi shown in the following image: divide the original problem \(f(n)\) into two subproblems \(f(n-1)\) and one subproblem \(f(1)\), and solve these three subproblems in the following order.

              +

              From this, we can summarize the divide-and-conquer strategy for solving the Tower of Hanoi shown in Figure 12-14: divide the original problem \(f(n)\) into two subproblems \(f(n-1)\) and one subproblem \(f(1)\), and solve these three subproblems in the following order.

              1. Move \(n-1\) discs with the help of C from A to B.
              2. Move the remaining one disc directly from A to C.
              3. @@ -4113,7 +4113,7 @@

                -

                As shown below, the Tower of Hanoi forms a recursive tree with a height of \(n\), each node representing a subproblem, corresponding to an open dfs() function, thus the time complexity is \(O(2^n)\), and the space complexity is \(O(n)\).

                +

                As shown in Figure 12-15, the Tower of Hanoi forms a recursive tree with a height of \(n\), each node representing a subproblem, corresponding to an open dfs() function, thus the time complexity is \(O(2^n)\), and the space complexity is \(O(n)\).

                Recursive tree of the Tower of Hanoi

                Figure 12-15   Recursive tree of the Tower of Hanoi

                diff --git a/en/chapter_dynamic_programming/dp_problem_features/index.html b/en/chapter_dynamic_programming/dp_problem_features/index.html index b1b047311..186987086 100644 --- a/en/chapter_dynamic_programming/dp_problem_features/index.html +++ b/en/chapter_dynamic_programming/dp_problem_features/index.html @@ -3604,7 +3604,7 @@

                Minimum cost of climbing stairs

                Given a staircase, you can step up 1 or 2 steps at a time, and each step on the staircase has a non-negative integer representing the cost you need to pay at that step. Given a non-negative integer array \(cost\), where \(cost[i]\) represents the cost you need to pay at the \(i\)-th step, \(cost[0]\) is the ground (starting point). What is the minimum cost required to reach the top?

          -

          As shown in the Figure 14-6 , if the costs of the 1st, 2nd, and 3rd steps are \(1\), \(10\), and \(1\) respectively, then the minimum cost to climb to the 3rd step from the ground is \(2\).

          +

          As shown in Figure 14-6, if the costs of the 1st, 2nd, and 3rd steps are \(1\), \(10\), and \(1\) respectively, then the minimum cost to climb to the 3rd step from the ground is \(2\).

          Minimum cost to climb to the 3rd step

          Figure 14-6   Minimum cost to climb to the 3rd step

          @@ -3886,7 +3886,7 @@ dp[i] = \min(dp[i-1], dp[i-2]) + cost[i]

          -

          The Figure 14-7 shows the dynamic programming process for the above code.

          +

          Figure 14-7 shows the dynamic programming process for the above code.

          Dynamic programming process for minimum cost of climbing stairs

          Figure 14-7   Dynamic programming process for minimum cost of climbing stairs

          @@ -4131,7 +4131,7 @@ dp[i] = \min(dp[i-1], dp[i-2]) + cost[i]

          Stair climbing with constraints

          Given a staircase with \(n\) steps, you can go up 1 or 2 steps each time, but you cannot jump 1 step twice in a row. How many ways are there to climb to the top?

          -

          As shown in the Figure 14-8 , there are only 2 feasible options for climbing to the 3rd step, among which the option of jumping 1 step three times in a row does not meet the constraint condition and is therefore discarded.

          +

          As shown in Figure 14-8, there are only 2 feasible options for climbing to the 3rd step, among which the option of jumping 1 step three times in a row does not meet the constraint condition and is therefore discarded.

          Number of feasible options for climbing to the 3rd step with constraints

          Figure 14-8   Number of feasible options for climbing to the 3rd step with constraints

          @@ -4142,7 +4142,7 @@ dp[i] = \min(dp[i-1], dp[i-2]) + cost[i]
        4. When the last round was a jump of 1 step, the round before last could only choose to jump 2 steps, that is, \(dp[i, 1]\) can only be transferred from \(dp[i-1, 2]\).
        5. When the last round was a jump of 2 steps, the round before last could choose to jump 1 step or 2 steps, that is, \(dp[i, 2]\) can be transferred from \(dp[i-2, 1]\) or \(dp[i-2, 2]\).
        6. -

          As shown in the Figure 14-9 , \(dp[i, j]\) represents the number of solutions for state \([i, j]\). At this point, the state transition equation is:

          +

          As shown in Figure 14-9, \(dp[i, j]\) represents the number of solutions for state \([i, j]\). At this point, the state transition equation is:

          \[ \begin{cases} dp[i, 1] = dp[i-1, 2] \\ diff --git a/en/chapter_dynamic_programming/dp_solution_pipeline/index.html b/en/chapter_dynamic_programming/dp_solution_pipeline/index.html index 0e9358726..964f41e33 100644 --- a/en/chapter_dynamic_programming/dp_solution_pipeline/index.html +++ b/en/chapter_dynamic_programming/dp_solution_pipeline/index.html @@ -3702,14 +3702,14 @@

          Question

          Given an \(n \times m\) two-dimensional grid grid, each cell in the grid contains a non-negative integer representing the cost of that cell. The robot starts from the top-left cell and can only move down or right at each step until it reaches the bottom-right cell. Return the minimum path sum from the top-left to the bottom-right.

          -

          The following figure shows an example, where the given grid's minimum path sum is \(13\).

          +

          Figure 14-10 shows an example, where the given grid's minimum path sum is \(13\).

          Minimum Path Sum Example Data

          Figure 14-10   Minimum Path Sum Example Data

          First step: Think about each round of decisions, define the state, and thereby obtain the \(dp\) table

          Each round of decisions in this problem is to move one step down or right from the current cell. Suppose the row and column indices of the current cell are \([i, j]\), then after moving down or right, the indices become \([i+1, j]\) or \([i, j+1]\). Therefore, the state should include two variables: the row index and the column index, denoted as \([i, j]\).

          The state \([i, j]\) corresponds to the subproblem: the minimum path sum from the starting point \([0, 0]\) to \([i, j]\), denoted as \(dp[i, j]\).

          -

          Thus, we obtain the two-dimensional \(dp\) matrix shown below, whose size is the same as the input grid \(grid\).

          +

          Thus, we obtain the two-dimensional \(dp\) matrix shown in Figure 14-11, whose size is the same as the input grid \(grid\).

          State definition and DP table

          Figure 14-11   State definition and DP table

          @@ -3720,7 +3720,7 @@

        Second step: Identify the optimal substructure, then derive the state transition equation

        For the state \([i, j]\), it can only be derived from the cell above \([i-1, j]\) or the cell to the left \([i, j-1]\). Therefore, the optimal substructure is: the minimum path sum to reach \([i, j]\) is determined by the smaller of the minimum path sums of \([i, j-1]\) and \([i-1, j]\).

        -

        Based on the above analysis, the state transition equation shown in the following figure can be derived:

        +

        Based on the above analysis, the state transition equation shown in Figure 14-12 can be derived:

        \[ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j] \]
        @@ -3734,7 +3734,7 @@ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]

        Third step: Determine boundary conditions and state transition order

        In this problem, the states in the first row can only come from the states to their left, and the states in the first column can only come from the states above them, so the first row \(i = 0\) and the first column \(j = 0\) are the boundary conditions.

        -

        As shown in the Figure 14-13 , since each cell is derived from the cell to its left and the cell above it, we use loops to traverse the matrix, the outer loop iterating over the rows and the inner loop iterating over the columns.

        +

        As shown in Figure 14-13, since each cell is derived from the cell to its left and the cell above it, we use loops to traverse the matrix, the outer loop iterating over the rows and the inner loop iterating over the columns.

        Boundary conditions and state transition order

        Figure 14-13   Boundary conditions and state transition order

        @@ -4015,7 +4015,7 @@ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]

        Full Screen >

        -

        The following figure shows the recursive tree rooted at \(dp[2, 1]\), which includes some overlapping subproblems, the number of which increases sharply as the size of the grid grid increases.

        +

        Figure 14-14 shows the recursive tree rooted at \(dp[2, 1]\), which includes some overlapping subproblems, the number of which increases sharply as the size of the grid grid increases.

        Essentially, the reason for overlapping subproblems is: there are multiple paths to reach a certain cell from the top-left corner.

        Brute-force search recursive tree

        Figure 14-14   Brute-force search recursive tree

        @@ -4358,7 +4358,7 @@ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]

        Full Screen >

        -

        As shown in the Figure 14-15 , after introducing memoization, all subproblem solutions only need to be calculated once, so the time complexity depends on the total number of states, i.e., the grid size \(O(nm)\).

        +

        As shown in Figure 14-15, after introducing memoization, all subproblem solutions only need to be calculated once, so the time complexity depends on the total number of states, i.e., the grid size \(O(nm)\).

        Memoized search recursive tree

        Figure 14-15   Memoized search recursive tree

        @@ -4716,7 +4716,7 @@ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]

        Full Screen >

        -

        The following figures show the state transition process of the minimum path sum, traversing the entire grid, thus the time complexity is \(O(nm)\).

        +

        Figure 14-16 show the state transition process of the minimum path sum, traversing the entire grid, thus the time complexity is \(O(nm)\).

        The array dp is of size \(n \times m\), therefore the space complexity is \(O(nm)\).

        diff --git a/en/chapter_dynamic_programming/edit_distance_problem/index.html b/en/chapter_dynamic_programming/edit_distance_problem/index.html index fa00278b4..0d1e87b30 100644 --- a/en/chapter_dynamic_programming/edit_distance_problem/index.html +++ b/en/chapter_dynamic_programming/edit_distance_problem/index.html @@ -3615,12 +3615,12 @@

        Given two strings \(s\) and \(t\), return the minimum number of edits required to transform \(s\) into \(t\).

        You can perform three types of edits on a string: insert a character, delete a character, or replace a character with any other character.

        -

        As shown in the Figure 14-27 , transforming kitten into sitting requires 3 edits, including 2 replacements and 1 insertion; transforming hello into algo requires 3 steps, including 2 replacements and 1 deletion.

        +

        As shown in Figure 14-27, transforming kitten into sitting requires 3 edits, including 2 replacements and 1 insertion; transforming hello into algo requires 3 steps, including 2 replacements and 1 deletion.

        Example data of edit distance

        Figure 14-27   Example data of edit distance

        The edit distance problem can naturally be explained with a decision tree model. Strings correspond to tree nodes, and a round of decision (an edit operation) corresponds to an edge of the tree.

        -

        As shown in the Figure 14-28 , with unrestricted operations, each node can derive many edges, each corresponding to one operation, meaning there are many possible paths to transform hello into algo.

        +

        As shown in Figure 14-28, with unrestricted operations, each node can derive many edges, each corresponding to one operation, meaning there are many possible paths to transform hello into algo.

        From the perspective of the decision tree, the goal of this problem is to find the shortest path between the node hello and the node algo.

        Edit distance problem represented based on decision tree model

        Figure 14-28   Edit distance problem represented based on decision tree model

        @@ -3637,7 +3637,7 @@

        State \([i, j]\) corresponds to the subproblem: The minimum number of edits required to change the first \(i\) characters of \(s\) into the first \(j\) characters of \(t\).

        From this, we obtain a two-dimensional \(dp\) table of size \((i+1) \times (j+1)\).

        Step two: Identify the optimal substructure and then derive the state transition equation

        -

        Consider the subproblem \(dp[i, j]\), whose corresponding tail characters of the two strings are \(s[i-1]\) and \(t[j-1]\), which can be divided into three scenarios as shown below.

        +

        Consider the subproblem \(dp[i, j]\), whose corresponding tail characters of the two strings are \(s[i-1]\) and \(t[j-1]\), which can be divided into three scenarios as shown in Figure 14-29.

        1. Add \(t[j-1]\) after \(s[i-1]\), then the remaining subproblem is \(dp[i, j-1]\).
        2. Delete \(s[i-1]\), then the remaining subproblem is \(dp[i-1, j]\).
        3. @@ -4050,7 +4050,7 @@ dp[i, j] = dp[i-1, j-1]

          -

          As shown below, the process of state transition in the edit distance problem is very similar to that in the knapsack problem, which can be seen as filling a two-dimensional grid.

          +

          As shown in Figure 14-30, the process of state transition in the edit distance problem is very similar to that in the knapsack problem, which can be seen as filling a two-dimensional grid.

          diff --git a/en/chapter_dynamic_programming/intro_to_dynamic_programming/index.html b/en/chapter_dynamic_programming/intro_to_dynamic_programming/index.html index 22253cd49..858b59a16 100644 --- a/en/chapter_dynamic_programming/intro_to_dynamic_programming/index.html +++ b/en/chapter_dynamic_programming/intro_to_dynamic_programming/index.html @@ -3633,7 +3633,7 @@

          Climbing stairs

          Given a staircase with \(n\) steps, where you can climb \(1\) or \(2\) steps at a time, how many different ways are there to reach the top?

          -

          As shown in the Figure 14-1 , there are \(3\) ways to reach the top of a \(3\)-step staircase.

          +

          As shown in Figure 14-1, there are \(3\) ways to reach the top of a \(3\)-step staircase.

          Number of ways to reach the 3rd step

          Figure 14-1   Number of ways to reach the 3rd step

          @@ -4043,7 +4043,7 @@ dp[i-1], dp[i-2], \dots, dp[2], dp[1]
          \[ dp[i] = dp[i-1] + dp[i-2] \]
          -

          This means that in the stair climbing problem, there is a recursive relationship between the subproblems, the solution to the original problem can be constructed from the solutions to the subproblems. The following image shows this recursive relationship.

          +

          This means that in the stair climbing problem, there is a recursive relationship between the subproblems, the solution to the original problem can be constructed from the solutions to the subproblems. Figure 14-2 shows this recursive relationship.

          Recursive relationship of solution counts

          Figure 14-2   Recursive relationship of solution counts

          @@ -4283,11 +4283,11 @@ dp[i] = dp[i-1] + dp[i-2]

          -

          The following image shows the recursive tree formed by brute force search. For the problem \(dp[n]\), the depth of its recursive tree is \(n\), with a time complexity of \(O(2^n)\). Exponential order represents explosive growth, and entering a long wait if a relatively large \(n\) is input.

          +

          Figure 14-3 shows the recursive tree formed by brute force search. For the problem \(dp[n]\), the depth of its recursive tree is \(n\), with a time complexity of \(O(2^n)\). Exponential order represents explosive growth, and entering a long wait if a relatively large \(n\) is input.

          Recursive tree for climbing stairs

          Figure 14-3   Recursive tree for climbing stairs

          -

          Observing the above image, the exponential time complexity is caused by 'overlapping subproblems'. For example, \(dp[9]\) is decomposed into \(dp[8]\) and \(dp[7]\), \(dp[8]\) into \(dp[7]\) and \(dp[6]\), both containing the subproblem \(dp[7]\).

          +

          Observing Figure 14-3, the exponential time complexity is caused by 'overlapping subproblems'. For example, \(dp[9]\) is decomposed into \(dp[8]\) and \(dp[7]\), \(dp[8]\) into \(dp[7]\) and \(dp[6]\), both containing the subproblem \(dp[7]\).

          Thus, subproblems include even smaller overlapping subproblems, endlessly. A vast majority of computational resources are wasted on these overlapping subproblems.

          To enhance algorithm efficiency, we hope that all overlapping subproblems are calculated only once. For this purpose, we declare an array mem to record the solution of each subproblem, and prune overlapping subproblems during the search process.

          @@ -4632,7 +4632,7 @@ dp[i] = dp[i-1] + dp[i-2]

          -

          Observe the following image, after memoization, all overlapping subproblems need to be calculated only once, optimizing the time complexity to \(O(n)\), which is a significant leap.

          +

          Observe Figure 14-4, after memoization, all overlapping subproblems need to be calculated only once, optimizing the time complexity to \(O(n)\), which is a significant leap.

          Recursive tree with memoized search

          Figure 14-4   Recursive tree with memoized search

          @@ -4888,7 +4888,7 @@ dp[i] = dp[i-1] + dp[i-2]

          -

          The image below simulates the execution process of the above code.

          +

          Figure 14-5 simulates the execution process of the above code.

          Dynamic programming process for climbing stairs

          Figure 14-5   Dynamic programming process for climbing stairs

          diff --git a/en/chapter_dynamic_programming/knapsack_problem/index.html b/en/chapter_dynamic_programming/knapsack_problem/index.html index 34b833bd9..b0ce21319 100644 --- a/en/chapter_dynamic_programming/knapsack_problem/index.html +++ b/en/chapter_dynamic_programming/knapsack_problem/index.html @@ -3633,7 +3633,7 @@

          Question

          Given \(n\) items, the weight of the \(i\)-th item is \(wgt[i-1]\) and its value is \(val[i-1]\), and a knapsack with a capacity of \(cap\). Each item can be chosen only once. What is the maximum value of items that can be placed in the knapsack under the capacity limit?

          -

          Observe the following figure, since the item number \(i\) starts counting from 1, and the array index starts from 0, thus the weight of item \(i\) corresponds to \(wgt[i-1]\) and the value corresponds to \(val[i-1]\).

          +

          Observe Figure 14-17, since the item number \(i\) starts counting from 1, and the array index starts from 0, thus the weight of item \(i\) corresponds to \(wgt[i-1]\) and the value corresponds to \(val[i-1]\).

          Example data of the 0-1 knapsack

          Figure 14-17   Example data of the 0-1 knapsack

          @@ -3933,7 +3933,7 @@ dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1])

          -

          As shown in the Figure 14-18 , since each item generates two search branches of not selecting and selecting, the time complexity is \(O(2^n)\).

          +

          As shown in Figure 14-18, since each item generates two search branches of not selecting and selecting, the time complexity is \(O(2^n)\).

          Observing the recursive tree, it is easy to see that there are overlapping sub-problems, such as \(dp[1, 10]\), etc. When there are many items and the knapsack capacity is large, especially when there are many items of the same weight, the number of overlapping sub-problems will increase significantly.

          The brute force search recursive tree of the 0-1 knapsack problem

          Figure 14-18   The brute force search recursive tree of the 0-1 knapsack problem

          @@ -4284,12 +4284,12 @@ dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1])

          -

          The following figure shows the search branches that are pruned in memoized search.

          +

          Figure 14-19 shows the search branches that are pruned in memoized search.

          The memoized search recursive tree of the 0-1 knapsack problem

          Figure 14-19   The memoized search recursive tree of the 0-1 knapsack problem

          3.   Method three: Dynamic programming

          -

          Dynamic programming essentially involves filling the \(dp\) table during the state transition, the code is shown below:

          +

          Dynamic programming essentially involves filling the \(dp\) table during the state transition, the code is shown in Figure 14-20:

          diff --git a/en/chapter_dynamic_programming/unbounded_knapsack_problem/index.html b/en/chapter_dynamic_programming/unbounded_knapsack_problem/index.html index 37d909243..8ad0bb7aa 100644 --- a/en/chapter_dynamic_programming/unbounded_knapsack_problem/index.html +++ b/en/chapter_dynamic_programming/unbounded_knapsack_problem/index.html @@ -4155,7 +4155,7 @@ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1])

          3.   Space optimization

          Since the current state comes from the state to the left and above, the space-optimized solution should perform a forward traversal for each row in the \(dp\) table.

          -

          This traversal order is the opposite of that for the 0-1 knapsack. Please refer to the following figures to understand the difference.

          +

          This traversal order is the opposite of that for the 0-1 knapsack. Please refer to Figure 14-23 to understand the difference.

          @@ -4908,7 +4908,7 @@ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1)

          -

          The following images show the dynamic programming process for the coin change problem, which is very similar to the unbounded knapsack problem.

          +

          Figure 14-25 show the dynamic programming process for the coin change problem, which is very similar to the unbounded knapsack problem.

          diff --git a/en/chapter_graph/graph/index.html b/en/chapter_graph/graph/index.html index 4c429db4b..b8d4228ad 100644 --- a/en/chapter_graph/graph/index.html +++ b/en/chapter_graph/graph/index.html @@ -3665,12 +3665,12 @@ E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline G & = \{ V, E \} \newline \end{aligned} \]
          -

          If vertices are viewed as nodes and edges as references (pointers) connecting the nodes, graphs can be seen as a data structure that extends from linked lists. As shown below, compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) are more complex due to their higher degree of freedom.

          +

          If vertices are viewed as nodes and edges as references (pointers) connecting the nodes, graphs can be seen as a data structure that extends from linked lists. As shown in Figure 9-1, compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) are more complex due to their higher degree of freedom.

          Relationship between linked lists, trees, and graphs

          Figure 9-1   Relationship between linked lists, trees, and graphs

          9.1.1   Common types of graphs

          -

          Based on whether edges have direction, graphs can be divided into "undirected graphs" and "directed graphs", as shown below.

          +

          Based on whether edges have direction, graphs can be divided into "undirected graphs" and "directed graphs", as shown in Figure 9-2.

          • In undirected graphs, edges represent a "bidirectional" connection between two vertices, for example, the "friendship" in WeChat or QQ.
          • In directed graphs, edges have directionality, that is, the edges \(A \rightarrow B\) and \(A \leftarrow B\) are independent of each other, for example, the "follow" and "be followed" relationship on Weibo or TikTok.
          • @@ -3678,7 +3678,7 @@ G & = \{ V, E \} \newline

            Directed and undirected graphs

            Figure 9-2   Directed and undirected graphs

            -

            Based on whether all vertices are connected, graphs can be divided into "connected graphs" and "disconnected graphs", as shown below.

            +

            Based on whether all vertices are connected, graphs can be divided into "connected graphs" and "disconnected graphs", as shown in Figure 9-3.

            • For connected graphs, it is possible to reach any other vertex starting from a certain vertex.
            • For disconnected graphs, there is at least one vertex that cannot be reached from a certain starting vertex.
            • @@ -3686,21 +3686,21 @@ G & = \{ V, E \} \newline

              Connected and disconnected graphs

              Figure 9-3   Connected and disconnected graphs

              -

              We can also add a "weight" variable to edges, resulting in "weighted graphs" as shown below. For example, in mobile games like "Honor of Kings", the system calculates the "closeness" between players based on shared gaming time, and this closeness network can be represented with a weighted graph.

              +

              We can also add a "weight" variable to edges, resulting in "weighted graphs" as shown in Figure 9-4. For example, in mobile games like "Honor of Kings", the system calculates the "closeness" between players based on shared gaming time, and this closeness network can be represented with a weighted graph.

              Weighted and unweighted graphs

              Figure 9-4   Weighted and unweighted graphs

              Graph data structures include the following commonly used terms.

                -
              • "Adjacency": When there is an edge connecting two vertices, these two vertices are said to be "adjacent". In the above figure, the adjacent vertices of vertex 1 are vertices 2, 3, and 5.
              • -
              • "Path": The sequence of edges passed from vertex A to vertex B is called a "path" from A to B. In the above figure, the edge sequence 1-5-2-4 is a path from vertex 1 to vertex 4.
              • +
              • "Adjacency": When there is an edge connecting two vertices, these two vertices are said to be "adjacent". In Figure 9-4, the adjacent vertices of vertex 1 are vertices 2, 3, and 5.
              • +
              • "Path": The sequence of edges passed from vertex A to vertex B is called a "path" from A to B. In Figure 9-4, the edge sequence 1-5-2-4 is a path from vertex 1 to vertex 4.
              • "Degree": The number of edges a vertex has. For directed graphs, "in-degree" refers to how many edges point to the vertex, and "out-degree" refers to how many edges point out from the vertex.

              9.1.2   Representation of graphs

              Common representations of graphs include "adjacency matrices" and "adjacency lists". The following examples use undirected graphs.

              1.   Adjacency matrix

              Let the number of vertices in the graph be \(n\), the "adjacency matrix" uses an \(n \times n\) matrix to represent the graph, where each row (column) represents a vertex, and the matrix elements represent edges, with \(1\) or \(0\) indicating whether there is an edge between two vertices.

              -

              As shown below, let the adjacency matrix be \(M\), and the list of vertices be \(V\), then the matrix element \(M[i, j] = 1\) indicates there is an edge between vertex \(V[i]\) and vertex \(V[j]\), conversely \(M[i, j] = 0\) indicates there is no edge between the two vertices.

              +

              As shown in Figure 9-5, let the adjacency matrix be \(M\), and the list of vertices be \(V\), then the matrix element \(M[i, j] = 1\) indicates there is an edge between vertex \(V[i]\) and vertex \(V[j]\), conversely \(M[i, j] = 0\) indicates there is no edge between the two vertices.

              Representation of a graph with an adjacency matrix

              Figure 9-5   Representation of a graph with an adjacency matrix

              @@ -3712,14 +3712,14 @@ G & = \{ V, E \} \newline

            When representing graphs with adjacency matrices, it is possible to directly access matrix elements to obtain edges, thus operations of addition, deletion, lookup, and modification are very efficient, all with a time complexity of \(O(1)\). However, the space complexity of the matrix is \(O(n^2)\), which consumes more memory.

            2.   Adjacency list

            -

            The "adjacency list" uses \(n\) linked lists to represent the graph, with each linked list node representing a vertex. The \(i\)-th linked list corresponds to vertex \(i\) and contains all adjacent vertices (vertices connected to that vertex). The Figure 9-6 shows an example of a graph stored using an adjacency list.

            +

            The "adjacency list" uses \(n\) linked lists to represent the graph, with each linked list node representing a vertex. The \(i\)-th linked list corresponds to vertex \(i\) and contains all adjacent vertices (vertices connected to that vertex). Figure 9-6 shows an example of a graph stored using an adjacency list.

            Representation of a graph with an adjacency list

            Figure 9-6   Representation of a graph with an adjacency list

            The adjacency list only stores actual edges, and the total number of edges is often much less than \(n^2\), making it more space-efficient. However, finding edges in the adjacency list requires traversing the linked list, so its time efficiency is not as good as that of the adjacency matrix.

            -

            Observing the above figure, the structure of the adjacency list is very similar to the "chaining" in hash tables, hence we can use similar methods to optimize efficiency. For example, when the linked list is long, it can be transformed into an AVL tree or red-black tree, thus optimizing the time efficiency from \(O(n)\) to \(O(\log n)\); the linked list can also be transformed into a hash table, thus reducing the time complexity to \(O(1)\).

            +

            Observing Figure 9-6, the structure of the adjacency list is very similar to the "chaining" in hash tables, hence we can use similar methods to optimize efficiency. For example, when the linked list is long, it can be transformed into an AVL tree or red-black tree, thus optimizing the time efficiency from \(O(n)\) to \(O(\log n)\); the linked list can also be transformed into a hash table, thus reducing the time complexity to \(O(1)\).

            9.1.3   Common applications of graphs

            -

            As shown in the Table 9-1 , many real-world systems can be modeled with graphs, and corresponding problems can be reduced to graph computing problems.

            +

            As shown in Table 9-1, many real-world systems can be modeled with graphs, and corresponding problems can be reduced to graph computing problems.

            Table 9-1   Common graphs in real life

            diff --git a/en/chapter_graph/graph_operations/index.html b/en/chapter_graph/graph_operations/index.html index d592aca2b..5bb8163dd 100644 --- a/en/chapter_graph/graph_operations/index.html +++ b/en/chapter_graph/graph_operations/index.html @@ -3611,7 +3611,7 @@

            9.2   Basic operations on graphs

            The basic operations on graphs can be divided into operations on "edges" and operations on "vertices". Under the two representation methods of "adjacency matrix" and "adjacency list", the implementation methods are different.

            9.2.1   Implementation based on adjacency matrix

            -

            Given an undirected graph with \(n\) vertices, the various operations are implemented as shown in the Figure 9-7 .

            +

            Given an undirected graph with \(n\) vertices, the various operations are implemented as shown in Figure 9-7.

            • Adding or removing an edge: Directly modify the specified edge in the adjacency matrix, using \(O(1)\) time. Since it is an undirected graph, it is necessary to update the edges in both directions simultaneously.
            • Adding a vertex: Add a row and a column at the end of the adjacency matrix and fill them all with \(0\)s, using \(O(n)\) time.
            • @@ -4800,7 +4800,7 @@

              9.2.2   Implementation based on adjacency list

              -

              Given an undirected graph with a total of \(n\) vertices and \(m\) edges, the various operations can be implemented as shown in the Figure 9-8 .

              +

              Given an undirected graph with a total of \(n\) vertices and \(m\) edges, the various operations can be implemented as shown in Figure 9-8.

              • Adding an edge: Simply add the edge at the end of the corresponding vertex's linked list, using \(O(1)\) time. Because it is an undirected graph, it is necessary to add edges in both directions simultaneously.
              • Removing an edge: Find and remove the specified edge in the corresponding vertex's linked list, using \(O(m)\) time. In an undirected graph, it is necessary to remove edges in both directions simultaneously.
              • @@ -5947,7 +5947,7 @@

                9.2.3   Efficiency comparison

                -

                Assuming there are \(n\) vertices and \(m\) edges in the graph, the Table 9-2 compares the time efficiency and space efficiency of the adjacency matrix and adjacency list.

                +

                Assuming there are \(n\) vertices and \(m\) edges in the graph, Table 9-2 compares the time efficiency and space efficiency of the adjacency matrix and adjacency list.

                Table 9-2   Comparison of adjacency matrix and adjacency list

                @@ -6000,7 +6000,7 @@
                -

                Observing the Table 9-2 , it seems that the adjacency list (hash table) has the best time efficiency and space efficiency. However, in practice, operating on edges in the adjacency matrix is more efficient, requiring only a single array access or assignment operation. Overall, the adjacency matrix exemplifies the principle of "space for time", while the adjacency list exemplifies "time for space".

                +

                Observing Table 9-2, it seems that the adjacency list (hash table) has the best time efficiency and space efficiency. However, in practice, operating on edges in the adjacency matrix is more efficient, requiring only a single array access or assignment operation. Overall, the adjacency matrix exemplifies the principle of "space for time", while the adjacency list exemplifies "time for space".

                diff --git a/en/chapter_graph/graph_traversal/index.html b/en/chapter_graph/graph_traversal/index.html index 640fa88ac..77b158f79 100644 --- a/en/chapter_graph/graph_traversal/index.html +++ b/en/chapter_graph/graph_traversal/index.html @@ -3690,7 +3690,7 @@

                Trees represent a "one-to-many" relationship, while graphs have a higher degree of freedom and can represent any "many-to-many" relationship. Therefore, we can consider trees as a special case of graphs. Clearly, tree traversal operations are also a special case of graph traversal operations.

                Both graphs and trees require the application of search algorithms to implement traversal operations. Graph traversal can be divided into two types: "Breadth-First Search (BFS)" and "Depth-First Search (DFS)".

                -

                Breadth-first search is a near-to-far traversal method, starting from a certain node, always prioritizing the visit to the nearest vertices and expanding outwards layer by layer. As shown in the Figure 9-9 , starting from the top left vertex, first traverse all adjacent vertices of that vertex, then traverse all adjacent vertices of the next vertex, and so on, until all vertices have been visited.

                +

                Breadth-first search is a near-to-far traversal method, starting from a certain node, always prioritizing the visit to the nearest vertices and expanding outwards layer by layer. As shown in Figure 9-9, starting from the top left vertex, first traverse all adjacent vertices of that vertex, then traverse all adjacent vertices of the next vertex, and so on, until all vertices have been visited.

                Breadth-first traversal of a graph

                Figure 9-9   Breadth-first traversal of a graph

                @@ -4135,7 +4135,7 @@

                -

                The code is relatively abstract, it is suggested to compare with the following figure to deepen the understanding.

                +

                The code is relatively abstract, it is suggested to compare with Figure 9-10 to deepen the understanding.

                @@ -4177,13 +4177,13 @@

                Is the sequence of breadth-first traversal unique?

                -

                Not unique. Breadth-first traversal only requires traversing in a "from near to far" order, and the traversal order of multiple vertices at the same distance can be arbitrarily shuffled. For example, in the above figure, the visitation order of vertices \(1\) and \(3\) can be switched, as can the order of vertices \(2\), \(4\), and \(6\).

                +

                Not unique. Breadth-first traversal only requires traversing in a "from near to far" order, and the traversal order of multiple vertices at the same distance can be arbitrarily shuffled. For example, in Figure 9-10, the visitation order of vertices \(1\) and \(3\) can be switched, as can the order of vertices \(2\), \(4\), and \(6\).

                2.   Complexity analysis

                Time complexity: All vertices will be enqueued and dequeued once, using \(O(|V|)\) time; in the process of traversing adjacent vertices, since it is an undirected graph, all edges will be visited \(2\) times, using \(O(2|E|)\) time; overall using \(O(|V| + |E|)\) time.

                Space complexity: The maximum number of vertices in list res, hash table visited, and queue que is \(|V|\), using \(O(|V|)\) space.

                -

                Depth-first search is a traversal method that prioritizes going as far as possible and then backtracks when no further paths are available. As shown in the Figure 9-11 , starting from the top left vertex, visit some adjacent vertex of the current vertex until no further path is available, then return and continue until all vertices are traversed.

                +

                Depth-first search is a traversal method that prioritizes going as far as possible and then backtracks when no further paths are available. As shown in Figure 9-11, starting from the top left vertex, visit some adjacent vertex of the current vertex until no further path is available, then return and continue until all vertices are traversed.

                Depth-first traversal of a graph

                Figure 9-11   Depth-first traversal of a graph

                @@ -4574,12 +4574,12 @@

                -

                The algorithm process of depth-first search is shown in the following figure.

                +

                The algorithm process of depth-first search is shown in Figure 9-12.

                • Dashed lines represent downward recursion, indicating that a new recursive method has been initiated to visit a new vertex.
                • Curved dashed lines represent upward backtracking, indicating that this recursive method has returned to the position where this method was initiated.
                -

                To deepen the understanding, it is suggested to combine the following figure with the code to simulate (or draw) the entire DFS process in your mind, including when each recursive method is initiated and when it returns.

                +

                To deepen the understanding, it is suggested to combine Figure 9-12 with the code to simulate (or draw) the entire DFS process in your mind, including when each recursive method is initiated and when it returns.

                diff --git a/en/chapter_greedy/fractional_knapsack_problem/index.html b/en/chapter_greedy/fractional_knapsack_problem/index.html index d9b608511..556797874 100644 --- a/en/chapter_greedy/fractional_knapsack_problem/index.html +++ b/en/chapter_greedy/fractional_knapsack_problem/index.html @@ -3611,13 +3611,13 @@

                15.2   Fractional knapsack problem

                Question

                -

                Given \(n\) items, the weight of the \(i\)-th item is \(wgt[i-1]\) and its value is \(val[i-1]\), and a knapsack with a capacity of \(cap\). Each item can be chosen only once, but a part of the item can be selected, with its value calculated based on the proportion of the weight chosen, what is the maximum value of the items in the knapsack under the limited capacity? An example is shown below.

                +

                Given \(n\) items, the weight of the \(i\)-th item is \(wgt[i-1]\) and its value is \(val[i-1]\), and a knapsack with a capacity of \(cap\). Each item can be chosen only once, but a part of the item can be selected, with its value calculated based on the proportion of the weight chosen, what is the maximum value of the items in the knapsack under the limited capacity? An example is shown in Figure 15-3.

                Example data of the fractional knapsack problem

                Figure 15-3   Example data of the fractional knapsack problem

                The fractional knapsack problem is very similar overall to the 0-1 knapsack problem, involving the current item \(i\) and capacity \(c\), aiming to maximize the value within the limited capacity of the knapsack.

                -

                The difference is that, in this problem, only a part of an item can be chosen. As shown in the Figure 15-4 , we can arbitrarily split the items and calculate the corresponding value based on the weight proportion.

                +

                The difference is that, in this problem, only a part of an item can be chosen. As shown in Figure 15-4, we can arbitrarily split the items and calculate the corresponding value based on the weight proportion.

                1. For item \(i\), its value per unit weight is \(val[i-1] / wgt[i-1]\), referred to as the unit value.
                2. Suppose we put a part of item \(i\) with weight \(w\) into the knapsack, then the value added to the knapsack is \(w \times val[i-1] / wgt[i-1]\).
                3. @@ -3626,7 +3626,7 @@

                  Figure 15-4   Value per unit weight of the item

                  1.   Greedy strategy determination

                  -

                  Maximizing the total value of the items in the knapsack essentially means maximizing the value per unit weight. From this, the greedy strategy shown below can be deduced.

                  +

                  Maximizing the total value of the items in the knapsack essentially means maximizing the value per unit weight. From this, the greedy strategy shown in Figure 15-5 can be deduced.

                  1. Sort the items by their unit value from high to low.
                  2. Iterate over all items, greedily choosing the item with the highest unit value in each round.
                  3. @@ -4094,7 +4094,7 @@

                    Using proof by contradiction. Suppose item \(x\) has the highest unit value, and some algorithm yields a maximum value res, but the solution does not include item \(x\).

                    Now remove a unit weight of any item from the knapsack and replace it with a unit weight of item \(x\). Since the unit value of item \(x\) is the highest, the total value after replacement will definitely be greater than res. This contradicts the assumption that res is the optimal solution, proving that the optimal solution must include item \(x\).

                    For other items in this solution, we can also construct the above contradiction. Overall, items with greater unit value are always better choices, proving that the greedy strategy is effective.

                    -

                    As shown in the Figure 15-6 , if the item weight and unit value are viewed as the horizontal and vertical axes of a two-dimensional chart respectively, the fractional knapsack problem can be transformed into "seeking the largest area enclosed within a limited horizontal axis range". This analogy can help us understand the effectiveness of the greedy strategy from a geometric perspective.

                    +

                    As shown in Figure 15-6, if the item weight and unit value are viewed as the horizontal and vertical axes of a two-dimensional chart respectively, the fractional knapsack problem can be transformed into "seeking the largest area enclosed within a limited horizontal axis range". This analogy can help us understand the effectiveness of the greedy strategy from a geometric perspective.

                    Geometric representation of the fractional knapsack problem

                    Figure 15-6   Geometric representation of the fractional knapsack problem

                    diff --git a/en/chapter_greedy/greedy_algorithm/index.html b/en/chapter_greedy/greedy_algorithm/index.html index 7d3b32449..349b38f81 100644 --- a/en/chapter_greedy/greedy_algorithm/index.html +++ b/en/chapter_greedy/greedy_algorithm/index.html @@ -3638,7 +3638,7 @@

                    Question

                    Given \(n\) types of coins, where the denomination of the \(i\)th type of coin is \(coins[i - 1]\), and the target amount is \(amt\), with each type of coin available indefinitely, what is the minimum number of coins needed to make up the target amount? If it is not possible to make up the target amount, return \(-1\).

                -

                The greedy strategy adopted in this problem is shown in the following figure. Given the target amount, we greedily choose the coin that is closest to and not greater than it, repeatedly following this step until the target amount is met.

                +

                The greedy strategy adopted in this problem is shown in Figure 15-1. Given the target amount, we greedily choose the coin that is closest to and not greater than it, repeatedly following this step until the target amount is met.

                Greedy strategy for coin change

                Figure 15-1   Greedy strategy for coin change

                @@ -3921,7 +3921,7 @@

                You might exclaim: So clean! The greedy algorithm solves the coin change problem in about ten lines of code.

                15.1.1   Advantages and limitations of greedy algorithms

                Greedy algorithms are not only straightforward and simple to implement, but they are also usually very efficient. In the code above, if the smallest coin denomination is \(\min(coins)\), the greedy choice loops at most \(amt / \min(coins)\) times, giving a time complexity of \(O(amt / \min(coins))\). This is an order of magnitude smaller than the time complexity of the dynamic programming solution, which is \(O(n \times amt)\).

                -

                However, for some combinations of coin denominations, greedy algorithms cannot find the optimal solution. The following figure provides two examples.

                +

                However, for some combinations of coin denominations, greedy algorithms cannot find the optimal solution. Figure 15-2 provides two examples.

                • Positive example \(coins = [1, 5, 10, 20, 50, 100]\): In this coin combination, given any \(amt\), the greedy algorithm can find the optimal solution.
                • Negative example \(coins = [1, 20, 50]\): Suppose \(amt = 60\), the greedy algorithm can only find the combination \(50 + 1 \times 10\), totaling 11 coins, but dynamic programming can find the optimal solution of \(20 + 20 + 20\), needing only 3 coins.
                • diff --git a/en/chapter_greedy/max_capacity_problem/index.html b/en/chapter_greedy/max_capacity_problem/index.html index 490685274..342cfe902 100644 --- a/en/chapter_greedy/max_capacity_problem/index.html +++ b/en/chapter_greedy/max_capacity_problem/index.html @@ -3613,7 +3613,7 @@

                  Question

                  Input an array \(ht\), where each element represents the height of a vertical partition. Any two partitions in the array, along with the space between them, can form a container.

                  The capacity of the container is the product of the height and the width (area), where the height is determined by the shorter partition, and the width is the difference in array indices between the two partitions.

                  -

                  Please select two partitions in the array that maximize the container's capacity and return this maximum capacity. An example is shown in the following figure.

                  +

                  Please select two partitions in the array that maximize the container's capacity and return this maximum capacity. An example is shown in Figure 15-7.

                Example data for the maximum capacity problem

                Figure 15-7   Example data for the maximum capacity problem

                @@ -3625,21 +3625,21 @@ cap[i, j] = \min(ht[i], ht[j]) \times (j - i) \]

                Assuming the length of the array is \(n\), the number of combinations of two partitions (total number of states) is \(C_n^2 = \frac{n(n - 1)}{2}\). The most straightforward approach is to enumerate all possible states, resulting in a time complexity of \(O(n^2)\).

                1.   Determination of a greedy strategy

                -

                There is a more efficient solution to this problem. As shown in the following figure, we select a state \([i, j]\) where the indices \(i < j\) and the height \(ht[i] < ht[j]\), meaning \(i\) is the shorter partition, and \(j\) is the taller one.

                +

                There is a more efficient solution to this problem. As shown in Figure 15-8, we select a state \([i, j]\) where the indices \(i < j\) and the height \(ht[i] < ht[j]\), meaning \(i\) is the shorter partition, and \(j\) is the taller one.

                Initial state

                Figure 15-8   Initial state

                -

                As shown in the following figure, if we move the taller partition \(j\) closer to the shorter partition \(i\), the capacity will definitely decrease.

                +

                As shown in Figure 15-9, if we move the taller partition \(j\) closer to the shorter partition \(i\), the capacity will definitely decrease.

                This is because when moving the taller partition \(j\), the width \(j-i\) definitely decreases; and since the height is determined by the shorter partition, the height can only remain the same (if \(i\) remains the shorter partition) or decrease (if the moved \(j\) becomes the shorter partition).

                State after moving the taller partition inward

                Figure 15-9   State after moving the taller partition inward

                -

                Conversely, we can only possibly increase the capacity by moving the shorter partition \(i\) inward. Although the width will definitely decrease, the height may increase (if the moved shorter partition \(i\) becomes taller). For example, in the Figure 15-10 , the area increases after moving the shorter partition.

                +

                Conversely, we can only possibly increase the capacity by moving the shorter partition \(i\) inward. Although the width will definitely decrease, the height may increase (if the moved shorter partition \(i\) becomes taller). For example, in Figure 15-10, the area increases after moving the shorter partition.

                State after moving the shorter partition inward

                Figure 15-10   State after moving the shorter partition inward

                This leads us to the greedy strategy for this problem: initialize two pointers at the ends of the container, and in each round, move the pointer corresponding to the shorter partition inward until the two pointers meet.

                -

                The following figures illustrate the execution of the greedy strategy.

                +

                Figure 15-11 illustrate the execution of the greedy strategy.

                1. Initially, the pointers \(i\) and \(j\) are positioned at the ends of the array.
                2. Calculate the current state's capacity \(cap[i, j]\) and update the maximum capacity.
                3. @@ -3979,7 +3979,7 @@ cap[i, j] = \min(ht[i], ht[j]) \times (j - i)

                  3.   Proof of correctness

                  The reason why the greedy method is faster than enumeration is that each round of greedy selection "skips" some states.

                  -

                  For example, under the state \(cap[i, j]\) where \(i\) is the shorter partition and \(j\) is the taller partition, greedily moving the shorter partition \(i\) inward by one step leads to the "skipped" states shown below. This means that these states' capacities cannot be verified later.

                  +

                  For example, under the state \(cap[i, j]\) where \(i\) is the shorter partition and \(j\) is the taller partition, greedily moving the shorter partition \(i\) inward by one step leads to the "skipped" states shown in Figure 15-12. This means that these states' capacities cannot be verified later.

                  \[ cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1] \]
                  diff --git a/en/chapter_greedy/max_product_cutting_problem/index.html b/en/chapter_greedy/max_product_cutting_problem/index.html index 9b8f67a96..968466df0 100644 --- a/en/chapter_greedy/max_product_cutting_problem/index.html +++ b/en/chapter_greedy/max_product_cutting_problem/index.html @@ -3634,13 +3634,13 @@ n = \sum_{i=1}^{m}n_i n & \geq 4 \end{aligned} \]
                -

                As shown below, when \(n \geq 4\), splitting out a \(2\) increases the product, which indicates that integers greater than or equal to \(4\) should be split.

                +

                As shown in Figure 15-14, when \(n \geq 4\), splitting out a \(2\) increases the product, which indicates that integers greater than or equal to \(4\) should be split.

                Greedy strategy one: If the splitting scheme includes factors \(\geq 4\), they should be further split. The final split should only include factors \(1\), \(2\), and \(3\).

                Product increase due to splitting

                Figure 15-14   Product increase due to splitting

                Next, consider which factor is optimal. Among the factors \(1\), \(2\), and \(3\), clearly \(1\) is the worst, as \(1 \times (n-1) < n\) always holds, meaning splitting out \(1\) actually decreases the product.

                -

                As shown below, when \(n = 6\), \(3 \times 3 > 2 \times 2 \times 2\). This means splitting out \(3\) is better than splitting out \(2\).

                +

                As shown in Figure 15-15, when \(n = 6\), \(3 \times 3 > 2 \times 2 \times 2\). This means splitting out \(3\) is better than splitting out \(2\).

                Greedy strategy two: In the splitting scheme, there should be at most two \(2\)s. Because three \(2\)s can always be replaced by two \(3\)s to obtain a higher product.

                Optimal splitting factors

                Figure 15-15   Optimal splitting factors

                @@ -3653,7 +3653,7 @@ n & \geq 4
              • When the remainder is \(1\), since \(2 \times 2 > 1 \times 3\), the last \(3\) should be replaced with \(2\).

        2.   Code implementation

        -

        As shown below, we do not need to use loops to split the integer but can use the floor division operation to get the number of \(3\)s, \(a\), and the modulo operation to get the remainder, \(b\), thus:

        +

        As shown in Figure 15-16, we do not need to use loops to split the integer but can use the floor division operation to get the number of \(3\)s, \(a\), and the modulo operation to get the remainder, \(b\), thus:

        \[ n = 3a + b \]
        diff --git a/en/chapter_hashing/hash_algorithm/index.html b/en/chapter_hashing/hash_algorithm/index.html index 9e5da9570..902aa3f78 100644 --- a/en/chapter_hashing/hash_algorithm/index.html +++ b/en/chapter_hashing/hash_algorithm/index.html @@ -3610,7 +3610,7 @@

        6.3   Hash algorithms

        The previous two sections introduced the working principle of hash tables and the methods to handle hash collisions. However, both open addressing and chaining can only ensure that the hash table functions normally when collisions occur, but cannot reduce the frequency of hash collisions.

        -

        If hash collisions occur too frequently, the performance of the hash table will deteriorate drastically. As shown in the Figure 6-8 , for a chaining hash table, in the ideal case, the key-value pairs are evenly distributed across the buckets, achieving optimal query efficiency; in the worst case, all key-value pairs are stored in the same bucket, degrading the time complexity to \(O(n)\).

        +

        If hash collisions occur too frequently, the performance of the hash table will deteriorate drastically. As shown in Figure 6-8, for a chaining hash table, in the ideal case, the key-value pairs are evenly distributed across the buckets, achieving optimal query efficiency; in the worst case, all key-value pairs are stored in the same bucket, degrading the time complexity to \(O(n)\).

        Ideal and worst cases of hash collisions

        Figure 6-8   Ideal and worst cases of hash collisions

        @@ -4252,7 +4252,7 @@

        6.3.3   Common hash algorithms

        It is not hard to see that the simple hash algorithms mentioned above are quite "fragile" and far from reaching the design goals of hash algorithms. For example, since addition and XOR obey the commutative law, additive hash and XOR hash cannot distinguish strings with the same content but in different order, which may exacerbate hash collisions and cause security issues.

        In practice, we usually use some standard hash algorithms, such as MD5, SHA-1, SHA-2, and SHA-3. They can map input data of any length to a fixed-length hash value.

        -

        Over the past century, hash algorithms have been in a continuous process of upgrading and optimization. Some researchers strive to improve the performance of hash algorithms, while others, including hackers, are dedicated to finding security issues in hash algorithms. The Table 6-2 shows hash algorithms commonly used in practical applications.

        +

        Over the past century, hash algorithms have been in a continuous process of upgrading and optimization. Some researchers strive to improve the performance of hash algorithms, while others, including hackers, are dedicated to finding security issues in hash algorithms. Table 6-2 shows hash algorithms commonly used in practical applications.

        • MD5 and SHA-1 have been successfully attacked multiple times and are thus abandoned in various security applications.
        • SHA-2 series, especially SHA-256, is one of the most secure hash algorithms to date, with no successful attacks reported, hence commonly used in various security applications and protocols.
        • diff --git a/en/chapter_hashing/hash_collision/index.html b/en/chapter_hashing/hash_collision/index.html index 993f70a45..e76e05488 100644 --- a/en/chapter_hashing/hash_collision/index.html +++ b/en/chapter_hashing/hash_collision/index.html @@ -3683,7 +3683,7 @@

      There are mainly two methods for improving the structure of hash tables: "Separate Chaining" and "Open Addressing".

      6.2.1   Separate chaining

      -

      In the original hash table, each bucket can store only one key-value pair. "Separate chaining" transforms individual elements into a linked list, with key-value pairs as list nodes, storing all colliding key-value pairs in the same list. The Figure 6-5 shows an example of a hash table with separate chaining.

      +

      In the original hash table, each bucket can store only one key-value pair. "Separate chaining" transforms individual elements into a linked list, with key-value pairs as list nodes, storing all colliding key-value pairs in the same list. Figure 6-5 shows an example of a hash table with separate chaining.

      Separate chaining hash table

      Figure 6-5   Separate chaining hash table

      @@ -5184,12 +5184,12 @@
    1. Inserting elements: Calculate the bucket index using the hash function. If the bucket already contains an element, linearly traverse forward from the conflict position (usually with a step size of \(1\)) until an empty bucket is found, then insert the element.
    2. Searching for elements: If a hash collision is found, use the same step size to linearly traverse forward until the corresponding element is found and return value; if an empty bucket is encountered, it means the target element is not in the hash table, so return None.
    3. -

      The Figure 6-6 shows the distribution of key-value pairs in an open addressing (linear probing) hash table. According to this hash function, keys with the same last two digits will be mapped to the same bucket. Through linear probing, they are stored consecutively in that bucket and the buckets below it.

      +

      Figure 6-6 shows the distribution of key-value pairs in an open addressing (linear probing) hash table. According to this hash function, keys with the same last two digits will be mapped to the same bucket. Through linear probing, they are stored consecutively in that bucket and the buckets below it.

      Distribution of key-value pairs in open addressing (linear probing) hash table

      Figure 6-6   Distribution of key-value pairs in open addressing (linear probing) hash table

      However, linear probing tends to create "clustering". Specifically, the longer a continuous position in the array is occupied, the more likely these positions are to encounter hash collisions, further promoting the growth of these clusters and eventually leading to deterioration in the efficiency of operations.

      -

      It's important to note that we cannot directly delete elements in an open addressing hash table. Deleting an element creates an empty bucket None in the array. When searching for elements, if linear probing encounters this empty bucket, it will return, making the elements below this bucket inaccessible. The program may incorrectly assume these elements do not exist, as shown in the Figure 6-7 .

      +

      It's important to note that we cannot directly delete elements in an open addressing hash table. Deleting an element creates an empty bucket None in the array. When searching for elements, if linear probing encounters this empty bucket, it will return, making the elements below this bucket inaccessible. The program may incorrectly assume these elements do not exist, as shown in Figure 6-7.

      Query issues caused by deletion in open addressing

      Figure 6-7   Query issues caused by deletion in open addressing

      diff --git a/en/chapter_hashing/hash_map/index.html b/en/chapter_hashing/hash_map/index.html index 6e1f5d9a4..d48bfa6ff 100644 --- a/en/chapter_hashing/hash_map/index.html +++ b/en/chapter_hashing/hash_map/index.html @@ -3610,11 +3610,11 @@

      6.1   Hash table

      A "hash table", also known as a "hash map", achieves efficient element querying by establishing a mapping between keys and values. Specifically, when we input a key into the hash table, we can retrieve the corresponding value in \(O(1)\) time.

      -

      As shown in the Figure 6-1 , given \(n\) students, each with two pieces of data: "name" and "student number". If we want to implement a query feature that returns the corresponding name when given a student number, we can use the hash table shown in the Figure 6-1 .

      +

      As shown in Figure 6-1, given \(n\) students, each with two pieces of data: "name" and "student number". If we want to implement a query feature that returns the corresponding name when given a student number, we can use the hash table shown in Figure 6-1.

      Abstract representation of a hash table

      Figure 6-1   Abstract representation of a hash table

      -

      Apart from hash tables, arrays and linked lists can also be used to implement querying functions. Their efficiency is compared in the Table 6-1 .

      +

      Apart from hash tables, arrays and linked lists can also be used to implement querying functions. Their efficiency is compared in Table 6-1.

      • Adding elements: Simply add the element to the end of the array (or linked list), using \(O(1)\) time.
      • Querying elements: Since the array (or linked list) is unordered, it requires traversing all the elements, using \(O(n)\) time.
      • @@ -4083,7 +4083,7 @@
        index = hash(key) % capacity
         

        Afterward, we can use index to access the corresponding bucket in the hash table and thereby retrieve the value.

        -

        Assuming array length capacity = 100 and hash algorithm hash(key) = key, the hash function is key % 100. The Figure 6-2 uses key as the student number and value as the name to demonstrate the working principle of the hash function.

        +

        Assuming array length capacity = 100 and hash algorithm hash(key) = key, the hash function is key % 100. Figure 6-2 uses key as the student number and value as the name to demonstrate the working principle of the hash function.

        Working principle of hash function

        Figure 6-2   Working principle of hash function

        @@ -5362,12 +5362,12 @@
        12836 % 100 = 36
         20336 % 100 = 36
         
        -

        As shown in the Figure 6-3 , both student numbers point to the same name, which is obviously incorrect. This situation where multiple inputs correspond to the same output is known as "hash collision".

        +

        As shown in Figure 6-3, both student numbers point to the same name, which is obviously incorrect. This situation where multiple inputs correspond to the same output is known as "hash collision".

        Example of hash collision

        Figure 6-3   Example of hash collision

        It is easy to understand that the larger the capacity \(n\) of the hash table, the lower the probability of multiple keys being allocated to the same bucket, and the fewer the collisions. Therefore, expanding the capacity of the hash table can reduce hash collisions.

        -

        As shown in the Figure 6-4 , before expansion, key-value pairs (136, A) and (236, D) collided; after expansion, the collision is resolved.

        +

        As shown in Figure 6-4, before expansion, key-value pairs (136, A) and (236, D) collided; after expansion, the collision is resolved.

        Hash table expansion

        Figure 6-4   Hash table expansion

        diff --git a/en/chapter_heap/build_heap/index.html b/en/chapter_heap/build_heap/index.html index d8b9b4707..ccb5df9d7 100644 --- a/en/chapter_heap/build_heap/index.html +++ b/en/chapter_heap/build_heap/index.html @@ -3919,7 +3919,7 @@

        Node counts at each level of a perfect binary tree

        Figure 8-5   Node counts at each level of a perfect binary tree

        -

        As shown in the Figure 8-5 , the maximum number of iterations for a node "to be heapified from top to bottom" is equal to the distance from that node to the leaf nodes, which is precisely "node height." Therefore, we can sum the "number of nodes \(\times\) node height" at each level, to get the total number of heapification iterations for all nodes.

        +

        As shown in Figure 8-5, the maximum number of iterations for a node "to be heapified from top to bottom" is equal to the distance from that node to the leaf nodes, which is precisely "node height." Therefore, we can sum the "number of nodes \(\times\) node height" at each level, to get the total number of heapification iterations for all nodes.

        \[ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 \]
        diff --git a/en/chapter_heap/heap/index.html b/en/chapter_heap/heap/index.html index bd04f2491..3d8c1a8bd 100644 --- a/en/chapter_heap/heap/index.html +++ b/en/chapter_heap/heap/index.html @@ -3693,7 +3693,7 @@

        8.1   Heap

        -

        A "heap" is a complete binary tree that satisfies specific conditions and can be mainly divided into two types, as shown in the Figure 8-1 .

        +

        A "heap" is a complete binary tree that satisfies specific conditions and can be mainly divided into two types, as shown in Figure 8-1.

        • "Min heap": The value of any node \(\leq\) the values of its child nodes.
        • "Max heap": The value of any node \(\geq\) the values of its child nodes.
        • @@ -3710,7 +3710,7 @@

          8.1.1   Common operations on heaps

          It should be noted that many programming languages provide a "priority queue," which is an abstract data structure defined as a queue with priority sorting.

          In fact, heaps are often used to implement priority queues, with max heaps equivalent to priority queues where elements are dequeued in descending order. From a usage perspective, we can consider "priority queue" and "heap" as equivalent data structures. Therefore, this book does not make a special distinction between the two, uniformly referring to them as "heap."

          -

          Common operations on heaps are shown in the Table 8-1 , and the method names depend on the programming language.

          +

          Common operations on heaps are shown in Table 8-1, and the method names depend on the programming language.

          Table 8-1   Efficiency of Heap Operations

          @@ -4117,7 +4117,7 @@

          1.   Storage and representation of heaps

          As mentioned in the "Binary Trees" section, complete binary trees are well-suited for array representation. Since heaps are a type of complete binary tree, we will use arrays to store heaps.

          When using an array to represent a binary tree, elements represent node values, and indexes represent node positions in the binary tree. Node pointers are implemented through an index mapping formula.

          -

          As shown in the Figure 8-2 , given an index \(i\), the index of its left child is \(2i + 1\), the index of its right child is \(2i + 2\), and the index of its parent is \((i - 1) / 2\) (floor division). When the index is out of bounds, it signifies a null node or the node does not exist.

          +

          As shown in Figure 8-2, given an index \(i\), the index of its left child is \(2i + 1\), the index of its right child is \(2i + 2\), and the index of its parent is \((i - 1) / 2\) (floor division). When the index is out of bounds, it signifies a null node or the node does not exist.

          Representation and storage of heaps

          Figure 8-2   Representation and storage of heaps

          @@ -4473,7 +4473,7 @@

          3.   Inserting an element into the heap

          Given an element val, we first add it to the bottom of the heap. After addition, since val may be larger than other elements in the heap, the heap's integrity might be compromised, thus it's necessary to repair the path from the inserted node to the root node. This operation is called "heapifying".

          -

          Considering starting from the node inserted, perform heapify from bottom to top. As shown in the Figure 8-3 , we compare the value of the inserted node with its parent node, and if the inserted node is larger, we swap them. Then continue this operation, repairing each node in the heap from bottom to top until passing the root node or encountering a node that does not need to be swapped.

          +

          Considering starting from the node inserted, perform heapify from bottom to top. As shown in Figure 8-3, we compare the value of the inserted node with its parent node, and if the inserted node is larger, we swap them. Then continue this operation, repairing each node in the heap from bottom to top until passing the root node or encountering a node that does not need to be swapped.

          @@ -4886,7 +4886,7 @@
        • After swapping, remove the bottom of the heap from the list (note, since it has been swapped, what is actually being removed is the original top element).
        • Starting from the root node, perform heapify from top to bottom.
    -

    As shown in the Figure 8-4 , the direction of "heapify from top to bottom" is opposite to "heapify from bottom to top". We compare the value of the root node with its two children and swap it with the largest child. Then repeat this operation until passing the leaf node or encountering a node that does not need to be swapped.

    +

    As shown in Figure 8-4, the direction of "heapify from top to bottom" is opposite to "heapify from bottom to top". We compare the value of the root node with its two children and swap it with the largest child. Then repeat this operation until passing the leaf node or encountering a node that does not need to be swapped.

    diff --git a/en/chapter_heap/top_k/index.html b/en/chapter_heap/top_k/index.html index ac9bcab79..164170d15 100644 --- a/en/chapter_heap/top_k/index.html +++ b/en/chapter_heap/top_k/index.html @@ -3615,7 +3615,7 @@

    For this problem, we will first introduce two straightforward solutions, then explain a more efficient heap-based method.

    8.3.1   Method 1: Iterative selection

    -

    We can perform \(k\) rounds of iterations as shown in the Figure 8-6 , extracting the \(1^{st}\), \(2^{nd}\), \(\dots\), \(k^{th}\) largest elements in each round, with a time complexity of \(O(nk)\).

    +

    We can perform \(k\) rounds of iterations as shown in Figure 8-6, extracting the \(1^{st}\), \(2^{nd}\), \(\dots\), \(k^{th}\) largest elements in each round, with a time complexity of \(O(nk)\).

    This method is only suitable when \(k \ll n\), as the time complexity approaches \(O(n^2)\) when \(k\) is close to \(n\), which is very time-consuming.

    Iteratively finding the largest k elements

    Figure 8-6   Iteratively finding the largest k elements

    @@ -3625,7 +3625,7 @@

    When \(k = n\), we can obtain a complete ordered sequence, which is equivalent to the "selection sort" algorithm.

    8.3.2   Method 2: Sorting

    -

    As shown in the Figure 8-7 , we can first sort the array nums and then return the last \(k\) elements, with a time complexity of \(O(n \log n)\).

    +

    As shown in Figure 8-7, we can first sort the array nums and then return the last \(k\) elements, with a time complexity of \(O(n \log n)\).

    Clearly, this method "overachieves" the task, as we only need to find the largest \(k\) elements, without the need to sort the other elements.

    Sorting to find the largest k elements

    Figure 8-7   Sorting to find the largest k elements

    diff --git a/en/chapter_introduction/algorithms_are_everywhere/index.html b/en/chapter_introduction/algorithms_are_everywhere/index.html index e8eb5968a..3a378e6fe 100644 --- a/en/chapter_introduction/algorithms_are_everywhere/index.html +++ b/en/chapter_introduction/algorithms_are_everywhere/index.html @@ -3557,7 +3557,7 @@

    Figure 1-2   Playing cards sorting process

    The above method of organizing playing cards is essentially the "Insertion Sort" algorithm, which is very efficient for small datasets. Many programming languages' sorting functions include the insertion sort.

    -

    Example 3: Making Change. Suppose we buy goods worth \(69\) yuan at a supermarket and give the cashier \(100\) yuan, then the cashier needs to give us \(31\) yuan in change. They would naturally complete the thought process as shown below.

    +

    Example 3: Making Change. Suppose we buy goods worth \(69\) yuan at a supermarket and give the cashier \(100\) yuan, then the cashier needs to give us \(31\) yuan in change. They would naturally complete the thought process as shown in Figure 1-3.

    1. The options are currencies smaller than \(31\), including \(1\), \(5\), \(10\), and \(20\).
    2. Take out the largest \(20\) from the options, leaving \(31 - 20 = 11\).
    3. diff --git a/en/chapter_introduction/what_is_dsa/index.html b/en/chapter_introduction/what_is_dsa/index.html index 77eaa8e5e..6c75c1930 100644 --- a/en/chapter_introduction/what_is_dsa/index.html +++ b/en/chapter_introduction/what_is_dsa/index.html @@ -3629,7 +3629,7 @@
    4. Graphs, compared to linked lists, provide richer logical information but require more memory space.
    5. 1.2.3   Relationship between data structures and algorithms

      -

      As shown in the Figure 1-4 , data structures and algorithms are highly related and closely integrated, specifically in the following three aspects:

      +

      As shown in Figure 1-4, data structures and algorithms are highly related and closely integrated, specifically in the following three aspects:

      • Data structures are the foundation of algorithms. They provide structured data storage and methods for manipulating data for algorithms.
      • Algorithms are the stage where data structures come into play. The data structure alone only stores data information; it is through the application of algorithms that specific problems can be solved.
      • @@ -3638,11 +3638,11 @@

        Relationship between data structures and algorithms

        Figure 1-4   Relationship between data structures and algorithms

        -

        Data structures and algorithms can be likened to a set of building blocks, as illustrated in the Figure 1-5 . A building block set includes numerous pieces, accompanied by detailed assembly instructions. Following these instructions step by step allows us to construct an intricate block model.

        +

        Data structures and algorithms can be likened to a set of building blocks, as illustrated in Figure 1-5. A building block set includes numerous pieces, accompanied by detailed assembly instructions. Following these instructions step by step allows us to construct an intricate block model.

        Assembling blocks

        Figure 1-5   Assembling blocks

        -

        The detailed correspondence between the two is shown in the Table 1-1 .

        +

        The detailed correspondence between the two is shown in Table 1-1.

        Table 1-1   Comparing data structures and algorithms to building blocks

        diff --git a/en/chapter_preface/about_the_book/index.html b/en/chapter_preface/about_the_book/index.html index d412ca804..225b74321 100644 --- a/en/chapter_preface/about_the_book/index.html +++ b/en/chapter_preface/about_the_book/index.html @@ -3624,7 +3624,7 @@

        You should know how to write and read simple code in at least one programming language.

        0.1.2   Content structure

        -

        The main content of the book is shown in the following figure.

        +

        The main content of the book is shown in Figure 0-1.

        • Complexity analysis: explores aspects and methods for evaluating data structures and algorithms. Covers methods of deriving time complexity and space complexity, along with common types and examples.
        • Data structures: focuses on fundamental data types, classification methods, definitions, pros and cons, common operations, types, applications, and implementation methods of data structures such as array, linked list, stack, queue, hash table, tree, heap, graph, etc.
        • diff --git a/en/chapter_preface/suggestions/index.html b/en/chapter_preface/suggestions/index.html index 66f47a973..a305dacbc 100644 --- a/en/chapter_preface/suggestions/index.html +++ b/en/chapter_preface/suggestions/index.html @@ -3805,12 +3805,12 @@

    0.2.2   Efficient learning via animated illustrations

    Compared with text, videos and pictures have a higher density of information and are more structured, making them easier to understand. In this book, key and difficult concepts are mainly presented through animations and illustrations, with text serving as explanations and supplements.

    -

    When encountering content with animations or illustrations as shown in the Figure 0-2 , prioritize understanding the figure, with text as supplementary, integrating both for a comprehensive understanding.

    +

    When encountering content with animations or illustrations as shown in Figure 0-2, prioritize understanding the figure, with text as supplementary, integrating both for a comprehensive understanding.

    Animated illustration example

    Figure 0-2   Animated illustration example

    0.2.3   Deepen understanding through coding practice

    -

    The source code of this book is hosted on the GitHub Repository. As shown in the Figure 0-3 , the source code comes with test examples and can be executed with just a single click.

    +

    The source code of this book is hosted on the GitHub Repository. As shown in Figure 0-3, the source code comes with test examples and can be executed with just a single click.

    If time permits, it's recommended to type out the code yourself. If pressed for time, at least read and run all the codes.

    Compared to just reading code, writing code often yields more learning. Learning by doing is the real way to learn.

    Running code example

    @@ -3822,17 +3822,17 @@

    If Git is installed, use the following command to clone the repository:

    git clone https://github.com/krahets/hello-algo.git
     
    -

    Alternatively, you can also click the "Download ZIP" button at the location shown in the Figure 0-4 to directly download the code as a compressed ZIP file. Then, you can simply extract it locally.

    +

    Alternatively, you can also click the "Download ZIP" button at the location shown in Figure 0-4 to directly download the code as a compressed ZIP file. Then, you can simply extract it locally.

    Cloning repository and downloading code

    Figure 0-4   Cloning repository and downloading code

    -

    Step 3: Run the source code. As shown in the Figure 0-5 , for the code block labeled with the file name at the top, we can find the corresponding source code file in the codes folder of the repository. These files can be executed with a single click, which will help you save unnecessary debugging time and allow you to focus on learning.

    +

    Step 3: Run the source code. As shown in Figure 0-5, for the code block labeled with the file name at the top, we can find the corresponding source code file in the codes folder of the repository. These files can be executed with a single click, which will help you save unnecessary debugging time and allow you to focus on learning.

    Code block and corresponding source code file

    Figure 0-5   Code block and corresponding source code file

    0.2.4   Learning together in discussion

    While reading this book, please don't skip over the points that you didn't learn. Feel free to post your questions in the comment section. We will be happy to answer them and can usually respond within two days.

    -

    As illustrated in the Figure 0-6 , each chapter features a comment section at the bottom. I encourage you to pay attention to these comments. They not only expose you to others' encountered problems, aiding in identifying knowledge gaps and sparking deeper contemplation, but also invite you to generously contribute by answering fellow readers' inquiries, sharing insights, and fostering mutual improvement.

    +

    As illustrated in Figure 0-6, each chapter features a comment section at the bottom. I encourage you to pay attention to these comments. They not only expose you to others' encountered problems, aiding in identifying knowledge gaps and sparking deeper contemplation, but also invite you to generously contribute by answering fellow readers' inquiries, sharing insights, and fostering mutual improvement.

    Comment section example

    Figure 0-6   Comment section example

    @@ -3843,7 +3843,7 @@
  4. Stage 2: Practicing algorithm problems. It is recommended to start from popular problems, such as Sword for Offer and LeetCode Hot 100, and accumulate at least 100 questions to familiarize yourself with mainstream algorithmic problems. Forgetfulness can be a challenge when you start practicing, but rest assured that this is normal. We can follow the "Ebbinghaus Forgetting Curve" to review the questions, and usually after 3~5 rounds of repetitions, we will be able to memorize them.
  5. Stage 3: Building the knowledge system. In terms of learning, we can read algorithm column articles, solution frameworks, and algorithm textbooks to continuously enrich the knowledge system. In terms of practicing, we can try advanced strategies, such as categorizing by topic, multiple solutions for a single problem, and one solution for multiple problems, etc. Insights on these strategies can be found in various communities.
-

As shown in the Figure 0-7 , this book mainly covers “Stage 1,” aiming to help you more efficiently embark on Stages 2 and 3.

+

As shown in Figure 0-7, this book mainly covers “Stage 1,” aiming to help you more efficiently embark on Stages 2 and 3.

Algorithm learning path

Figure 0-7   Algorithm learning path

diff --git a/en/chapter_searching/binary_search/index.html b/en/chapter_searching/binary_search/index.html index 9f08e352b..5766b687c 100644 --- a/en/chapter_searching/binary_search/index.html +++ b/en/chapter_searching/binary_search/index.html @@ -3594,12 +3594,12 @@

Binary search is an efficient search algorithm based on the divide-and-conquer strategy. It utilizes the orderliness of data, reducing the search range by half each round until the target element is found or the search interval is empty.

Question

-

Given an array nums of length \(n\), with elements arranged in ascending order and non-repeating. Please find and return the index of element target in this array. If the array does not contain the element, return \(-1\). An example is shown below.

+

Given an array nums of length \(n\), with elements arranged in ascending order and non-repeating. Please find and return the index of element target in this array. If the array does not contain the element, return \(-1\). An example is shown in Figure 10-1.

Binary search example data

Figure 10-1   Binary search example data

-

As shown in the Figure 10-2 , we first initialize pointers \(i = 0\) and \(j = n - 1\), pointing to the first and last elements of the array, representing the search interval \([0, n - 1]\). Please note that square brackets indicate a closed interval, which includes the boundary values themselves.

+

As shown in Figure 10-2, we first initialize pointers \(i = 0\) and \(j = n - 1\), pointing to the first and last elements of the array, representing the search interval \([0, n - 1]\). Please note that square brackets indicate a closed interval, which includes the boundary values themselves.

Next, perform the following two steps in a loop.

  1. Calculate the midpoint index \(m = \lfloor {(i + j) / 2} \rfloor\), where \(\lfloor \: \rfloor\) denotes the floor operation.
  2. @@ -4273,7 +4273,7 @@

    Full Screen >

    -

    As shown in the Figure 10-3 , in the two types of interval representations, the initialization of the binary search algorithm, the loop condition, and the narrowing interval operation are different.

    +

    As shown in Figure 10-3, in the two types of interval representations, the initialization of the binary search algorithm, the loop condition, and the narrowing interval operation are different.

    Since both boundaries in the "closed interval" representation are defined as closed, the operations to narrow the interval through pointers \(i\) and \(j\) are also symmetrical. This makes it less prone to errors, therefore, it is generally recommended to use the "closed interval" approach.

    Two types of interval definitions

    Figure 10-3   Two types of interval definitions

    diff --git a/en/chapter_searching/binary_search_edge/index.html b/en/chapter_searching/binary_search_edge/index.html index ff1f8446a..d4898d1a2 100644 --- a/en/chapter_searching/binary_search_edge/index.html +++ b/en/chapter_searching/binary_search_edge/index.html @@ -3848,7 +3848,7 @@

    Below we introduce two more cunning methods.

    1.   Reusing the search for the left boundary

    In fact, we can use the function for finding the leftmost element to find the rightmost element, specifically by transforming the search for the rightmost target into a search for the leftmost target + 1.

    -

    As shown in the Figure 10-7 , after the search is completed, the pointer \(i\) points to the leftmost target + 1 (if it exists), while \(j\) points to the rightmost target, thus returning \(j\) is sufficient.

    +

    As shown in Figure 10-7, after the search is completed, the pointer \(i\) points to the leftmost target + 1 (if it exists), while \(j\) points to the rightmost target, thus returning \(j\) is sufficient.

    Transforming the search for the right boundary into the search for the left boundary

    Figure 10-7   Transforming the search for the right boundary into the search for the left boundary

    @@ -4074,7 +4074,7 @@

    We know that when the array does not contain target, \(i\) and \(j\) will eventually point to the first element greater and smaller than target respectively.

    -

    Thus, as shown in the Figure 10-8 , we can construct an element that does not exist in the array, to search for the left and right boundaries.

    +

    Thus, as shown in Figure 10-8, we can construct an element that does not exist in the array, to search for the left and right boundaries.