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.

      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.

    -

    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.