Skip to content

Commit

Permalink
Merge branch 'krahets:main' into yuelinxin-en
Browse files Browse the repository at this point in the history
  • Loading branch information
yuelinxin authored Feb 15, 2025
2 parents 0a0b7ca + 9dfd021 commit 983abff
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 103 deletions.
7 changes: 3 additions & 4 deletions codes/rust/chapter_array_and_linkedlist/my_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ struct MyList {
impl MyList {
/* 构造方法 */
pub fn new(capacity: usize) -> Self {
let mut vec = Vec::new();
vec.resize(capacity, 0);
let mut vec = vec![0; capacity];
Self {
arr: vec,
capacity,
Expand Down Expand Up @@ -92,7 +91,7 @@ impl MyList {
};
let num = self.arr[index];
// 将将索引 index 之后的元素都向前移动一位
for j in (index..self.size - 1) {
for j in index..self.size - 1 {
self.arr[j] = self.arr[j + 1];
}
// 更新元素数量
Expand All @@ -111,7 +110,7 @@ impl MyList {
}

/* 将列表转换为数组 */
pub fn to_array(&mut self) -> Vec<i32> {
pub fn to_array(&self) -> Vec<i32> {
// 仅转换有效长度范围内的列表元素
let mut arr = Vec::new();
for i in 0..self.size {
Expand Down
18 changes: 11 additions & 7 deletions codes/rust/chapter_stack_and_queue/linkedlist_deque.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ impl<T: Copy> LinkedListDeque<T> {
}
}
self.que_size -= 1; // 更新队列长度
Rc::try_unwrap(old_front).ok().unwrap().into_inner().val
old_front.borrow().val
})
}
// 队尾出队操作
Expand All @@ -136,7 +136,7 @@ impl<T: Copy> LinkedListDeque<T> {
}
}
self.que_size -= 1; // 更新队列长度
Rc::try_unwrap(old_rear).ok().unwrap().into_inner().val
old_rear.borrow().val
})
}
}
Expand All @@ -163,12 +163,16 @@ impl<T: Copy> LinkedListDeque<T> {

/* 返回数组用于打印 */
pub fn to_array(&self, head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {
if let Some(node) = head {
let mut nums = self.to_array(node.borrow().next.as_ref());
nums.insert(0, node.borrow().val);
return nums;
let mut res: Vec<T> = Vec::new();
fn recur<T: Copy>(cur: Option<&Rc<RefCell<ListNode<T>>>>, res: &mut Vec<T>) {
if let Some(cur) = cur {
res.push(cur.borrow().val);
recur(cur.borrow().next.as_ref(), res);
}
}
return Vec::new();

recur(head, &mut res);
res
}
}

Expand Down
18 changes: 12 additions & 6 deletions codes/rust/chapter_stack_and_queue/linkedlist_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl<T: Copy> LinkedListQueue<T> {
}
}
self.que_size -= 1;
Rc::try_unwrap(old_front).ok().unwrap().into_inner().val
old_front.borrow().val
})
}

