diff --git a/docs-en/chapter_array_and_linkedlist/array.md b/docs-en/chapter_array_and_linkedlist/array.md index 5857b780d..5e27eafda 100755 --- a/docs-en/chapter_array_and_linkedlist/array.md +++ b/docs-en/chapter_array_and_linkedlist/array.md @@ -1232,8 +1232,8 @@ To expand an array, we need to create a larger array and then copy the elements ??? pythontutor "Visualizing Code" - - 全屏观看 > + + 全屏观看 > ## 4.1.2   Advantages and Limitations of Arrays diff --git a/docs-en/chapter_array_and_linkedlist/index.md b/docs-en/chapter_array_and_linkedlist/index.md index ad4a31675..138ed3cc5 100644 --- a/docs-en/chapter_array_and_linkedlist/index.md +++ b/docs-en/chapter_array_and_linkedlist/index.md @@ -9,9 +9,9 @@ icon: material/view-list-outline !!! abstract - The world of data structures is like a solid brick wall. + The world of data structures resembles a sturdy brick wall. - The bricks of an array are neatly arranged, each closely connected to the next. In contrast, the bricks of a linked list are scattered, with vines of connections freely weaving through the gaps between bricks. + In arrays, envision bricks snugly aligned, each resting seamlessly beside the next, creating a unified formation. Meanwhile, in linked lists, these bricks disperse freely, embraced by vines gracefully knitting connections between them. ## Chapter Contents diff --git a/docs-en/chapter_array_and_linkedlist/linked_list.md b/docs-en/chapter_array_and_linkedlist/linked_list.md index 64cbb151b..133c00a03 100755 --- a/docs-en/chapter_array_and_linkedlist/linked_list.md +++ b/docs-en/chapter_array_and_linkedlist/linked_list.md @@ -542,8 +542,8 @@ In contrast, the time complexity of inserting an element in an array is $O(n)$, ??? pythontutor "Visualizing Code" - - 全屏观看 > + + 全屏观看 > ### 3.   Deleting a Node @@ -732,8 +732,8 @@ Note that although node `P` still points to `n1` after the deletion operation is ??? pythontutor "Visualizing Code" - - 全屏观看 > + + 全屏观看 > ### 4.   Accessing Nodes @@ -911,8 +911,8 @@ Note that although node `P` still points to `n1` after the deletion operation is ??? pythontutor "Visualizing Code" - - 全屏观看 > + + 全屏观看 > ### 5.   Finding Nodes @@ -1113,8 +1113,8 @@ Traverse the linked list to find a node with a value equal to `target`, and outp ??? pythontutor "Visualizing Code" - - 全屏观看 > + + 全屏观看 > ## 4.2.2   Arrays vs. Linked Lists diff --git a/docs-en/chapter_computational_complexity/iteration_and_recursion.md b/docs-en/chapter_computational_complexity/iteration_and_recursion.md index e20437a9c..8d2297f9f 100644 --- a/docs-en/chapter_computational_complexity/iteration_and_recursion.md +++ b/docs-en/chapter_computational_complexity/iteration_and_recursion.md @@ -1652,12 +1652,12 @@ Therefore, **we can use an explicit stack to simulate the behavior of the call s const stack = []; let res = 0; // 递:递归调用 - for (let i = 1; i <= n; i++) { + for (let i = n; i > 0; i--) { // 通过“入栈操作”模拟“递” stack.push(i); } // 归:返回结果 - while (stack.length) { + while (stack.length) { // 通过“出栈操作”模拟“归” res += stack.pop(); } @@ -1675,12 +1675,12 @@ Therefore, **we can use an explicit stack to simulate the behavior of the call s const stack: number[] = []; let res: number = 0; // 递:递归调用 - for (let i = 1; i <= n; i++) { + for (let i = n; i > 0; i--) { // 通过“入栈操作”模拟“递” stack.push(i); } // 归:返回结果 - while (stack.length) { + while (stack.length) { // 通过“出栈操作”模拟“归” res += stack.pop(); } diff --git a/docs-en/chapter_data_structure/number_encoding.md b/docs-en/chapter_data_structure/number_encoding.md index 7022b3f63..f4336a745 100644 --- a/docs-en/chapter_data_structure/number_encoding.md +++ b/docs-en/chapter_data_structure/number_encoding.md @@ -6,11 +6,11 @@ comments: true !!! note - In this book, chapters marked with an * symbol are optional reads. If you are short on time or find them challenging, you may skip these initially and return to them after completing the essential chapters. + In this book, chapters marked with an asterisk '*' are optional readings. If you are short on time or find them challenging, you may skip these initially and return to them after completing the essential chapters. ## 3.3.1   Integer Encoding -In the table from the previous section, we noticed that all integer types can represent one more negative number than positive numbers, such as the `byte` range of $[-128, 127]$. This phenomenon, somewhat counterintuitive, is rooted in the concepts of sign-magnitude, one's complement, and two's complement encoding. +In the table from the previous section, we observed that all integer types can represent one more negative number than positive numbers, such as the `byte` range of $[-128, 127]$. This phenomenon seems counterintuitive, and its underlying reason involves knowledge of sign-magnitude, one's complement, and two's complement encoding. Firstly, it's important to note that **numbers are stored in computers using the two's complement form**. Before analyzing why this is the case, let's define these three encoding methods: @@ -69,9 +69,9 @@ $$ Adding $1$ to the one's complement of negative zero produces a carry, but with `byte` length being only 8 bits, the carried-over $1$ to the 9th bit is discarded. Therefore, **the two's complement of negative zero is $0000 \; 0000$**, the same as positive zero, thus resolving the ambiguity. -One last puzzle is the $[-128, 127]$ range for `byte`, with an additional negative number, $-128$. We observe that for the interval $[-127, +127]$, all integers have corresponding sign-magnitude, one's complement, and two's complement, and these can be converted between each other. +One last puzzle is the $[-128, 127]$ range for `byte`, with an additional negative number, $-128$. We observe that for the interval $[-127, +127]$, all integers have corresponding sign-magnitude, one's complement, and two's complement, allowing for mutual conversion between them. -However, **the two's complement $1000 \; 0000$ is an exception without a corresponding sign-magnitude**. According to the conversion method, its sign-magnitude would be $0000 \; 0000$, which is a contradiction since this represents zero, and its two's complement should be itself. Computers designate this special two's complement $1000 \; 0000$ as representing $-128$. In fact, the calculation of $(-1) + (-127)$ in two's complement results in $-128$. +However, **the two's complement $1000 \; 0000$ is an exception without a corresponding sign-magnitude**. According to the conversion method, its sign-magnitude would be $0000 \; 0000$, indicating zero. This presents a contradiction because its two's complement should represent itself. Computers designate this special two's complement $1000 \; 0000$ as representing $-128$. In fact, the calculation of $(-1) + (-127)$ in two's complement results in $-128$. $$ \begin{aligned} diff --git a/docs-en/chapter_preface/suggestions.md b/docs-en/chapter_preface/suggestions.md index 28c8c1691..9db805709 100644 --- a/docs-en/chapter_preface/suggestions.md +++ b/docs-en/chapter_preface/suggestions.md @@ -8,233 +8,235 @@ comments: true For the best reading experience, it is recommended that you read through this section. -## 0.2.1   Conventions Of Style +## 0.2.1   Writing Conventions -- Those labeled `*` after the title are optional chapters with relatively difficult content. If you have limited time, it is advisable to skip them. -- Proper nouns and words and phrases with specific meanings are marked with `"double quotes"` to avoid ambiguity. -- Important proper nouns and their English translations are marked with `" "` in parentheses, e.g. `"array array"` . It is recommended to memorize them for reading the literature. -- **Bolded text** Indicates key content or summary statements, which deserve special attention. -- When it comes to terms that are inconsistent between programming languages, this book follows Python, for example using `None` to mean "empty". -- This book partially abandons the specification of annotations in programming languages in exchange for a more compact layout of the content. There are three main types of annotations: title annotations, content annotations, and multi-line annotations. +- Chapters marked with '*' after the title are optional and contain relatively challenging content. If you are short on time, it is advisable to skip them. +- Key technical terms and their English equivalents are enclosed in **Bold** + *italics* brackets, for example, ***array***. It's advisable to familiarize yourself with these for better comprehension of technical texts. +- Proprietary terms and words with specific meanings are indicated with “quotation marks” to avoid ambiguity. +- **Bolded text** indicates key content or summary statements, which deserve special attention. +- When it comes to terms that are inconsistent between programming languages, this book follows Python, for example using $\text{None}$ to mean "null". +- This book partially ignores the comment conventions for programming languages in exchange for a more compact layout of the content. The comments primarily consist of three types: title comments, content comments, and multi-line comments. === "Python" ```python title="" - """Header comments for labeling functions, classes, test samples, etc."""" + """Header comments for labeling functions, classes, test samples, etc"""" - # Content comments for detailed code solutions + # Comments for explaining details """ - multi-line - marginal notes + Multiline + comments """ ``` === "C++" ```cpp title="" - /* Header comments for labeling functions, classes, test samples, etc. */ + /* Header comments for labeling functions, classes, test samples, etc */ - // Content comments for detailed code solutions. + // Comments for explaining details. /** - * multi-line - * marginal notes + * Multiline + * comments */ ``` === "Java" ```java title="" - /* Header comments for labeling functions, classes, test samples, etc. */ + /* Header comments for labeling functions, classes, test samples, etc */ - // Content comments for detailed code solutions. + // Comments for explaining details. /** - * multi-line - * marginal notes + * Multiline + * comments */ ``` === "C#" ```csharp title="" - /* Header comments for labeling functions, classes, test samples, etc. */ + /* Header comments for labeling functions, classes, test samples, etc */ - // Content comments for detailed code solutions. + // Comments for explaining details. /** - * multi-line - * marginal notes + * Multiline + * comments */ ``` === "Go" ```go title="" - /* Header comments for labeling functions, classes, test samples, etc. */ + /* Header comments for labeling functions, classes, test samples, etc */ - // Content comments for detailed code solutions. + // Comments for explaining details. /** - * multi-line - * marginal notes + * Multiline + * comments */ ``` === "Swift" ```swift title="" - /* Header comments for labeling functions, classes, test samples, etc. */ + /* Header comments for labeling functions, classes, test samples, etc */ - // Content comments for detailed code solutions. + // Comments for explaining details. /** - * multi-line - * marginal notes + * Multiline + * comments */ ``` === "JS" ```javascript title="" - /* Header comments for labeling functions, classes, test samples, etc. */ + /* Header comments for labeling functions, classes, test samples, etc */ - // Content comments for detailed code solutions. + // Comments for explaining details. /** - * multi-line - * marginal notes + * Multiline + * comments */ ``` === "TS" ```typescript title="" - /* Header comments for labeling functions, classes, test samples, etc. */ + /* Header comments for labeling functions, classes, test samples, etc */ - // Content comments for detailed code solutions. + // Comments for explaining details. /** - * multi-line - * marginal notes + * Multiline + * comments */ ``` === "Dart" ```dart title="" - /* Header comments for labeling functions, classes, test samples, etc. */ + /* Header comments for labeling functions, classes, test samples, etc */ - // Content comments for detailed code solutions. + // Comments for explaining details. /** - * multi-line - * marginal notes + * Multiline + * comments */ ``` === "Rust" ```rust title="" - /* Header comments for labeling functions, classes, test samples, etc. */ + /* Header comments for labeling functions, classes, test samples, etc */ - // Content comments for detailed code solutions. + // Comments for explaining details. /** - * multi-line - * marginal notes + * Multiline + * comments */ ``` === "C" ```c title="" - /* Header comments for labeling functions, classes, test samples, etc. */ + /* Header comments for labeling functions, classes, test samples, etc */ - // Content comments for detailed code solutions. + // Comments for explaining details. /** - * multi-line - * marginal notes + * Multiline + * comments */ ``` === "Zig" ```zig title="" - // Header comments for labeling functions, classes, test samples, etc. + // Header comments for labeling functions, classes, test samples, etc - // Content comments for detailed code solutions. + // Comments for explaining details. - // Multi-line - // Annotation + // Multiline + // comments ``` -## 0.2.2   Learn Efficiently In Animated Graphic Solutions +## 0.2.2   Efficient Learning via Animated Illustrations -Compared with text, videos and pictures have a higher degree of information density and structure and are easier to understand. In this book, **key and difficult knowledge will be presented mainly in the form of animations and graphs**, while the text serves as an explanation and supplement to the animations and graphs. +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. -If, while reading the book, you find that a particular paragraph provides an animation or a graphic solution as shown below, **please use the figure as the primary source and the text as a supplement and synthesize the two to understand the content**. +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. -![Example animation](../index.assets/animation.gif){ class="animation-figure" } +![Animated Illustration Example](../index.assets/animation.gif){ class="animation-figure" } -

