mirror of
https://github.com/krahets/hello-algo.git
synced 2026-08-10 19:10:57 +00:00
build
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 4.6 Exercises
|
||||
|
||||
## 4.6.1 Concept Review
|
||||
|
||||
### 1. How Arrays and Linked Lists Find an Element
|
||||
|
||||
An array and a singly linked list both store `[A, B, C, D, E]` in order. You now need to access the fourth element, `D`:
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Which index can the array use directly?
|
||||
2. Starting from the head node `A`, which nodes are visited in order as you follow `next`?
|
||||
3. As the requested element moves farther toward the end, how does the number of steps change for each structure? Which structure is better for repeated access by position, and why?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. With zero-based indexing, the fourth element has index 3, so the array can access `arr[3]` directly.
|
||||
|
||||
2. The singly linked list must start at the head. Its access path is `A → B → C → D`, requiring three moves through `next`.
|
||||
|
||||
3. An array can locate an element directly from its starting address and index, so access by position has a time complexity of $O(1)$.
|
||||
To access the $k$th node, a singly linked list must start at the head node and follow `next` $k-1$ times,
|
||||
which takes $O(n)$ time in the worst case.
|
||||
|
||||
This comparison concerns only access by position; it does not mean that linked lists are slower for every operation.
|
||||
|
||||
### 2. How Arrays and Linked Lists Insert an Element
|
||||
|
||||
An array and a singly linked list both store `A, B, C, D`. You now need to insert `X` after `B`:
|
||||
|
||||
- The array has capacity 5 and is currently `[A, B, C, D, _]`.
|
||||
- The linked list is `A → B → C → D`, and you already have a reference to node `B`.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Which elements must the array move? Write the array after insertion.
|
||||
2. In what order should the linked list update `X.next` and `B.next`? Write the linked list after insertion.
|
||||
3. When comparing insertion efficiency, why is it important to state that "you already have a reference to node B"?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The array first moves `D` one position to the right, then moves `C` one position to the right, and finally places `X` at index 2,
|
||||
producing `[A, B, X, C, D]`.
|
||||
|
||||
2. `B.next` originally points to `C`. First set `X.next = B.next`, making `X` point to `C`;
|
||||
then set `B.next = X`. The result is `A → B → X → C → D`.
|
||||
If `B.next` is overwritten first without saving the original link, `C` may become unreachable.
|
||||
|
||||
3. Once the location of `B` is known, insertion into the linked list only changes two links and takes $O(1)$ time.
|
||||
If `B` must first be found from the head, that search alone may take $O(n)$ time.
|
||||
|
||||
### 3. How a List's Capacity Grows
|
||||
|
||||
An array-based list currently contains `[A, B, C]`, with length `size = 3` and capacity `capacity = 4`.
|
||||
When there is not enough capacity, a new array with twice the old capacity is created.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. After appending `D`, what are the list's length and capacity? Is capacity expansion needed?
|
||||
2. When `E` is appended next, what does the capacity become? How many existing elements must be copied?
|
||||
3. The length of the underlying array cannot change. Why does the list's capacity nevertheless appear able to grow?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. `D` fits in the last empty position. The contents are now `[A, B, C, D]`,
|
||||
with `size = 4` and `capacity = 4`, so no capacity expansion is needed.
|
||||
|
||||
2. There is no empty position when `E` is appended, so a new array with capacity 8 must be created.
|
||||
The four existing elements `A, B, C, D` are copied into it before `E` is added.
|
||||
The result is `size = 5` and `capacity = 8`.
|
||||
|
||||
3. The original array itself does not grow. The list creates a larger new array, copies the existing elements,
|
||||
and then uses the new array as its underlying storage. From the user's perspective, the capacity has grown.
|
||||
|
||||
## 4.6.2 Programming Exercises
|
||||
|
||||
### 1. Add One to a Large Integer Stored as an Array
|
||||
|
||||
The array `digits` stores the decimal digits of a non-negative integer from left to right. For example, `[3, 0, 8]` represents 308.
|
||||
The number 0 is represented by `[0]`; for every other input, the first digit is not 0.
|
||||
|
||||
Simulate decimal column addition to add 1 to this integer, and return the result in the same array format.
|
||||
You may modify `digits` directly. If a new carry appears at the front, you may return a longer array.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. Start with the last digit of the array, just as in ordinary column addition
|
||||
2. If the current digit is less than 9, add one and return immediately
|
||||
3. If the current digit is 9, change it to 0; if every digit is 9, place a 1 at the front
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/plus-one/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
|
||||
### 2. Reverse a Singly Linked List
|
||||
|
||||
You are given the head node `head` of a singly linked list. Each node contains a value and a `next` reference to the following node.
|
||||
|
||||
Use an iterative approach to reverse all links between the nodes, and return the new head node.
|
||||
Do not create any new linked list nodes.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. First draw three connected nodes and the two pointers prev and cur on paper
|
||||
2. Before changing cur.next, save the original next node in nxt
|
||||
3. After reversing cur.next, set prev = cur and then cur = nxt to continue with the next node in the original list
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/reverse-linked-list/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
@@ -20,3 +20,4 @@ icon: material/view-list-outline
|
||||
- [4.3 List](list.md)
|
||||
- [4.4 Random-Access Memory and Cache *](ram_and_cache.md)
|
||||
- [4.5 Summary](summary.md)
|
||||
- [4.6 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 13.6 Exercises
|
||||
|
||||
## 13.6.1 Concept Review
|
||||
|
||||
### 1. Will This Permutation Algorithm Miss Results?
|
||||
|
||||
A backtracking algorithm tries to generate all permutations using `1, 2, 3` in that order. Each time it chooses a number `x`, it:
|
||||
|
||||
1. Appends `x` to the current path.
|
||||
2. Marks `x` as "used."
|
||||
3. Recursively fills the next position.
|
||||
|
||||
When the recursive call returns, the student removes only `x` from the end of the path and then tries the next number.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Which permutation does the algorithm generate first? Can it still generate all 6 permutations?
|
||||
2. Before returning to the previous level, is removing only the last number from the path enough? If not, what else must be done? Explain why.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. It first generates `[1, 2, 3]`, but it cannot generate all the permutations. Although the path becomes shorter when calls return,
|
||||
the markers for 1, 2, and 3 all remain "used," leaving no available number for later branches.
|
||||
|
||||
2. It is not enough. After removing `x` from the end of the path, the algorithm must also mark `x` as "unused" again.
|
||||
The current path and the used markers together describe the search state. A choice changes both of them, so backtracking must restore both
|
||||
before another branch can choose `x` again.
|
||||
|
||||
### 2. Does the Order of Choosing Numbers Matter?
|
||||
|
||||
You are given the sorted array `[2, 3, 5]` and target value 5. Each number may be chosen repeatedly.
|
||||
The algorithm requires the numbers along each search path to appear only in nondecreasing order.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Which distinct combinations can be found?
|
||||
2. Why is there no need to search for the same group of numbers in different orders? What does the nondecreasing-order restriction accomplish?
|
||||
3. Suppose the current path is `[3]`, the remaining amount is 2, and the next candidate is 3. Why can the algorithm stop checking all later candidates at this level?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The distinct combinations are `[2, 3]` and `[5]`.
|
||||
|
||||
2. This exercise treats `[2, 3]` and `[3, 2]` as the same combination; the order in which numbers are selected does not create a different answer.
|
||||
Requiring numbers in a path to appear in nondecreasing order lets the search skip duplicate arrangements such as `[3, 2]`.
|
||||
|
||||
3. The remaining amount is 2, while candidate 3 is already greater than 2. Because the array is sorted,
|
||||
all later candidates are even larger and cannot be added to the current combination, so the algorithm can stop checking this level immediately.
|
||||
|
||||
### 3. Where Can the Next Queen Be Placed?
|
||||
|
||||
Place queens row by row on a `4 × 4` chessboard, with both row and column indices starting at 0.
|
||||
Queens have already been placed at `(0, 1)` and `(1, 3)`. The next queen must be placed in row 2.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Which columns are ruled out because they already contain a queen?
|
||||
2. Among the remaining columns, which positions are ruled out because they share a diagonal with an existing queen?
|
||||
3. Which positions in row 2 remain available to try?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. Columns 1 and 3 already contain queens, so positions `(2, 1)` and `(2, 3)` are ruled out.
|
||||
|
||||
2. Among the remaining positions, `(2, 2)` lies on the same diagonal as `(1, 3)`, so it is also ruled out.
|
||||
Position `(2, 0)` shares neither a column nor a diagonal with either existing queen.
|
||||
|
||||
3. The only position to try in row 2 is `(2, 0)`.
|
||||
|
||||
This step shows only that the current placement is valid. If the board cannot be completed later, the algorithm must still backtrack and try an earlier alternative.
|
||||
|
||||
## 13.6.2 Programming Exercises
|
||||
|
||||
### 1. Permutations of Distinct Elements
|
||||
|
||||
The integer array `nums` contains at least one element, and all its elements are distinct.
|
||||
List every possible order formed by using each element exactly once, and return each order as an array.
|
||||
The permutations may appear in any order in the result.
|
||||
Use backtracking, with a Boolean array recording whether the element at each position has already been selected for the current permutation.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. The recursion depth indicates which position of the permutation is currently being filled
|
||||
2. At each level, try only elements that have not yet been used
|
||||
3. When the path's length equals the length of `nums`, add a copy of the path to the answer
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/permutations/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
@@ -20,3 +20,4 @@ icon: material/map-marker-path
|
||||
- [13.3 Subset-Sum Problem](subset_sum_problem.md)
|
||||
- [13.4 N-Queens Problem](n_queens_problem.md)
|
||||
- [13.5 Summary](summary.md)
|
||||
- [13.6 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 2.6 Exercises
|
||||
|
||||
## 2.6.1 Concept Review
|
||||
|
||||
### 1. Time and Space Complexity of Iteration and Recursion
|
||||
|
||||
The two functions below both calculate $1 + 2 + \dots + n$ (assume $n \ge 1$). Set `n` to 4,
|
||||
answer the questions by following the program's actual execution order, and then compare the efficiency of the two approaches.
|
||||
|
||||
```python
|
||||
def sum_iter(n):
|
||||
s = 0
|
||||
for i in range(1, n + 1):
|
||||
s += i
|
||||
return s
|
||||
|
||||
def sum_recur(n):
|
||||
if n == 1:
|
||||
return 1
|
||||
return n + sum_recur(n - 1)
|
||||
```
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. When `sum_iter(4)` runs, what is the value of `s` after each loop iteration?
|
||||
2. When `sum_recur(4)` runs, which function calls occur in order? As the calls return from the deepest level, how is the result obtained?
|
||||
3. What are the time and space complexities of the two approaches? Explain your reasoning using the execution processes from Questions 1 and 2.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The loop variable `i` takes the values `1, 2, 3, 4`. After each iteration, `s` becomes
|
||||
`1, 3, 6, 10`, respectively, so `sum_iter(4)` returns 10.
|
||||
|
||||
2. The function calls occur in this order:
|
||||
`sum_recur(4) → sum_recur(3) → sum_recur(2) → sum_recur(1)`.
|
||||
`sum_recur(1)` returns 1. The remaining calls then obtain `2 + 1 = 3`, `3 + 3 = 6`, and `4 + 6 = 10`, in that order.
|
||||
At the deepest point, all four function calls are still unfinished.
|
||||
|
||||
3. Both functions perform a number of loop iterations or calls proportional to $n$, so both have a time complexity of $O(n)$.
|
||||
Their space complexities differ. The iterative version uses only a constant number of variables, so its space complexity is $O(1)$.
|
||||
In the recursive version, earlier calls must wait for a result before returning, so the call stack holds up to $n$ calls at the same time.
|
||||
Its space complexity is $O(n)$.
|
||||
|
||||
When analyzing space complexity, remember to include the space used by recursive calls as well as the variables written in the code.
|
||||
|
||||
### 2. Time Complexity of Three Code Fragments
|
||||
|
||||
Each of the following code fragments takes a positive integer $n$ as input. Order them from lowest to highest time complexity, and give the complexity of each one.
|
||||
|
||||
```python
|
||||
# Fragment 1
|
||||
s = 0
|
||||
for i in range(n):
|
||||
s += i
|
||||
|
||||
# Fragment 2
|
||||
s = 0
|
||||
for i in range(n):
|
||||
for j in range(i, n):
|
||||
s += j
|
||||
|
||||
# Fragment 3
|
||||
while n > 1:
|
||||
n = n // 2
|
||||
```
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
From lowest to highest, the order is Fragment 3 with $O(\log n)$, Fragment 1 with $O(n)$, and Fragment 2 with $O(n^2)$.
|
||||
Fragment 3 halves $n$ in each iteration, so it runs about $\log_2 n$ times.
|
||||
The loop in Fragment 1 runs exactly $n$ times. The inner loop in Fragment 2 runs
|
||||
$n,n-1,\dots,1$ times, for a total of $n(n+1)/2$, so its time complexity is quadratic.
|
||||
|
||||
### 3. Which Reversal Uses Less Space?
|
||||
|
||||
There are two ways to reverse all the elements in the array `nums`:
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Create a new array `res` of the same length, copy the elements into it in reverse order, and return it.
|
||||
2. Move two indices `i` and `j` inward from the beginning and end, swapping `nums[i]` and `nums[j]` at each step.
|
||||
|
||||
What is the space complexity of each approach? Which one is an "in-place" operation?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. This approach needs an auxiliary array with the same length as the input, so its space complexity is $O(n)$.
|
||||
|
||||
2. This approach uses only two index variables,
|
||||
so its space complexity is $O(1)$. It is an in-place operation.
|
||||
|
||||
Note that an in-place reversal changes the input array,
|
||||
so it should be preferred only when modifying the input is allowed. If the original array must be kept, the copying cost of the first approach is unavoidable.
|
||||
|
||||
## 2.6.2 Programming Exercises
|
||||
|
||||
### 1. Fibonacci Number
|
||||
|
||||
The Fibonacci sequence is defined by $F(0)=0$, $F(1)=1$, and, for $n\ge2$,
|
||||
$F(n)=F(n-1)+F(n-2)$.
|
||||
|
||||
Given a non-negative integer `n`, use a loop to calculate and return $F(n)$. Do not use recursion.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. Handle the cases where n is 0 or 1 separately
|
||||
2. Only the previous two terms are needed to calculate the next term; there is no need to store the entire sequence
|
||||
3. When updating the two variables, take care not to overwrite an old value before it is used
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/fibonacci-number/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
@@ -20,3 +20,4 @@ icon: material/timer-sand
|
||||
- [2.3 Time Complexity](time_complexity.md)
|
||||
- [2.4 Space Complexity](space_complexity.md)
|
||||
- [2.5 Summary](summary.md)
|
||||
- [2.6 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 3.6 Exercises
|
||||
|
||||
## 3.6.1 Concept Review
|
||||
|
||||
### 1. Data Relationships in Everyday Situations
|
||||
|
||||
Based on the relationships among the data, choose one of "linear structure," "tree structure," and "network structure" for each situation below, and explain why:
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Students stand in a line; we consider only each student's immediate neighbors in front of and behind them.
|
||||
2. A school is organized in levels: "school → grade → class."
|
||||
3. City roads connect many intersections. One intersection may lead to several others, and the roads may form cycles.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. This is a linear structure. Except for the first and last students, each person is adjacent to exactly one person in front and one behind, so the relationships extend along a single line.
|
||||
|
||||
2. This is a tree structure. Each class belongs to one grade, and each grade belongs to the school, so the relationships form levels from top to bottom.
|
||||
|
||||
3. This is a network structure. An intersection can connect to several other intersections, and the routes can form cycles, so they cannot be arranged in a single order or a strict hierarchy.
|
||||
|
||||
To identify a structure, first examine the relationships among the elements rather than how much space it uses in memory.
|
||||
|
||||
### 2. Storing a Logical Order in Memory
|
||||
|
||||
Consider two simplified memory layouts for storing the logical order `A → B → C`:
|
||||
|
||||
- Layout A: `A, B, C` are stored in memory cells numbered `20, 21, 22`, respectively.
|
||||
- Layout B: `A, B, C` are stored in memory cells numbered `20, 7, 31`, respectively. `A` records the location of `B`, and `B` records the location of `C`.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Which layout uses contiguous-space storage, and which uses dispersed-space storage?
|
||||
2. Which layout is more like an array, and which is more like a linked list?
|
||||
3. The memory-cell numbers in Layout B are not in increasing order. Why can it still represent the logical order `A → B → C`?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. Layout A uses consecutive memory cells, so it uses contiguous-space storage. The nodes in Layout B are scattered across different locations, so it uses dispersed-space storage.
|
||||
|
||||
2. Layout A is more like an array, and Layout B is more like a linked list.
|
||||
|
||||
3. The logical order is determined by the links recorded between nodes, not by the numerical order of the memory-cell numbers.
|
||||
The location stored by `A` leads to `B`, and the location stored by `B` then leads to `C`, so `A, B, C` can still be visited in order.
|
||||
|
||||
This also shows that logical structure and physical structure are two different ways of viewing the same data.
|
||||
|
||||
### 3. Data Types and Structures in Homework Records
|
||||
|
||||
A study group records, in seating order, whether each of four students submitted their homework:
|
||||
|
||||
`[true, false, true, true]`
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Which basic data type is suitable for each element?
|
||||
2. The four elements are arranged in a line by seating order. What logical structure does this use?
|
||||
3. Suppose the group later records each student's homework score as `[90, 0, 85, 100]`. Does this change the data's "content type" or its "organization"? Explain why.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. Each element represents only "yes" or "no," so the Boolean type `bool` is suitable.
|
||||
|
||||
2. The elements are arranged in seating order, forming a linear structure that can be stored in an array.
|
||||
|
||||
3. The content type changes: the elements change from Boolean values to integers. The organization does not change.
|
||||
The data is still arranged in a line by seating order and can still be stored in an array, which is a linear structure.
|
||||
|
||||
A basic data type describes "what is stored," while a data structure describes "how the data is organized."
|
||||
|
||||
## 3.6.2 Programming Exercises
|
||||
|
||||
### 1. Count the 1s in a Binary Representation
|
||||
|
||||
Given a non-negative integer `n`, count the number of 1s in its binary representation.
|
||||
|
||||
Use bitwise operations. Do not convert the binary representation to a string or use a built-in function that directly counts 1s.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. n & 1 extracts the rightmost bit of n, which tells you whether that bit is 1
|
||||
2. Shifting right by one discards the current rightmost bit; most languages use the operator >>
|
||||
3. After implementing the method that checks and shifts one bit at a time, observe that n & (n - 1) turns the rightmost 1 in n into 0
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/number-of-1-bits/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
@@ -20,3 +20,4 @@ icon: material/shape-outline
|
||||
- [3.3 Number Encoding *](number_encoding.md)
|
||||
- [3.4 Character Encoding *](character_encoding.md)
|
||||
- [3.5 Summary](summary.md)
|
||||
- [3.6 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 12.6 Exercises
|
||||
|
||||
## 12.6.1 Concept Review
|
||||
|
||||
### 1. Which Tasks Are Suitable for Divide and Conquer?
|
||||
|
||||
A student wants to solve each task below by "dividing it into two halves, solving each half separately, and then combining the results."
|
||||
Classify each task as "suitable for divide and conquer," "can use divide and conquer, but it will not reduce the total work," or "the two halves cannot be solved independently," and explain why.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Sort an unsorted array.
|
||||
2. Find the maximum value in an array.
|
||||
3. Execute a sequence of `push(x)` and `pop()` stack operations in order, and output the element returned by each `pop()`.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. Suitable: divide the array in half, sort each half independently, and merge them in $O(n)$ time. This is exactly merge sort.
|
||||
2. Divide and conquer can be used, but it does not reduce the total work. The two halves still require examining all $n$ elements in total,
|
||||
so the time complexity remains $O(n)$, just like a direct scan.
|
||||
3. The two halves cannot be solved independently. The stack's contents at the beginning of the second half depend on the result of executing the first half,
|
||||
so the two halves cannot be completed without knowing each other's results.
|
||||
|
||||
### 2. How Exponentiation by Squaring Reduces Computation
|
||||
|
||||
The recursive function below uses divide and conquer to calculate $x^n$:
|
||||
|
||||
```python
|
||||
def fast_pow(x, n):
|
||||
if n == 0:
|
||||
return 1
|
||||
half = fast_pow(x, n // 2)
|
||||
if n % 2 == 0:
|
||||
return half * half
|
||||
return half * half * x
|
||||
```
|
||||
|
||||
Use it to calculate `fast_pow(3, 5)`:
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. As the recursive calls proceed, which values does the argument `n` take in order?
|
||||
2. Starting from the deepest call, what value does each level return?
|
||||
3. Why should the result be stored in `half` instead of writing `fast_pow(x, n // 2)` twice?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The argument takes the values `5 → 2 → 1 → 0`. The exponent is halved at each step until the base case is reached.
|
||||
|
||||
2. When `n = 0`, the function returns 1. When `n = 1`, it returns $1×1×3=3$.
|
||||
When `n = 2`, it returns $3×3=9$. When `n = 5`, it returns $9×9×3=243$.
|
||||
|
||||
3. If `fast_pow(x, n // 2)` were written once on each side of the multiplication, the two recursive calls would calculate exactly the same subproblem.
|
||||
Storing the result in `half` means that each level makes only one recursive call, so the recursion depth is about $\log n$.
|
||||
Making two calls would cause a great deal of repeated computation.
|
||||
|
||||
### 3. Split Traversal Sequences into Left and Right Subtrees
|
||||
|
||||
A binary tree has no duplicate nodes. Its preorder and inorder traversals are:
|
||||
|
||||
- Preorder: `[A, B, D, E, C]`
|
||||
- Inorder: `[D, B, E, A, C]`
|
||||
|
||||
Split the sequences only once, at the root. You do not need to continue recursively or draw the whole tree:
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Which node is the root?
|
||||
2. Which subsequences of the inorder traversal correspond to the left and right subtrees?
|
||||
3. Which subsequences of the preorder traversal correspond to the left and right subtrees? Which nodes are the root's direct children?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The first node in a preorder traversal is the root, so the root is `A`.
|
||||
|
||||
2. `A` divides the inorder traversal into two parts: `[D, B, E]` for the left subtree and `[C]` for the right subtree.
|
||||
|
||||
3. The left subtree contains 3 nodes, so the 3 preorder elements after the root `A` belong to the left subtree,
|
||||
giving `[B, D, E]`. The remaining `[C]` belongs to the right subtree.
|
||||
Therefore, the root's left child is `B`, and its right child is `C`.
|
||||
|
||||
## 12.6.2 Programming Exercises
|
||||
|
||||
### 1. Exponentiation by Squaring
|
||||
|
||||
Given a real number `x` and an integer `n`, calculate $x^n$ without calling the language's built-in power function.
|
||||
Use recursive divide and conquer: halve the exponent at each step and reuse the result of the subproblem already calculated.
|
||||
This exercise defines $x^0=1$, including when `x = 0`. When `n < 0`, `x != 0` is guaranteed, and the answer can be transformed into $(1/x)^{-n}$.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. When n is 0, the answer is 1
|
||||
2. After calculating x to the power n // 2, store the result in half rather than making the recursive call a second time
|
||||
3. When n < 0, first change x to 1 / x and then change n to -n; in C++ or Java, first convert n to a 64-bit integer to avoid overflow when negating the smallest 32-bit integer
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/powx-n/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
@@ -20,3 +20,4 @@ icon: material/set-split
|
||||
- [12.3 Building a Binary Tree Problem](build_binary_tree_problem.md)
|
||||
- [12.4 Hanota Problem](hanota_problem.md)
|
||||
- [12.5 Summary](summary.md)
|
||||
- [12.6 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 14.8 Exercises
|
||||
|
||||
## 14.8.1 Concept Review
|
||||
|
||||
### 1. When Is Dynamic Programming Appropriate?
|
||||
|
||||
A student says, "Whenever a recurrence can be written, dynamic programming should be used."
|
||||
For each task below, decide whether dynamic programming, backtracking, or a loop or mathematical formula without a `dp` table is more appropriate. Give one key reason.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Using coin denominations `[1, 3, 4]`, make an amount of 6 with the fewest coins. Each denomination may be used repeatedly.
|
||||
2. Output all permutations of `[1, 2, 3]`.
|
||||
3. Calculate $1 + 2 + \dots + n$.
|
||||
|
||||
For the task you consider suitable for dynamic programming, also state what `dp[i]` represents.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. Dynamic programming is suitable. Let `dp[i]` be the minimum number of coins needed to make amount `i`.
|
||||
For every coin `c` that does not exceed `i`, `dp[i-c] + 1` is a candidate answer,
|
||||
and the minimum of these candidates is chosen. Different choices repeatedly encounter the same amounts, and an optimal solution for a larger amount can be built from optimal solutions for smaller amounts.
|
||||
The answer for amount 6 is 2, using `3 + 3`.
|
||||
|
||||
2. Backtracking is suitable. The task requires generating all 6 permutations one by one. Backtracking can systematically make a choice, continue searching,
|
||||
undo the choice, and then try another branch. Regardless of the method, actually outputting every permutation requires enumerating them.
|
||||
|
||||
3. A loop or the arithmetic-series formula is sufficient. Although the recurrence `S(i) = S(i-1) + i` can be written, calculating `S(i)` depends on only one smaller value, `S(i-1)`.
|
||||
Each partial sum needs to be calculated only once, so there are no repeated subproblems and no need for a `dp` table. "A recurrence can be written" does not mean "dynamic programming is needed."
|
||||
|
||||
### 2. Calculating One Cell in a Knapsack Table
|
||||
|
||||
Consider this 0-1 knapsack problem: item weights `wgt = [1, 2, 3]`, values `val = [5, 11, 15]`, and knapsack capacity 4.
|
||||
`dp[i][c]` is the maximum value obtainable using only the first $i$ items with a knapsack capacity limit of $c$;
|
||||
the knapsack does not have to be filled exactly.
|
||||
|
||||
Calculate only the state `dp[3][4]`. You are given `dp[2][4] = 16` and `dp[2][1] = 5`:
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. If the third item is not selected, what is the candidate value?
|
||||
2. If the third item is selected, how much capacity remains, and what is the candidate value?
|
||||
3. What should `dp[3][4]` be? Which items does this value correspond to selecting?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. If the third item is not selected, keep the result from the first two items. The candidate value is `dp[2][4] = 16`.
|
||||
|
||||
2. The third item has weight 3, so capacity $4-3=1$ remains after it is placed in the knapsack. The candidate value is
|
||||
`dp[2][1] + 15 = 5 + 15 = 20`.
|
||||
|
||||
3. Comparing 16 and 20 gives `dp[3][4] = 20`. This corresponds to selecting the first and third items,
|
||||
whose total weight is $1+3=4$ and total value is $5+15=20$.
|
||||
|
||||
This state calculation demonstrates one "select or do not select" comparison in the 0-1 knapsack problem.
|
||||
|
||||
### 3. In Which Order Should Knapsack Capacities Be Updated?
|
||||
|
||||
A 0-1 knapsack problem has only one item, with weight 2 and value 5, and the knapsack has capacity 4.
|
||||
The item can be selected at most once. The initial one-dimensional array is `dp = [0, 0, 0, 0, 0]`.
|
||||
|
||||
A student processes the item by updating capacities from 2 to 4:
|
||||
|
||||
- After updating `dp[2]`, its value is 5.
|
||||
- After updating `dp[3]`, its value is also 5.
|
||||
- When updating `dp[4]`, the student uses the newly obtained `dp[2]`, producing `dp[4] = 10`.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Is `dp[4] = 10` correct? Why or why not?
|
||||
2. Given that each item may be selected at most once, what should `dp[4]` be?
|
||||
3. When processing each item, should capacities be updated from largest to smallest or from smallest to largest? What problem does this avoid?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The result is incorrect. A value of 10 is equivalent to placing the item with value 5 into the knapsack twice,
|
||||
violating the condition that each item may be selected at most once.
|
||||
|
||||
2. The knapsack can contain at most this one item, so the correct value of `dp[4]` is 5.
|
||||
|
||||
3. Capacities should be updated from largest to smallest, in the order 4, 3, 2.
|
||||
Then, when calculating `dp[c]`, the value read from `dp[c-2]` still comes from before the current item was processed,
|
||||
preventing the current item from being reused during the same round.
|
||||
|
||||
## 14.8.2 Programming Exercises
|
||||
|
||||
### 1. Number of Ways to Climb Stairs
|
||||
|
||||
A staircase has `n` steps. Each move climbs either 1 or 2 steps, and you must land exactly on step `n`.
|
||||
Calculate the number of distinct ways to reach the top. Assume `n >= 1`; ways are distinguished only by their sequence of 1-step and 2-step moves.
|
||||
Use a one-dimensional dynamic programming array. For now, do not use the space optimization that keeps only two states.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. The last move to step i can cover only 1 or 2 steps
|
||||
2. Therefore, dp[i] = dp[i-1] + dp[i-2]
|
||||
3. Handle the cases where n is 1 or 2 first, then fill the table starting from step 3
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/climbing-stairs/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
|
||||
### 2. 0-1 Knapsack
|
||||
|
||||
You are given equal-length arrays `wgt` and `val`. Item `i` has positive integer weight `wgt[i]` and non-negative integer value `val[i]`.
|
||||
The knapsack capacity `cap` is a non-negative integer. Each item may be selected at most once. Find the maximum total value that can be placed in the knapsack
|
||||
without exceeding `cap`. Use one-dimensional dynamic programming.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. Initialize an array dp of length cap + 1, where dp[c] is the maximum value for a capacity limit of c
|
||||
2. When processing item i, compare dp[c], which does not select it, with dp[c-wgt[i]] + val[i], which does
|
||||
3. Update capacities from largest to smallest to avoid selecting the current item repeatedly in the same round
|
||||
@@ -22,3 +22,4 @@ icon: material/table-pivot
|
||||
- [14.5 Unbounded Knapsack Problem](unbounded_knapsack_problem.md)
|
||||
- [14.6 Edit Distance Problem](edit_distance_problem.md)
|
||||
- [14.7 Summary](summary.md)
|
||||
- [14.8 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 9.5 Exercises
|
||||
|
||||
## 9.5.1 Concept Review
|
||||
|
||||
### 1. Represent the Same Graph in Two Ways
|
||||
|
||||
An undirected graph has four vertices, `A, B, C, D`, and the edges
|
||||
`A-B, A-C, B-C, C-D`.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Write its adjacency list.
|
||||
2. Fill in its adjacency matrix using only 0s and 1s.
|
||||
3. To determine whether `A` and `D` are directly connected, which graph representation requires checking only one stored entry?
|
||||
4. If a graph has many vertices but few edges, which representation usually uses less space?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The adjacency list is:
|
||||
|
||||
```text
|
||||
A: B, C
|
||||
B: A, C
|
||||
C: A, B, D
|
||||
D: C
|
||||
```
|
||||
|
||||
2. The adjacency matrix is:
|
||||
|
||||
| | A | B | C | D |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| A | 0 | 1 | 1 | 0 |
|
||||
| B | 1 | 0 | 1 | 0 |
|
||||
| C | 1 | 1 | 0 | 1 |
|
||||
| D | 0 | 0 | 1 | 0 |
|
||||
|
||||
3. In an adjacency matrix, you can directly check row `A`, column `D`, making it well suited to determining whether any two vertices are directly connected.
|
||||
|
||||
4. When a graph has many vertices but few edges, an adjacency list records only the edges that actually exist. It usually uses less space than an adjacency matrix, which reserves a position for every pair of vertices.
|
||||
|
||||
### 2. Breadth-First and Depth-First Traversal Orders
|
||||
|
||||
An undirected graph has vertices `A, B, C, D, E` and edges
|
||||
`A-B, A-C, B-D, C-D, D-E`.
|
||||
|
||||
Start at A. Whenever there are several unvisited adjacent vertices, choose them in alphabetical order:
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Write the visit order of breadth-first traversal (BFS).
|
||||
2. Write the visit order of recursive depth-first traversal (DFS).
|
||||
3. Why must both traversals record which vertices have already been visited?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The BFS visit order is `A, B, C, D, E`. It first visits B and C, which are one edge away from A,
|
||||
and then visits the more distant D and E.
|
||||
|
||||
2. The DFS visit order is `A, B, D, C, E`. It repeatedly enters an unvisited adjacent vertex,
|
||||
first following `A → B → D → C`. When C has no new adjacent vertex, it returns to D and then visits E.
|
||||
|
||||
3. The graph contains a cycle, such as `A-B-D-C-A`. Without recording visited vertices,
|
||||
a traversal could repeatedly visit the same vertices around the cycle and fail to terminate normally.
|
||||
|
||||
### 3. Can One BFS Visit the Entire Graph?
|
||||
|
||||
An undirected graph has vertices `A, B, C, D, E, F` and only the edges
|
||||
`A-B, B-C, D-E`.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Which vertices can one BFS starting at A visit?
|
||||
2. Based on Question 1, has this BFS visited every vertex in the graph? Why or why not?
|
||||
3. Suppose you scan all vertices in alphabetical order and start a new BFS whenever you reach an unvisited vertex.
|
||||
What is the starting vertex of each BFS? Into how many mutually disconnected parts (connected components) is the graph divided?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. Starting from A, the traversal can visit only `A, B, C`.
|
||||
|
||||
2. It has not visited every vertex. `D, E` form another connected part, while F is an isolated vertex.
|
||||
None of them has a path to A, so they cannot be reached from A.
|
||||
|
||||
3. The three BFS traversals start at `A, D, F`, and visit
|
||||
`{A, B, C}`, `{D, E}`, and `{F}`, respectively. Therefore, the graph has 3 connected components.
|
||||
|
||||
## 9.5.2 Programming Exercises
|
||||
|
||||
### 1. Determine Whether a Path Exists in an Undirected Graph
|
||||
|
||||
You are given an undirected graph with $n$ vertices numbered from $0$ to $n-1$. Each entry `[u, v]` in the array `edges` represents an undirected edge between vertices `u` and `v`.
|
||||
|
||||
You are also given a starting vertex `source` and a destination vertex `destination`. First build an adjacency list from `edges`, then use BFS or DFS
|
||||
to determine whether a path exists from `source` to `destination`. Return `true` if one exists and `false` otherwise.
|
||||
The graph may contain cycles and may be disconnected.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. Add every undirected edge in both directions
|
||||
2. The graph may contain cycles, so you must record which vertices have already been visited
|
||||
3. Starting from source, return true if you encounter destination; if the traversal ends without reaching it, return false
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/find-if-path-exists-in-graph/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
@@ -19,3 +19,4 @@ icon: material/graphql
|
||||
- [9.2 Basic Operations on Graphs](graph_operations.md)
|
||||
- [9.3 Graph Traversal](graph_traversal.md)
|
||||
- [9.4 Summary](summary.md)
|
||||
- [9.5 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 15.6 Exercises
|
||||
|
||||
## 15.6.1 Concept Review
|
||||
|
||||
### 1. Is Choosing the Largest Coin Always Best?
|
||||
|
||||
The coin denominations are `[1, 7, 10]`, and the target amount is 14.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Follow the rule "always choose the largest denomination that does not exceed the remaining amount," and write the coins selected.
|
||||
2. Is there a solution using fewer coins? If so, give one; otherwise, explain why not.
|
||||
3. Does this example show that the greedy strategy is correct for every set of coin denominations?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The greedy strategy selects `10 + 1 + 1 + 1 + 1`, using 5 coins in total.
|
||||
|
||||
2. There is a solution using fewer coins: `7 + 7`, which uses only 2 coins.
|
||||
|
||||
3. No. This counterexample shows that, for arbitrary coin denominations, repeatedly choosing the largest currently available denomination does not necessarily minimize the number of coins.
|
||||
The largest immediate choice may prevent a better combination later.
|
||||
|
||||
### 2. Which Item Should Go into the Knapsack First?
|
||||
|
||||
A knapsack with a capacity of 4 kilograms can hold the following items. A fraction of an item may be taken,
|
||||
and the value obtained is proportional to its weight:
|
||||
|
||||
- Item A: weight 4 kilograms, value 20.
|
||||
- Item B: weight 3 kilograms, value 18.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. What is the value per kilogram of each item? Which item should be placed in the knapsack first?
|
||||
2. Fill the knapsack using the greedy strategy for the fractional knapsack problem. What is the final value?
|
||||
3. When items can be divided and the knapsack limits total weight, should items be compared by total value or by value per kilogram? Why?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. A has value `20 ÷ 4 = 5` per kilogram, while B has value `18 ÷ 3 = 6` per kilogram,
|
||||
so B, with the higher value per unit weight, should be placed in the knapsack first.
|
||||
|
||||
2. First take all of B, using 3 kilograms of capacity and gaining a value of 18. With 1 kilogram of capacity remaining,
|
||||
take 1 kilogram of A, gaining a value of 5. The final value is `18 + 5 = 23`.
|
||||
|
||||
3. The knapsack limits total weight, and items can be divided, so they should be compared by value per unit weight.
|
||||
Although A has a higher total value, its value per kilogram is lower than B's. Filling the knapsack with A first would yield only a value of 20.
|
||||
|
||||
### 3. Which Pointer Should Move Next?
|
||||
|
||||
The partition heights are `[1, 8, 6, 2, 5]`. Use two pointers, one at each end, to find the maximum capacity.
|
||||
The capacity equals "the height of the shorter partition × the distance between the partitions' indices."
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Initially, the left pointer is at index 0 and the right pointer is at index 4. What is the current capacity? Which pointer should move next?
|
||||
2. After making the move chosen in Question 1, at which indices are the two pointers? What is the capacity now? Which pointer should move next?
|
||||
3. For the current pair of partitions, you could move either the pointer at the shorter partition or the pointer at the taller partition. Which move could still produce a greater capacity, and why?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The current capacity is `min(1, 5) × (4 - 0) = 4`. The left partition is shorter, so move the left pointer.
|
||||
|
||||
2. After the left pointer moves, the pointers are at indices 1 and 4. The current capacity is
|
||||
`min(8, 5) × (4 - 1) = 15`. The right partition is shorter, so move the right pointer next.
|
||||
|
||||
3. Moving the pointer at the shorter partition is the only move that could still produce a greater capacity. If the taller partition's pointer moves, the distance certainly decreases while the height remains limited by the unmoved shorter partition,
|
||||
so the capacity can only stay the same or decrease. Only by moving the shorter partition can a taller partition possibly be found.
|
||||
|
||||
## 15.6.2 Programming Exercises
|
||||
|
||||
### 1. Fractional Knapsack
|
||||
|
||||
You are given equal-length arrays `wgt` and `val`, where `wgt[i] > 0` and `val[i] >= 0`. The knapsack has capacity `cap >= 0`.
|
||||
There is only one of each item, but any fraction of an item may be placed in the knapsack.
|
||||
The value obtained is proportional to the fraction of the item's total weight that is included. Use a greedy algorithm
|
||||
and return the maximum total value the knapsack can hold as a real number.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. First calculate each item's value per unit weight as val[i] / wgt[i], keeping the fractional part of the division
|
||||
2. Place items with higher value per unit weight into the knapsack first
|
||||
3. If the remaining capacity is less than the current item's weight, take exactly the fraction that fills the knapsack and stop
|
||||
@@ -20,3 +20,4 @@ icon: material/head-heart-outline
|
||||
- [15.3 Maximum Capacity Problem](max_capacity_problem.md)
|
||||
- [15.4 Maximum Product Cutting Problem](max_product_cutting_problem.md)
|
||||
- [15.5 Summary](summary.md)
|
||||
- [15.6 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 6.5 Exercises
|
||||
|
||||
## 6.5.1 Concept Review
|
||||
|
||||
### 1. Searching After a Hash Collision
|
||||
|
||||
A hash table has 5 buckets and uses the hash function $h(x)=x \bmod 5$. When a collision occurs, elements are placed one after another in a list within that bucket.
|
||||
Insert `[1, 6, 11, 7]` in order:
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Write the contents of buckets 0–4.
|
||||
2. When searching for 6, which bucket is checked first, and which elements are examined in order?
|
||||
3. Based on the bucket contents from Question 1, do later insertions overwrite earlier ones? Explain using this collision-resolution method.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. Because $1\bmod5=6\bmod5=11\bmod5=1$, while $7\bmod5=2$, the buckets are:
|
||||
|
||||
```text
|
||||
0: []
|
||||
1: [1, 6, 11]
|
||||
2: [7]
|
||||
3: []
|
||||
4: []
|
||||
```
|
||||
|
||||
2. A search for 6 first goes to bucket 1, then compares 1 and 6 in order. The target is found on the second comparison.
|
||||
|
||||
3. Equal hash values mean only that the elements go into the same bucket, not that the elements are equal. Separate chaining keeps all colliding elements in the bucket
|
||||
and compares them one at a time during a search, so 1, 6, and 11 do not overwrite one another.
|
||||
|
||||
### 2. Where Do Elements Go After a Hash Table Expands?
|
||||
|
||||
A hash table using separate chaining originally has 5 buckets and the hash function $h(x)=x\bmod5$.
|
||||
The keys `[1, 6, 11]` are all in bucket 1.
|
||||
|
||||
The table is now expanded to 7 buckets, and the hash function becomes $h(x)=x\bmod7$:
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Calculate the new bucket number for 1, 6, and 11.
|
||||
2. Which buckets contain elements after the expansion?
|
||||
3. During the expansion, can the list from the old bucket 1 simply be copied into the new bucket 1? Explain using the results from Questions 1 and 2.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The new bucket numbers are:
|
||||
|
||||
- $1\bmod7=1$;
|
||||
- $6\bmod7=6$;
|
||||
- $11\bmod7=4$.
|
||||
|
||||
2. Bucket 1 stores 1, bucket 4 stores 11, and bucket 6 stores 6. The three keys are no longer crowded into the same bucket.
|
||||
|
||||
3. The list cannot be copied as is. A bucket number is calculated by taking the key modulo the number of buckets. When the bucket count changes from 5 to 7, a key's bucket number may change,
|
||||
so the position of every key must be recalculated. If the old bucket 1 were copied unchanged, later searches using the new formula would go to bucket 6 for 6 and bucket 4 for 11
|
||||
and would fail to find them.
|
||||
|
||||
### 3. Can 11 Still Be Found After Deleting 6?
|
||||
|
||||
A hash table has 5 positions with indices `0–4` and uses the hash function $h(x)=x\bmod5$.
|
||||
When a collision occurs, it searches to the right from the index produced by the hash function for the first empty position.
|
||||
|
||||
Insert `[1, 6, 11]` in order:
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. At which index is each number ultimately stored?
|
||||
2. When searching for 11, which indices are examined in order?
|
||||
3. Suppose deleting 6 changes its position directly to an "unused empty position," and a search stops whenever it reaches an empty position.
|
||||
What happens when searching for 11 afterward? Is this search result correct? If there is a problem, how can it be avoided?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The number 1 is stored at index 1. The number 6 also maps to index 1, so after the collision it is stored at index 2.
|
||||
The number 11 also starts at index 1, skips the occupied indices 1 and 2, and is ultimately stored at index 3.
|
||||
|
||||
2. A search for 11 examines indices `1, 2, 3` in order and finds it at index 3.
|
||||
|
||||
3. If index 2 is changed to mean "never used," a search for 11 checks index 1 and then stops at index 2,
|
||||
incorrectly concluding that 11 is absent. Deletion should leave a "deleted" marker.
|
||||
A search that reaches this marker continues to the next index (wrapping from index 4 to index 0), while a later insertion may still reuse the position.
|
||||
|
||||
## 6.5.2 Programming Exercises
|
||||
|
||||
### 1. Compare the Character Counts of Two Strings
|
||||
|
||||
Given two strings `s` and `t` containing only lowercase English letters,
|
||||
you may rearrange the characters in `s` in any order, but you may not add, remove, or replace characters.
|
||||
|
||||
Determine whether the rearranged string can form `t`. Return `true` if it can and `false` otherwise.
|
||||
Use a hash table to record how many times each letter occurs. Do not sort the characters in the strings.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. If the two strings have different lengths, they cannot contain each character the same number of times
|
||||
2. Use a hash table to record the count of each letter; increment the corresponding count while scanning s
|
||||
3. Decrement the corresponding count while scanning t; the strings contain each character the same number of times only if every count is 0 at the end
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/valid-anagram/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
@@ -19,3 +19,4 @@ icon: material/table-search
|
||||
- [6.2 Hash Collision](hash_collision.md)
|
||||
- [6.3 Hash Algorithm](hash_algorithm.md)
|
||||
- [6.4 Summary](summary.md)
|
||||
- [6.5 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 8.5 Exercises
|
||||
|
||||
## 8.5.1 Concept Review
|
||||
|
||||
### 1. How Does the Heap Change After Inserting 10?
|
||||
|
||||
The array `[9, 7, 8, 3, 5]` represents a max heap. Now insert the number 10.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. First append 10 to the end of the array. What is the value of its parent node?
|
||||
2. Starting from the new node, perform bottom-to-top heapify and write the array after each swap.
|
||||
3. What is the final top element of the heap? How many swaps occur in total?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. After 10 is appended, its index is 5, so its parent's index is
|
||||
$\lfloor(5-1)/2\rfloor=2$. The parent node's value is 8.
|
||||
|
||||
2. Since 10 is greater than 8, the array after the first swap is `[9, 7, 10, 3, 5, 8]`.
|
||||
Since 10 is also greater than its parent 9, the array after the second swap is `[10, 7, 9, 3, 5, 8]`.
|
||||
The value 10 has now reached the root node, so heapification is complete.
|
||||
|
||||
3. The final top element is 10, and 2 swaps occur in total.
|
||||
|
||||
### 2. Check Parent–Child Relationships in a Min Heap
|
||||
|
||||
The array `[1, 4, 3, 7, 6, 2]` represents a complete binary tree. In a min heap, every parent node must be no greater than its children.
|
||||
For index $i$, the left and right child indices are $2i+1$ and $2i+2$, respectively.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. What are the indices and values of the children of index 2?
|
||||
2. The node at index 2 has value 3. Does it violate the min-heap rule with its child? If so, which two elements should be swapped?
|
||||
3. Based on your answer to Question 2, write the array after the swap if the rule is violated; otherwise, explain why no swap is needed. Finally, check the remaining parent–child relationships.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The left child of index 2 is at index 5 and has value 2. The right child's index is 6, but the array has length 6, so the right child does not exist.
|
||||
|
||||
2. The parent value 3 is greater than the child value 2, violating the min-heap rule. The elements at indices 2 and 5 should be swapped.
|
||||
|
||||
3. After the swap, the array is `[1, 4, 2, 7, 6, 3]`. Check each relationship:
|
||||
`1 ≤ 4`, `1 ≤ 2`; `4 ≤ 7`, `4 ≤ 6`; and `2 ≤ 3`.
|
||||
Every parent is now no greater than its children, so the min-heap rule is satisfied.
|
||||
|
||||
### 3. Keep the Three Largest Numbers with a Min Heap
|
||||
|
||||
To keep the 3 largest numbers from the data stream `[4, 1, 7, 3, 8]`, maintain a min heap containing no more than 3 elements.
|
||||
|
||||
First insert the first 3 numbers into the min heap in order. Once the heap is full, for each new number:
|
||||
if it is greater than the top element, remove the top and insert the new number; otherwise, leave the heap unchanged.
|
||||
|
||||
After each number is read, write the numbers kept in the heap and the top element.
|
||||
Write the kept numbers as a set; you do not need to give their order in the heap array.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
The result after each number is read is:
|
||||
|
||||
| Number read | Numbers kept | Top |
|
||||
| --- | --- | --- |
|
||||
| 4 | `{4}` | 4 |
|
||||
| 1 | `{1, 4}` | 1 |
|
||||
| 7 | `{1, 4, 7}` | 1 |
|
||||
| 3 | `{3, 4, 7}` | 3 |
|
||||
| 8 | `{4, 7, 8}` | 4 |
|
||||
|
||||
Once the heap is full, its top is the smallest of the numbers currently kept. A new number replaces the top only when it is greater than the top.
|
||||
The final set `{4, 7, 8}` contains exactly the 3 largest numbers.
|
||||
|
||||
## 8.5.2 Programming Exercises
|
||||
|
||||
### 1. Find the Kth Largest Element in an Array
|
||||
|
||||
Given an integer array `nums` and an integer $k$, where $1 \le k \le n$ and $n$ is the array's length, return the element that would appear at position $k$ if the array were arranged from largest to smallest.
|
||||
|
||||
Count duplicate elements separately. For example, the second-largest element of `[5, 5, 2]` is still 5. Use a min heap containing no more than $k$ elements.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. The kth largest element is the smallest among the k largest numbers
|
||||
2. Insert each number into the min heap, and remove the smallest value whenever the heap's size exceeds k
|
||||
3. After the traversal, the heap contains the k largest numbers, and its top is the answer
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/kth-largest-element-in-an-array/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
@@ -19,3 +19,4 @@ icon: material/family-tree
|
||||
- [8.2 Heap Construction Operation](build_heap.md)
|
||||
- [8.3 Top-k Problem](top_k.md)
|
||||
- [8.4 Summary](summary.md)
|
||||
- [8.5 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 10.7 Exercises
|
||||
|
||||
## 10.7.1 Concept Review
|
||||
|
||||
### 1. How Binary Search Narrows the Search Interval
|
||||
|
||||
Search for 16 in the sorted array `[2, 5, 8, 12, 16, 23, 38]`.
|
||||
Use the closed interval `[i, j]` and calculate the midpoint as
|
||||
$m=i+(j-i)/2$, rounded down.
|
||||
|
||||
For each round, write `(i, j, m)`, the middle element, and how the interval is narrowed next,
|
||||
until the target is found.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
The search proceeds as follows:
|
||||
|
||||
| Round | `(i, j, m)` | Middle element | Next step |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `(0, 6, 3)` | 12 | `12 < 16`, set `i = 4` |
|
||||
| 2 | `(4, 6, 5)` | 23 | `23 > 16`, set `j = 4` |
|
||||
| 3 | `(4, 4, 4)` | 16 | Target found; return index 4 |
|
||||
|
||||
Because the array is sorted, when the middle value is smaller than the target, the middle and everything to its left can be excluded.
|
||||
When the middle value is greater than the target, the middle and everything to its right can be excluded.
|
||||
|
||||
### 2. Left and Right Boundaries of Duplicate Elements
|
||||
|
||||
Search for the number 2 in the array `[1, 2, 2, 2, 4, 6]`.
|
||||
A student uses binary search, returns immediately after finding the target at index 2, and says,
|
||||
"Index 2 is the left boundary of the number 2."
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Is the student's statement correct? What are the left and right boundaries of 2? Explain why.
|
||||
2. When searching for the left boundary, if the middle element equals the target, which side should be searched next?
|
||||
3. When searching for the right boundary, which side should be searched next? State only the direction; you do not need to write the complete search process.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The student's statement is incorrect. Returning as soon as a 2 is found guarantees only that some occurrence of 2 was found, not the leftmost or rightmost one.
|
||||
Here, the left boundary is index 1 and the right boundary is index 3.
|
||||
|
||||
2. When searching for the left boundary, continue searching on the left even if the middle element equals 2.
|
||||
For example, with a closed interval, set `j = m - 1`.
|
||||
|
||||
3. When searching for the right boundary, continue searching on the right after the middle element equals 2.
|
||||
For example, set `i = m + 1`.
|
||||
|
||||
### 3. Choosing a Search Method for Different Data
|
||||
|
||||
For each situation below, choose an appropriate method from "linear search," "binary search," and "hash table," and explain why:
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Repeatedly search among $10^7$ integers that are already sorted and never change, without building any additional data structure.
|
||||
2. Repeatedly test whether a key exists in a collection with frequent insertions and deletions. The collection need not remain sorted, and no range searches are needed.
|
||||
3. Search an unsorted array for a value only once.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. Binary search: the data is sorted and static, so searching takes $O(\log n)$ time without extra space.
|
||||
2. Hash table: when the hash function distributes keys fairly evenly among the buckets, insertion, deletion, and lookup by key all take $O(1)$ time on average.
|
||||
3. Scan directly from beginning to end. When searching only once, sorting the array or building a hash table still requires processing the entire array first,
|
||||
so neither reduces the total work for this single task.
|
||||
|
||||
The choice depends on whether the data is sorted, whether an additional structure is allowed, how many searches are needed, and which operations must be supported.
|
||||
|
||||
## 10.7.2 Programming Exercises
|
||||
|
||||
### 1. Binary Search in a Sorted Array
|
||||
|
||||
Given an integer array `nums` in strictly increasing order and a target value `target`, use binary search to find `target`. If it exists, return its array index; otherwise, return -1.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. The initial interval is left = 0 and right = n - 1; it is nonempty while left <= right
|
||||
2. Calculate the midpoint with mid = left + (right - left) // 2
|
||||
3. If `nums[mid]` is less than `target`, move the left boundary to `mid + 1`; if `nums[mid]` is greater than `target`, move the right boundary to `mid - 1`; if they are equal, return immediately
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/binary-search/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
|
||||
### 2. Insertion Point in a Sorted Array
|
||||
|
||||
You are given an integer array `nums` in strictly increasing order and a target value `target`.
|
||||
|
||||
Return the index of `target` if it is already in the array. Otherwise, return the insertion point at which `target` can be inserted while keeping the array in strictly increasing order.
|
||||
The insertion point may be 0 or may equal the array's length. Use binary search.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. The answer may be 0 or the array length n
|
||||
2. With a closed interval, if `nums[mid]` is greater than or equal to `target`, set `right = mid - 1` and continue checking farther left; otherwise, set `left = mid + 1`
|
||||
3. When the loop ends, left is the insertion point
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/search-insert-position/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
@@ -21,3 +21,4 @@ icon: material/text-search
|
||||
- [10.4 Hash Optimization Strategy](replace_linear_by_hashing.md)
|
||||
- [10.5 Searching Algorithms Revisited](searching_algorithm_revisited.md)
|
||||
- [10.6 Summary](summary.md)
|
||||
- [10.7 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 11.12 Exercises
|
||||
|
||||
## 11.12.1 Concept Review
|
||||
|
||||
### 1. The First Few Rounds of Selection Sort and Bubble Sort
|
||||
|
||||
Given the array `[4, 2, 5, 1, 3]`, sort it in ascending order in both parts below.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Simulate the first two rounds of selection sort. Write the array after each round and identify which positions are now fixed.
|
||||
2. Simulate the first round of bubble sort. Write the resulting array and the number of swaps, and identify which position is now fixed.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The first two rounds are:
|
||||
|
||||
| Round | Array | Explanation |
|
||||
| --- | --- | --- |
|
||||
| 1 | `[1, 2, 5, 4, 3]` | The smallest element, 1, is swapped with the first element |
|
||||
| 2 | `[1, 2, 5, 4, 3]` | The value 2 is already at index 1, so no swap is needed |
|
||||
|
||||
The first two positions are now fixed. Later rounds need to find the smallest element only within `[5, 4, 3]`.
|
||||
|
||||
2. Compare adjacent elements in order: swap 4 and 2; do not swap 4 and 5; swap 5 and 1; then swap 5 and 3.
|
||||
The result is `[2, 4, 1, 3, 5]`, after 3 swaps. The largest element, 5, has moved to the end of the array, so the last position is now fixed.
|
||||
|
||||
### 2. Can Equal Elements Change Their Relative Order?
|
||||
|
||||
In the array $[2_a, 2_b, 1]$, $2_a$ and $2_b$ have equal values, but their subscripts mark their original order.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Write the array after the first round of selection sort. Has the relative order of $2_a$ and $2_b$ changed?
|
||||
2. Write the array after the first round of bubble sort. Has the relative order of $2_a$ and $2_b$ changed?
|
||||
3. Based on the first two questions, explain how the two sorting algorithms differ in preserving the original order of equal elements.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. In its first round, selection sort selects the smallest element, 1, and swaps it with the first element, $2_a$, producing
|
||||
$[1, 2_b, 2_a]$. The relative order has changed because $2_a$ has moved behind $2_b$.
|
||||
|
||||
2. Bubble sort first compares $2_a$ and $2_b$. Because they are equal, it does not swap them. It then compares $2_b$ and 1 and swaps them,
|
||||
producing $[2_a, 1, 2_b]$ after the first round. $2_a$ is still before $2_b$, so their relative order has not changed.
|
||||
|
||||
3. In this example, selection sort changes the original order of equal elements. Bubble sort swaps adjacent elements only when the left element is greater than the right one.
|
||||
Equal elements are not swapped, so their original relative order is preserved.
|
||||
|
||||
### 3. Compare Counting Sort and Radix Sort
|
||||
|
||||
A school needs to sort many student ID numbers, each exactly 8 digits long. Answer the following questions:
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. How many rounds does radix sort need when it starts with the least significant digit?
|
||||
2. If the student IDs are treated directly as integers for counting sort, why would the count array need many entries that are never used?
|
||||
3. Based on the first two questions, which would you choose for sorting many fixed-length 8-digit student IDs: counting sort or radix sort? Why?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. A student ID has 8 digits, so 8 rounds are needed from the least significant to the most significant digit. Each round groups values only by 0–9.
|
||||
|
||||
2. Direct counting would require an entry for every possible 8-digit value, but only a small fraction of those values are actually assigned to students.
|
||||
Most entries in the count array would remain 0.
|
||||
|
||||
3. Radix sort is the better choice. It uses the facts that the length is fixed and each digit has only 10 possible values, requiring only 8 rounds of stable grouping.
|
||||
Counting sort, if it used the entire 8-digit ID as an integer index, would require count-array entries for many values that never occur.
|
||||
|
||||
## 11.12.2 Programming Exercises
|
||||
|
||||
### 1. Sort an Array with Merge Sort
|
||||
|
||||
Given an integer array `nums`, implement merge sort yourself, arrange its elements in nondecreasing order, and return the result. Do not call the language's built-in sorting function.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. An interval of length at most 1 is already sorted
|
||||
2. Divide the interval in half at its midpoint and recursively sort both halves
|
||||
3. Use two pointers to merge the two sorted halves, then write the result back into the original array
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/sort-an-array/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
|
||||
### 2. Sort an Integer Array with Counting Sort
|
||||
|
||||
You are given an integer array `nums` and a non-negative integer $K$. Every element of the array is between $0$ and $K$.
|
||||
|
||||
Implement counting sort, write the result back into `nums` in nondecreasing order, and return `nums`.
|
||||
Do not determine the order by comparing elements, and do not call the language's built-in sorting function.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. Because every element is between 0 and K, use each element's value directly as an index in the counting array
|
||||
2. Scan nums once and increment the count at the corresponding position
|
||||
3. Then scan the counting array from 0 to K; if the value x occurs a certain number of times, write x into nums that many consecutive times
|
||||
@@ -26,3 +26,4 @@ icon: material/sort-ascending
|
||||
- [11.9 Counting Sort](counting_sort.md)
|
||||
- [11.10 Radix Sort](radix_sort.md)
|
||||
- [11.11 Summary](summary.md)
|
||||
- [11.12 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 5.5 Exercises
|
||||
|
||||
## 5.5.1 Concept Review
|
||||
|
||||
### 1. Which Element Leaves a Stack or Queue First?
|
||||
|
||||
Prepare an empty stack `S` and an empty queue `Q`. Perform the same sequence of operations on each one:
|
||||
|
||||
Step 1: Add `A`.
|
||||
Step 2: Add `B`.
|
||||
Step 3: Remove and record one element.
|
||||
Step 4: Add `C`.
|
||||
Step 5: Keep removing and recording elements until the container is empty.
|
||||
|
||||
Write the order in which elements are removed from `S` and from `Q`. Explain the difference using "last-in-first-out" or "first-in-first-out."
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
The removal order for stack `S` is `B, C, A`. After the elements `A, B` are added, the most recently added element, `B`, is popped first. After `C` is added,
|
||||
`C, A` are popped in that order. This is "last-in-first-out."
|
||||
|
||||
The removal order for queue `Q` is `A, B, C`. After the elements `A, B` are added, the earliest added element, `A`, is removed first.
|
||||
After `C` is added, `B, C` are removed in that order. This is "first-in-first-out."
|
||||
|
||||
### 2. What Happens When the Rear Passes the End of the Array?
|
||||
|
||||
A queue is implemented with a circular array of length 5, whose indices are `0–4`.
|
||||
Currently, `front = 3` and `size = 2`; `A, B` are stored at indices 3 and 4, respectively.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. When `C` is enqueued, at which index should `C` be stored? What is `size` after the enqueue?
|
||||
2. Next, dequeue once. Which element is removed? What are the new values of `front` and `size`?
|
||||
3. What is the logical order from the front to the rear now? Does dequeuing require moving the other elements in the array? Why or why not?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The new element's position is
|
||||
`(front + size) % 5 = (3 + 2) % 5 = 0`,
|
||||
so `C` is stored at index 0. After the enqueue, `size = 3`.
|
||||
|
||||
2. Dequeuing removes the current front element, `A`. The new front index is
|
||||
`(3 + 1) % 5 = 4`, so `front = 4` and `size = 2`.
|
||||
|
||||
3. The logical order of valid elements is `B, C`, with `B` at index 4 and `C` at index 0.
|
||||
Dequeuing requires only changing `front` and `size`. The circular array uses the remainder operation to wrap the index back to the beginning,
|
||||
so there is no need to shift all the other elements forward.
|
||||
|
||||
### 3. Operations at Both Ends of a Deque
|
||||
|
||||
Here, `push_first` adds an element at the front, `push_last` adds one at the rear,
|
||||
`pop_first` removes one from the front, and `pop_last` removes one from the rear.
|
||||
|
||||
Perform the following operations on an empty deque `deq`:
|
||||
|
||||
1. `push_last(A)`
|
||||
2. `push_last(B)`
|
||||
3. `push_first(C)`
|
||||
4. `pop_last()`
|
||||
5. `push_last(D)`
|
||||
6. `pop_first()`
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Which elements are returned by the two pop operations?
|
||||
2. After all operations are complete, which elements remain from front to rear?
|
||||
3. Examine the six operations. Can a queue that allows insertion only at the rear and removal only at the front perform all of them? If not, identify the operations it cannot perform. Then state whether a deque can perform them and explain why.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
After the first three steps, the deque from front to rear is `[C, A, B]`.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. `pop_last()` removes `B`. After `D` is added, the deque is `[C, A, D]`,
|
||||
and `pop_first()` then removes `C`.
|
||||
|
||||
2. `[A, D]` remains.
|
||||
|
||||
3. A queue that permits insertion only at the rear and removal only at the front cannot perform all the operations.
|
||||
Step 3, `push_first(C)`, requires insertion at the front, and Step 4, `pop_last()`, requires removal at the rear. Both are outside the operations supported by such a queue.
|
||||
A deque permits insertion and removal at both ends, so it can perform all six operations.
|
||||
|
||||
## 5.5.2 Programming Exercises
|
||||
|
||||
### 1. Check a Bracket Sequence
|
||||
|
||||
Given a string `s` containing only the three types of brackets `()`, `[]`, and `{}`, use a stack to determine whether the string is valid.
|
||||
|
||||
A valid sequence must satisfy both conditions: every closing bracket matches the type of the most recent unmatched opening bracket,
|
||||
and no unmatched opening bracket remains after the traversal. Return a Boolean value for the result.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. You can create a mapping from each closing bracket to its matching opening bracket
|
||||
2. When you encounter a closing bracket, first check whether the stack is empty, and then check whether the top matches
|
||||
3. The stack must also be empty after the traversal
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/valid-parentheses/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
@@ -19,3 +19,4 @@ icon: material/stack-overflow
|
||||
- [5.2 Queue](queue.md)
|
||||
- [5.3 Deque](deque.md)
|
||||
- [5.4 Summary](summary.md)
|
||||
- [5.5 Exercises](exercises.md)
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
|
||||
|
||||
# 7.7 Exercises
|
||||
|
||||
## 7.7.1 Concept Review
|
||||
|
||||
### 1. Complete, Full, and Perfect Binary Trees
|
||||
|
||||
The following two arrays represent binary trees in level order, where `None` marks an empty position:
|
||||
|
||||
- Tree A: `[1, 2, 3, 4, 5, 6]`
|
||||
- Tree B: `[1, 2, 3, None, None, 6, 7]`
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Which tree is a complete binary tree?
|
||||
2. Which tree is a full binary tree, meaning every non-leaf node has two children?
|
||||
3. Is either tree a perfect binary tree? Explain the reason for each tree.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. Tree A is a complete binary tree. Only its lowest level is not full, and the nodes on that level occupy consecutive positions from left to right.
|
||||
Tree B is not complete because there are empty positions on the left of the lowest level while nodes still appear on the right.
|
||||
|
||||
2. Tree B is a full binary tree: nodes 1 and 3 each have two children, and all other nodes are leaves.
|
||||
Tree A is not full because node 3 has only one child, its left child 6.
|
||||
|
||||
3. Neither tree is perfect because the lowest level of each tree is not completely filled.
|
||||
|
||||
### 2. Three Traversal Orders for the Same Tree
|
||||
|
||||
Store the array `[1, 2, 3, 4, 5, 6, 7]` in level order in a complete binary tree.
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. Draw the tree.
|
||||
2. Write its preorder, inorder, and postorder traversal sequences.
|
||||
3. In the inorder sequence, which parts of the tree correspond to the subsequences to the left and right of root node 1?
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. The tree is:
|
||||
|
||||
```text
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
/ \ / \
|
||||
4 5 6 7
|
||||
```
|
||||
|
||||
2. The preorder traversal is `1, 2, 4, 5, 3, 6, 7`;
|
||||
the inorder traversal is `4, 2, 5, 1, 6, 3, 7`;
|
||||
the postorder traversal is `4, 5, 2, 6, 7, 3, 1`.
|
||||
|
||||
3. The sequence `4, 2, 5` to the left of root node 1 is the inorder traversal of the left subtree;
|
||||
the sequence `6, 3, 7` to its right is the inorder traversal of the right subtree.
|
||||
|
||||
### 3. Compare Two Binary Search Trees
|
||||
|
||||
Insert each of the following sequences from left to right into an empty binary search tree:
|
||||
|
||||
- Sequence A: `[4, 2, 6, 1, 3, 5, 7]`
|
||||
- Sequence B: `[1, 2, 3, 4, 5, 6, 7]`
|
||||
|
||||
<!-- numbered-subquestions -->
|
||||
|
||||
1. For each tree, write the nodes visited when searching for 7.
|
||||
2. If height is measured by the number of edges from the root node to the farthest leaf node, what is the height of each tree?
|
||||
3. Based on the first two questions, is searching for 7 equally efficient in the two trees? Explain using the trees' shapes and search paths.
|
||||
|
||||
??? success "Answer"
|
||||
|
||||
1. In the tree built from Sequence A, the search path is `4 → 6 → 7`.
|
||||
In the tree built from Sequence B, the search path is `1 → 2 → 3 → 4 → 5 → 6 → 7`.
|
||||
|
||||
2. Every level of the first tree is full, and its height is 2. The second tree has only right children, and its height is 6.
|
||||
|
||||
3. Searching for 7 is not equally efficient in the two trees. The insertion order changes the shape and height of a binary search tree. The search visits only 3 nodes in the first tree
|
||||
but all 7 nodes in the second. The taller the tree, the more nodes may need to be compared along a path in the worst case.
|
||||
|
||||
## 7.7.2 Programming Exercises
|
||||
|
||||
### 1. Maximum Depth of a Binary Tree
|
||||
|
||||
You are given the root node `root` of a binary tree. Each node contains an integer value and references to its left and right children.
|
||||
|
||||
The maximum depth is the **number of nodes** on the path from the root node to the farthest leaf node. Return the maximum depth of the tree; the maximum depth of an empty tree is 0.
|
||||
Use recursion.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. Depth is measured by the number of nodes in this exercise, so a tree containing only a root node has a maximum depth of 1
|
||||
2. Let the recursive function return the maximum depth of the subtree rooted at the current node
|
||||
3. Return 0 for an empty node; for a nonempty node, return max(depth(left), depth(right)) + 1
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/maximum-depth-of-binary-tree/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
|
||||
### 2. Traverse a Binary Tree Level by Level
|
||||
|
||||
Given the root node `root` of a binary tree, use a queue to visit all nodes level by level from top to bottom and from left to right within each level.
|
||||
|
||||
Return a two-dimensional array: the first subarray stores the values at the root's level, the second stores the values at the next level, and so on.
|
||||
If the tree is empty, return an empty array.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. Level-order traversal visits earlier enqueued nodes first, so use a queue
|
||||
2. At the beginning of each round, all nodes currently in the queue belong to the same level
|
||||
3. First record the queue's length, then remove exactly that many nodes and enqueue their children
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/binary-tree-level-order-traversal/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
|
||||
### 3. Kth Smallest Element in a Binary Search Tree
|
||||
|
||||
A binary search tree contains `n` nodes with distinct values.
|
||||
If all node values are arranged from smallest to largest, their positions are numbered starting from 1.
|
||||
|
||||
Given the root node `root` and an integer `k` satisfying `1 <= k <= n`, return the value at position `k`.
|
||||
Find the answer directly during an inorder traversal rather than collecting all node values first.
|
||||
|
||||
??? tip "Hints"
|
||||
|
||||
1. An inorder traversal of a binary search tree visits node values from smallest to largest
|
||||
2. Inorder traversal processes the left subtree, the current node, and then the right subtree; increment the count when visiting the current node
|
||||
3. When the count first equals k, the current node's value is the answer, so no further traversal is needed
|
||||
|
||||
[LeetCode](https://leetcode.com/problems/kth-smallest-element-in-a-bst/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
||||
@@ -21,3 +21,4 @@ icon: material/graph-outline
|
||||
- [7.4 Binary Search Tree](binary_search_tree.md)
|
||||
- [7.5 AVL Tree *](avl_tree.md)
|
||||
- [7.6 Summary](summary.md)
|
||||
- [7.7 Exercises](exercises.md)
|
||||
|
||||
Reference in New Issue
Block a user