Expand All @@ -78,12 +78,18 @@ impl<T: Copy> LinkedListQueue<T> {

/* 将链表转化为 Array 并返回 */
pub fn to_array(&self, head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {
if let Some(node) = head {
let mut nums = self.to_array(node.borrow().next.as_ref());
nums.insert(0, node.borrow().val);
return nums;
let mut res: Vec<T> = Vec::new();

fn recur<T: Copy>(cur: Option<&Rc<RefCell<ListNode<T>>>>, res: &mut Vec<T>) {
if let Some(cur) = cur {
res.push(cur.borrow().val);
recur(cur.borrow().next.as_ref(), res);
}
}
return Vec::new();

recur(head, &mut res);

res
}
}

Expand Down
12 changes: 3 additions & 9 deletions codes/rust/chapter_stack_and_queue/linkedlist_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,10 @@ impl<T: Copy> LinkedListStack<T> {
/* 出栈 */
pub fn pop(&mut self) -> Option<T> {
self.stack_peek.take().map(|old_head| {
match old_head.borrow_mut().next.take() {
Some(new_head) => {
self.stack_peek = Some(new_head);
}
None => {
self.stack_peek = None;
}
}
self.stack_peek = old_head.borrow_mut().next.take();
self.stk_size -= 1;
Rc::try_unwrap(old_head).ok().unwrap().into_inner().val

old_head.borrow().val
})
}

Expand Down
4 changes: 2 additions & 2 deletions en/docs/chapter_divide_and_conquer/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

!!! abstract

Difficult problems are decomposed layer by layer, each decomposition making them simpler.
Difficult problems are decomposed layer by layer, with each decomposition making them simpler.

Divide and conquer reveals an important truth: start with simplicity, and nothing is complex anymore.
Divide and conquer unveils a profound truth: begin with simplicity, and complexity dissolves.
18 changes: 9 additions & 9 deletions en/docs/chapter_divide_and_conquer/summary.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Summary

- Divide and conquer is a common algorithm design strategy, which includes dividing (partitioning) and conquering (merging) two stages, usually implemented based on recursion.
- The basis for judging whether it is a divide and conquer algorithm problem includes: whether the problem can be decomposed, whether the subproblems are independent, and whether the subproblems can be merged.
- Merge sort is a typical application of the divide and conquer strategy, which recursively divides the array into two equal-length subarrays until only one element remains, and then starts merging layer by layer to complete the sorting.
- Introducing the divide and conquer strategy can often improve algorithm efficiency. On one hand, the divide and conquer strategy reduces the number of operations; on the other hand, it is conducive to parallel optimization of the system after division.
- 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 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.
- Divide and conquer is a common algorithm design strategy that consists of two stages—divide (partition) and conquer (merge)—and is generally implemented using recursion.
- To determine whether a problem is suited for a divide and conquer approach, we check if the problem can be decomposed, whether the subproblems are independent, and whether the subproblems can be merged.
- Merge sort is a typical example of the divide and conquer strategy. It recursively splits an array into two equal-length subarrays until only one element remains, and then merges these subarrays layer by layer to complete the sorting.
- Introducing the divide and conquer strategy often improves algorithm efficiency. On one hand, it reduces the number of operations; on the other hand, it facilitates parallel optimization of the system after division.
- Divide and conquer can be applied to numerous algorithmic problems and is widely used in data structures and algorithm design, appearing in many scenarios.
- Compared to brute force search, adaptive search is more efficient. Search algorithms with a time complexity of $O(\log n)$ are typically based on the divide and conquer strategy.
- Binary search is another classic application of the divide-and-conquer strategy. It does not involve merging subproblem solutions and can be implemented via a recursive divide-and-conquer approach.
- In the problem of constructing binary trees, building the tree (the original problem) can be divided into building the left subtree and right subtree (the subproblems). This can be achieved by partitioning the index ranges of the preorder and inorder traversals.
- In the Tower of Hanoi problem, a problem of size $n$ can be broken down into two subproblems of size $n-1$ and one subproblem of size $1$. By solving these three subproblems in sequence, the original problem is resolved.
26 changes: 13 additions & 13 deletions en/docs/chapter_searching/binary_search_edge.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

Given a sorted array `nums` of length $n$, which may contain duplicate elements, return the index of the leftmost element `target`. If the element is not present in the array, return $-1$.

Recall the method of binary search for an insertion point, after the search is completed, $i$ points to the leftmost `target`, **thus searching for the insertion point is essentially searching for the index of the leftmost `target`**.
Recalling the method of binary search for an insertion point, after the search is completed, the index $i$ will point to the leftmost occurrence of `target`. Therefore, **searching for the insertion point is essentially the same as finding the index of the leftmost `target`**.

Consider implementing the search for the left boundary using the function for finding an insertion point. Note that the array might not contain `target`, which could lead to the following two results:
We can use the function for finding an insertion point to find the left boundary of `target`. Note that the array might not contain `target`, which could lead to the following two results:

- The index $i$ of the insertion point is out of bounds.
- The element `nums[i]` is not equal to `target`.
Expand All @@ -21,27 +21,27 @@ In these cases, simply return $-1$. The code is as follows:

## Find the right boundary

So how do we find the rightmost `target`? The most straightforward way is to modify the code, replacing the pointer contraction operation in the case of `nums[m] == target`. The code is omitted here, but interested readers can implement it on their own.
How do we find the rightmost occurrence of `target`? The most straightforward way is to modify the traditional binary search logic by changing how we adjust the search boundaries in the case of `nums[m] == target`. The code is omitted here. If you are interested, try to implement the code on your own.

Below we introduce two more cunning methods.
Below we are going to introduce two more ingenious methods.

### Reusing the search for the left boundary
### Reuse the left boundary search

In fact, we can use the function for finding the leftmost element to find the rightmost element, specifically by **transforming the search for the rightmost `target` into a search for the leftmost `target + 1`**.
To find the rightmost occurrence of `target`, we can reuse the function used for locating the leftmost `target`. Specifically, we transform the search for the rightmost target into a search for the leftmost target + 1.

As shown in the figure below, after the search is completed, the pointer $i$ points to the leftmost `target + 1` (if it exists), while $j$ points to the rightmost `target`, **thus returning $j$ is sufficient**.
As shown in the figure below, after the search is complete, pointer $i$ will point to the leftmost `target + 1` (if exists), while pointer $j$ will point to the rightmost occurrence of `target`. Therefore, returning $j$ will give us the right boundary.

![Transforming the search for the right boundary into the search for the left boundary](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png)

Please note, the insertion point returned is $i$, therefore, it should be subtracted by $1$ to obtain $j$:
Note that the insertion point returned is $i$, therefore, it should be subtracted by $1$ to obtain $j$:

```src
[file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge}
```

### Transforming into an element search
### Transform into an element search

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

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

Expand All @@ -50,7 +50,7 @@ Thus, as shown in the figure below, we can construct an element that does not ex

![Transforming the search for boundaries into the search for an element](binary_search_edge.assets/binary_search_edge_by_element.png)

The code is omitted here, but two points are worth noting.
The code is omitted here, but here are two important points to note about this approach.

- The given array does not contain decimals, meaning we do not need to worry about how to handle equal situations.
- Since this method introduces decimals, the variable `target` in the function needs to be changed to a floating point type (no change needed in Python).
- The given array `nums` does not contain decimal, so handling equal cases is not a concern.
- However, introducing decimals in this approach requires modifying the `target` variable to a floating-point type (no change needed in Python).
Loading

0 comments on commit 983abff

Please sign in to comment.