diff --git a/codes/c/chapter_stack_and_queue/array_stack.c b/codes/c/chapter_stack_and_queue/array_stack.c index 807c9bca4d..39e1cff4d7 100644 --- a/codes/c/chapter_stack_and_queue/array_stack.c +++ b/codes/c/chapter_stack_and_queue/array_stack.c @@ -90,11 +90,11 @@ int main() { /* 获取栈的长度 */ int size = stack->size; - printf("栈的长度 size = %d\n", size); + printf("栈的长度 size = %d\n", size); /* 判断是否为空 */ bool empty = isEmpty(stack); - printf("栈是否为空 = %stack\n", empty ? "true" : "false"); + printf("栈是否为空 = %s\n", empty ? "true" : "false"); // 释放内存 delArrayStack(stack); diff --git a/codes/kotlin/chapter_sorting/merge_sort.kt b/codes/kotlin/chapter_sorting/merge_sort.kt index 40ca9ca979..d08d3d925c 100644 --- a/codes/kotlin/chapter_sorting/merge_sort.kt +++ b/codes/kotlin/chapter_sorting/merge_sort.kt @@ -19,7 +19,7 @@ fun merge(nums: IntArray, left: Int, mid: Int, right: Int) { while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++] - else + else tmp[k++] = nums[j++] } // 将左子数组和右子数组的剩余元素复制到临时数组中 diff --git a/codes/python/chapter_computational_complexity/time_complexity.py b/codes/python/chapter_computational_complexity/time_complexity.py index ac404c831e..5e25f2798b 100644 --- a/codes/python/chapter_computational_complexity/time_complexity.py +++ b/codes/python/chapter_computational_complexity/time_complexity.py @@ -97,7 +97,9 @@ def linear_log_recur(n: int) -> int: """线性对数阶""" if n <= 1: return 1 - count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2) + # 一分为二,子问题的规模减小一半 + count = linear_log_recur(n // 2) + linear_log_recur(n // 2) + # 当前子问题包含 n 个操作 for _ in range(n): count += 1 return count @@ -120,32 +122,32 @@ def factorial_recur(n: int) -> int: n = 8 print("输入数据大小 n =", n) - count: int = constant(n) + count = constant(n) print("常数阶的操作数量 =", count) - count: int = linear(n) + count = linear(n) print("线性阶的操作数量 =", count) - count: int = array_traversal([0] * n) + count = array_traversal([0] * n) print("线性阶(遍历数组)的操作数量 =", count) - count: int = quadratic(n) + count = quadratic(n) print("平方阶的操作数量 =", count) nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1] - count: int = bubble_sort(nums) + count = bubble_sort(nums) print("平方阶(冒泡排序)的操作数量 =", count) - count: int = exponential(n) + count = exponential(n) print("指数阶(循环实现)的操作数量 =", count) - count: int = exp_recur(n) + count = exp_recur(n) print("指数阶(递归实现)的操作数量 =", count) - count: int = logarithmic(n) + count = logarithmic(n) print("对数阶(循环实现)的操作数量 =", count) - count: int = log_recur(n) + count = log_recur(n) print("对数阶(递归实现)的操作数量 =", count) - count: int = linear_log_recur(n) + count = linear_log_recur(n) print("线性对数阶(递归实现)的操作数量 =", count) - count: int = factorial_recur(n) + count = factorial_recur(n) print("阶乘阶(递归实现)的操作数量 =", count) diff --git a/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb b/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb index 1421d09f52..4d6bf065f5 100644 --- a/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb +++ b/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb @@ -5,7 +5,7 @@ =end ### 带约束爬楼梯:动态规划 ### -def climbing_stairs_backtrack(n) +def climbing_stairs_constraint_dp(n) return 1 if n == 1 || n == 2 # 初始化 dp 表,用于存储子问题的解 @@ -26,6 +26,6 @@ def climbing_stairs_backtrack(n) if __FILE__ == $0 n = 9 - res = climbing_stairs_backtrack(n) + res = climbing_stairs_constraint_dp(n) puts "爬 #{n} 阶楼梯共有 #{res} 种方案" end diff --git a/docs/assets/avatar/avatar_khoaxuantu.jpg b/docs/assets/avatar/avatar_khoaxuantu.jpg new file mode 100644 index 0000000000..f7e1e8f3cc Binary files /dev/null and b/docs/assets/avatar/avatar_khoaxuantu.jpg differ diff --git a/docs/chapter_array_and_linkedlist/summary.md b/docs/chapter_array_and_linkedlist/summary.md index 522d055d05..9a42b3df1a 100644 --- a/docs/chapter_array_and_linkedlist/summary.md +++ b/docs/chapter_array_and_linkedlist/summary.md @@ -73,4 +73,4 @@ **Q**:初始化列表 `res = [0] * self.size()` 操作,会导致 `res` 的每个元素引用相同的地址吗? -不会。但二维数组会有这个问题,例如初始化二维列表 `res = [[0] * self.size()]` ,则多次引用了同一个列表 `[0]` 。 +不会。但二维数组会有这个问题,例如初始化二维列表 `res = [[0]] * self.size()` ,则多次引用了同一个列表 `[0]` 。 diff --git a/docs/chapter_computational_complexity/performance_evaluation.md b/docs/chapter_computational_complexity/performance_evaluation.md index 3c286d0be0..8ede9795ab 100644 --- a/docs/chapter_computational_complexity/performance_evaluation.md +++ b/docs/chapter_computational_complexity/performance_evaluation.md @@ -7,7 +7,7 @@ 也就是说,在能够解决问题的前提下,算法效率已成为衡量算法优劣的主要评价指标,它包括以下两个维度。 -- **时间效率**:算法运行速度的快慢。 +- **时间效率**:算法运行时间的长短。 - **空间效率**:算法占用内存空间的大小。 简而言之,**我们的目标是设计“既快又省”的数据结构与算法**。而有效地评估算法效率至关重要,因为只有这样,我们才能将各种算法进行对比,进而指导算法设计与优化过程。 @@ -18,7 +18,7 @@ 假设我们现在有算法 `A` 和算法 `B` ,它们都能解决同一问题,现在需要对比这两个算法的效率。最直接的方法是找一台计算机,运行这两个算法,并监控记录它们的运行时间和内存占用情况。这种评估方式能够反映真实情况,但也存在较大的局限性。 -一方面,**难以排除测试环境的干扰因素**。硬件配置会影响算法的性能。比如在某台计算机中,算法 `A` 的运行时间比算法 `B` 短;但在另一台配置不同的计算机中,可能得到相反的测试结果。这意味着我们需要在各种机器上进行测试,统计平均效率,而这是不现实的。 +一方面,**难以排除测试环境的干扰因素**。硬件配置会影响算法的性能表现。比如一个算法的并行度较高,那么它就更适合在多核 CPU 上运行,一个算法的内存操作密集,那么它在高性能内存上的表现就会更好。也就是说,算法在不同的机器上的测试结果可能是不一致的。这意味着我们需要在各种机器上进行测试,统计平均效率,而这是不现实的。 另一方面,**展开完整测试非常耗费资源**。随着输入数据量的变化,算法会表现出不同的效率。例如,在输入数据量较小时,算法 `A` 的运行时间比算法 `B` 短;而在输入数据量较大时,测试结果可能恰恰相反。因此,为了得到有说服力的结论,我们需要测试各种规模的输入数据,而这需要耗费大量的计算资源。 @@ -32,8 +32,9 @@ - “随着输入数据大小的增加”意味着复杂度反映了算法运行效率与输入数据体量之间的关系。 - “时间和空间的增长趋势”表示复杂度分析关注的不是运行时间或占用空间的具体值,而是时间或空间增长的“快慢”。 -**复杂度分析克服了实际测试方法的弊端**,体现在以下两个方面。 +**复杂度分析克服了实际测试方法的弊端**,体现在以下几个方面。 +- 它无需实际运行代码,更加绿色节能。 - 它独立于测试环境,分析结果适用于所有运行平台。 - 它可以体现不同数据量下的算法效率,尤其是在大数据量下的算法性能。 diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index c02f914430..3255714946 100755 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -534,7 +534,7 @@ $$ - **时间复杂度能够有效评估算法效率**。例如,算法 `B` 的运行时间呈线性增长,在 $n > 1$ 时比算法 `A` 更慢,在 $n > 1000000$ 时比算法 `C` 更慢。事实上,只要输入数据大小 $n$ 足够大,复杂度为“常数阶”的算法一定优于“线性阶”的算法,这正是时间增长趋势的含义。 - **时间复杂度的推算方法更简便**。显然,运行平台和计算操作类型都与算法运行时间的增长趋势无关。因此在时间复杂度分析中,我们可以简单地将所有计算操作的执行时间视为相同的“单位时间”,从而将“计算操作运行时间统计”简化为“计算操作数量统计”,这样一来估算难度就大大降低了。 -- **时间复杂度也存在一定的局限性**。例如,尽管算法 `A` 和 `C` 的时间复杂度相同,但实际运行时间差别很大。同样,尽管算法 `B` 的时间复杂度比 `C` 高,但在输入数据大小 $n$ 较小时,算法 `B` 明显优于算法 `C` 。在这些情况下,我们很难仅凭时间复杂度判断算法效率的高低。当然,尽管存在上述问题,复杂度分析仍然是评判算法效率最有效且常用的方法。 +- **时间复杂度也存在一定的局限性**。例如,尽管算法 `A` 和 `C` 的时间复杂度相同,但实际运行时间差别很大。同样,尽管算法 `B` 的时间复杂度比 `C` 高,但在输入数据大小 $n$ 较小时,算法 `B` 明显优于算法 `C` 。对于此类情况,我们时常难以仅凭时间复杂度判断算法效率的高低。当然,尽管存在上述问题,复杂度分析仍然是评判算法效率最有效且常用的方法。 ## 函数渐近上界 diff --git a/docs/chapter_data_structure/summary.md b/docs/chapter_data_structure/summary.md index 38425fa467..54af466001 100644 --- a/docs/chapter_data_structure/summary.md +++ b/docs/chapter_data_structure/summary.md @@ -35,7 +35,7 @@ **Q**:原码转补码的方法是“先取反后加 1”,那么补码转原码应该是逆运算“先减 1 后取反”,而补码转原码也一样可以通过“先取反后加 1”得到,这是为什么呢? -**A**:这是因为原码和补码的相互转换实际上是计算“补数”的过程。我们先给出补数的定义:假设 $a + b = c$ ,那么我们称 $a$ 是 $b$ 到 $c$ 的补数,反之也称 $b$ 是 $a$ 到 $c$ 的补数。 +这是因为原码和补码的相互转换实际上是计算“补数”的过程。我们先给出补数的定义:假设 $a + b = c$ ,那么我们称 $a$ 是 $b$ 到 $c$ 的补数,反之也称 $b$ 是 $a$ 到 $c$ 的补数。 给定一个 $n = 4$ 位长度的二进制数 $0010$ ,如果将这个数字看作原码(不考虑符号位),那么它的补码需通过“先取反后加 1”得到: @@ -63,4 +63,4 @@ $$ 本质上看,“取反”操作实际上是求到 $1111$ 的补数(因为恒有 `原码 + 反码 = 1111`);而在反码基础上再加 1 得到的补码,就是到 $10000$ 的补数。 -上述 $n = 4$ 为例,其可推广至任意位数的二进制数。 +上述以 $n = 4$ 为例,其可被推广至任意位数的二进制数。 diff --git a/docs/chapter_introduction/what_is_dsa.md b/docs/chapter_introduction/what_is_dsa.md index 953a79b7fd..9bc5e0d5a4 100644 --- a/docs/chapter_introduction/what_is_dsa.md +++ b/docs/chapter_introduction/what_is_dsa.md @@ -10,7 +10,7 @@ ## 数据结构定义 -数据结构(data structure)是计算机中组织和存储数据的方式,具有以下设计目标。 +数据结构(data structure)是组织和存储数据的方式,涵盖数据内容、数据之间关系和数据操作方法,它具有以下设计目标。 - 空间占用尽量少,以节省计算机内存。 - 数据操作尽可能快速,涵盖数据访问、添加、删除、更新等。 diff --git a/docs/index.html b/docs/index.html index 9726d5b331..965774d9a5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -322,6 +322,13 @@

代码审阅者


JS, TS +
+ + Reviewer: khoaxuantu +
khoaxuantu +
Ruby +
+
Reviewer: krahets diff --git a/en/docs/chapter_array_and_linkedlist/summary.md b/en/docs/chapter_array_and_linkedlist/summary.md index 299d5af0e3..73b021c7ad 100644 --- a/en/docs/chapter_array_and_linkedlist/summary.md +++ b/en/docs/chapter_array_and_linkedlist/summary.md @@ -74,7 +74,7 @@ On the other hand, linked lists are primarily necessary for binary trees and gra **Q**: Does initializing a list `res = [0] * self.size()` result in each element of `res` referencing the same address? -No. However, this issue arises with two-dimensional arrays, for example, initializing a two-dimensional list `res = [[0] * self.size()]` would reference the same list `[0]` multiple times. +No. However, this issue arises with two-dimensional arrays, for example, initializing a two-dimensional list `res = [[0]] * self.size()` would reference the same list `[0]` multiple times. **Q**: In deleting a node, is it necessary to break the reference to its successor node? diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.md b/en/docs/chapter_backtracking/backtracking_algorithm.md index eed9cf6515..a298ed48c5 100644 --- a/en/docs/chapter_backtracking/backtracking_algorithm.md +++ b/en/docs/chapter_backtracking/backtracking_algorithm.md @@ -2,19 +2,19 @@ Backtracking algorithm is a method to solve problems by exhaustive search, where the core idea is to start from an initial state and brute force all possible solutions, recording the correct ones until a solution is found or all possible choices are exhausted without finding a solution. -Backtracking typically employs "depth-first search" to traverse the solution space. In the "Binary Tree" chapter, we mentioned that preorder, inorder, and postorder traversals are all depth-first searches. Next, we use preorder traversal to construct a backtracking problem to gradually understand the workings of the backtracking algorithm. +Backtracking typically employs "depth-first search" to traverse the solution space. In the "Binary Tree" chapter, we mentioned that pre-order, in-order, and post-order traversals are all depth-first searches. Next, we use pre-order traversal to construct a backtracking problem to gradually understand the workings of the backtracking algorithm. !!! question "Example One" Given a binary tree, search and record all nodes with a value of $7$, please return a list of nodes. -For this problem, we traverse this tree in preorder and check if the current node's value is $7$. If it is, we add the node's value to the result list `res`. The relevant process is shown in the figure below: +For this problem, we traverse this tree in pre-order and check if the current node's value is $7$. If it is, we add the node's value to the result list `res`. The relevant process is shown in the figure below: ```src [file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order} ``` -![Searching nodes in preorder traversal](backtracking_algorithm.assets/preorder_find_nodes.png) +![Searching nodes in pre-order traversal](backtracking_algorithm.assets/preorder_find_nodes.png) ## Trying and retreating @@ -425,7 +425,7 @@ As per the requirements, after finding a node with a value of $7$, the search sh ![Comparison of retaining and removing the return in the search process](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) -Compared to the implementation based on preorder traversal, the code implementation based on the backtracking algorithm framework seems verbose, but it has better universality. In fact, **many backtracking problems can be solved within this framework**. We just need to define `state` and `choices` according to the specific problem and implement the methods in the framework. +Compared to the implementation based on pre-order traversal, the code implementation based on the backtracking algorithm framework seems verbose, but it has better universality. In fact, **many backtracking problems can be solved within this framework**. We just need to define `state` and `choices` according to the specific problem and implement the methods in the framework. ## Common terminology diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md index 1d4de018e6..0ca7fc009f 100644 --- a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md +++ b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -2,7 +2,7 @@ !!! question - Given the preorder traversal `preorder` and inorder traversal `inorder` of a binary tree, construct the binary tree and return the root node of the binary tree. Assume that there are no duplicate values in the nodes of the binary tree (as shown in the figure below). + Given the pre-order traversal `preorder` and in-order traversal `inorder` of a binary tree, construct the binary tree and return the root node of the binary tree. Assume that there are no duplicate values in the nodes of the binary tree (as shown in the figure below). ![Example data for building a binary tree](build_binary_tree_problem.assets/build_tree_example.png) @@ -11,25 +11,25 @@ The original problem of constructing a binary tree from `preorder` and `inorder` is a typical divide and conquer problem. - **The problem can be decomposed**: From the perspective of divide and conquer, we can divide the original problem into two subproblems: building the left subtree and building the right subtree, plus one operation: initializing the root node. For each subtree (subproblem), we can still use the above division method, dividing it into smaller subtrees (subproblems), until the smallest subproblem (empty subtree) is reached. -- **The subproblems are independent**: The left and right subtrees are independent of each other, with no overlap. When building the left subtree, we only need to focus on the parts of the inorder and preorder traversals that correspond to the left subtree. The same applies to the right subtree. +- **The subproblems are independent**: The left and right subtrees are independent of each other, with no overlap. When building the left subtree, we only need to focus on the parts of the in-order and pre-order traversals that correspond to the left subtree. The same applies to the right subtree. - **Solutions to subproblems can be combined**: Once the solutions for the left and right subtrees (solutions to subproblems) are obtained, we can link them to the root node to obtain the solution to the original problem. ### How to divide the subtrees -Based on the above analysis, this problem can be solved using divide and conquer, **but how do we use the preorder traversal `preorder` and inorder traversal `inorder` to divide the left and right subtrees?** +Based on the above analysis, this problem can be solved using divide and conquer, **but how do we use the pre-order traversal `preorder` and in-order traversal `inorder` to divide the left and right subtrees?** By definition, `preorder` and `inorder` can be divided into three parts. -- Preorder traversal: `[ Root | Left Subtree | Right Subtree ]`, for example, the tree in the figure corresponds to `[ 3 | 9 | 2 1 7 ]`. -- Inorder traversal: `[ Left Subtree | Root | Right Subtree ]`, for example, the tree in the figure corresponds to `[ 9 | 3 | 1 2 7 ]`. +- Pre-order traversal: `[ Root | Left Subtree | Right Subtree ]`, for example, the tree in the figure corresponds to `[ 3 | 9 | 2 1 7 ]`. +- In-order traversal: `[ Left Subtree | Root | Right Subtree ]`, for example, the tree in the figure corresponds to `[ 9 | 3 | 1 2 7 ]`. Using the data in the figure above, we can obtain the division results as shown in the figure below. -1. The first element 3 in the preorder traversal is the value of the root node. +1. The first element 3 in the pre-order traversal is the value of the root node. 2. Find the index of the root node 3 in `inorder`, and use this index to divide `inorder` into `[ 9 | 3 | 1 2 7 ]`. 3. Based on the division results of `inorder`, it is easy to determine the number of nodes in the left and right subtrees as 1 and 3, respectively, thus dividing `preorder` into `[ 3 | 9 | 2 1 7 ]`. -![Dividing the subtrees in preorder and inorder traversals](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png) +![Dividing the subtrees in pre-order and in-order traversals](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png) ### Describing subtree intervals based on variables @@ -41,7 +41,7 @@ Based on the above division method, **we have now obtained the index intervals o As shown in the table below, 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   Indexes of the root node and subtrees in preorder and inorder traversals

+

Table   Indexes of the root node and subtrees in pre-order and in-order traversals

| | Root node index in `preorder` | Subtree index interval in `inorder` | | ------------- | ----------------------------- | ----------------------------------- | diff --git a/en/docs/chapter_divide_and_conquer/summary.md b/en/docs/chapter_divide_and_conquer/summary.md index 923f6f497e..fa6739d53a 100644 --- a/en/docs/chapter_divide_and_conquer/summary.md +++ b/en/docs/chapter_divide_and_conquer/summary.md @@ -7,5 +7,5 @@ - Divide and conquer can solve many algorithm problems and is widely used in data structure and algorithm design, where its presence is ubiquitous. - Compared to brute force search, adaptive search is more efficient. Search algorithms with a time complexity of $O(\log n)$ are usually based on the divide and conquer strategy. - Binary search is another typical application of the divide and conquer strategy, which does not include the step of merging the solutions of subproblems. We can implement binary search through recursive divide and conquer. -- In the problem of constructing binary trees, building the tree (original problem) can be divided into building the left and right subtree (subproblems), which can be achieved by partitioning the index intervals of the preorder and inorder traversals. +- In the problem of constructing binary trees, building the tree (original problem) can be divided into building the left and right subtree (subproblems), which can be achieved by partitioning the index intervals of the pre-order and in-order traversals. - In the Tower of Hanoi problem, a problem of size $n$ can be divided into two subproblems of size $n-1$ and one subproblem of size $1$. By solving these three subproblems in sequence, the original problem is consequently resolved. diff --git a/en/docs/chapter_graph/graph_traversal.md b/en/docs/chapter_graph/graph_traversal.md index ebce7f6954..4aad3fa4f0 100644 --- a/en/docs/chapter_graph/graph_traversal.md +++ b/en/docs/chapter_graph/graph_traversal.md @@ -127,7 +127,7 @@ To deepen the understanding, it is suggested to combine the figure below with th Similar to breadth-first traversal, the order of the depth-first traversal sequence is also not unique. Given a certain vertex, exploring in any direction first is possible, that is, the order of adjacent vertices can be arbitrarily shuffled, all being part of depth-first traversal. - Taking tree traversal as an example, "root $\rightarrow$ left $\rightarrow$ right", "left $\rightarrow$ root $\rightarrow$ right", "left $\rightarrow$ right $\rightarrow$ root" correspond to preorder, inorder, and postorder traversals, respectively. They showcase three types of traversal priorities, yet all three are considered depth-first traversal. + Taking tree traversal as an example, "root $\rightarrow$ left $\rightarrow$ right", "left $\rightarrow$ root $\rightarrow$ right", "left $\rightarrow$ right $\rightarrow$ root" correspond to pre-order, in-order, and post-order traversals, respectively. They showcase three types of traversal priorities, yet all three are considered depth-first traversal. ### Complexity analysis diff --git a/en/docs/chapter_tree/array_representation_of_tree.md b/en/docs/chapter_tree/array_representation_of_tree.md index e3e7257d32..a2e814a587 100644 --- a/en/docs/chapter_tree/array_representation_of_tree.md +++ b/en/docs/chapter_tree/array_representation_of_tree.md @@ -143,7 +143,7 @@ This means that when using an array to represent a complete binary tree, it's po The following code implements a binary tree based on array representation, including the following operations: - Given a node, obtain its value, left (right) child node, and parent node. -- Obtain the preorder, inorder, postorder, and level-order traversal sequences. +- Obtain the pre-order, in-order, post-order, and level-order traversal sequences. ```src [file]{array_binary_tree}-[class]{array_binary_tree}-[func]{} diff --git a/en/docs/chapter_tree/binary_tree_traversal.md b/en/docs/chapter_tree/binary_tree_traversal.md index 37bc031ddc..ab29d041c5 100755 --- a/en/docs/chapter_tree/binary_tree_traversal.md +++ b/en/docs/chapter_tree/binary_tree_traversal.md @@ -2,7 +2,7 @@ From the perspective of physical structure, a tree is a data structure based on linked lists, hence its traversal method involves accessing nodes one by one through pointers. However, a tree is a non-linear data structure, which makes traversing a tree more complex than traversing a linked list, requiring the assistance of search algorithms to achieve. -Common traversal methods for binary trees include level-order traversal, preorder traversal, inorder traversal, and postorder traversal, among others. +Common traversal methods for binary trees include level-order traversal, pre-order traversal, in-order traversal, and post-order traversal, among others. ## Level-order traversal @@ -25,13 +25,13 @@ Breadth-first traversal is usually implemented with the help of a "queue". The q - **Time complexity is $O(n)$**: All nodes are visited once, using $O(n)$ time, where $n$ is the number of nodes. - **Space complexity is $O(n)$**: In the worst case, i.e., a full binary tree, before traversing to the lowest level, the queue can contain at most $(n + 1) / 2$ nodes at the same time, occupying $O(n)$ space. -## Preorder, inorder, and postorder traversal +## Preorder, in-order, and post-order traversal -Correspondingly, preorder, inorder, and postorder traversal all belong to depth-first traversal, also known as depth-first search (DFS), which embodies a "proceed to the end first, then backtrack and continue" traversal method. +Correspondingly, pre-order, in-order, and post-order traversal all belong to depth-first traversal, also known as depth-first search (DFS), which embodies a "proceed to the end first, then backtrack and continue" traversal method. -The figure below shows the working principle of performing a depth-first traversal on a binary tree. **Depth-first traversal is like walking around the perimeter of the entire binary tree**, encountering three positions at each node, corresponding to preorder traversal, inorder traversal, and postorder traversal. +The figure below shows the working principle of performing a depth-first traversal on a binary tree. **Depth-first traversal is like walking around the perimeter of the entire binary tree**, encountering three positions at each node, corresponding to pre-order traversal, in-order traversal, and post-order traversal. -![Preorder, inorder, and postorder traversal of a binary search tree](binary_tree_traversal.assets/binary_tree_dfs.png) +![Preorder, in-order, and post-order traversal of a binary search tree](binary_tree_traversal.assets/binary_tree_dfs.png) ### Code implementation @@ -45,13 +45,13 @@ Depth-first search is usually implemented based on recursion: Depth-first search can also be implemented based on iteration, interested readers can study this on their own. -The figure below shows the recursive process of preorder traversal of a binary tree, which can be divided into two opposite parts: "recursion" and "return". +The figure below shows the recursive process of pre-order traversal of a binary tree, which can be divided into two opposite parts: "recursion" and "return". 1. "Recursion" means starting a new method, the program accesses the next node in this process. 2. "Return" means the function returns, indicating the current node has been fully accessed. === "<1>" - ![The recursive process of preorder traversal](binary_tree_traversal.assets/preorder_step1.png) + ![The recursive process of pre-order traversal](binary_tree_traversal.assets/preorder_step1.png) === "<2>" ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) diff --git a/en/docs/index.html b/en/docs/index.html index 9e72ce177f..85ac7bdb0c 100644 --- a/en/docs/index.html +++ b/en/docs/index.html @@ -322,6 +322,13 @@

Code reviewers


JS, TS
+
+ + Reviewer: khoaxuantu +
khoaxuantu +
Ruby +
+
Reviewer: krahets diff --git a/overrides/stylesheets/extra.css b/overrides/stylesheets/extra.css index d85e159165..7badd49bc4 100644 --- a/overrides/stylesheets/extra.css +++ b/overrides/stylesheets/extra.css @@ -461,7 +461,7 @@ a:hover .text-button span { .profile-cell { flex: 1; /* even distribution */ - flex-basis: 15%; + flex-basis: 20%; margin: 1em 0.5em; text-align: center; } diff --git a/zh-hant/codes/c/chapter_hashing/simple_hash.c b/zh-hant/codes/c/chapter_hashing/simple_hash.c index 05d21e7b38..4d2fdcf33c 100644 --- a/zh-hant/codes/c/chapter_hashing/simple_hash.c +++ b/zh-hant/codes/c/chapter_hashing/simple_hash.c @@ -50,7 +50,7 @@ int rotHash(char *key) { /* Driver Code */ int main() { - char *key = "Hello dsad3241241dsa算123法"; + char *key = "Hello 演算法"; int hash = addHash(key); printf("加法雜湊值為 %d\n", hash); diff --git a/zh-hant/codes/c/chapter_stack_and_queue/array_stack.c b/zh-hant/codes/c/chapter_stack_and_queue/array_stack.c index d70cab7f89..4593c6ba1c 100644 --- a/zh-hant/codes/c/chapter_stack_and_queue/array_stack.c +++ b/zh-hant/codes/c/chapter_stack_and_queue/array_stack.c @@ -90,11 +90,11 @@ int main() { /* 獲取堆疊的長度 */ int size = stack->size; - printf("堆疊的長度 size = %d\n", size); + printf("堆疊的長度 size = %d\n", size); /* 判斷是否為空 */ bool empty = isEmpty(stack); - printf("堆疊是否為空 = %stack\n", empty ? "true" : "false"); + printf("堆疊是否為空 = %s\n", empty ? "true" : "false"); // 釋放記憶體 delArrayStack(stack); diff --git a/zh-hant/codes/cpp/chapter_hashing/simple_hash.cpp b/zh-hant/codes/cpp/chapter_hashing/simple_hash.cpp index 2b43e166ae..b8a3173a68 100644 --- a/zh-hant/codes/cpp/chapter_hashing/simple_hash.cpp +++ b/zh-hant/codes/cpp/chapter_hashing/simple_hash.cpp @@ -48,7 +48,7 @@ int rotHash(string key) { /* Driver Code */ int main() { - string key = "Hello dsad3241241dsa算123法"; + string key = "Hello 演算法"; int hash = addHash(key); cout << "加法雜湊值為 " << hash << endl; diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change.js b/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change.js index 27660a9a0a..0d6aca277d 100644 --- a/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change.js +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change.js @@ -31,7 +31,7 @@ function coinChangeDP(coins, amt) { return dp[n][amt] !== MAX ? dp[n][amt] : -1; } -/* 零錢兌換:狀態壓縮後的動態規劃 */ +/* 零錢兌換:空間最佳化後的動態規劃 */ function coinChangeDPComp(coins, amt) { const n = coins.length; const MAX = amt + 1; @@ -61,6 +61,6 @@ const amt = 4; let res = coinChangeDP(coins, amt); console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); -// 狀態壓縮後的動態規劃 +// 空間最佳化後的動態規劃 res = coinChangeDPComp(coins, amt); console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change_ii.js b/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change_ii.js index c4346524d2..39edcb6dff 100644 --- a/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change_ii.js +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change_ii.js @@ -30,7 +30,7 @@ function coinChangeIIDP(coins, amt) { return dp[n][amt]; } -/* 零錢兌換 II:狀態壓縮後的動態規劃 */ +/* 零錢兌換 II:空間最佳化後的動態規劃 */ function coinChangeIIDPComp(coins, amt) { const n = coins.length; // 初始化 dp 表 @@ -59,6 +59,6 @@ const amt = 5; let res = coinChangeIIDP(coins, amt); console.log(`湊出目標金額的硬幣組合數量為 ${res}`); -// 狀態壓縮後的動態規劃 +// 空間最佳化後的動態規劃 res = coinChangeIIDPComp(coins, amt); console.log(`湊出目標金額的硬幣組合數量為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/edit_distance.js b/zh-hant/codes/javascript/chapter_dynamic_programming/edit_distance.js index 0a121c5d79..fccb443a3e 100644 --- a/zh-hant/codes/javascript/chapter_dynamic_programming/edit_distance.js +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/edit_distance.js @@ -82,7 +82,7 @@ function editDistanceDP(s, t) { return dp[n][m]; } -/* 編輯距離:狀態壓縮後的動態規劃 */ +/* 編輯距離:空間最佳化後的動態規劃 */ function editDistanceDPComp(s, t) { const n = s.length, m = t.length; @@ -130,6 +130,6 @@ console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); res = editDistanceDP(s, t); console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); -// 狀態壓縮後的動態規劃 +// 空間最佳化後的動態規劃 res = editDistanceDPComp(s, t); console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/knapsack.js b/zh-hant/codes/javascript/chapter_dynamic_programming/knapsack.js index 54f112200b..9bcd4727aa 100644 --- a/zh-hant/codes/javascript/chapter_dynamic_programming/knapsack.js +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/knapsack.js @@ -69,7 +69,7 @@ function knapsackDP(wgt, val, cap) { return dp[n][cap]; } -/* 0-1 背包:狀態壓縮後的動態規劃 */ +/* 0-1 背包:空間最佳化後的動態規劃 */ function knapsackDPComp(wgt, val, cap) { const n = wgt.length; // 初始化 dp 表 @@ -108,6 +108,6 @@ console.log(`不超過背包容量的最大物品價值為 ${res}`); res = knapsackDP(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); -// 狀態壓縮後的動態規劃 +// 空間最佳化後的動態規劃 res = knapsackDPComp(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js b/zh-hant/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js index 7f63d01d6a..7224d5a3c3 100644 --- a/zh-hant/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js @@ -22,7 +22,7 @@ function minCostClimbingStairsDP(cost) { return dp[n]; } -/* 爬樓梯最小代價:狀態壓縮後的動態規劃 */ +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ function minCostClimbingStairsDPComp(cost) { const n = cost.length - 1; if (n === 1 || n === 2) { diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/min_path_sum.js b/zh-hant/codes/javascript/chapter_dynamic_programming/min_path_sum.js index a1b2f9855e..bd49d9f607 100644 --- a/zh-hant/codes/javascript/chapter_dynamic_programming/min_path_sum.js +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/min_path_sum.js @@ -69,7 +69,7 @@ function minPathSumDP(grid) { return dp[n - 1][m - 1]; } -/* 最小路徑和:狀態壓縮後的動態規劃 */ +/* 最小路徑和:空間最佳化後的動態規劃 */ function minPathSumDPComp(grid) { const n = grid.length, m = grid[0].length; @@ -116,6 +116,6 @@ console.log(`從左上角到右下角的最小路徑和為 ${res}`); res = minPathSumDP(grid); console.log(`從左上角到右下角的最小路徑和為 ${res}`); -// 狀態壓縮後的動態規劃 +// 空間最佳化後的動態規劃 res = minPathSumDPComp(grid); console.log(`從左上角到右下角的最小路徑和為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js b/zh-hant/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js index f7f504c546..f5c48c8c17 100644 --- a/zh-hant/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js @@ -29,7 +29,7 @@ function unboundedKnapsackDP(wgt, val, cap) { return dp[n][cap]; } -/* 完全背包:狀態壓縮後的動態規劃 */ +/* 完全背包:空間最佳化後的動態規劃 */ function unboundedKnapsackDPComp(wgt, val, cap) { const n = wgt.length; // 初始化 dp 表 @@ -58,6 +58,6 @@ const cap = 4; let res = unboundedKnapsackDP(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); -// 狀態壓縮後的動態規劃 +// 空間最佳化後的動態規劃 res = unboundedKnapsackDPComp(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); diff --git a/zh-hant/codes/python/chapter_computational_complexity/time_complexity.py b/zh-hant/codes/python/chapter_computational_complexity/time_complexity.py index aa49bd059f..dc17e7f0a4 100644 --- a/zh-hant/codes/python/chapter_computational_complexity/time_complexity.py +++ b/zh-hant/codes/python/chapter_computational_complexity/time_complexity.py @@ -97,7 +97,9 @@ def linear_log_recur(n: int) -> int: """線性對數階""" if n <= 1: return 1 - count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2) + # 一分為二,子問題的規模減小一半 + count = linear_log_recur(n // 2) + linear_log_recur(n // 2) + # 當前子問題包含 n 個操作 for _ in range(n): count += 1 return count @@ -120,32 +122,32 @@ def factorial_recur(n: int) -> int: n = 8 print("輸入資料大小 n =", n) - count: int = constant(n) + count = constant(n) print("常數階的操作數量 =", count) - count: int = linear(n) + count = linear(n) print("線性階的操作數量 =", count) - count: int = array_traversal([0] * n) + count = array_traversal([0] * n) print("線性階(走訪陣列)的操作數量 =", count) - count: int = quadratic(n) + count = quadratic(n) print("平方階的操作數量 =", count) nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1] - count: int = bubble_sort(nums) + count = bubble_sort(nums) print("平方階(泡沫排序)的操作數量 =", count) - count: int = exponential(n) + count = exponential(n) print("指數階(迴圈實現)的操作數量 =", count) - count: int = exp_recur(n) + count = exp_recur(n) print("指數階(遞迴實現)的操作數量 =", count) - count: int = logarithmic(n) + count = logarithmic(n) print("對數階(迴圈實現)的操作數量 =", count) - count: int = log_recur(n) + count = log_recur(n) print("對數階(遞迴實現)的操作數量 =", count) - count: int = linear_log_recur(n) + count = linear_log_recur(n) print("線性對數階(遞迴實現)的操作數量 =", count) - count: int = factorial_recur(n) + count = factorial_recur(n) print("階乘階(遞迴實現)的操作數量 =", count) diff --git a/zh-hant/codes/python/chapter_sorting/merge_sort.py b/zh-hant/codes/python/chapter_sorting/merge_sort.py index 48d4f706dc..b0d93ba738 100644 --- a/zh-hant/codes/python/chapter_sorting/merge_sort.py +++ b/zh-hant/codes/python/chapter_sorting/merge_sort.py @@ -41,7 +41,7 @@ def merge_sort(nums: list[int], left: int, right: int): if left >= right: return # 當子陣列長度為 1 時終止遞迴 # 劃分階段 - mid = left + (right - left) // 2 # 計算中點 + mid = (left + right) // 2 # 計算中點 merge_sort(nums, left, mid) # 遞迴左子陣列 merge_sort(nums, mid + 1, right) # 遞迴右子陣列 # 合併階段 diff --git a/zh-hant/codes/ruby/chapter_backtracking/n_queens.rb b/zh-hant/codes/ruby/chapter_backtracking/n_queens.rb new file mode 100644 index 0000000000..c4111e2ae7 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/n_queens.rb @@ -0,0 +1,61 @@ +=begin +File: n_queens.rb +Created Time: 2024-05-21 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯演算法:n 皇后 ### +def backtrack(row, n, state, res, cols, diags1, diags2) + # 當放置完所有行時,記錄解 + if row == n + res << state.map { |row| row.dup } + return + end + + # 走訪所有列 + for col in 0...n + # 計算該格子對應的主對角線和次對角線 + diag1 = row - col + n - 1 + diag2 = row + col + # 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if !cols[col] && !diags1[diag1] && !diags2[diag2] + # 嘗試:將皇后放置在該格子 + state[row][col] = "Q" + cols[col] = diags1[diag1] = diags2[diag2] = true + # 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2) + # 回退:將該格子恢復為空位 + state[row][col] = "#" + cols[col] = diags1[diag1] = diags2[diag2] = false + end + end +end + +### 求解 n 皇后 ### +def n_queens(n) + # 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + state = Array.new(n) { Array.new(n, "#") } + cols = Array.new(n, false) # 記錄列是否有皇后 + diags1 = Array.new(2 * n - 1, false) # 記錄主對角線上是否有皇后 + diags2 = Array.new(2 * n - 1, false) # 記錄次對角線上是否有皇后 + res = [] + backtrack(0, n, state, res, cols, diags1, diags2) + + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 4 + res = n_queens(n) + + puts "輸入棋盤長寬為 #{n}" + puts "皇后放置方案共有 #{res.length} 種" + + for state in res + puts "--------------------" + for row in state + p row + end + end +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/permutations_i.rb b/zh-hant/codes/ruby/chapter_backtracking/permutations_i.rb new file mode 100644 index 0000000000..dd5a7ec13e --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/permutations_i.rb @@ -0,0 +1,46 @@ +=begin +File: permutations_i.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯演算法:全排列 I ### +def backtrack(state, choices, selected, res) + # 當狀態長度等於元素數量時,記錄解 + if state.length == choices.length + res << state.dup + return + end + + # 走訪所有選擇 + choices.each_with_index do |choice, i| + # 剪枝:不允許重複選擇元素 + unless selected[i] + # 嘗試:做出選擇,更新狀態 + selected[i] = true + state << choice + # 進行下一輪選擇 + backtrack(state, choices, selected, res) + # 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false + state.pop + end + end +end + +### 全排列 I ### +def permutations_i(nums) + res = [] + backtrack([], nums, Array.new(nums.length, false), res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 2, 3] + + res = permutations_i(nums) + + puts "輸入陣列 nums = #{nums}" + puts "所有排列 res = #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/permutations_ii.rb b/zh-hant/codes/ruby/chapter_backtracking/permutations_ii.rb new file mode 100644 index 0000000000..682fc71629 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/permutations_ii.rb @@ -0,0 +1,48 @@ +=begin +File: permutations_ii.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯演算法:全排列 II ### +def backtrack(state, choices, selected, res) + # 當狀態長度等於元素數量時,記錄解 + if state.length == choices.length + res << state.dup + return + end + + # 走訪所有選擇 + duplicated = Set.new + choices.each_with_index do |choice, i| + # 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if !selected[i] && !duplicated.include?(choice) + # 嘗試:做出選擇,更新狀態 + duplicated.add(choice) + selected[i] = true + state << choice + # 進行下一輪選擇 + backtrack(state, choices, selected, res) + # 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false + state.pop + end + end +end + +### 全排列 II ### +def permutations_ii(nums) + res = [] + backtrack([], nums, Array.new(nums.length, false), res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 2, 2] + + res = permutations_ii(nums) + + puts "輸入陣列 nums = #{nums}" + puts "所有排列 res = #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb new file mode 100644 index 0000000000..b0de96d915 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb @@ -0,0 +1,33 @@ +=begin +File: preorder_traversal_i_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序走訪:例題一 ### +def pre_order(root) + return unless root + + # 記錄解 + $res << root if root.val == 7 + + pre_order(root.left) + pre_order(root.right) +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二元樹" + print_tree(root) + + # 前序走訪 + $res = [] + pre_order(root) + + puts "\n輸出所有值為 7 的節點" + p $res.map { |node| node.val } +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb new file mode 100644 index 0000000000..de25757cdd --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb @@ -0,0 +1,41 @@ +=begin +File: preorder_traversal_ii_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序走訪:例題二 ### +def pre_order(root) + return unless root + + # 嘗試 + $path << root + + # 記錄解 + $res << $path.dup if root.val == 7 + + pre_order(root.left) + pre_order(root.right) + + # 回退 + $path.pop +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二元樹" + print_tree(root) + + # 前序走訪 + $path, $res = [], [] + pre_order(root) + + puts "\n輸出所有根節點到節點 7 的路徑" + for path in $res + p path.map { |node| node.val } + end +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb new file mode 100644 index 0000000000..44d12956c8 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb @@ -0,0 +1,42 @@ +=begin +File: preorder_traversal_iii_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序走訪:例題三 ### +def pre_order(root) + # 剪枝 + return if !root || root.val == 3 + + # 嘗試 + $path.append(root) + + # 記錄解 + $res << $path.dup if root.val == 7 + + pre_order(root.left) + pre_order(root.right) + + # 回退 + $path.pop +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二元樹" + print_tree(root) + + # 前序走訪 + $path, $res = [], [] + pre_order(root) + + puts "\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點" + for path in $res + p path.map { |node| node.val } + end +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb new file mode 100644 index 0000000000..cf770b5122 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb @@ -0,0 +1,68 @@ +=begin +File: preorder_traversal_iii_template.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 判斷當前狀態是否為解 ### +def is_solution?(state) + !state.empty? && state.last.val == 7 +end + +### 記錄解 ### +def record_solution(state, res) + res << state.dup +end + +### 判斷在當前狀態下,該選擇是否合法 ### +def is_valid?(state, choice) + choice && choice.val != 3 +end + +### 更新狀態 ### +def make_choice(state, choice) + state << choice +end + +### 恢復狀態 ### +def undo_choice(state, choice) + state.pop +end + +### 回溯演算法:例題三 ### +def backtrack(state, choices, res) + # 檢查是否為解 + record_solution(state, res) if is_solution?(state) + + # 走訪所有選擇 + for choice in choices + # 剪枝:檢查選擇是否合法 + if is_valid?(state, choice) + # 嘗試:做出選擇,更新狀態 + make_choice(state, choice) + # 進行下一輪選擇 + backtrack(state, [choice.left, choice.right], res) + # 回退:撤銷選擇,恢復到之前的狀態 + undo_choice(state, choice) + end + end +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二元樹" + print_tree(root) + + # 回溯演算法 + res = [] + backtrack([], [root], res) + + puts "\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點" + for path in res + p path.map { |node| node.val } + end +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/subset_sum_i.rb b/zh-hant/codes/ruby/chapter_backtracking/subset_sum_i.rb new file mode 100644 index 0000000000..3390e98b8a --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/subset_sum_i.rb @@ -0,0 +1,47 @@ +=begin +File: subset_sum_i.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯演算法:子集和 I ### +def backtrack(state, target, choices, start, res) + # 子集和等於 target 時,記錄解 + if target.zero? + res << state.dup + return + end + # 走訪所有選擇 + # 剪枝二:從 start 開始走訪,避免生成重複子集 + for i in start...choices.length + # 剪枝一:若子集和超過 target ,則直接結束迴圈 + # 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + break if target - choices[i] < 0 + # 嘗試:做出選擇,更新 target, start + state << choices[i] + # 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res) + # 回退:撤銷選擇,恢復到之前的狀態 + state.pop + end +end + +### 求解子集和 I ### +def subset_sum_i(nums, target) + state = [] # 狀態(子集) + nums.sort! # 對 nums 進行排序 + start = 0 # 走訪起始點 + res = [] # 結果串列(子集串列) + backtrack(state, target, nums, start, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [3, 4, 5] + target = 9 + res = subset_sum_i(nums, target) + + puts "輸入陣列 = #{nums}, target = #{target}" + puts "所有和等於 #{target} 的子集 res = #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb b/zh-hant/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb new file mode 100644 index 0000000000..d5b3ac06b1 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb @@ -0,0 +1,46 @@ +=begin +File: subset_sum_i_naive.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯演算法:子集和 I ### +def backtrack(state, target, total, choices, res) + # 子集和等於 target 時,記錄解 + if total == target + res << state.dup + return + end + + # 走訪所有選擇 + for i in 0...choices.length + # 剪枝:若子集和超過 target ,則跳過該選擇 + next if total + choices[i] > target + # 嘗試:做出選擇,更新元素和 total + state << choices[i] + # 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res) + # 回退:撤銷選擇,恢復到之前的狀態 + state.pop + end +end + +### 求解子集和 I(包含重複子集)### +def subset_sum_i_naive(nums, target) + state = [] # 狀態(子集) + total = 0 # 子集和 + res = [] # 結果串列(子集串列) + backtrack(state, target, total, nums, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [3, 4, 5] + target = 9 + res = subset_sum_i_naive(nums, target) + + puts "輸入陣列 nums = #{nums}, target = #{target}" + puts "所有和等於 #{target} 的子集 res = #{res}" + puts "請注意,該方法輸出的結果包含重複集合" +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/subset_sum_ii.rb b/zh-hant/codes/ruby/chapter_backtracking/subset_sum_ii.rb new file mode 100644 index 0000000000..343437f6bd --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/subset_sum_ii.rb @@ -0,0 +1,51 @@ +=begin +File: subset_sum_ii.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯演算法:子集和 II ### +def backtrack(state, target, choices, start, res) + # 子集和等於 target 時,記錄解 + if target.zero? + res << state.dup + return + end + + # 走訪所有選擇 + # 剪枝二:從 start 開始走訪,避免生成重複子集 + # 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for i in start...choices.length + # 剪枝一:若子集和超過 target ,則直接結束迴圈 + # 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + break if target - choices[i] < 0 + # 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + next if i > start && choices[i] == choices[i - 1] + # 嘗試:做出選擇,更新 target, start + state << choices[i] + # 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res) + # 回退:撤銷選擇,恢復到之前的狀態 + state.pop + end +end + +### 求解子集和 II ### +def subset_sum_ii(nums, target) + state = [] # 狀態(子集) + nums.sort! # 對 nums 進行排序 + start = 0 # 走訪起始點 + res = [] # 結果串列(子集串列) + backtrack(state, target, nums, start, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 4, 5] + target = 9 + res = subset_sum_ii(nums, target) + + puts "輸入陣列 nums = #{nums}, target = #{target}" + puts "所有和等於 #{target} 的子集 res = #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb b/zh-hant/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb new file mode 100644 index 0000000000..5a624bac45 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb @@ -0,0 +1,42 @@ +=begin +File: binary_search_recur.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 二分搜尋:問題 f(i, j) ### +def dfs(nums, target, i, j) + # 若區間為空,代表無目標元素,則返回 -1 + return -1 if i > j + + # 計算中點索引 m + m = (i + j) / 2 + + if nums[m] < target + # 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j) + elsif nums[m] > target + # 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1) + else + # 找到目標元素,返回其索引 + return m + end +end + +### 二分搜尋 ### +def binary_search(nums, target) + n = nums.length + # 求解問題 f(0, n-1) + dfs(nums, target, 0, n - 1) +end + +### Driver Code ### +if __FILE__ == $0 + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分搜尋(雙閉區間) + index = binary_search(nums, target) + puts "目標元素 6 的索引 = #{index}" +end diff --git a/zh-hant/codes/ruby/chapter_divide_and_conquer/build_tree.rb b/zh-hant/codes/ruby/chapter_divide_and_conquer/build_tree.rb new file mode 100644 index 0000000000..2ffdbaf07e --- /dev/null +++ b/zh-hant/codes/ruby/chapter_divide_and_conquer/build_tree.rb @@ -0,0 +1,46 @@ +=begin +File: build_tree.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 構建二元樹:分治 ### +def dfs(preorder, inorder_map, i, l, r) + # 子樹區間為空時終止 + return if r - l < 0 + + # 初始化根節點 + root = TreeNode.new(preorder[i]) + # 查詢 m ,從而劃分左右子樹 + m = inorder_map[preorder[i]] + # 子問題:構建左子樹 + root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) + # 子問題:構建右子樹 + root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) + + # 返回根節點 + root +end + +### 構建二元樹 ### +def build_tree(preorder, inorder) + # 初始化雜湊表,儲存 inorder 元素到索引的對映 + inorder_map = {} + inorder.each_with_index { |val, i| inorder_map[val] = i } + dfs(preorder, inorder_map, 0, 0, inorder.length - 1) +end + +### Driver Code ### +if __FILE__ == $0 + preorder = [3, 9, 2, 1, 7] + inorder = [9, 3, 1, 2, 7] + puts "前序走訪 = #{preorder}" + puts "中序走訪 = #{inorder}" + + root = build_tree(preorder, inorder) + puts "構建的二元樹為:" + print_tree(root) +end diff --git a/zh-hant/codes/ruby/chapter_divide_and_conquer/hanota.rb b/zh-hant/codes/ruby/chapter_divide_and_conquer/hanota.rb new file mode 100644 index 0000000000..e68a64bf49 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_divide_and_conquer/hanota.rb @@ -0,0 +1,55 @@ +=begin +File: hanota.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 移動一個圓盤 ### +def move(src, tar) + # 從 src 頂部拿出一個圓盤 + pan = src.pop + # 將圓盤放入 tar 頂部 + tar << pan +end + +### 求解河內塔問題 f(i) ### +def dfs(i, src, buf, tar) + # 若 src 只剩下一個圓盤,則直接將其移到 tar + if i == 1 + move(src, tar) + return + end + + # 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, tar, buf) + # 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, tar) + # 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, src, tar) +end + +### 求解河內塔問題 ### +def solve_hanota(_A, _B, _C) + n = _A.length + # 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, _A, _B, _C) +end + +### Driver Code ### +if __FILE__ == $0 + # 串列尾部是柱子頂部 + A = [5, 4, 3, 2, 1] + B = [] + C = [] + puts "初始狀態下:" + puts "A = #{A}" + puts "B = #{B}" + puts "C = #{C}" + + solve_hanota(A, B, C) + + puts "圓盤移動完成後:" + puts "A = #{A}" + puts "B = #{B}" + puts "C = #{C}" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb new file mode 100644 index 0000000000..bebadd5f62 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb @@ -0,0 +1,37 @@ +=begin +File: climbing_stairs_backtrack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯 ### +def backtrack(choices, state, n, res) + # 當爬到第 n 階時,方案數量加 1 + res[0] += 1 if state == n + # 走訪所有選擇 + for choice in choices + # 剪枝:不允許越過第 n 階 + next if state + choice > n + + # 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res) + end + # 回退 +end + +### 爬樓梯:回溯 ### +def climbing_stairs_backtrack(n) + choices = [1, 2] # 可選擇向上爬 1 階或 2 階 + state = 0 # 從第 0 階開始爬 + res = [0] # 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res) + res.first +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_backtrack(n) + puts "爬 #{n} 階樓梯共有 #{res} 種方案" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb new file mode 100644 index 0000000000..35bab162ec --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb @@ -0,0 +1,31 @@ +=begin +File: climbing_stairs_constraint_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 帶約束爬樓梯:動態規劃 ### +def climbing_stairs_constraint_dp(n) + return 1 if n == 1 || n == 2 + + # 初始化 dp 表,用於儲存子問題的解 + dp = Array.new(n + 1) { Array.new(3, 0) } + # 初始狀態:預設最小子問題的解 + dp[1][1], dp[1][2] = 1, 0 + dp[2][1], dp[2][2] = 0, 1 + # 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3...(n + 1) + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + end + + dp[n][1] + dp[n][2] +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_constraint_dp(n) + puts "爬 #{n} 階樓梯共有 #{res} 種方案" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb new file mode 100644 index 0000000000..a3b0f3f176 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb @@ -0,0 +1,26 @@ +=begin +File: climbing_stairs_dfs.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 搜尋 ### +def dfs(i) + # 已知 dp[1] 和 dp[2] ,返回之 + return i if i == 1 || i == 2 + # dp[i] = dp[i-1] + dp[i-2] + dfs(i - 1) + dfs(i - 2) +end + +### 爬樓梯:搜尋 ### +def climbing_stairs_dfs(n) + dfs(n) +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dfs(n) + puts "爬 #{n} 階樓梯共有 #{res} 種方案" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb new file mode 100644 index 0000000000..66a758f6f9 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb @@ -0,0 +1,33 @@ +=begin +File: climbing_stairs_dfs_mem.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 記憶化搜尋 ### +def dfs(i, mem) + # 已知 dp[1] 和 dp[2] ,返回之 + return i if i == 1 || i == 2 + # 若存在記錄 dp[i] ,則直接返回之 + return mem[i] if mem[i] != -1 + + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1, mem) + dfs(i - 2, mem) + # 記錄 dp[i] + mem[i] = count +end + +### 爬樓梯:記憶化搜尋 ### +def climbing_stairs_dfs_mem(n) + # mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + mem = Array.new(n + 1, -1) + dfs(n, mem) +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dfs_mem(n) + puts "爬 #{n} 階樓梯共有 #{res} 種方案" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb new file mode 100644 index 0000000000..8b9d7fb582 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb @@ -0,0 +1,40 @@ +=begin +File: climbing_stairs_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 爬樓梯:動態規劃 ### +def climbing_stairs_dp(n) + return n if n == 1 || n == 2 + + # 初始化 dp 表,用於儲存子問題的解 + dp = Array.new(n + 1, 0) + # 初始狀態:預設最小子問題的解 + dp[1], dp[2] = 1, 2 + # 狀態轉移:從較小子問題逐步求解較大子問題 + (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] } + + dp[n] +end + +### 爬樓梯:空間最佳化後的動態規劃 ### +def climbing_stairs_dp_comp(n) + return n if n == 1 || n == 2 + + a, b = 1, 2 + (3...(n + 1)).each { a, b = b, a + b } + + b +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dp(n) + puts "爬 #{n} 階樓梯共有 #{res} 種方案" + + res = climbing_stairs_dp_comp(n) + puts "爬 #{n} 階樓梯共有 #{res} 種方案" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/coin_change.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/coin_change.rb new file mode 100644 index 0000000000..73ce685ef0 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/coin_change.rb @@ -0,0 +1,65 @@ +=begin +File: coin_change.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 零錢兌換:動態規劃 ### +def coin_change_dp(coins, amt) + n = coins.length + _MAX = amt + 1 + # 初始化 dp 表 + dp = Array.new(n + 1) { Array.new(amt + 1, 0) } + # 狀態轉移:首行首列 + (1...(amt + 1)).each { |a| dp[0][a] = _MAX } + # 狀態轉移:其餘行和列 + for i in 1...(n + 1) + for a in 1...(amt + 1) + if coins[i - 1] > a + # 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a] + else + # 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min + end + end + end + dp[n][amt] != _MAX ? dp[n][amt] : -1 +end + +### 零錢兌換:空間最佳化後的動態規劃 ### +def coin_change_dp_comp(coins, amt) + n = coins.length + _MAX = amt + 1 + # 初始化 dp 表 + dp = Array.new(amt + 1, _MAX) + dp[0] = 0 + # 狀態轉移 + for i in 1...(n + 1) + # 正序走訪 + for a in 1...(amt + 1) + if coins[i - 1] > a + # 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + else + # 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min + end + end + end + dp[amt] != _MAX ? dp[amt] : -1 +end + +### Driver Code ### +if __FILE__ == $0 + coins = [1, 2, 5] + amt = 4 + + # 動態規劃 + res = coin_change_dp(coins, amt) + puts "湊到目標金額所需的最少硬幣數量為 #{res}" + + # 空間最佳化後的動態規劃 + res = coin_change_dp_comp(coins, amt) + puts "湊到目標金額所需的最少硬幣數量為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb new file mode 100644 index 0000000000..6fbeb51078 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb @@ -0,0 +1,63 @@ +=begin +File: coin_change_ii.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 零錢兌換 II:動態規劃 ### +def coin_change_ii_dp(coins, amt) + n = coins.length + # 初始化 dp 表 + dp = Array.new(n + 1) { Array.new(amt + 1, 0) } + # 初始化首列 + (0...(n + 1)).each { |i| dp[i][0] = 1 } + # 狀態轉移 + for i in 1...(n + 1) + for a in 1...(amt + 1) + if coins[i - 1] > a + # 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a] + else + # 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + end + end + end + dp[n][amt] +end + +### 零錢兌換 II:空間最佳化後的動態規劃 ### +def coin_change_ii_dp_comp(coins, amt) + n = coins.length + # 初始化 dp 表 + dp = Array.new(amt + 1, 0) + dp[0] = 1 + # 狀態轉移 + for i in 1...(n + 1) + # 正序走訪 + for a in 1...(amt + 1) + if coins[i - 1] > a + # 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + else + # 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + end + end + end + dp[amt] +end + +### Driver Code ### +if __FILE__ == $0 + coins = [1, 2, 5] + amt = 5 + + # 動態規劃 + res = coin_change_ii_dp(coins, amt) + puts "湊出目標金額的硬幣組合數量為 #{res}" + + # 空間最佳化後的動態規劃 + res = coin_change_ii_dp_comp(coins, amt) + puts "湊出目標金額的硬幣組合數量為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/edit_distance.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/edit_distance.rb new file mode 100644 index 0000000000..1531d88d42 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/edit_distance.rb @@ -0,0 +1,115 @@ +=begin +File: edit_distance.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 編輯距離:暴力搜尋 ### +def edit_distance_dfs(s, t, i, j) + # 若 s 和 t 都為空,則返回 0 + return 0 if i == 0 && j == 0 + # 若 s 為空,則返回 t 長度 + return j if i == 0 + # 若 t 為空,則返回 s 長度 + return i if j == 0 + # 若兩字元相等,則直接跳過此兩字元 + return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1] + # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + insert = edit_distance_dfs(s, t, i, j - 1) + delete = edit_distance_dfs(s, t, i - 1, j) + replace = edit_distance_dfs(s, t, i - 1, j - 1) + # 返回最少編輯步數 + [insert, delete, replace].min + 1 +end + +def edit_distance_dfs_mem(s, t, mem, i, j) + # 若 s 和 t 都為空,則返回 0 + return 0 if i == 0 && j == 0 + # 若 s 為空,則返回 t 長度 + return j if i == 0 + # 若 t 為空,則返回 s 長度 + return i if j == 0 + # 若已有記錄,則直接返回之 + return mem[i][j] if mem[i][j] != -1 + # 若兩字元相等,則直接跳過此兩字元 + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1] + # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) + delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) + replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # 記錄並返回最少編輯步數 + mem[i][j] = [insert, delete, replace].min + 1 +end + +### 編輯距離:動態規劃 ### +def edit_distance_dp(s, t) + n, m = s.length, t.length + dp = Array.new(n + 1) { Array.new(m + 1, 0) } + # 狀態轉移:首行首列 + (1...(n + 1)).each { |i| dp[i][0] = i } + (1...(m + 1)).each { |j| dp[0][j] = j } + # 狀態轉移:其餘行和列 + for i in 1...(n + 1) + for j in 1...(m +1) + if s[i - 1] == t[j - 1] + # 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1] + else + # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1 + end + end + end + dp[n][m] +end + +### 編輯距離:空間最佳化後的動態規劃 ### +def edit_distance_dp_comp(s, t) + n, m = s.length, t.length + dp = Array.new(m + 1, 0) + # 狀態轉移:首行 + (1...(m + 1)).each { |j| dp[j] = j } + # 狀態轉移:其餘行 + for i in 1...(n + 1) + # 狀態轉移:首列 + leftup = dp.first # 暫存 dp[i-1, j-1] + dp[0] += 1 + # 狀態轉移:其餘列 + for j in 1...(m + 1) + temp = dp[j] + if s[i - 1] == t[j - 1] + # 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup + else + # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = [dp[j - 1], dp[j], leftup].min + 1 + end + leftup = temp # 更新為下一輪的 dp[i-1, j-1] + end + end + dp[m] +end + +### Driver Code ### +if __FILE__ == $0 + s = 'bag' + t = 'pack' + n, m = s.length, t.length + + # 暴力搜尋 + res = edit_distance_dfs(s, t, n, m) + puts "將 #{s} 更改為 #{t} 最少需要編輯 #{res} 步" + + # 記憶化搜尋 + mem = Array.new(n + 1) { Array.new(m + 1, -1) } + res = edit_distance_dfs_mem(s, t, mem, n, m) + puts "將 #{s} 更改為 #{t} 最少需要編輯 #{res} 步" + + # 動態規劃 + res = edit_distance_dp(s, t) + puts "將 #{s} 更改為 #{t} 最少需要編輯 #{res} 步" + + # 空間最佳化後的動態規劃 + res = edit_distance_dp_comp(s, t) + puts "將 #{s} 更改為 #{t} 最少需要編輯 #{res} 步" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/knapsack.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/knapsack.rb new file mode 100644 index 0000000000..ccc425b4f4 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/knapsack.rb @@ -0,0 +1,99 @@ +=begin +File: knapsack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 0-1 背包:暴力搜尋 ### +def knapsack_dfs(wgt, val, i, c) + # 若已選完所有物品或背包無剩餘容量,則返回價值 0 + return 0 if i == 0 || c == 0 + # 若超過背包容量,則只能選擇不放入背包 + return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c + # 計算不放入和放入物品 i 的最大價值 + no = knapsack_dfs(wgt, val, i - 1, c) + yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] + # 返回兩種方案中價值更大的那一個 + [no, yes].max +end + +### 0-1 背包:記憶化搜尋 ### +def knapsack_dfs_mem(wgt, val, mem, i, c) + # 若已選完所有物品或背包無剩餘容量,則返回價值 0 + return 0 if i == 0 || c == 0 + # 若已有記錄,則直接返回 + return mem[i][c] if mem[i][c] != -1 + # 若超過背包容量,則只能選擇不放入背包 + return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c + # 計算不放入和放入物品 i 的最大價值 + no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] + # 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = [no, yes].max +end + +### 0-1 背包:動態規劃 ### +def knapsack_dp(wgt, val, cap) + n = wgt.length + # 初始化 dp 表 + dp = Array.new(n + 1) { Array.new(cap + 1, 0) } + # 狀態轉移 + for i in 1...(n + 1) + for c in 1...(cap + 1) + if wgt[i - 1] > c + # 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c] + else + # 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[n][cap] +end + +### 0-1 背包:空間最佳化後的動態規劃 ### +def knapsack_dp_comp(wgt, val, cap) + n = wgt.length + # 初始化 dp 表 + dp = Array.new(cap + 1, 0) + # 狀態轉移 + for i in 1...(n + 1) + # 倒序走訪 + for c in cap.downto(1) + if wgt[i - 1] > c + # 若超過背包容量,則不選物品 i + dp[c] = dp[c] + else + # 不選和選物品 i 這兩種方案的較大值 + dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[cap] +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = wgt.length + + # 暴力搜尋 + res = knapsack_dfs(wgt, val, n, cap) + puts "不超過背包容量的最大物品價值為 #{res}" + + # 記憶化搜尋 + mem = Array.new(n + 1) { Array.new(cap + 1, -1) } + res = knapsack_dfs_mem(wgt, val, mem, n, cap) + puts "不超過背包容量的最大物品價值為 #{res}" + + # 動態規劃 + res = knapsack_dp(wgt, val, cap) + puts "不超過背包容量的最大物品價值為 #{res}" + + # 空間最佳化後的動態規劃 + res = knapsack_dp_comp(wgt, val, cap) + puts "不超過背包容量的最大物品價值為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb new file mode 100644 index 0000000000..37de099b7b --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb @@ -0,0 +1,39 @@ +=begin +File: min_cost_climbing_stairs_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 爬樓梯最小代價:動態規劃 ### +def min_cost_climbing_stairs_dp(cost) + n = cost.length - 1 + return cost[n] if n == 1 || n == 2 + # 初始化 dp 表,用於儲存子問題的解 + dp = Array.new(n + 1, 0) + # 初始狀態:預設最小子問題的解 + dp[1], dp[2] = cost[1], cost[2] + # 狀態轉移:從較小子問題逐步求解較大子問題 + (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] } + dp[n] +end + +# 爬樓梯最小代價:空間最佳化後的動態規劃 +def min_cost_climbing_stairs_dp_comp(cost) + n = cost.length - 1 + return cost[n] if n == 1 || n == 2 + a, b = cost[1], cost[2] + (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] } + b +end + +### Driver Code ### +if __FILE__ == $0 + cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + puts "輸入樓梯的代價串列為 #{cost}" + + res = min_cost_climbing_stairs_dp(cost) + puts "爬完樓梯的最低代價為 #{res}" + + res = min_cost_climbing_stairs_dp_comp(cost) + puts "爬完樓梯的最低代價為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/min_path_sum.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/min_path_sum.rb new file mode 100644 index 0000000000..32591613ef --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/min_path_sum.rb @@ -0,0 +1,93 @@ +=begin +File: min_path_sum.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 最小路徑和:暴力搜尋 ### +def min_path_sum_dfs(grid, i, j) + # 若為左上角單元格,則終止搜尋 + return grid[i][j] if i == 0 && j == 0 + # 若行列索引越界,則返回 +∞ 代價 + return Float::INFINITY if i < 0 || j < 0 + # 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + up = min_path_sum_dfs(grid, i - 1, j) + left = min_path_sum_dfs(grid, i, j - 1) + # 返回從左上角到 (i, j) 的最小路徑代價 + [left, up].min + grid[i][j] +end + +### 最小路徑和:記憶化搜尋 ### +def min_path_sum_dfs_mem(grid, mem, i, j) + # 若為左上角單元格,則終止搜尋 + return grid[0][0] if i == 0 && j == 0 + # 若行列索引越界,則返回 +∞ 代價 + return Float::INFINITY if i < 0 || j < 0 + # 若已有記錄,則直接返回 + return mem[i][j] if mem[i][j] != -1 + # 左邊和上邊單元格的最小路徑代價 + up = min_path_sum_dfs_mem(grid, mem, i - 1, j) + left = min_path_sum_dfs_mem(grid, mem, i, j - 1) + # 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = [left, up].min + grid[i][j] +end + +### 最小路徑和:動態規劃 ### +def min_path_sum_dp(grid) + n, m = grid.length, grid.first.length + # 初始化 dp 表 + dp = Array.new(n) { Array.new(m, 0) } + dp[0][0] = grid[0][0] + # 狀態轉移:首行 + (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] } + # 狀態轉移:首列 + (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] } + # 狀態轉移:其餘行和列 + for i in 1...n + for j in 1...m + dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j] + end + end + dp[n -1][m -1] +end + +### 最小路徑和:空間最佳化後的動態規劃 ### +def min_path_sum_dp_comp(grid) + n, m = grid.length, grid.first.length + # 初始化 dp 表 + dp = Array.new(m, 0) + # 狀態轉移:首行 + dp[0] = grid[0][0] + (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] } + # 狀態轉移:其餘行 + for i in 1...n + # 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0] + # 狀態轉移:其餘列 + (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] } + end + dp[m - 1] +end + +### Driver Code ### +if __FILE__ == $0 + grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] + n, m = grid.length, grid.first.length + + # 暴力搜尋 + res = min_path_sum_dfs(grid, n - 1, m - 1) + puts "從左上角到右下角的做小路徑和為 #{res}" + + # 記憶化搜尋 + mem = Array.new(n) { Array.new(m, - 1) } + res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1) + puts "從左上角到右下角的做小路徑和為 #{res}" + + # 動態規劃 + res = min_path_sum_dp(grid) + puts "從左上角到右下角的做小路徑和為 #{res}" + + # 空間最佳化後的動態規劃 + res = min_path_sum_dp_comp(grid) + puts "從左上角到右下角的做小路徑和為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb new file mode 100644 index 0000000000..33ef0051cc --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb @@ -0,0 +1,61 @@ +=begin +File: unbounded_knapsack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 完全背包:動態規劃 ### +def unbounded_knapsack_dp(wgt, val, cap) + n = wgt.length + # 初始化 dp 表 + dp = Array.new(n + 1) { Array.new(cap + 1, 0) } + # 狀態轉移 + for i in 1...(n + 1) + for c in 1...(cap + 1) + if wgt[i - 1] > c + # 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c] + else + # 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[n][cap] +end + +### 完全背包:空間最佳化後的動態規劃 ##3 +def unbounded_knapsack_dp_comp(wgt, val, cap) + n = wgt.length + # 初始化 dp 表 + dp = Array.new(cap + 1, 0) + # 狀態轉移 + for i in 1...(n + 1) + # 正序走訪 + for c in 1...(cap + 1) + if wgt[i -1] > c + # 若超過背包容量,則不選物品 i + dp[c] = dp[c] + else + # 不選和選物品 i 這兩種方案的較大值 + dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[cap] +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [1, 2, 3] + val = [5, 11, 15] + cap = 4 + + # 動態規劃 + res = unbounded_knapsack_dp(wgt, val, cap) + puts "不超過背包容量的最大物品價值為 #{res}" + + # 空間最佳化後的動態規劃 + res = unbounded_knapsack_dp_comp(wgt, val, cap) + puts "不超過背包容量的最大物品價值為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_greedy/coin_change_greedy.rb b/zh-hant/codes/ruby/chapter_greedy/coin_change_greedy.rb new file mode 100644 index 0000000000..513d15eb72 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_greedy/coin_change_greedy.rb @@ -0,0 +1,50 @@ +=begin +File: coin_change_greedy.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 零錢兌換:貪婪 ### +def coin_change_greedy(coins, amt) + # 假設 coins 串列有序 + i = coins.length - 1 + count = 0 + # 迴圈進行貪婪選擇,直到無剩餘金額 + while amt > 0 + # 找到小於且最接近剩餘金額的硬幣 + while i > 0 && coins[i] > amt + i -= 1 + end + # 選擇 coins[i] + amt -= coins[i] + count += 1 + end + # 若未找到可行方案, 則返回 -1 + amt == 0 ? count : -1 +end + +### Driver Code ### +if __FILE__ == $0 + # 貪婪:能夠保證找到全域性最優解 + coins = [1, 5, 10, 20, 50, 100] + amt = 186 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "湊到 #{amt} 所需的最少硬幣數量為 #{res}" + + # 貪婪:無法保證找到全域性最優解 + coins = [1, 20, 50] + amt = 60 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "湊到 #{amt} 所需的最少硬幣數量為 #{res}" + puts "實際上需要的最少數量為 3 , 即 20 + 20 + 20" + + # 貪婪:無法保證找到全域性最優解 + coins = [1, 49, 50] + amt = 98 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "湊到 #{amt} 所需的最少硬幣數量為 #{res}" + puts "實際上需要的最少數量為 2 , 即 49 + 49" +end diff --git a/zh-hant/codes/ruby/chapter_greedy/fractional_knapsack.rb b/zh-hant/codes/ruby/chapter_greedy/fractional_knapsack.rb new file mode 100644 index 0000000000..838954ccae --- /dev/null +++ b/zh-hant/codes/ruby/chapter_greedy/fractional_knapsack.rb @@ -0,0 +1,51 @@ +=begin +File: fractional_knapsack.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 物品 ### +class Item + attr_accessor :w # 物品重量 + attr_accessor :v # 物品價值 + + def initialize(w, v) + @w = w + @v = v + end +end + +### 分數背包:貪婪 ### +def fractional_knapsack(wgt, val, cap) + # 建立物品串列,包含兩個屬性:重量,價值 + items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) } + # 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) } + # 迴圈貪婪選擇 + res = 0 + for item in items + if item.w <= cap + # 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v + cap -= item.w + else + # 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (item.v.to_f / item.w) * cap + # 已無剩餘容量,因此跳出迴圈 + break + end + end + res +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = wgt.length + + # 貪婪演算法 + res = fractional_knapsack(wgt, val, cap) + puts "不超過背包容量的最大物品價值為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_greedy/max_capacity.rb b/zh-hant/codes/ruby/chapter_greedy/max_capacity.rb new file mode 100644 index 0000000000..ab826c58bf --- /dev/null +++ b/zh-hant/codes/ruby/chapter_greedy/max_capacity.rb @@ -0,0 +1,37 @@ +=begin +File: max_capacity.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 最大容量:貪婪 ### +def max_capacity(ht) + # 初始化 i, j,使其分列陣列兩端 + i, j = 0, ht.length - 1 + # 初始最大容量為 0 + res = 0 + + # 迴圈貪婪選擇,直至兩板相遇 + while i < j + # 更新最大容量 + cap = [ht[i], ht[j]].min * (j - i) + res = [res, cap].max + # 向內移動短板 + if ht[i] < ht[j] + i += 1 + else + j -= 1 + end + end + + res +end + +### Driver Code ### +if __FILE__ == $0 + ht = [3, 8, 5, 2, 7, 7, 3, 4] + + # 貪婪演算法 + res = max_capacity(ht) + puts "最大容量為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_greedy/max_product_cutting.rb b/zh-hant/codes/ruby/chapter_greedy/max_product_cutting.rb new file mode 100644 index 0000000000..eba81d601b --- /dev/null +++ b/zh-hant/codes/ruby/chapter_greedy/max_product_cutting.rb @@ -0,0 +1,28 @@ +=begin +File: max_product_cutting.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 最大切分乘積:貪婪 ### +def max_product_cutting(n) + # 當 n <= 3 時,必須切分出一個 1 + return 1 * (n - 1) if n <= 3 + # 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + a, b = n / 3, n % 3 + # 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return (3.pow(a - 1) * 2 * 2).to_i if b == 1 + # 當餘數為 2 時,不做處理 + return (3.pow(a) * 2).to_i if b == 2 + # 當餘數為 0 時,不做處理 + 3.pow(a).to_i +end + +### Driver Code ### +if __FILE__ == $0 + n = 58 + + # 貪婪演算法 + res = max_product_cutting(n) + puts "最大切分乘積為 #{res}" +end diff --git a/zh-hant/codes/rust/chapter_array_and_linkedlist/array.rs b/zh-hant/codes/rust/chapter_array_and_linkedlist/array.rs index 519bc711b1..f276b6d74d 100644 --- a/zh-hant/codes/rust/chapter_array_and_linkedlist/array.rs +++ b/zh-hant/codes/rust/chapter_array_and_linkedlist/array.rs @@ -74,12 +74,11 @@ fn find(nums: &[i32], target: i32) -> Option { fn main() { /* 初始化陣列 */ let arr: [i32; 5] = [0; 5]; - let slice: &[i32] = &[0; 5]; print!("陣列 arr = "); print_util::print_array(&arr); - // 在 Rust 中,指定長度时([i32; 5])爲陣列,不指定長度時(&[i32])爲切片 + // 在 Rust 中,指定長度時([i32; 5])為陣列,不指定長度時(&[i32])為切片 // 由於 Rust 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 - // Vector 是 Rust 一般情況下用作動態陣列的類型 + // Vector 是 Rust 一般情況下用作動態陣列的型別 // 為了方便實現擴容 extend() 方法,以下將 vector 看作陣列(array) let nums: Vec = vec![1, 3, 2, 5, 4]; print!("\n陣列 nums = "); diff --git a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs index 40cbb09e95..7e94e8c539 100644 --- a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs +++ b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs @@ -10,7 +10,7 @@ use std::{cell::RefCell, rc::Rc}; use tree_node::{vec_to_tree, TreeNode}; /* 前序走訪:例題一 */ -fn pre_order(res: &mut Vec>>, root: Option>>) { +fn pre_order(res: &mut Vec>>, root: Option<&Rc>>) { if root.is_none() { return; } @@ -19,8 +19,8 @@ fn pre_order(res: &mut Vec>>, root: Option>>>, path: &mut Vec>>, - root: Option>>, + root: Option<&Rc>>, ) { if root.is_none() { return; @@ -25,10 +25,10 @@ fn pre_order( // 記錄解 res.push(path.clone()); } - pre_order(res, path, node.borrow().left.clone()); - pre_order(res, path, node.borrow().right.clone()); + pre_order(res, path, node.borrow().left.as_ref()); + pre_order(res, path, node.borrow().right.as_ref()); // 回退 - path.remove(path.len() - 1); + path.pop(); } } @@ -41,7 +41,7 @@ pub fn main() { // 前序走訪 let mut path = Vec::new(); let mut res = Vec::new(); - pre_order(&mut res, &mut path, root); + pre_order(&mut res, &mut path, root.as_ref()); println!("\n輸出所有根節點到節點 7 的路徑"); for path in res { diff --git a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs index b4ed63f6ee..f61b862889 100644 --- a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs +++ b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs @@ -13,7 +13,7 @@ use tree_node::{vec_to_tree, TreeNode}; fn pre_order( res: &mut Vec>>>, path: &mut Vec>>, - root: Option>>, + root: Option<&Rc>>, ) { // 剪枝 if root.is_none() || root.as_ref().unwrap().borrow().val == 3 { @@ -26,10 +26,10 @@ fn pre_order( // 記錄解 res.push(path.clone()); } - pre_order(res, path, node.borrow().left.clone()); - pre_order(res, path, node.borrow().right.clone()); + pre_order(res, path, node.borrow().left.as_ref()); + pre_order(res, path, node.borrow().right.as_ref()); // 回退 - path.remove(path.len() - 1); + path.pop(); } } @@ -42,7 +42,7 @@ pub fn main() { // 前序走訪 let mut path = Vec::new(); let mut res = Vec::new(); - pre_order(&mut res, &mut path, root); + pre_order(&mut res, &mut path, root.as_ref()); println!("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點"); for path in res { diff --git a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs index 7c8ecf5ea8..8b7deff790 100644 --- a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs +++ b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs @@ -11,7 +11,7 @@ use tree_node::{vec_to_tree, TreeNode}; /* 判斷當前狀態是否為解 */ fn is_solution(state: &mut Vec>>) -> bool { - return !state.is_empty() && state.get(state.len() - 1).unwrap().borrow().val == 7; + return !state.is_empty() && state.last().unwrap().borrow().val == 7; } /* 記錄解 */ @@ -23,8 +23,8 @@ fn record_solution( } /* 判斷在當前狀態下,該選擇是否合法 */ -fn is_valid(_: &mut Vec>>, choice: Rc>) -> bool { - return choice.borrow().val != 3; +fn is_valid(_: &mut Vec>>, choice: Option<&Rc>>) -> bool { + return choice.is_some() && choice.unwrap().borrow().val != 3; } /* 更新狀態 */ @@ -34,13 +34,13 @@ fn make_choice(state: &mut Vec>>, choice: Rc>>, _: Rc>) { - state.remove(state.len() - 1); + state.pop(); } /* 回溯演算法:例題三 */ fn backtrack( state: &mut Vec>>, - choices: &mut Vec>>, + choices: &Vec>>>, res: &mut Vec>>>, ) { // 檢查是否為解 @@ -49,22 +49,22 @@ fn backtrack( record_solution(state, res); } // 走訪所有選擇 - for choice in choices { + for &choice in choices.iter() { // 剪枝:檢查選擇是否合法 - if is_valid(state, choice.clone()) { + if is_valid(state, choice) { // 嘗試:做出選擇,更新狀態 - make_choice(state, choice.clone()); + make_choice(state, choice.unwrap().clone()); // 進行下一輪選擇 backtrack( state, - &mut vec![ - choice.borrow().left.clone().unwrap(), - choice.borrow().right.clone().unwrap(), + &vec![ + choice.unwrap().borrow().left.as_ref(), + choice.unwrap().borrow().right.as_ref(), ], res, ); // 回退:撤銷選擇,恢復到之前的狀態 - undo_choice(state, choice.clone()); + undo_choice(state, choice.unwrap().clone()); } } } @@ -77,7 +77,7 @@ pub fn main() { // 回溯演算法 let mut res = Vec::new(); - backtrack(&mut Vec::new(), &mut vec![root.unwrap()], &mut res); + backtrack(&mut Vec::new(), &mut vec![root.as_ref()], &mut res); println!("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點"); for path in res { diff --git a/zh-hant/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs b/zh-hant/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs index 85d638ea33..4ac7a9fb67 100644 --- a/zh-hant/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs +++ b/zh-hant/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs @@ -10,7 +10,7 @@ fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 { if i > j { return -1; } - let m: i32 = (i + j) / 2; + let m: i32 = i + (j - i) / 2; if nums[m as usize] < target { // 遞迴子問題 f(m+1, j) return dfs(nums, target, m + 1, j); diff --git a/zh-hant/codes/rust/chapter_divide_and_conquer/hanota.rs b/zh-hant/codes/rust/chapter_divide_and_conquer/hanota.rs index ccdd77a316..7b76499246 100644 --- a/zh-hant/codes/rust/chapter_divide_and_conquer/hanota.rs +++ b/zh-hant/codes/rust/chapter_divide_and_conquer/hanota.rs @@ -9,7 +9,7 @@ /* 移動一個圓盤 */ fn move_pan(src: &mut Vec, tar: &mut Vec) { // 從 src 頂部拿出一個圓盤 - let pan = src.remove(src.len() - 1); + let pan = src.pop().unwrap(); // 將圓盤放入 tar 頂部 tar.push(pan); } diff --git a/zh-hant/codes/rust/chapter_sorting/bubble_sort.rs b/zh-hant/codes/rust/chapter_sorting/bubble_sort.rs index 576b76bafe..f68602589e 100644 --- a/zh-hant/codes/rust/chapter_sorting/bubble_sort.rs +++ b/zh-hant/codes/rust/chapter_sorting/bubble_sort.rs @@ -14,9 +14,7 @@ fn bubble_sort(nums: &mut [i32]) { for j in 0..i { if nums[j] > nums[j + 1] { // 交換 nums[j] 與 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; + nums.swap(j, j + 1); } } } @@ -31,9 +29,7 @@ fn bubble_sort_with_flag(nums: &mut [i32]) { for j in 0..i { if nums[j] > nums[j + 1] { // 交換 nums[j] 與 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; + nums.swap(j, j + 1); flag = true; // 記錄交換元素 } } diff --git a/zh-hant/codes/rust/chapter_sorting/bucket_sort.rs b/zh-hant/codes/rust/chapter_sorting/bucket_sort.rs index 27c4ef2cd4..c85d64f87e 100644 --- a/zh-hant/codes/rust/chapter_sorting/bucket_sort.rs +++ b/zh-hant/codes/rust/chapter_sorting/bucket_sort.rs @@ -12,7 +12,7 @@ fn bucket_sort(nums: &mut [f64]) { let k = nums.len() / 2; let mut buckets = vec![vec![]; k]; // 1. 將陣列元素分配到各個桶中 - for &mut num in &mut *nums { + for &num in nums.iter() { // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] let i = (num * k as f64) as usize; // 將 num 新增進桶 i @@ -25,8 +25,8 @@ fn bucket_sort(nums: &mut [f64]) { } // 3. 走訪桶合併結果 let mut i = 0; - for bucket in &mut buckets { - for &mut num in bucket { + for bucket in buckets.iter() { + for &num in bucket.iter() { nums[i] = num; i += 1; } diff --git a/zh-hant/codes/rust/chapter_sorting/counting_sort.rs b/zh-hant/codes/rust/chapter_sorting/counting_sort.rs index 169c30e864..e854b88bb2 100644 --- a/zh-hant/codes/rust/chapter_sorting/counting_sort.rs +++ b/zh-hant/codes/rust/chapter_sorting/counting_sort.rs @@ -10,11 +10,11 @@ include!("../include/include.rs"); // 簡單實現,無法用於排序物件 fn counting_sort_naive(nums: &mut [i32]) { // 1. 統計陣列最大元素 m - let m = *nums.into_iter().max().unwrap(); + let m = *nums.iter().max().unwrap(); // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 let mut counter = vec![0; m as usize + 1]; - for &num in &*nums { + for &num in nums.iter() { counter[num as usize] += 1; } // 3. 走訪 counter ,將各元素填入原陣列 nums @@ -31,16 +31,16 @@ fn counting_sort_naive(nums: &mut [i32]) { // 完整實現,可排序物件,並且是穩定排序 fn counting_sort(nums: &mut [i32]) { // 1. 統計陣列最大元素 m - let m = *nums.into_iter().max().unwrap(); + let m = *nums.iter().max().unwrap() as usize; // 2. 統計各數字的出現次數 // counter[num] 代表 num 的出現次數 - let mut counter = vec![0; m as usize + 1]; - for &num in &*nums { + let mut counter = vec![0; m + 1]; + for &num in nums.iter() { counter[num as usize] += 1; } // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 - for i in 0..m as usize { + for i in 0..m { counter[i + 1] += counter[i]; } // 4. 倒序走訪 nums ,將各元素填入結果陣列 res @@ -53,9 +53,7 @@ fn counting_sort(nums: &mut [i32]) { counter[num as usize] -= 1; // 令前綴和自減 1 ,得到下次放置 num 的索引 } // 使用結果陣列 res 覆蓋原陣列 nums - for i in 0..n { - nums[i] = res[i]; - } + nums.copy_from_slice(&res) } /* Driver Code */ diff --git a/zh-hant/codes/rust/chapter_sorting/heap_sort.rs b/zh-hant/codes/rust/chapter_sorting/heap_sort.rs index 83cfe0c6b0..2d71c348c3 100644 --- a/zh-hant/codes/rust/chapter_sorting/heap_sort.rs +++ b/zh-hant/codes/rust/chapter_sorting/heap_sort.rs @@ -24,9 +24,7 @@ fn sift_down(nums: &mut [i32], n: usize, mut i: usize) { break; } // 交換兩節點 - let temp = nums[i]; - nums[i] = nums[ma]; - nums[ma] = temp; + nums.swap(i, ma); // 迴圈向下堆積化 i = ma; } @@ -35,15 +33,13 @@ fn sift_down(nums: &mut [i32], n: usize, mut i: usize) { /* 堆積排序 */ fn heap_sort(nums: &mut [i32]) { // 建堆積操作:堆積化除葉節點以外的其他所有節點 - for i in (0..=nums.len() / 2 - 1).rev() { + for i in (0..nums.len() / 2).rev() { sift_down(nums, nums.len(), i); } // 從堆積中提取最大元素,迴圈 n-1 輪 - for i in (1..=nums.len() - 1).rev() { + for i in (1..nums.len()).rev() { // 交換根節點與最右葉節點(交換首元素與尾元素) - let tmp = nums[0]; - nums[0] = nums[i]; - nums[i] = tmp; + nums.swap(0, i); // 以根節點為起點,從頂至底進行堆積化 sift_down(nums, i, 0); } diff --git a/zh-hant/codes/rust/chapter_sorting/radix_sort.rs b/zh-hant/codes/rust/chapter_sorting/radix_sort.rs index 71689908f6..d818a58f1b 100644 --- a/zh-hant/codes/rust/chapter_sorting/radix_sort.rs +++ b/zh-hant/codes/rust/chapter_sorting/radix_sort.rs @@ -35,9 +35,7 @@ fn counting_sort_digit(nums: &mut [i32], exp: i32) { counter[d] -= 1; // 將 d 的數量減 1 } // 使用結果覆蓋原陣列 nums - for i in 0..n { - nums[i] = res[i]; - } + nums.copy_from_slice(&res); } /* 基數排序 */ diff --git a/zh-hant/codes/rust/include/vertex.rs b/zh-hant/codes/rust/include/vertex.rs index b17bef164f..6d9b5e5508 100644 --- a/zh-hant/codes/rust/include/vertex.rs +++ b/zh-hant/codes/rust/include/vertex.rs @@ -7,7 +7,7 @@ /* 頂點型別 */ #[derive(Copy, Clone, Hash, PartialEq, Eq)] pub struct Vertex { - pub val: i32 + pub val: i32, } /* 輸入值串列 vals ,返回頂點串列 vets */ @@ -18,4 +18,4 @@ pub fn vals_to_vets(vals: Vec) -> Vec { /* 輸入頂點串列 vets ,返回值串列 vals */ pub fn vets_to_vals(vets: Vec) -> Vec { vets.into_iter().map(|vet| vet.val).collect() -} \ No newline at end of file +} diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change.ts index 222ff7ecbc..6fe7c251cf 100644 --- a/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change.ts +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change.ts @@ -31,7 +31,7 @@ function coinChangeDP(coins: Array, amt: number): number { return dp[n][amt] !== MAX ? dp[n][amt] : -1; } -/* 零錢兌換:狀態壓縮後的動態規劃 */ +/* 零錢兌換:空間最佳化後的動態規劃 */ function coinChangeDPComp(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; @@ -61,7 +61,7 @@ const amt = 4; let res = coinChangeDP(coins, amt); console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); -// 狀態壓縮後的動態規劃 +// 空間最佳化後的動態規劃 res = coinChangeDPComp(coins, amt); console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts index 0c1376918c..fa41017a74 100644 --- a/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts @@ -30,7 +30,7 @@ function coinChangeIIDP(coins: Array, amt: number): number { return dp[n][amt]; } -/* 零錢兌換 II:狀態壓縮後的動態規劃 */ +/* 零錢兌換 II:空間最佳化後的動態規劃 */ function coinChangeIIDPComp(coins: Array, amt: number): number { const n = coins.length; // 初始化 dp 表 @@ -59,7 +59,7 @@ const amt = 5; let res = coinChangeIIDP(coins, amt); console.log(`湊出目標金額的硬幣組合數量為 ${res}`); -// 狀態壓縮後的動態規劃 +// 空間最佳化後的動態規劃 res = coinChangeIIDPComp(coins, amt); console.log(`湊出目標金額的硬幣組合數量為 ${res}`); diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/edit_distance.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/edit_distance.ts index de304b0701..906d662aaa 100644 --- a/zh-hant/codes/typescript/chapter_dynamic_programming/edit_distance.ts +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/edit_distance.ts @@ -90,7 +90,7 @@ function editDistanceDP(s: string, t: string): number { return dp[n][m]; } -/* 編輯距離:狀態壓縮後的動態規劃 */ +/* 編輯距離:空間最佳化後的動態規劃 */ function editDistanceDPComp(s: string, t: string): number { const n = s.length, m = t.length; @@ -141,7 +141,7 @@ console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); res = editDistanceDP(s, t); console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); -// 狀態壓縮後的動態規劃 +// 空間最佳化後的動態規劃 res = editDistanceDPComp(s, t); console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/knapsack.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/knapsack.ts index 310c876fa6..ea23a6cba8 100644 --- a/zh-hant/codes/typescript/chapter_dynamic_programming/knapsack.ts +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/knapsack.ts @@ -84,7 +84,7 @@ function knapsackDP( return dp[n][cap]; } -/* 0-1 背包:狀態壓縮後的動態規劃 */ +/* 0-1 背包:空間最佳化後的動態規劃 */ function knapsackDPComp( wgt: Array, val: Array, @@ -127,7 +127,7 @@ console.log(`不超過背包容量的最大物品價值為 ${res}`); res = knapsackDP(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); -// 狀態壓縮後的動態規劃 +// 空間最佳化後的動態規劃 res = knapsackDPComp(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts index 66980475d6..bdeaca5963 100644 --- a/zh-hant/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts @@ -22,7 +22,7 @@ function minCostClimbingStairsDP(cost: Array): number { return dp[n]; } -/* 爬樓梯最小代價:狀態壓縮後的動態規劃 */ +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ function minCostClimbingStairsDPComp(cost: Array): number { const n = cost.length - 1; if (n === 1 || n === 2) { diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/min_path_sum.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/min_path_sum.ts index cb1d7ece5d..5316095f62 100644 --- a/zh-hant/codes/typescript/chapter_dynamic_programming/min_path_sum.ts +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/min_path_sum.ts @@ -78,7 +78,7 @@ function minPathSumDP(grid: Array>): number { return dp[n - 1][m - 1]; } -/* 最小路徑和:狀態壓縮後的動態規劃 */ +/* 最小路徑和:空間最佳化後的動態規劃 */ function minPathSumDPComp(grid: Array>): number { const n = grid.length, m = grid[0].length; @@ -125,7 +125,7 @@ console.log(`從左上角到右下角的最小路徑和為 ${res}`); res = minPathSumDP(grid); console.log(`從左上角到右下角的最小路徑和為 ${res}`); -// 狀態壓縮後的動態規劃 +// 空間最佳化後的動態規劃 res = minPathSumDPComp(grid); console.log(`從左上角到右下角的最小路徑和為 ${res}`); diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts index 2e3b4989e4..6f0f3ca316 100644 --- a/zh-hant/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts @@ -33,7 +33,7 @@ function unboundedKnapsackDP( return dp[n][cap]; } -/* 完全背包:狀態壓縮後的動態規劃 */ +/* 完全背包:空間最佳化後的動態規劃 */ function unboundedKnapsackDPComp( wgt: Array, val: Array, @@ -66,7 +66,7 @@ const cap = 4; let res = unboundedKnapsackDP(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); -// 狀態壓縮後的動態規劃 +// 空間最佳化後的動態規劃 res = unboundedKnapsackDPComp(wgt, val, cap); console.log(`不超過背包容量的最大物品價值為 ${res}`); diff --git a/zh-hant/codes/zig/chapter_sorting/merge_sort.zig b/zh-hant/codes/zig/chapter_sorting/merge_sort.zig index e0a12d11a2..c60434fef3 100644 --- a/zh-hant/codes/zig/chapter_sorting/merge_sort.zig +++ b/zh-hant/codes/zig/chapter_sorting/merge_sort.zig @@ -48,7 +48,7 @@ fn mergeSort(nums: []i32, left: usize, right: usize) !void { // 終止條件 if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 // 劃分階段 - var mid = left + (right - left) / 2; // 計算中點 + var mid = left + (right - left) / 2; // 計算中點 try mergeSort(nums, left, mid); // 遞迴左子陣列 try mergeSort(nums, mid + 1, right); // 遞迴右子陣列 // 合併階段 diff --git a/zh-hant/docs/chapter_array_and_linkedlist/array.md b/zh-hant/docs/chapter_array_and_linkedlist/array.md index c849a642d3..823d0103b8 100755 --- a/zh-hant/docs/chapter_array_and_linkedlist/array.md +++ b/zh-hant/docs/chapter_array_and_linkedlist/array.md @@ -95,9 +95,9 @@ /* 初始化陣列 */ let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0] let slice: &[i32] = &[0; 5]; - // 在 Rust 中,指定長度时([i32; 5])爲陣列,不指定長度時(&[i32])爲切片 + // 在 Rust 中,指定長度時([i32; 5])為陣列,不指定長度時(&[i32])為切片 // 由於 Rust 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 - // Vector 是 Rust 一般情況下用作動態陣列的類型 + // Vector 是 Rust 一般情況下用作動態陣列的型別 // 為了方便實現擴容 extend() 方法,以下將 vector 看作陣列(array) let nums: Vec = vec![1, 3, 2, 5, 4]; ``` diff --git a/zh-hant/docs/chapter_array_and_linkedlist/summary.md b/zh-hant/docs/chapter_array_and_linkedlist/summary.md index 41d7c35596..8b0e496352 100644 --- a/zh-hant/docs/chapter_array_and_linkedlist/summary.md +++ b/zh-hant/docs/chapter_array_and_linkedlist/summary.md @@ -73,4 +73,4 @@ **Q**:初始化串列 `res = [0] * self.size()` 操作,會導致 `res` 的每個元素引用相同的位址嗎? -不會。但二維陣列會有這個問題,例如初始化二維串列 `res = [[0] * self.size()]` ,則多次引用了同一個串列 `[0]` 。 +不會。但二維陣列會有這個問題,例如初始化二維串列 `res = [[0]] * self.size()` ,則多次引用了同一個串列 `[0]` 。 diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.md b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.md index f4c2bda296..56cbc9cc78 100644 --- a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.md +++ b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.md @@ -406,7 +406,27 @@ === "Ruby" ```ruby title="" + ### 回溯演算法框架 ### + def backtrack(state, choices, res) + # 判斷是否為解 + if is_solution?(state) + # 記錄解 + record_solution(state, res) + return + end + # 走訪所有選擇 + for choice in choices + # 剪枝:判斷選擇是否合法 + if is_valid?(state, choice) + # 嘗試:做出選擇,更新狀態 + make_choice(state, choice) + backtrack(state, choices, res) + # 回退:撤銷選擇,恢復到之前的狀態 + undo_choice(state, choice) + end + end + end ``` === "Zig" diff --git a/zh-hant/docs/chapter_computational_complexity/performance_evaluation.md b/zh-hant/docs/chapter_computational_complexity/performance_evaluation.md index a32d8fe8f3..9f101dfa44 100644 --- a/zh-hant/docs/chapter_computational_complexity/performance_evaluation.md +++ b/zh-hant/docs/chapter_computational_complexity/performance_evaluation.md @@ -7,7 +7,7 @@ 也就是說,在能夠解決問題的前提下,演算法效率已成為衡量演算法優劣的主要評價指標,它包括以下兩個維度。 -- **時間效率**:演算法執行速度的快慢。 +- **時間效率**:演算法執行時間的長短。 - **空間效率**:演算法佔用記憶體空間的大小。 簡而言之,**我們的目標是設計“既快又省”的資料結構與演算法**。而有效地評估演算法效率至關重要,因為只有這樣,我們才能將各種演算法進行對比,進而指導演算法設計與最佳化過程。 @@ -18,7 +18,7 @@ 假設我們現在有演算法 `A` 和演算法 `B` ,它們都能解決同一問題,現在需要對比這兩個演算法的效率。最直接的方法是找一臺計算機,執行這兩個演算法,並監控記錄它們的執行時間和記憶體佔用情況。這種評估方式能夠反映真實情況,但也存在較大的侷限性。 -一方面,**難以排除測試環境的干擾因素**。硬體配置會影響演算法的效能。比如在某臺計算機中,演算法 `A` 的執行時間比演算法 `B` 短;但在另一臺配置不同的計算機中,可能得到相反的測試結果。這意味著我們需要在各種機器上進行測試,統計平均效率,而這是不現實的。 +一方面,**難以排除測試環境的干擾因素**。硬體配置會影響演算法的效能表現。比如一個演算法的並行度較高,那麼它就更適合在多核 CPU 上執行,一個演算法的記憶體操作密集,那麼它在高效能記憶體上的表現就會更好。也就是說,演算法在不同的機器上的測試結果可能是不一致的。這意味著我們需要在各種機器上進行測試,統計平均效率,而這是不現實的。 另一方面,**展開完整測試非常耗費資源**。隨著輸入資料量的變化,演算法會表現出不同的效率。例如,在輸入資料量較小時,演算法 `A` 的執行時間比演算法 `B` 短;而在輸入資料量較大時,測試結果可能恰恰相反。因此,為了得到有說服力的結論,我們需要測試各種規模的輸入資料,而這需要耗費大量的計算資源。 @@ -32,8 +32,9 @@ - “隨著輸入資料大小的增加”意味著複雜度反映了演算法執行效率與輸入資料體量之間的關係。 - “時間和空間的增長趨勢”表示複雜度分析關注的不是執行時間或佔用空間的具體值,而是時間或空間增長的“快慢”。 -**複雜度分析克服了實際測試方法的弊端**,體現在以下兩個方面。 +**複雜度分析克服了實際測試方法的弊端**,體現在以下幾個方面。 +- 它無需實際執行程式碼,更加綠色節能。 - 它獨立於測試環境,分析結果適用於所有執行平臺。 - 它可以體現不同資料量下的演算法效率,尤其是在大資料量下的演算法效能。 diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.md b/zh-hant/docs/chapter_computational_complexity/time_complexity.md index 4c23d40021..96f1d7ff3b 100755 --- a/zh-hant/docs/chapter_computational_complexity/time_complexity.md +++ b/zh-hant/docs/chapter_computational_complexity/time_complexity.md @@ -534,7 +534,7 @@ $$ - **時間複雜度能夠有效評估演算法效率**。例如,演算法 `B` 的執行時間呈線性增長,在 $n > 1$ 時比演算法 `A` 更慢,在 $n > 1000000$ 時比演算法 `C` 更慢。事實上,只要輸入資料大小 $n$ 足夠大,複雜度為“常數階”的演算法一定優於“線性階”的演算法,這正是時間增長趨勢的含義。 - **時間複雜度的推算方法更簡便**。顯然,執行平臺和計算操作型別都與演算法執行時間的增長趨勢無關。因此在時間複雜度分析中,我們可以簡單地將所有計算操作的執行時間視為相同的“單位時間”,從而將“計算操作執行時間統計”簡化為“計算操作數量統計”,這樣一來估算難度就大大降低了。 -- **時間複雜度也存在一定的侷限性**。例如,儘管演算法 `A` 和 `C` 的時間複雜度相同,但實際執行時間差別很大。同樣,儘管演算法 `B` 的時間複雜度比 `C` 高,但在輸入資料大小 $n$ 較小時,演算法 `B` 明顯優於演算法 `C` 。在這些情況下,我們很難僅憑時間複雜度判斷演算法效率的高低。當然,儘管存在上述問題,複雜度分析仍然是評判演算法效率最有效且常用的方法。 +- **時間複雜度也存在一定的侷限性**。例如,儘管演算法 `A` 和 `C` 的時間複雜度相同,但實際執行時間差別很大。同樣,儘管演算法 `B` 的時間複雜度比 `C` 高,但在輸入資料大小 $n$ 較小時,演算法 `B` 明顯優於演算法 `C` 。對於此類情況,我們時常難以僅憑時間複雜度判斷演算法效率的高低。當然,儘管存在上述問題,複雜度分析仍然是評判演算法效率最有效且常用的方法。 ## 函式漸近上界 diff --git a/zh-hant/docs/chapter_data_structure/summary.md b/zh-hant/docs/chapter_data_structure/summary.md index c933fddcda..03f326648c 100644 --- a/zh-hant/docs/chapter_data_structure/summary.md +++ b/zh-hant/docs/chapter_data_structure/summary.md @@ -35,7 +35,7 @@ **Q**:原碼轉二補數的方法是“先取反後加 1”,那麼二補數轉原碼應該是逆運算“先減 1 後取反”,而二補數轉原碼也一樣可以透過“先取反後加 1”得到,這是為什麼呢? -**A**:這是因為原碼和二補數的相互轉換實際上是計算“補數”的過程。我們先給出補數的定義:假設 $a + b = c$ ,那麼我們稱 $a$ 是 $b$ 到 $c$ 的補數,反之也稱 $b$ 是 $a$ 到 $c$ 的補數。 +這是因為原碼和二補數的相互轉換實際上是計算“補數”的過程。我們先給出補數的定義:假設 $a + b = c$ ,那麼我們稱 $a$ 是 $b$ 到 $c$ 的補數,反之也稱 $b$ 是 $a$ 到 $c$ 的補數。 給定一個 $n = 4$ 位長度的二進位制數 $0010$ ,如果將這個數字看作原碼(不考慮符號位),那麼它的二補數需透過“先取反後加 1”得到: @@ -63,4 +63,4 @@ $$ 本質上看,“取反”操作實際上是求到 $1111$ 的補數(因為恆有 `原碼 + 一補數 = 1111`);而在一補數基礎上再加 1 得到的二補數,就是到 $10000$ 的補數。 -上述 $n = 4$ 為例,其可推廣至任意位數的二進位制數。 +上述以 $n = 4$ 為例,其可被推廣至任意位數的二進位制數。 diff --git a/zh-hant/docs/chapter_heap/heap.md b/zh-hant/docs/chapter_heap/heap.md index cf7ecb9eeb..7d04a45d35 100644 --- a/zh-hant/docs/chapter_heap/heap.md +++ b/zh-hant/docs/chapter_heap/heap.md @@ -122,17 +122,17 @@ Queue minHeap = new PriorityQueue<>(); // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); - + /* 元素入堆積 */ maxHeap.offer(1); maxHeap.offer(3); maxHeap.offer(2); maxHeap.offer(5); maxHeap.offer(4); - + /* 獲取堆積頂元素 */ int peek = maxHeap.peek(); // 5 - + /* 堆積頂元素出堆積 */ // 出堆積元素會形成一個從大到小的序列 peek = maxHeap.poll(); // 5 @@ -140,13 +140,13 @@ peek = maxHeap.poll(); // 3 peek = maxHeap.poll(); // 2 peek = maxHeap.poll(); // 1 - + /* 獲取堆積大小 */ int size = maxHeap.size(); - + /* 判斷堆積是否為空 */ boolean isEmpty = maxHeap.isEmpty(); - + /* 輸入串列並建堆積 */ minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); ``` @@ -337,7 +337,7 @@ max_heap.push(2); max_heap.push(5); max_heap.push(4); - + /* 獲取堆積頂元素 */ let peek = max_heap.peek().unwrap(); // 5 @@ -373,17 +373,17 @@ var minHeap = PriorityQueue() // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } - + /* 元素入堆積 */ maxHeap.offer(1) maxHeap.offer(3) maxHeap.offer(2) maxHeap.offer(5) maxHeap.offer(4) - + /* 獲取堆積頂元素 */ var peek = maxHeap.peek() // 5 - + /* 堆積頂元素出堆積 */ // 出堆積元素會形成一個從大到小的序列 peek = maxHeap.poll() // 5 @@ -391,13 +391,13 @@ peek = maxHeap.poll() // 3 peek = maxHeap.poll() // 2 peek = maxHeap.poll() // 1 - + /* 獲取堆積大小 */ val size = maxHeap.size - + /* 判斷堆積是否為空 */ val isEmpty = maxHeap.isEmpty() - + /* 輸入串列並建堆積 */ minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) ``` @@ -405,7 +405,7 @@ === "Ruby" ```ruby title="heap.rb" - + # Ruby 未提供內建 Heap 類別 ``` === "Zig" @@ -533,6 +533,6 @@ ## 堆積的常見應用 -- **優先佇列**:堆積通常作為實現優先佇列的首選資料結構,其入列和出列操作的時間複雜度均為 $O(\log n)$ ,而建隊操作為 $O(n)$ ,這些操作都非常高效。 +- **優先佇列**:堆積通常作為實現優先佇列的首選資料結構,其入列和出列操作的時間複雜度均為 $O(\log n)$ ,而建堆積操作為 $O(n)$ ,這些操作都非常高效。 - **堆積排序**:給定一組資料,我們可以用它們建立一個堆積,然後不斷地執行元素出堆積操作,從而得到有序資料。然而,我們通常會使用一種更優雅的方式實現堆積排序,詳見“堆積排序”章節。 - **獲取最大的 $k$ 個元素**:這是一個經典的演算法問題,同時也是一種典型應用,例如選擇熱度前 10 的新聞作為微博熱搜,選取銷量前 10 的商品等。 diff --git a/zh-hant/docs/chapter_introduction/what_is_dsa.md b/zh-hant/docs/chapter_introduction/what_is_dsa.md index 953b0b7a62..24d5db0e44 100644 --- a/zh-hant/docs/chapter_introduction/what_is_dsa.md +++ b/zh-hant/docs/chapter_introduction/what_is_dsa.md @@ -10,7 +10,7 @@ ## 資料結構定義 -資料結構(data structure)是計算機中組織和儲存資料的方式,具有以下設計目標。 +資料結構(data structure)是組織和儲存資料的方式,涵蓋資料內容、資料之間關係和資料操作方法,它具有以下設計目標。 - 空間佔用儘量少,以節省計算機記憶體。 - 資料操作儘可能快速,涵蓋資料訪問、新增、刪除、更新等。 diff --git a/zh-hant/docs/index.html b/zh-hant/docs/index.html index e28b7eb866..578ddaca81 100644 --- a/zh-hant/docs/index.html +++ b/zh-hant/docs/index.html @@ -314,6 +314,13 @@

程式碼審閱者


JS, TS
+
+ + Reviewer: khoaxuantu +
khoaxuantu +
Ruby +
+
Reviewer: krahets