Figure 0-2   Example animation

+

Figure 0-2   Animated Illustration Example

-## 0.2.3   Deeper Understanding In Code Practice +## 0.2.3   Deepen Understanding through Coding Practice -The companion code for this book is hosted in the [GitHub repository](https://github.com/krahets/hello-algo). As shown in the Figure 0-3 , **the source code is accompanied by test samples that can be run with a single click**. +The source code of this book is hosted on the [GitHub Repository](https://github.com/krahets/hello-algo). As shown in the Figure 0-3 , **the source code comes with test examples and can be executed with just a single click**. -If time permits, **it is recommended that you refer to the code and knock it through on your own**. If you have limited time to study, please read through and run all the code at least once. +If time permits, **it's recommended to type out the code yourself**. If pressed for time, at least read and run all the codes. -The process of writing code is often more rewarding than reading it. **Learning by doing is really learning**. +Compared to just reading code, writing code often yields more learning. **Learning by doing is the real way to learn.** -![Running code example](../index.assets/running_code.gif){ class="animation-figure" } +![Running Code Example](../index.assets/running_code.gif){ class="animation-figure" } -

Figure 0-3   Running code example

+

Figure 0-3   Running Code Example

-The preliminaries for running the code are divided into three main steps. +Setting up to run the code involves three main steps. -**Step 1: Install the local programming environment**. Please refer to [Appendix Tutorial](https://www.hello-algo.com/chapter_appendix/installation/) for installation, or skip this step if already installed. +**Step 1: Install a local programming environment**. Follow the [tutorial](https://www.hello-algo.com/chapter_appendix/installation/) in the appendix for installation, or skip this step if already installed. -**Step 2: Clone or download the code repository**. If [Git](https://git-scm.com/downloads) is already installed, you can clone this repository with the following command. +**Step 2: Clone or download the code repository**. Visit the [GitHub Repository](https://github.com/krahets/hello-algo). + +If [Git](https://git-scm.com/downloads) is installed, use the following command to clone the repository: ```shell git clone https://github.com/krahets/hello-algo.git ``` -Of course, you can also in the location shown in the Figure 0-4 , click "Download ZIP" directly download the code zip, and then in the local solution. +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. -![Clone repository with download code](suggestions.assets/download_code.png){ class="animation-figure" } +![Cloning Repository and Downloading Code](suggestions.assets/download_code.png){ class="animation-figure" } -

Figure 0-4   Clone repository with download 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. The source code files can be run with a single click, which will help you save unnecessary debugging time and allow you to focus on what you are learning. +**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. -![Code block with corresponding source file](suggestions.assets/code_md_to_repo.png){ class="animation-figure" } +![Code Block and Corresponding Source Code File](suggestions.assets/code_md_to_repo.png){ class="animation-figure" } -

Figure 0-5   Code block with corresponding source file

+

Figure 0-5   Code Block and Corresponding Source Code File

-## 0.2.4   Growing Together In Questioning And Discussion +## 0.2.4   Learning Together in Discussion -While reading this book, please don't skip over the points that you didn't learn. **Feel free to ask your questions in the comment section**. We will be happy to answer them and can usually respond within two days. +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 you can see in the Figure 0-6 , each post comes with a comment section at the bottom. I hope you'll pay more attention to the comments section. On the one hand, you can learn about the problems that people encounter, so as to check the gaps and stimulate deeper thinking. On the other hand, we expect you to generously answer other partners' questions, share your insights, and help others improve. +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. -![Example of comment section](../index.assets/comment.gif){ class="animation-figure" } +![Comment Section Example](../index.assets/comment.gif){ class="animation-figure" } -

Figure 0-6   Example of comment section

+

Figure 0-6   Comment Section Example

-## 0.2.5   Algorithm Learning Route +## 0.2.5   Algorithm Learning Path -From a general point of view, we can divide the process of learning data structures and algorithms into three stages. +Overall, the journey of mastering data structures and algorithms can be divided into three stages: -1. **Introduction to Algorithms**. We need to familiarize ourselves with the characteristics and usage of various data structures and learn about the principles, processes, uses and efficiency of different algorithms. -2. **Brush up on algorithm questions**. It is recommended to start brushing from popular topics, such as [Sword to Offer](https://leetcode.cn/studyplan/coding-interviews/) and [LeetCode Hot 100](https://leetcode.cn/studyplan/top-100- liked/), first accumulate at least 100 questions to familiarize yourself with mainstream algorithmic problems. Forgetfulness can be a challenge when first brushing up, 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. -3. **Build 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 brushing, we can try to adopt advanced brushing strategies, such as categorizing by topic, multiple solutions, multiple solutions, etc. Related brushing tips can be found in various communities. +1. **Stage 1: Introduction to algorithms**. We need to familiarize ourselves with the characteristics and usage of various data structures and learn about the principles, processes, uses, and efficiency of different algorithms. +2. **Stage 2: Practicing algorithm problems**. It is recommended to start from popular problems, such as [Sword for Offer](https://leetcode.cn/studyplan/coding-interviews/) and [LeetCode Hot 100](https://leetcode.cn/studyplan/top-100- liked/), 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. +3. **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 "Phase 1" and is designed to help you start Phase 2 and 3 more efficiently. +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. -![algorithm learning route](suggestions.assets/learning_route.png){ class="animation-figure" } +![Algorithm Learning Path](suggestions.assets/learning_route.png){ class="animation-figure" } -

Figure 0-7   algorithm learning route

+

Figure 0-7   Algorithm Learning Path

diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 92b139fd9..bbf2bdce2 100755 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -1236,8 +1236,8 @@ comments: true ??? pythontutor "可视化运行" - - 全屏观看 > + + 全屏观看 > ## 4.1.2   数组的优点与局限性 diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index f2b5bbc35..cb28b1371 100755 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -546,8 +546,8 @@ comments: true ??? pythontutor "可视化运行" - - 全屏观看 > + + 全屏观看 > ### 3.   删除节点 @@ -736,8 +736,8 @@ comments: true ??? pythontutor "可视化运行" - - 全屏观看 > + + 全屏观看 > ### 4.   访问节点 @@ -915,8 +915,8 @@ comments: true ??? pythontutor "可视化运行" - - 全屏观看 > + + 全屏观看 > ### 5.   查找节点 @@ -1117,8 +1117,8 @@ comments: true ??? pythontutor "可视化运行" - - 全屏观看 > + + 全屏观看 > ## 4.2.2   数组 vs. 链表 diff --git a/docs/chapter_backtracking/backtracking_algorithm.md b/docs/chapter_backtracking/backtracking_algorithm.md index d9e6c39c0..9e5f6bd49 100644 --- a/docs/chapter_backtracking/backtracking_algorithm.md +++ b/docs/chapter_backtracking/backtracking_algorithm.md @@ -1670,6 +1670,11 @@ comments: true [class]{}-[func]{backtrack} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 根据题意,我们在找到值为 $7$ 的节点后应该继续搜索,**因此需要将记录解之后的 `return` 语句删除**。图 13-4 对比了保留或删除 `return` 语句的搜索过程。 ![保留与删除 return 的搜索过程对比](backtracking_algorithm.assets/backtrack_remove_return_or_not.png){ class="animation-figure" } diff --git a/docs/chapter_backtracking/subset_sum_problem.md b/docs/chapter_backtracking/subset_sum_problem.md index 90ab8ed99..bebd5511a 100644 --- a/docs/chapter_backtracking/subset_sum_problem.md +++ b/docs/chapter_backtracking/subset_sum_problem.md @@ -430,8 +430,8 @@ comments: true ??? pythontutor "可视化运行" - - 全屏观看 > + + 全屏观看 > 向以上代码输入数组 $[3, 4, 5]$ 和目标元素 $9$ ,输出结果为 $[3, 3, 3], [4, 5], [5, 4]$ 。**虽然成功找出了所有和为 $9$ 的子集,但其中存在重复的子集 $[4, 5]$ 和 $[5, 4]$** 。 @@ -913,8 +913,8 @@ comments: true ??? pythontutor "可视化运行" - - 全屏观看 > + + 全屏观看 > 图 13-12 所示为将数组 $[3, 4, 5]$ 和目标元素 $9$ 输入以上代码后的整体回溯过程。 @@ -1437,8 +1437,8 @@ comments: true ??? pythontutor "可视化运行" - - 全屏观看 > + + 全屏观看 > 图 13-14 展示了数组 $[4, 4, 5]$ 和目标元素 $9$ 的回溯过程,共包含四种剪枝操作。请你将图示与代码注释相结合,理解整个搜索过程,以及每种剪枝操作是如何工作的。 diff --git a/docs/chapter_computational_complexity/iteration_and_recursion.md b/docs/chapter_computational_complexity/iteration_and_recursion.md index 6c5f03f0d..45fa47c7e 100644 --- a/docs/chapter_computational_complexity/iteration_and_recursion.md +++ b/docs/chapter_computational_complexity/iteration_and_recursion.md @@ -1652,12 +1652,12 @@ comments: true const stack = []; let res = 0; // 递:递归调用 - for (let i = 1; i <= n; i++) { + for (let i = n; i > 0; i--) { // 通过“入栈操作”模拟“递” stack.push(i); } // 归:返回结果 - while (stack.length) { + while (stack.length) { // 通过“出栈操作”模拟“归” res += stack.pop(); } @@ -1675,12 +1675,12 @@ comments: true const stack: number[] = []; let res: number = 0; // 递:递归调用 - for (let i = 1; i <= n; i++) { + for (let i = n; i > 0; i--) { // 通过“入栈操作”模拟“递” stack.push(i); } // 归:返回结果 - while (stack.length) { + while (stack.length) { // 通过“出栈操作”模拟“归” res += stack.pop(); } diff --git a/docs/chapter_data_structure/basic_data_types.md b/docs/chapter_data_structure/basic_data_types.md index fa5c0a1a6..18d9ff3de 100644 --- a/docs/chapter_data_structure/basic_data_types.md +++ b/docs/chapter_data_structure/basic_data_types.md @@ -146,7 +146,7 @@ comments: true ```rust title="" // 使用多种基本数据类型来初始化数组 let numbers: Vec = vec![0; 5]; - let decimals: Vec = vec![0.0, 5]; + let decimals: Vec = vec![0.0; 5]; let characters: Vec = vec!['0'; 5]; let bools: Vec = vec![false; 5]; ``` diff --git a/docs/chapter_divide_and_conquer/binary_search_recur.md b/docs/chapter_divide_and_conquer/binary_search_recur.md index 5d9847bf4..105fdcb79 100644 --- a/docs/chapter_divide_and_conquer/binary_search_recur.md +++ b/docs/chapter_divide_and_conquer/binary_search_recur.md @@ -389,3 +389,8 @@ comments: true [class]{}-[func]{binarySearch} ``` + +??? pythontutor "可视化运行" + + + 全屏观看 > diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/docs/chapter_divide_and_conquer/build_binary_tree_problem.md index 0e9c9325e..c79586911 100644 --- a/docs/chapter_divide_and_conquer/build_binary_tree_problem.md +++ b/docs/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -445,6 +445,11 @@ comments: true [class]{}-[func]{buildTree} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 图 12-8 展示了构建二叉树的递归过程,各个节点是在向下“递”的过程中建立的,而各条边(引用)是在向上“归”的过程中建立的。 === "<1>" diff --git a/docs/chapter_divide_and_conquer/hanota_problem.md b/docs/chapter_divide_and_conquer/hanota_problem.md index d5183e7bd..cc26878b3 100644 --- a/docs/chapter_divide_and_conquer/hanota_problem.md +++ b/docs/chapter_divide_and_conquer/hanota_problem.md @@ -483,6 +483,11 @@ comments: true [class]{}-[func]{solveHanota} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 如图 12-15 所示,汉诺塔问题形成一棵高度为 $n$ 的递归树,每个节点代表一个子问题,对应一个开启的 `dfs()` 函数,**因此时间复杂度为 $O(2^n)$ ,空间复杂度为 $O(n)$** 。 ![汉诺塔问题的递归树](hanota_problem.assets/hanota_recursive_tree.png){ class="animation-figure" } diff --git a/docs/chapter_dynamic_programming/dp_problem_features.md b/docs/chapter_dynamic_programming/dp_problem_features.md index d48ec0cbd..180b52caf 100644 --- a/docs/chapter_dynamic_programming/dp_problem_features.md +++ b/docs/chapter_dynamic_programming/dp_problem_features.md @@ -301,6 +301,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 图 14-7 展示了以上代码的动态规划过程。 ![爬楼梯最小代价的动态规划过程](dp_problem_features.assets/min_cost_cs_dp.png){ class="animation-figure" } @@ -534,6 +539,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 14.2.2   无后效性 无后效性是动态规划能够有效解决问题的重要特性之一,其定义为:**给定一个确定的状态,它的未来发展只与当前状态有关,而与过去经历的所有状态无关**。 @@ -866,6 +876,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 在上面的案例中,由于仅需多考虑前面一个状态,因此我们仍然可以通过扩展状态定义,使得问题重新满足无后效性。然而,某些问题具有非常严重的“有后效性”。 !!! question "爬楼梯与障碍生成" diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.md b/docs/chapter_dynamic_programming/dp_solution_pipeline.md index 78fb9e611..b708431f1 100644 --- a/docs/chapter_dynamic_programming/dp_solution_pipeline.md +++ b/docs/chapter_dynamic_programming/dp_solution_pipeline.md @@ -366,6 +366,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 图 14-14 给出了以 $dp[2, 1]$ 为根节点的递归树,其中包含一些重叠子问题,其数量会随着网格 `grid` 的尺寸变大而急剧增多。 从本质上看,造成重叠子问题的原因为:**存在多条路径可以从左上角到达某一单元格**。 @@ -697,6 +702,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 如图 14-15 所示,在引入记忆化后,所有子问题的解只需计算一次,因此时间复杂度取决于状态总数,即网格尺寸 $O(nm)$ 。 ![记忆化搜索递归树](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png){ class="animation-figure" } @@ -1044,6 +1054,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 图 14-16 展示了最小路径和的状态转移过程,其遍历了整个网格,**因此时间复杂度为 $O(nm)$** 。 数组 `dp` 大小为 $n \times m$ ,**因此空间复杂度为 $O(nm)$** 。 @@ -1403,3 +1418,8 @@ $$ return dp[m - 1]; } ``` + +??? pythontutor "可视化运行" + + + 全屏观看 > diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.md b/docs/chapter_dynamic_programming/edit_distance_problem.md index 5f320bbdf..7c12ff43f 100644 --- a/docs/chapter_dynamic_programming/edit_distance_problem.md +++ b/docs/chapter_dynamic_programming/edit_distance_problem.md @@ -450,6 +450,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 如图 14-30 所示,编辑距离问题的状态转移过程与背包问题非常类似,都可以看作填写一个二维网格的过程。 === "<1>" @@ -902,3 +907,8 @@ $$ return dp[m]; } ``` + +??? pythontutor "可视化运行" + + + 全屏观看 > diff --git a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md index 55908d4cd..c19c6d5f2 100644 --- a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md +++ b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -385,6 +385,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 14.1.1   方法一:暴力搜索 回溯算法通常并不显式地对问题进行拆解,而是将求解问题看作一系列决策步骤,通过试探和剪枝,搜索所有可能的解。 @@ -638,6 +643,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 图 14-3 展示了暴力搜索形成的递归树。对于问题 $dp[n]$ ,其递归树的深度为 $n$ ,时间复杂度为 $O(2^n)$ 。指数阶属于爆炸式增长,如果我们输入一个比较大的 $n$ ,则会陷入漫长的等待之中。 ![爬楼梯对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png){ class="animation-figure" } @@ -975,6 +985,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 观察图 14-4 ,**经过记忆化处理后,所有重叠子问题都只需计算一次,时间复杂度优化至 $O(n)$** ,这是一个巨大的飞跃。 ![记忆化搜索对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png){ class="animation-figure" } @@ -1229,6 +1244,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 图 14-5 模拟了以上代码的执行过程。 ![爬楼梯的动态规划过程](intro_to_dynamic_programming.assets/climbing_stairs_dp.png){ class="animation-figure" } @@ -1447,6 +1467,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 观察以上代码,由于省去了数组 `dp` 占用的空间,因此空间复杂度从 $O(n)$ 降至 $O(1)$ 。 在动态规划问题中,当前状态往往仅与前面有限个状态有关,这时我们可以只保留必要的状态,通过“降维”来节省内存空间。**这种空间优化技巧被称为“滚动变量”或“滚动数组”**。 diff --git a/docs/chapter_dynamic_programming/knapsack_problem.md b/docs/chapter_dynamic_programming/knapsack_problem.md index fb83e1390..e86af6999 100644 --- a/docs/chapter_dynamic_programming/knapsack_problem.md +++ b/docs/chapter_dynamic_programming/knapsack_problem.md @@ -316,6 +316,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 如图 14-18 所示,由于每个物品都会产生不选和选两条搜索分支,因此时间复杂度为 $O(2^n)$ 。 观察递归树,容易发现其中存在重叠子问题,例如 $dp[1, 10]$ 等。而当物品较多、背包容量较大,尤其是相同重量的物品较多时,重叠子问题的数量将会大幅增多。 @@ -654,6 +659,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 图 14-19 展示了在记忆化搜索中被剪掉的搜索分支。 ![0-1 背包问题的记忆化搜索递归树](knapsack_problem.assets/knapsack_dfs_mem.png){ class="animation-figure" } @@ -973,6 +983,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 如图 14-20 所示,时间复杂度和空间复杂度都由数组 `dp` 大小决定,即 $O(n \times cap)$ 。 === "<1>" @@ -1325,3 +1340,8 @@ $$ return dp[cap]; } ``` + +??? pythontutor "可视化运行" + + + 全屏观看 > diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md index 1f05c2dac..072307206 100644 --- a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md +++ b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md @@ -347,6 +347,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ### 3.   空间优化 由于当前状态是从左边和上边的状态转移而来的,**因此空间优化后应该对 $dp$ 表中的每一行进行正序遍历**。 @@ -667,6 +672,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 14.5.2   零钱兑换问题 背包问题是一大类动态规划问题的代表,其拥有很多变种,例如零钱兑换问题。 @@ -1082,6 +1092,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 图 14-25 展示了零钱兑换的动态规划过程,和完全背包问题非常相似。 === "<1>" @@ -1461,6 +1476,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 14.5.3   零钱兑换问题 II !!! question @@ -1758,7 +1778,7 @@ $$ // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { - // 不选和选硬币 i 这两种方案的较小值 + // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize]; } } @@ -1832,6 +1852,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ### 3.   空间优化 空间优化处理方式相同,删除硬币维度即可: @@ -2075,7 +2100,7 @@ $$ // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { - // 不选和选硬币 i 这两种方案的较小值 + // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1] as usize]; } } @@ -2136,3 +2161,8 @@ $$ return dp[amt]; } ``` + +??? pythontutor "可视化运行" + + + 全屏观看 > diff --git a/docs/chapter_graph/graph_operations.md b/docs/chapter_graph/graph_operations.md index c84f9fbf9..4306024d9 100644 --- a/docs/chapter_graph/graph_operations.md +++ b/docs/chapter_graph/graph_operations.md @@ -1045,6 +1045,11 @@ comments: true [class]{GraphAdjMat}-[func]{} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 9.2.2   基于邻接表的实现 设无向图的顶点总数为 $n$、边总数为 $m$ ,则可根据图 9-8 所示的方法实现各种操作。 @@ -2065,6 +2070,11 @@ comments: true [class]{GraphAdjList}-[func]{} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 9.2.3   效率对比 设图中共有 $n$ 个顶点和 $m$ 条边,表 9-2 对比了邻接矩阵和邻接表的时间效率和空间效率。 diff --git a/docs/chapter_graph/graph_traversal.md b/docs/chapter_graph/graph_traversal.md index 8c5adfc34..741173618 100644 --- a/docs/chapter_graph/graph_traversal.md +++ b/docs/chapter_graph/graph_traversal.md @@ -418,6 +418,11 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先 [class]{}-[func]{graphBFS} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 代码相对抽象,建议对照图 9-10 来加深理解。 === "<1>" @@ -821,6 +826,11 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先 [class]{}-[func]{graphDFS} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 深度优先遍历的算法流程如图 9-12 所示。 - **直虚线代表向下递推**,表示开启了一个新的递归方法来访问新顶点。 diff --git a/docs/chapter_greedy/fractional_knapsack_problem.md b/docs/chapter_greedy/fractional_knapsack_problem.md index 701e11464..f89261f9c 100644 --- a/docs/chapter_greedy/fractional_knapsack_problem.md +++ b/docs/chapter_greedy/fractional_knapsack_problem.md @@ -464,6 +464,11 @@ comments: true [class]{}-[func]{fractionalKnapsack} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 除排序之外,在最差情况下,需要遍历整个物品列表,**因此时间复杂度为 $O(n)$** ,其中 $n$ 为物品数量。 由于初始化了一个 `Item` 对象列表,**因此空间复杂度为 $O(n)$** 。 diff --git a/docs/chapter_greedy/greedy_algorithm.md b/docs/chapter_greedy/greedy_algorithm.md index 935ae682d..6e59243ea 100644 --- a/docs/chapter_greedy/greedy_algorithm.md +++ b/docs/chapter_greedy/greedy_algorithm.md @@ -289,6 +289,11 @@ comments: true [class]{}-[func]{coinChangeGreedy} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 你可能会不由地发出感叹:So clean !贪心算法仅用约十行代码就解决了零钱兑换问题。 ## 15.1.1   贪心算法的优点与局限性 diff --git a/docs/chapter_greedy/max_capacity_problem.md b/docs/chapter_greedy/max_capacity_problem.md index bd20acd36..129f7797c 100644 --- a/docs/chapter_greedy/max_capacity_problem.md +++ b/docs/chapter_greedy/max_capacity_problem.md @@ -374,6 +374,11 @@ $$ [class]{}-[func]{maxCapacity} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ### 3.   正确性证明 之所以贪心比穷举更快,是因为每轮的贪心选择都会“跳过”一些状态。 diff --git a/docs/chapter_greedy/max_product_cutting_problem.md b/docs/chapter_greedy/max_product_cutting_problem.md index 538a75882..049687a71 100644 --- a/docs/chapter_greedy/max_product_cutting_problem.md +++ b/docs/chapter_greedy/max_product_cutting_problem.md @@ -349,6 +349,11 @@ $$ [class]{}-[func]{maxProductCutting} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ![最大切分乘积的计算方法](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png){ class="animation-figure" }

图 15-16   最大切分乘积的计算方法

diff --git a/docs/chapter_heap/build_heap.md b/docs/chapter_heap/build_heap.md index 99e41500d..b7630d9ec 100644 --- a/docs/chapter_heap/build_heap.md +++ b/docs/chapter_heap/build_heap.md @@ -202,6 +202,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 8.2.3   复杂度分析 下面,我们来尝试推算第二种建堆方法的时间复杂度。 diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md index 5f04c94fd..442aa7c64 100644 --- a/docs/chapter_heap/heap.md +++ b/docs/chapter_heap/heap.md @@ -353,6 +353,10 @@ comments: true ``` +??? pythontutor "可视化运行" + + + ## 8.1.2   堆的实现 下文实现的是大顶堆。若要将其转换为小顶堆,只需将所有大小逻辑判断取逆(例如,将 $\geq$ 替换为 $\leq$ )。感兴趣的读者可以自行实现。 @@ -709,6 +713,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ### 3.   元素入堆 给定元素 `val` ,我们首先将其添加到堆底。添加之后,由于 `val` 可能大于堆中其他元素,堆的成立条件可能已被破坏,**因此需要修复从插入节点到根节点的路径上的各个节点**,这个操作被称为「堆化 heapify」。 @@ -1082,6 +1091,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ### 4.   堆顶元素出堆 堆顶元素是二叉树的根节点,即列表首元素。如果我们直接从列表中删除首元素,那么二叉树中所有节点的索引都会发生变化,这将使得后续使用堆化进行修复变得困难。为了尽量减少元素索引的变动,我们采用以下操作步骤。 @@ -1597,6 +1611,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 8.1.3   堆的常见应用 - **优先队列**:堆通常作为实现优先队列的首选数据结构,其入队和出队操作的时间复杂度均为 $O(\log n)$ ,而建队操作为 $O(n)$ ,这些操作都非常高效。 diff --git a/docs/chapter_heap/top_k.md b/docs/chapter_heap/top_k.md index 760118ad1..b6c0b9768 100644 --- a/docs/chapter_heap/top_k.md +++ b/docs/chapter_heap/top_k.md @@ -417,6 +417,11 @@ comments: true [class]{}-[func]{topKHeap} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 总共执行了 $n$ 轮入堆和出堆,堆的最大长度为 $k$ ,因此时间复杂度为 $O(n \log k)$ 。该方法的效率很高,当 $k$ 较小时,时间复杂度趋向 $O(n)$ ;当 $k$ 较大时,时间复杂度不会超过 $O(n \log n)$ 。 另外,该方法适用于动态数据流的使用场景。在不断加入数据时,我们可以持续维护堆内的元素,从而实现最大的 $k$ 个元素的动态更新。 diff --git a/docs/chapter_preface/suggestions.md b/docs/chapter_preface/suggestions.md index 6f862687f..2dde6943b 100644 --- a/docs/chapter_preface/suggestions.md +++ b/docs/chapter_preface/suggestions.md @@ -197,9 +197,7 @@ comments: true **第一步:安装本地编程环境**。请参照附录所示的[教程](https://www.hello-algo.com/chapter_appendix/installation/)进行安装,如果已安装,则可跳过此步骤。 -**第二步:克隆或下载代码仓库**。前往 [GitHub 仓库](https://github.com/krahets/hello-algo)。 - -如果已经安装 [Git](https://git-scm.com/downloads) ,可以通过以下命令克隆本仓库: +**第二步:克隆或下载代码仓库**。前往 [GitHub 仓库](https://github.com/krahets/hello-algo)。如果已经安装 [Git](https://git-scm.com/downloads) ,可以通过以下命令克隆本仓库: ```shell git clone https://github.com/krahets/hello-algo.git @@ -217,15 +215,21 @@ git clone https://github.com/krahets/hello-algo.git

图 0-5   代码块与对应的源代码文件

+除了本地运行代码,**网页版还支持 Python 代码的可视化运行**(基于 [pythontutor](https://pythontutor.com/) 实现)。如图 0-6 所示,你可以点击代码块下方的“可视化运行”来展开视图,观察算法代码的执行过程;也可以点击“全屏观看”,以获得更好的阅览体验。 + +![Python 代码的可视化运行](suggestions.assets/pythontutor_example.png){ class="animation-figure" } + +

图 0-6   Python 代码的可视化运行

+ ## 0.2.4   在提问讨论中共同成长 在阅读本书时,请不要轻易跳过那些没学明白的知识点。**欢迎在评论区提出你的问题**,我和小伙伴们将竭诚为你解答,一般情况下可在两天内回复。 -如图 0-6 所示,网页版每个章节的底部都配有评论区。希望你能多关注评论区的内容。一方面,你可以了解大家遇到的问题,从而查漏补缺,激发更深入的思考。另一方面,期待你能慷慨地回答其他小伙伴的问题,分享你的见解,帮助他人进步。 +如图 0-7 所示,网页版每个章节的底部都配有评论区。希望你能多关注评论区的内容。一方面,你可以了解大家遇到的问题,从而查漏补缺,激发更深入的思考。另一方面,期待你能慷慨地回答其他小伙伴的问题,分享你的见解,帮助他人进步。 ![评论区示例](../index.assets/comment.gif){ class="animation-figure" } -

图 0-6   评论区示例

+

图 0-7   评论区示例

## 0.2.5   算法学习路线 @@ -235,8 +239,8 @@ git clone https://github.com/krahets/hello-algo.git 2. **阶段二:刷算法题**。建议从热门题目开刷,如“[剑指 Offer](https://leetcode.cn/studyplan/coding-interviews/)”和“[LeetCode Hot 100](https://leetcode.cn/studyplan/top-100-liked/)”,先积累至少 100 道题目,熟悉主流的算法问题。初次刷题时,“知识遗忘”可能是一个挑战,但请放心,这是很正常的。我们可以按照“艾宾浩斯遗忘曲线”来复习题目,通常在进行 3~5 轮的重复后,就能将其牢记在心。 3. **阶段三:搭建知识体系**。在学习方面,我们可以阅读算法专栏文章、解题框架和算法教材,以不断丰富知识体系。在刷题方面,可以尝试采用进阶刷题策略,如按专题分类、一题多解、一解多题等,相关的刷题心得可以在各个社区找到。 -如图 0-7 所示,本书内容主要涵盖“阶段一”,旨在帮助你更高效地展开阶段二和阶段三的学习。 +如图 0-8 所示,本书内容主要涵盖“阶段一”,旨在帮助你更高效地展开阶段二和阶段三的学习。 ![算法学习路线](suggestions.assets/learning_route.png){ class="animation-figure" } -

图 0-7   算法学习路线

+

图 0-8   算法学习路线

diff --git a/docs/chapter_searching/binary_search.md b/docs/chapter_searching/binary_search.md index fd52f5383..397940d34 100755 --- a/docs/chapter_searching/binary_search.md +++ b/docs/chapter_searching/binary_search.md @@ -334,6 +334,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + **时间复杂度为 $O(\log n)$** :在二分循环中,区间每轮缩小一半,循环次数为 $\log_2 n$ 。 **空间复杂度为 $O(1)$** :指针 $i$ 和 $j$ 使用常数大小空间。 @@ -625,6 +630,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 如图 10-3 所示,在两种区间表示下,二分查找算法的初始化、循环条件和缩小区间操作皆有所不同。 由于“双闭区间”表示中的左右边界都被定义为闭区间,因此通过指针 $i$ 和指针 $j$ 缩小区间的操作也是对称的。这样更不容易出错,**因此一般建议采用“双闭区间”的写法**。 diff --git a/docs/chapter_searching/binary_search_edge.md b/docs/chapter_searching/binary_search_edge.md index da03c5a9b..2505be35f 100644 --- a/docs/chapter_searching/binary_search_edge.md +++ b/docs/chapter_searching/binary_search_edge.md @@ -199,6 +199,11 @@ comments: true [class]{}-[func]{binarySearchLeftEdge} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 10.3.2   查找右边界 那么如何查找最右一个 `target` 呢?最直接的方式是修改代码,替换在 `nums[m] == target` 情况下的指针收缩操作。代码在此省略,有兴趣的读者可以自行实现。 @@ -419,6 +424,11 @@ comments: true [class]{}-[func]{binarySearchRightEdge} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ### 2.   转化为查找元素 我们知道,当数组不包含 `target` 时,最终 $i$ 和 $j$ 会分别指向首个大于、小于 `target` 的元素。 diff --git a/docs/chapter_searching/binary_search_insertion.md b/docs/chapter_searching/binary_search_insertion.md index 62b9eab65..ad647617c 100644 --- a/docs/chapter_searching/binary_search_insertion.md +++ b/docs/chapter_searching/binary_search_insertion.md @@ -272,6 +272,11 @@ comments: true [class]{}-[func]{binarySearchInsertionSimple} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 10.2.2   存在重复元素的情况 !!! question @@ -569,6 +574,11 @@ comments: true [class]{}-[func]{binarySearchInsertion} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + !!! tip 本节的代码都是“双闭区间”写法。有兴趣的读者可以自行实现“左闭右开”写法。 diff --git a/docs/chapter_searching/replace_linear_by_hashing.md b/docs/chapter_searching/replace_linear_by_hashing.md index 6e9a8f165..86c55c986 100755 --- a/docs/chapter_searching/replace_linear_by_hashing.md +++ b/docs/chapter_searching/replace_linear_by_hashing.md @@ -229,6 +229,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 此方法的时间复杂度为 $O(n^2)$ ,空间复杂度为 $O(1)$ ,在大数据量下非常耗时。 ## 10.4.2   哈希查找:以空间换时间 @@ -503,6 +508,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 此方法通过哈希查找将时间复杂度从 $O(n^2)$ 降至 $O(n)$ ,大幅提升运行效率。 由于需要维护一个额外的哈希表,因此空间复杂度为 $O(n)$ 。**尽管如此,该方法的整体时空效率更为均衡,因此它是本题的最优解法**。 diff --git a/docs/chapter_sorting/bubble_sort.md b/docs/chapter_sorting/bubble_sort.md index 4ccf1a90e..54d60fffa 100755 --- a/docs/chapter_sorting/bubble_sort.md +++ b/docs/chapter_sorting/bubble_sort.md @@ -277,6 +277,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 11.3.2   效率优化 我们发现,如果某轮“冒泡”中没有执行任何交换操作,说明数组已经完成排序,可直接返回结果。因此,可以增加一个标志位 `flag` 来监测这种情况,一旦出现就立即返回。 @@ -557,6 +562,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 11.3.3   算法特性 - **时间复杂度为 $O(n^2)$、自适应排序**:各轮“冒泡”遍历的数组长度依次为 $n - 1$、$n - 2$、$\dots$、$2$、$1$ ,总和为 $(n - 1) n / 2$ 。在引入 `flag` 优化后,最佳时间复杂度可达到 $O(n)$ 。 diff --git a/docs/chapter_sorting/bucket_sort.md b/docs/chapter_sorting/bucket_sort.md index 4d8a47cdd..75a8efa0a 100644 --- a/docs/chapter_sorting/bucket_sort.md +++ b/docs/chapter_sorting/bucket_sort.md @@ -394,6 +394,11 @@ comments: true [class]{}-[func]{bucketSort} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 11.8.2   算法特性 桶排序适用于处理体量很大的数据。例如,输入数据包含 100 万个元素,由于空间限制,系统内存无法一次性加载所有数据。此时,可以将数据分成 1000 个桶,然后分别对每个桶进行排序,最后将结果合并。 diff --git a/docs/chapter_sorting/counting_sort.md b/docs/chapter_sorting/counting_sort.md index 1ca30240b..612bcb758 100644 --- a/docs/chapter_sorting/counting_sort.md +++ b/docs/chapter_sorting/counting_sort.md @@ -321,6 +321,11 @@ comments: true [class]{}-[func]{countingSortNaive} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + !!! note "计数排序与桶排序的联系" 从桶排序的角度看,我们可以将计数排序中的计数数组 `counter` 的每个索引视为一个桶,将统计数量的过程看作将各个元素分配到对应的桶中。本质上,计数排序是桶排序在整型数据下的一个特例。 @@ -778,6 +783,11 @@ $$ [class]{}-[func]{countingSort} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 11.9.3   算法特性 - **时间复杂度为 $O(n + m)$** :涉及遍历 `nums` 和遍历 `counter` ,都使用线性时间。一般情况下 $n \gg m$ ,时间复杂度趋于 $O(n)$ 。 diff --git a/docs/chapter_sorting/heap_sort.md b/docs/chapter_sorting/heap_sort.md index 11fd2b035..e7102b1f4 100644 --- a/docs/chapter_sorting/heap_sort.md +++ b/docs/chapter_sorting/heap_sort.md @@ -542,6 +542,11 @@ comments: true [class]{}-[func]{heapSort} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 11.7.2   算法特性 - **时间复杂度为 $O(n \log n)$、非自适应排序**:建堆操作使用 $O(n)$ 时间。从堆中提取最大元素的时间复杂度为 $O(\log n)$ ,共循环 $n - 1$ 轮。 diff --git a/docs/chapter_sorting/insertion_sort.md b/docs/chapter_sorting/insertion_sort.md index c053f88e4..54a50405e 100755 --- a/docs/chapter_sorting/insertion_sort.md +++ b/docs/chapter_sorting/insertion_sort.md @@ -250,6 +250,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 11.4.2   算法特性 - **时间复杂度为 $O(n^2)$、自适应排序**:在最差情况下,每次插入操作分别需要循环 $n - 1$、$n-2$、$\dots$、$2$、$1$ 次,求和得到 $(n - 1) n / 2$ ,因此时间复杂度为 $O(n^2)$ 。在遇到有序数据时,插入操作会提前终止。当输入数组完全有序时,插入排序达到最佳时间复杂度 $O(n)$ 。 diff --git a/docs/chapter_sorting/merge_sort.md b/docs/chapter_sorting/merge_sort.md index 8d8738c81..22bfc2094 100755 --- a/docs/chapter_sorting/merge_sort.md +++ b/docs/chapter_sorting/merge_sort.md @@ -633,6 +633,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 11.6.2   算法特性 - **时间复杂度为 $O(n \log n)$、非自适应排序**:划分产生高度为 $\log n$ 的递归树,每层合并的总操作数量为 $n$ ,因此总体时间复杂度为 $O(n \log n)$ 。 diff --git a/docs/chapter_sorting/quick_sort.md b/docs/chapter_sorting/quick_sort.md index cf5b8d2c7..8f8535f7d 100755 --- a/docs/chapter_sorting/quick_sort.md +++ b/docs/chapter_sorting/quick_sort.md @@ -358,6 +358,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 11.5.1   算法流程 快速排序的整体流程如图 11-9 所示。 @@ -586,6 +591,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 11.5.2   算法特性 - **时间复杂度为 $O(n \log n)$、自适应排序**:在平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。在最差情况下,每轮哨兵划分操作都将长度为 $n$ 的数组划分为长度为 $0$ 和 $n - 1$ 的两个子数组,此时递归层数达到 $n$ ,每层中的循环数为 $n$ ,总体使用 $O(n^2)$ 时间。 @@ -1042,6 +1052,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 11.5.5   尾递归优化 **在某些输入下,快速排序可能占用空间较多**。以完全有序的输入数组为例,设递归中的子数组长度为 $m$ ,每轮哨兵划分操作都将产生长度为 $0$ 的左子数组和长度为 $m - 1$ 的右子数组,这意味着每一层递归调用减少的问题规模非常小(只减少一个元素),递归树的高度会达到 $n - 1$ ,此时需要占用 $O(n)$ 大小的栈帧空间。 @@ -1301,3 +1316,8 @@ comments: true } } ``` + +??? pythontutor "可视化运行" + + + 全屏观看 > diff --git a/docs/chapter_sorting/radix_sort.md b/docs/chapter_sorting/radix_sort.md index 08adcf485..85e457811 100644 --- a/docs/chapter_sorting/radix_sort.md +++ b/docs/chapter_sorting/radix_sort.md @@ -684,6 +684,11 @@ $$ } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + !!! question "为什么从最低位开始排序?" 在连续的排序轮次中,后一轮排序会覆盖前一轮排序的结果。举例来说,如果第一轮排序结果 $a < b$ ,而第二轮排序结果 $a > b$ ,那么第二轮的结果将取代第一轮的结果。由于数字的高位优先级高于低位,因此应该先排序低位再排序高位。 diff --git a/docs/chapter_sorting/selection_sort.md b/docs/chapter_sorting/selection_sort.md index 8cb51303d..30636cdeb 100644 --- a/docs/chapter_sorting/selection_sort.md +++ b/docs/chapter_sorting/selection_sort.md @@ -284,6 +284,11 @@ comments: true [class]{}-[func]{selectionSort} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 11.2.1   算法特性 - **时间复杂度为 $O(n^2)$、非自适应排序**:外循环共 $n - 1$ 轮,第一轮的未排序区间长度为 $n$ ,最后一轮的未排序区间长度为 $2$ ,即各轮外循环分别包含 $n$、$n - 1$、$\dots$、$3$、$2$ 轮内循环,求和为 $\frac{(n - 1)(n + 2)}{2}$ 。 diff --git a/docs/chapter_tree/array_representation_of_tree.md b/docs/chapter_tree/array_representation_of_tree.md index b57bbc4e2..8fc5053f3 100644 --- a/docs/chapter_tree/array_representation_of_tree.md +++ b/docs/chapter_tree/array_representation_of_tree.md @@ -1161,6 +1161,11 @@ comments: true [class]{ArrayBinaryTree}-[func]{} ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ## 7.3.3   优点与局限性 二叉树的数组表示主要有以下优点。 diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index f27146f2e..ebbdfe05f 100755 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -314,6 +314,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ### 2.   插入节点 给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根节点 < 右子树”的性质,插入操作流程如图 7-18 所示。 @@ -738,6 +743,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + 与查找节点相同,插入节点使用 $O(\log n)$ 时间。 ### 3.   删除节点 @@ -1455,6 +1465,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ### 4.   中序遍历有序 如图 7-22 所示,二叉树的中序遍历遵循“左 $\rightarrow$ 根 $\rightarrow$ 右”的遍历顺序,而二叉搜索树满足“左子节点 $<$ 根节点 $<$ 右子节点”的大小关系。 diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index 9a47a867c..5d14f8268 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -411,6 +411,10 @@ comments: true ``` +??? pythontutor "可视化运行" + + + ### 2.   插入与删除节点 与链表类似,在二叉树中插入与删除节点可以通过修改指针来实现。图 7-3 给出了一个示例。 @@ -554,6 +558,10 @@ comments: true ``` +??? pythontutor "可视化运行" + + + !!! note 需要注意的是,插入节点可能会改变二叉树的原有逻辑结构,而删除节点通常意味着删除该节点及其所有子树。因此,在二叉树中,插入与删除通常是由一套操作配合完成的,以实现有实际意义的操作。 diff --git a/docs/chapter_tree/binary_tree_traversal.md b/docs/chapter_tree/binary_tree_traversal.md index bdfdd7660..99bad2871 100755 --- a/docs/chapter_tree/binary_tree_traversal.md +++ b/docs/chapter_tree/binary_tree_traversal.md @@ -324,6 +324,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + ### 2.   复杂度分析 - **时间复杂度为 $O(n)$** :所有节点被访问一次,使用 $O(n)$ 时间,其中 $n$ 为节点数量。 @@ -754,6 +759,11 @@ comments: true } ``` +??? pythontutor "可视化运行" + + + 全屏观看 > + !!! tip 深度优先搜索也可以基于迭代实现,有兴趣的读者可以自行研究。