Simplify generated exercise page sources (#1943)

This commit is contained in:
Yudong Jin
2026-07-19 07:19:42 +08:00
committed by GitHub
parent 668f9b0cfb
commit a3166c2018
71 changed files with 1391 additions and 113 deletions
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
-2
View File
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
-2
View File
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
-2
View File
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
-2
View File
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
-2
View File
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
-2
View File
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
-2
View File
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
-2
View File
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
-2
View File
@@ -1,5 +1,3 @@
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
# 练习
## 知识巩固
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
-2
View File
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
-2
View File
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
-2
View File
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
-2
View File
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
-2
View File
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
-2
View File
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
-2
View File
@@ -1,5 +1,3 @@
<!-- Generated by utils/exercises/publish_exercises.py; do not edit directly. -->
# Exercises
## Concept Review
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
-2
View File
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
-2
View File
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
-2
View File
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
-2
View File
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
-2
View File
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
-2
View File
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
-2
View File
@@ -1,5 +1,3 @@
<!-- utils/exercises/publish_exercises.py により自動生成されています。直接編集しないでください。 -->
# 演習
## 確認問題
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
-2
View File
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
-2
View File
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
-2
View File
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
-2
View File
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
-2
View File
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
-2
View File
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
-2
View File
@@ -1,5 +1,3 @@
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# Упражнения
## Вопросы для самопроверки
@@ -0,0 +1,106 @@
# 練習
## 知識鞏固
### 陣列和鏈結串列怎樣找到元素
陣列和單向鏈結串列中都按順序儲存了 `[A, B, C, D, E]`。現在要讀取第 4 個元素 `D`
<!-- numbered-subquestions -->
1. 陣列可以直接使用哪個索引?
2. 單向鏈結串列從頭節點 `A` 開始,要沿著 `next` 依次經過哪些節點?
3. 當要讀取的元素位置越來越靠後時,兩種結構所需的步驟會怎樣變化?哪一種更適合反覆按位置讀取?為什麼?
??? success "參考答案"
1. 若索引從 0 開始,第 4 個元素的索引是 3,陣列可直接讀取 `arr[3]`
2. 單向鏈結串列必須從頭開始,訪問路徑為 `A → B → C → D`,需要沿 `next` 前進 3 次。
3. 陣列可以根據首位址和索引直接定位元素,按位置訪問的時間複雜度為 $O(1)$。
單向鏈結串列訪問第 $k$ 個節點時,必須從頭節點開始,沿著 `next` 前進 $k-1$ 次,
最壞需要 $O(n)$ 時間。
這裡只比較按位置讀取,不表示鏈結串列在所有操作上都更慢。
### 陣列和鏈結串列怎樣插入元素
陣列和單向鏈結串列中都儲存了 `A、B、C、D`。現在要在 `B` 後面插入 `X`
- 陣列容量為 5,當前狀態是 `[A, B, C, D, _]`
- 鏈結串列為 `A → B → C → D`,並且已經拿到了指向節點 `B` 的引用。
<!-- numbered-subquestions -->
1. 陣列需要移動哪些元素?寫出插入後的陣列。
2. 鏈結串列應按什麼順序修改 `X.next``B.next`?寫出插入後的鏈結串列。
3. 為什麼比較插入效率時,要特別說明“已經拿到了節點 B 的引用”?
??? success "參考答案"
1. 陣列要先把 `D` 向右移動一格,再把 `C` 向右移動一格,最後把 `X` 放入索引 2
得到 `[A, B, X, C, D]`
2. `B.next` 原本指向 `C`。應先令 `X.next = B.next`,讓 `X` 指向 `C`
再令 `B.next = X`。結果為 `A → B → X → C → D`
如果先覆蓋 `B.next` 又沒有儲存原來的連線,就可能找不到 `C`
3. 已知 `B` 的位置後,鏈結串列插入只需修改兩個連線,可以在 $O(1)$ 時間完成。
如果還要從頭查詢 `B`,查詢過程本身可能需要 $O(n)$ 時間。
### 串列容量是怎樣增長的
一個基於陣列實現的串列當前內容為 `[A, B, C]`,長度 `size = 3`,容量 `capacity = 4`
規定容量不足時,新陣列的容量擴大為原來的 2 倍。
<!-- numbered-subquestions -->
1. 追加 `D` 後,串列的長度和容量分別是多少?是否需要擴容?
2. 接著追加 `E` 時,容量會變為多少?需要複製幾個原有元素?
3. 底層陣列的長度不可變,為什麼串列的容量看起來卻可以增長?
??? success "參考答案"
1. `D` 可以放入最後一個空位。此時內容為 `[A, B, C, D]`
`size = 4``capacity = 4`,不需要擴容。
2. 再追加 `E` 時已經沒有空位,需要建立容量為 8 的新陣列,
`A、B、C、D` 共 4 個原有元素複製過去,再加入 `E`
此時 `size = 5``capacity = 8`
3. 原來的陣列本身沒有變長。串列建立了一個更大的新陣列,複製原有元素,
再改用新陣列作為底層儲存,因此對使用者來說容量增長了。
## 程式設計練習
### 陣列表示的大整數加一
陣列 `digits` 從左到右儲存一個非負整數的各位數字,例如 `[3, 0, 8]` 表示 308。
數字 0 用 `[0]` 表示;其他輸入的第一位均不為 0。
請模擬一次十進位制豎式加法,將這個整數增加 1,並把結果仍按相同的陣列形式返回。
可以直接修改 `digits`;若最前面產生新的進位,可以返回一個更長的陣列。
??? tip "解題提示"
1. 像做豎式加法一樣,從陣列最後一位開始
2. 當前位小於 9 時加一後即可立即返回
3. 當前位等於 9 時把它改成 0;若所有位都是 9,需要在最前面補 1
[LeetCode](https://leetcode.cn/problems/plus-one/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
### 反轉單向鏈結串列
給定一個單向鏈結串列的頭節點 `head`。每個節點包含一個值和指向下一節點的 `next`
請使用迭代方法反轉所有節點之間的連線,並返回反轉後的頭節點。
要求不建立新的鏈結串列節點。
??? tip "解題提示"
1. 先在紙上畫出三個相連的節點和 prev、cur 兩個指標
2. 改寫 cur.next 前,必須先用 nxt 儲存原來的下一個節點
3. 反轉 cur.next 後,令 prev = cur,再令 cur = nxt,繼續處理原鏈結串列中的下一個節點
[LeetCode](https://leetcode.cn/problems/reverse-linked-list/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/reverse-linked-list/solutions/2361282/206-fan-zhuan-lian-biao-shuang-zhi-zhen-r1jel/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
@@ -0,0 +1,89 @@
# 練習
## 知識鞏固
### 這段排列演算法會漏掉結果嗎
一個回溯演算法按 `1、2、3` 的順序嘗試生成全部排列。每次選擇數字 `x` 時,它會:
1. 把 `x` 加到當前路徑末尾;
2. 把 `x` 標記為“已使用”;
3. 遞迴填寫下一個位置。
遞迴返回後,同學只從路徑末尾刪除了 `x`,然後繼續嘗試下一個數字。
<!-- numbered-subquestions -->
1. 演算法首先得到哪個排列?它還能得到全部 6 個排列嗎?
2. 遞迴返回上一層前,只刪除路徑末尾的數字是否足夠?如果不夠,還需要做什麼?說明理由。
??? success "參考答案"
1. 它首先得到 `[1, 2, 3]`,但無法得到全部排列。雖然返回時路徑變短了,
數字 1、2、3 的標記仍都是“已使用”,後續分支便沒有可選數字。
2. 不夠。刪除路徑末尾的 `x` 後,還必須把 `x` 重新標記為“未使用”。
當前路徑和已使用標記共同描述搜尋狀態;選擇時修改了兩處,回退時也必須把兩處都恢復,
其他分支才能再次選擇 `x`
### 數字的選擇順序重要嗎
給定排好序的陣列 `[2, 3, 5]` 和目標值 5,每個數可以重複選擇。
演算法規定每條搜尋路徑中的數字只能按從小到大的順序出現。
<!-- numbered-subquestions -->
1. 能得到哪些不同的組合?
2. 為什麼同一組數字不需要按不同順序重複搜尋?“從小到大”的限制起到了什麼作用?
3. 當前路徑為 `[3]`、還差 2 時,下一個候選數是 3。為什麼此時可以停止檢查這一層後面的所有候選數?
??? success "參考答案"
1. 不同的組合為 `[2, 3]``[5]`
2. 本題把 `[2, 3]``[3, 2]` 看作同一種組合,數字的選擇順序不計入答案。
規定路徑中的數字從小到大出現,就能在搜尋時直接跳過 `[3, 2]` 這類重複組合。
3. 當前還差 2,而候選數 3 已經大於 2。因為陣列已經排好序,
3 後面的候選數只會更大,也都不可能加入當前組合,所以可以直接結束這一層的檢查。
### 下一枚皇后可以放在哪些位置
在一個 `4 × 4` 棋盤上按行放置皇后,行、列下標都從 0 開始。
目前已經在 `(0, 1)``(1, 3)` 放置了皇后,現在要在第 2 行放置下一個皇后。
<!-- numbered-subquestions -->
1. 哪些列會因為“同列”而被排除?
2. 在剩餘列中,哪些位置會因為“同一條對角線”而被排除?
3. 第 2 行還剩哪些位置可以嘗試?
??? success "參考答案"
1. 第 1 列和第 3 列已經有皇后,因此位置 `(2, 1)``(2, 3)` 被排除。
2. 在剩餘位置中,`(2, 2)``(1, 3)` 位於同一條對角線上,因此也被排除。
位置 `(2, 0)` 與已有兩個皇后都不在同列或同一條對角線上。
3. 第 2 行可以嘗試的位置只有 `(2, 0)`
這一步只說明當前放置合法;之後若無法完成棋盤,仍需回退並嘗試更早的其他選擇。
## 程式設計練習
### 無重複元素的全排列
整數陣列 `nums` 至少包含一個元素,並且其中各元素互不相同。
請列出把這些元素各使用一次所能形成的全部順序,並將每一種順序作為一個陣列返回。
結果中各排列的先後次序不作要求。
請使用回溯,並用布林陣列記錄每個位置的元素是否已經選入當前排列。
??? tip "解題提示"
1. 遞迴深度表示正在填寫排列中的第幾個位置
2. 每一層只嘗試尚未使用的元素
3. 路徑長度達到 `nums` 的長度時,把它的副本加入答案
[LeetCode](https://leetcode.cn/problems/permutations/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/permutations/solutions/2363882/46-quan-pai-lie-hui-su-qing-xi-tu-jie-by-6o7h/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
> **說明:** 連結題解透過交換陣列元素,把已選元素依次放到陣列前部;本練習使用布林陣列記錄每個元素是否已選。兩種方法都能避免同一元素被重複選擇,但程式碼結構不同
@@ -0,0 +1,110 @@
# 練習
## 知識鞏固
### 迭代與遞迴的時間和空間
下面兩段程式碼都計算 $1 + 2 + \dots + n$(設 $n \ge 1$)。請把 `n` 設為 4
按照程式實際執行的順序回答問題,然後比較兩種寫法的效率。
```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. 執行 `sum_iter(4)` 時,每輪迴圈結束後,變數 `s` 的值分別是多少?
2. 執行 `sum_recur(4)` 時,會依次呼叫哪些函式?從最深的一層開始返回時,結果怎樣得到?
3. 兩種寫法的時間複雜度和空間複雜度分別是多少?結合第 1、2 問的執行過程說明理由。
??? success "參考答案"
1. 迴圈變數 `i` 依次為 `1、2、3、4`,每輪結束後,`s` 依次變為
`1、3、6、10`,所以 `sum_iter(4)` 返回 10。
2. 函式依次呼叫
`sum_recur(4) → sum_recur(3) → sum_recur(2) → sum_recur(1)`
`sum_recur(1)` 返回 1,隨後各層依次得到 `2 + 1 = 3``3 + 3 = 6``4 + 6 = 10`
在最深處,4 次函式呼叫都尚未結束。
3. 兩段程式碼都進行與 $n$ 成正比的迴圈或呼叫,因此時間複雜度均為 $O(n)$ 。
空間複雜度不同:迭代版只使用常數個變數,為 $O(1)$ ;
遞迴版在到達終止條件前,前面的函式呼叫都要等待返回結果,因此呼叫堆疊中最多同時儲存 $n$ 次呼叫,
空間複雜度為 $O(n)$。
分析空間複雜度時,除程式碼中的變數外,還要考慮遞迴呼叫佔用的空間。
### 三段程式碼的時間複雜度
以下三個程式碼片段的輸入均為正整數 $n$ 。請按時間複雜度從低到高排序,並寫出各自的複雜度。
```python
# 片段一
s = 0
for i in range(n):
s += i
# 片段二
s = 0
for i in range(n):
for j in range(i, n):
s += j
# 片段三
while n > 1:
n = n // 2
```
??? success "參考答案"
從低到高為:片段三 $O(\log n)$、片段一 $O(n)$、片段二 $O(n^2)$。
片段三每輪把 $n$ 縮小為原來的一半,約迴圈 $\log_2 n$ 次。
片段一的迴圈恰好執行 $n$ 次。片段二的內層迴圈次數依次為
$n,n-1,\dots,1$,總次數為 $n(n+1)/2$,因此屬於平方階。
### 哪種反轉更節省空間
要將陣列 `nums` 中的元素全部反轉,有兩種做法:
<!-- numbered-subquestions -->
1. 新建一個等長陣列 `res`,倒序複製後返回;
2. 用兩個索引 `i``j` 分別從首、尾向中間移動,逐對交換 `nums[i]``nums[j]`
兩種做法的空間複雜度各是多少?哪種屬於“原地”操作?
??? success "參考答案"
1. 需要與輸入等長的輔助陣列,空間複雜度 $O(n)$。
2. 只使用兩個索引變數,
空間複雜度 $O(1)$ ,屬於原地操作。
需要注意:原地反轉會修改輸入陣列,
僅在允許修改輸入時才應優先選用;若需保留原陣列,第 1 種做法的複製開銷不可避免。
## 程式設計練習
### 費波那契數
費波那契數列滿足:$F(0)=0$、$F(1)=1$,並且當 $n\ge2$ 時,
$F(n)=F(n-1)+F(n-2)$。
給定非負整數 `n`,請使用迴圈計算並返回 $F(n)$,不使用遞迴。
??? tip "解題提示"
1. 先單獨處理 n 為 0 和 1 的情況
2. 計算下一項時只需要前兩項,無須儲存整個數列
3. 更新兩個變數時,注意不要過早覆蓋仍會用到的舊值
[LeetCode](https://leetcode.cn/problems/fibonacci-number/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/fibonacci-number/solutions/2361746/509-fei-bo-na-qi-shu-dong-tai-gui-hua-qi-so8h/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
@@ -0,0 +1,86 @@
# 練習
## 知識鞏固
### 生活場景中的資料關係
請根據資料之間的關係,為下面三個場景選擇“線性結構、樹形結構、網狀結構”中的一種,並說明理由:
<!-- numbered-subquestions -->
1. 同學們排成一列,每人只關心自己前面和後面的人;
2. 學校按“學校 → 年級 → 班級”分層管理;
3. 城市道路連線多個路口,一個路口可以通向多個其他路口,也可能形成環路。
??? success "參考答案"
1. 是線性結構。除排在最前和最後的人外,每個人都只與前面和後面各一人相鄰,關係沿一條線展開。
2. 是樹形結構。每個班級屬於一個年級,每個年級又屬於學校,關係從上到下分層展開。
3. 是網狀結構。一個路口可以連線多個路口,路線之間還能形成環,不能排成單一順序或嚴格層級。
判斷結構時應先觀察元素之間的關係,而不是先考慮它在記憶體中佔多少空間。
### 邏輯順序怎樣存進記憶體
為了儲存邏輯順序 `A → B → C`,現有兩種簡化的記憶體安排:
- 方案甲:`A、B、C` 分別放在編號為 `20、21、22` 的記憶體格中;
- 方案乙:`A、B、C` 分別放在編號為 `20、7、31` 的記憶體格中,並由 `A` 記錄 `B` 的位置、`B` 記錄 `C` 的位置。
<!-- numbered-subquestions -->
1. 哪個方案屬於連續空間儲存?哪個屬於分散空間儲存?
2. 兩個方案分別更接近陣列還是鏈結串列?
3. 方案乙的記憶體格編號沒有按大小排列,為什麼仍能表示 `A → B → C` 的邏輯順序?
??? success "參考答案"
1. 方案甲使用連續的記憶體格,屬於連續空間儲存;方案乙的節點分散在不同位置,屬於分散空間儲存。
2. 方案甲更接近陣列,方案乙更接近鏈結串列。
3. 邏輯順序由節點之間記錄的連線關係決定,而不是由記憶體格編號的大小決定。
`A` 記錄的位置可以找到 `B`,再從 `B` 記錄的位置找到 `C`,所以仍能依次訪問 `A、B、C`
這也說明邏輯結構和物理結構是觀察同一組資料的兩個不同角度。
### 作業記錄中的資料型別與結構
某學習小組按座位順序記錄 4 名同學是否交了作業,得到:
`[true, false, true, true]`
<!-- numbered-subquestions -->
1. 每個元素適合使用哪種基本資料型別?
2. 這 4 個元素按座位順序排成一列,使用了什麼邏輯結構?
3. 如果以後改為記錄每人的作業分數 `[90, 0, 85, 100]`,改變的是資料的“內容型別”還是“組織方式”?說明理由。
??? success "參考答案"
1. 每個元素只表示“是”或“否”,適合使用布林型別 `bool`
2. 這些元素按座位順序排列,形成線性結構,可以用陣列儲存。
3. 改變的是內容型別:元素由布林值變成了整數。組織方式沒有改變,
這些資料仍然按座位順序排成一列,仍可使用陣列這一線性結構。
基本資料型別描述“存的是什麼”,資料結構描述“資料怎樣組織”。
## 程式設計練習
### 統計二進位制表示中的 1
給定非負整數 `n`,請統計它的二進位制表示中共有多少個 1。
要求使用位運算完成,不把二進位制表示轉換成字串,也不使用直接統計 1 的內建函式。
??? tip "解題提示"
1. n & 1 可以取出 n 的最右邊一位,用它判斷這一位是否為 1
2. 右移一位表示丟掉當前最右邊的二進位制位;多數語言使用運算子 >>
3. 完成逐位檢查並右移的方法後,再觀察:n & (n - 1) 會把 n 中最右邊的一個 1 變成 0
[LeetCode](https://leetcode.cn/problems/number-of-1-bits/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/number-of-1-bits/solutions/2361978/191-wei-1-de-ge-shu-wei-yun-suan-qing-xi-40rw/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
@@ -0,0 +1,98 @@
# 練習
## 知識鞏固
### 哪些任務適合分治
一位同學想用“先分成兩半,分別解決,再合併結果”的方法完成以下任務。
請分別判斷它們是“適合分治”“可以分治,但不會減少總工作量”還是“兩半不能獨立解決”,並說明理由。
<!-- numbered-subquestions -->
1. 將一個無序陣列排序;
2. 求一個陣列中的最大值;
3. 按順序執行一串 `push(x)``pop()` 堆疊操作,並輸出每次 `pop()` 得到的元素。
??? success "參考答案"
1. 適合:對半分解、兩半獨立排序、$O(n)$ 合併——這正是合併排序。
2. 可以分治,但不會減少總工作量:左右兩半仍需合計檢查全部 $n$ 個元素,
和直接掃描一樣都是 $O(n)$ 時間。
3. 兩半不能獨立解決:後半段開始時的堆疊內容取決於前半段執行結果,
兩半不能在互不知道結果時獨立完成。
### 快速冪是怎樣減少計算的
下面的遞迴函式用分治計算 $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
```
用它計算 `fast_pow(3, 5)`
<!-- numbered-subquestions -->
1. 遞迴呼叫時,參數 `n` 依次變成哪些值?
2. 從最深層開始返回時,各層依次返回什麼值?
3. 為什麼要先儲存 `half`,而不是把 `fast_pow(x, n // 2)` 寫兩遍?
??? success "參考答案"
1. 參數依次為 `5 → 2 → 1 → 0`,每次都把指數減半,直到到達終止條件。
2. `n = 0` 時返回 1`n = 1` 時返回 $1×1×3=3$
`n = 2` 時返回 $3×3=9$`n = 5` 時返回 $9×9×3=243$。
3. 如果把 `fast_pow(x, n // 2)` 在乘法兩邊各寫一次,兩次遞迴會計算完全相同的子問題。
先把結果儲存為 `half`,每層就只遞迴一次,遞迴深度約為 $\log n$;
呼叫兩次會造成大量重複計算。
### 從走訪序列拆分左右子樹
一棵沒有重複節點的二元樹,其前序走訪和中序走訪分別為:
- 前序走訪:`[A, B, D, E, C]`
- 中序走訪:`[D, B, E, A, C]`
只完成根節點這一層的拆分,不必繼續遞迴,也不必畫出整棵樹:
<!-- numbered-subquestions -->
1. 根節點是什麼?
2. 左、右子樹在中序走訪中分別對應哪一段?
3. 左、右子樹在前序走訪中分別對應哪一段?根節點有哪些直接孩子?
??? success "參考答案"
1. 前序走訪的第一個節點是根節點,因此根節點為 `A`
2. `A` 把中序走訪分成兩部分:左子樹為 `[D, B, E]`,右子樹為 `[C]`
3. 左子樹有 3 個節點,因此根節點 `A` 後面的 3 個前序元素屬於左子樹,
`[B, D, E]`;剩餘的 `[C]` 屬於右子樹。
所以根節點的左孩子是 `B`,右孩子是 `C`
## 程式設計練習
### 快速冪
給定實數 `x` 和整數 `n`,請計算 $x^n$,且不能呼叫語言內建的冪函式。
要求使用遞迴分治:每次把指數縮小一半,並複用已經算出的子問題結果。
本題規定 $x^0=1$,包括 `x = 0` 時;當 `n < 0` 時保證 `x != 0`,答案可轉化為 $(1/x)^{-n}$。
??? tip "解題提示"
1. n 等於 0 時答案是 1
2. 算出 x 的 n // 2 次方後儲存為 half,不要遞迴呼叫第二次
3. 當 n < 0 時,先把 x 改為 1 / x,再把 n 改為 -n;使用 C++ 或 Java 時,可先把 n 轉為 64 位整數,避免最小的 32 位整數取相反數時溢位
[LeetCode](https://leetcode.cn/problems/powx-n/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/powx-n/solutions/241471/50-powx-n-kuai-su-mi-qing-xi-tu-jie-by-jyd/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
> **說明:** 連結中的題解使用迭代位運算,與本練習指定的遞迴分治方法不同;請根據本題的解題提示完成遞迴版本
@@ -0,0 +1,113 @@
# 練習
## 知識鞏固
### 什麼時候適合使用動態規劃
一位同學說:“只要能寫出遞推式,就應該使用動態規劃。”
請判斷下面三個任務分別更適合使用動態規劃、回溯,還是使用迴圈或數學公式而不建立 `dp` 表,並寫出一個關鍵理由。
<!-- numbered-subquestions -->
1. 使用面值 `[1, 3, 4]` 的硬幣湊出金額 6,求最少硬幣數,每種硬幣可重複使用;
2. 輸出 `[1, 2, 3]` 的全部排列;
3. 計算 $1 + 2 + \dots + n$。
對於你判斷為適合動態規劃的任務,再說明 `dp[i]` 表示什麼。
??? success "參考答案"
1. 適合動態規劃。令 `dp[i]` 表示湊出金額 `i` 所需的最少硬幣數。
對於每枚不超過 `i` 的硬幣 `c`,都可以把 `dp[i-c] + 1` 作為候選答案,
再從這些候選值中取最小值。不同選擇會反覆遇到同一金額,較大金額的最優解也能由較小金額的最優解構成。
金額 6 的答案是 2,即 `3 + 3`
2. 應使用回溯。題目要求逐個生成全部 6 個排列,回溯可以系統地嘗試一個選擇、繼續搜尋,
再撤銷選擇並嘗試其他分支。無論採用什麼方法,實際輸出這些排列時都不能跳過列舉。
3. 使用迴圈或等差數列公式即可。雖然能寫出 `S(i) = S(i-1) + i`,但計算 `S(i)` 時只依賴一個更小的 `S(i-1)`
每個部分和只需計算一次,不存在重複子問題,因此無需建立 `dp` 表。“能寫遞推式”不等於“需要動態規劃”。
### 背包表中的一個格子怎麼算
0-1 背包:物品重量 `wgt = [1, 2, 3]` ,價值 `val = [5, 11, 15]` ,背包容量 4 。
`dp[i][c]` 表示只考慮前 $i$ 件物品、背包容量上限為 $c$ 時,能夠取得的最大價值;
不要求恰好裝滿背包。
現在只計算狀態 `dp[3][4]`。已知 `dp[2][4] = 16``dp[2][1] = 5`
<!-- numbered-subquestions -->
1. 不選第 3 件物品時,候選價值是多少?
2. 選擇第 3 件物品時,還剩多少容量,候選價值是多少?
3. `dp[3][4]` 應取多少?對應選擇了哪些物品?
??? success "參考答案"
1. 不選第 3 件物品時,沿用前兩件物品的結果,候選價值為 `dp[2][4] = 16`
2. 第 3 件物品重量為 3,放入後還剩容量 $4-3=1$;候選價值為
`dp[2][1] + 15 = 5 + 15 = 20`
3. 比較 16 和 20,得到 `dp[3][4] = 20`。它對應選擇第 1、3 件物品,
總重量為 $1+3=4$,總價值為 $5+15=20$。
這個狀態的計算體現了 0-1 背包的一次“選或不選”比較。
### 背包容量應該按什麼順序更新
一個 0-1 背包只有一件物品:重量為 2,價值為 5;背包容量為 4。
每件物品最多選擇一次,初始一維陣列為 `dp = [0, 0, 0, 0, 0]`
一位同學在處理這件物品時,按容量從 2 到 4 更新:
- 更新 `dp[2]` 後得到 5
- 更新 `dp[3]` 後也得到 5
- 更新 `dp[4]` 時又使用剛得到的 `dp[2]`,於是得到 `dp[4] = 10`
<!-- numbered-subquestions -->
1. `dp[4] = 10` 這個結果是否正確?為什麼?
2. 根據“每件物品最多選擇一次”的條件,`dp[4]` 應為多少?
3. 處理每件物品時,容量應從大到小還是從小到大更新?這樣做避免了什麼問題?
??? success "參考答案"
1. 這個結果不正確。價值 10 相當於把這件價值為 5 的物品放入了兩次,
違反了“每件物品最多選擇一次”的條件。
2. 背包中最多隻能放入這一件物品,所以正確的 `dp[4]` 為 5。
3. 容量應從大到小更新,即依次處理 4、3、2。
這樣在計算 `dp[c]` 時,所讀取的 `dp[c-2]` 仍是處理當前物品之前的結果,
不會在同一輪中重複使用當前物品。
## 程式設計練習
### 爬樓梯的方案數
一段樓梯共有 `n` 階。你每次只能向上走 1 階或 2 階,且必須恰好到達第 `n` 階。
請計算共有多少種不同的走法。規定 `n >= 1`,走法只按每一步走 1 階還是 2 階來區分。
請使用一維動態規劃陣列完成,暫不使用只保留兩個狀態的空間最佳化寫法。
??? tip "解題提示"
1. 到達第 i 階的最後一步,只可能跨 1 階或 2 階
2. 因此有 dp[i] = dp[i-1] + dp[i-2]
3. 先處理 n 為 1 和 2 的情況,再從第 3 階開始填表
[LeetCode](https://leetcode.cn/problems/climbing-stairs/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/climbing-stairs/solutions/2361764/70-pa-lou-ti-dong-tai-gui-hua-qing-xi-tu-ruwa/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
> **說明:** 題解講解了一維 dp 狀態和轉移,但給出的程式碼採用滾動變數壓縮空間;本練習要求先實現一維 dp 表,滾動變數僅作為選做最佳化
### 0-1 背包
給定等長陣列 `wgt``val`,第 `i` 件物品的重量為正整數 `wgt[i]`、價值為非負整數 `val[i]`
背包容量 `cap` 為非負整數。每件物品最多選擇一次,請在總重量不超過 `cap` 的條件下,
求能夠裝入背包的最大總價值。請使用一維動態規劃實現。
??? tip "解題提示"
1. 初始化長度為 cap + 1 的陣列 dp,其中 dp[c] 表示容量上限為 c 時的最大價值
2. 處理物品 i 時,比較“不選它”的 dp[c] 與“選它”的 dp[c-wgt[i]] + val[i]
3. 容量必須從大到小更新,避免在同一輪中重複選擇當前物品
+103
View File
@@ -0,0 +1,103 @@
# 練習
## 知識鞏固
### 用兩種方式表示同一張圖
一個無向圖有 4 個頂點 `A、B、C、D`,邊為
`A-B、A-C、B-C、C-D`
<!-- numbered-subquestions -->
1. 寫出它的鄰接表;
2. 填寫只含 0 和 1 的鄰接矩陣;
3. 如果要判斷 `A``D` 是否直接相連,哪種圖的表示方法只需檢視一個儲存條目?
4. 如果圖的頂點很多但邊很少,哪種表示通常更節省空間?
??? success "參考答案"
1. 鄰接表為:
```text
A: B, C
B: A, C
C: A, B, D
D: C
```
2. 鄰接矩陣為:
| | 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. 鄰接矩陣可直接檢視第 `A` 行、第 `D` 列,因此很適合判斷任意兩點是否直接相連。
4. 頂點很多但邊很少時,鄰接表只記錄實際存在的邊,通常比為每一對頂點都預留位置的鄰接矩陣更節省空間。
### 廣度優先與深度優先訪問順序
一個無向圖的頂點為 `A、B、C、D、E`,邊為
`A-B、A-C、B-D、C-D、D-E`
從 A 出發,並規定遇到多個未訪問鄰接頂點時按字母順序選擇:
<!-- numbered-subquestions -->
1. 寫出廣度優先走訪(BFS)的訪問順序;
2. 寫出遞迴深度優先走訪(DFS)的訪問順序;
3. 為什麼兩種走訪都需要記錄已經訪問過的頂點?
??? success "參考答案"
1. BFS 的訪問順序為 `A, B, C, D, E`。它先訪問離 A 一條邊的 B、C
再訪問更遠的 D、E。
2. DFS 的訪問順序為 `A, B, D, C, E`。它依次進入當前頂點的未訪問鄰接頂點,
因此先走過 `A → B → D → C`;C 沒有新的鄰接頂點時回到 D,再訪問 E。
3. 圖中存在環,例如 `A-B-D-C-A`。如果不記錄已訪問頂點,
走訪可能反覆沿環訪問同一批頂點,無法正常結束。
### 一次 BFS 能訪問整張圖嗎
一個無向圖有頂點 `A、B、C、D、E、F`,邊只有
`A-B、B-C、D-E`
<!-- numbered-subquestions -->
1. 從 A 開始進行一次 BFS,可以訪問哪些頂點?
2. 根據第 1 問,這一次 BFS 是否已經訪問圖中的所有頂點?為什麼?
3. 若按字母順序掃描所有頂點,每遇到一個未訪問頂點就重新開始 BFS,
各次 BFS 的起點是什麼?這個圖被分成幾個互不連通的部分(連通分量)?
??? success "參考答案"
1. 從 A 出發只能訪問 `A、B、C`
2. 沒有訪問所有頂點。`D、E` 組成另一個互相連通的部分,F 是單獨的頂點;
它們與 A 之間都沒有路徑,因此從 A 出發無法到達。
3. 三次 BFS 的起點依次為 `A、D、F`,分別訪問
`{A, B, C}``{D, E}``{F}`。因此這個圖有 3 個連通分量。
## 程式設計練習
### 判斷無向圖中是否存在路徑
給定一個含有 $n$ 個頂點的無向圖,頂點編號為 $0$ 到 $n-1$。陣列 `edges` 中的每一項 `[u, v]` 表示頂點 `u``v` 之間有一條無向邊。
再給定起點 `source` 和終點 `destination`。請先根據 `edges` 建立鄰接表,再使用 BFS 或 DFS
判斷是否存在一條從 `source``destination` 的路徑:存在則返回 `true`,否則返回 `false`
圖中可能有環,也可能不連通。
??? tip "解題提示"
1. 每條無向邊需要同時加入兩個方向
2. 圖中可能有環,必須記錄已經訪問過的節點
3. 從 source 出發,遇到 destination 返回 true,走訪結束仍未遇到則返回 false
[LeetCode](https://leetcode.cn/problems/find-if-path-exists-in-graph/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
+83
View File
@@ -0,0 +1,83 @@
# 練習
## 知識鞏固
### 每次選最大硬幣一定最好嗎
硬幣面值為 `[1, 7, 10]`,目標金額為 14。
<!-- numbered-subquestions -->
1. 按“每次取不超過剩餘金額的最大面值”執行,寫出所取硬幣;
2. 是否存在使用更少硬幣的方案?如果存在,請寫出一種;如果不存在,請說明理由;
3. 這能否說明該貪婪策略對任意硬幣面值都正確?
??? success "參考答案"
1. 貪婪依次選擇 `10 + 1 + 1 + 1 + 1`,共 5 枚硬幣。
2. 存在使用更少硬幣的方案:`7 + 7`,只需 2 枚硬幣。
3. 不能。這個反例說明,對任意硬幣面值,每次選擇當前能取的最大面值並不一定得到最少硬幣數;
眼前最大的選擇可能破壞後續更好的組合。
### 背包應該先裝哪件物品
一個容量為 4 千克的背包中可以裝入以下物品,物品允許只取一部分,
所取價值與重量成正比:
- 物品 A:重量 4 千克,價值 20;
- 物品 B:重量 3 千克,價值 18。
<!-- numbered-subquestions -->
1. 兩件物品每千克的價值分別是多少?應先裝哪一件?
2. 按分數背包的貪婪策略裝滿背包,最終價值是多少?
3. 在物品可以切分、背包限制總重量的情況下,選擇物品時應該比較總價值還是每千克價值?為什麼?
??? success "參考答案"
1. A 每千克價值為 `20 ÷ 4 = 5`B 每千克價值為 `18 ÷ 3 = 6`
因此應先裝單位價值更高的 B。
2. 先裝入全部 B,佔用 3 千克、獲得價值 18;剩餘 1 千克容量,
再裝入 1 千克 A,獲得價值 5。最終價值為 `18 + 5 = 23`
3. 背包限制的是總重量,而且物品可以切分,所以應比較單位重量的價值。
A 的總價值雖然更高,但每千克價值低於 B;若先裝滿 A,只能得到價值 20。
### 兩個指標下一步怎樣移動
隔板高度為 `[1, 8, 6, 2, 5]`,用首尾雙指標尋找最大容量。
容量等於“兩塊隔板中較短者的高度 × 兩塊隔板的下標距離”。
<!-- numbered-subquestions -->
1. 初始時左指標在下標 0、右指標在下標 4,當前容量是多少?下一步應移動哪個指標?
2. 按照第 1 問的選擇移動一次後,兩個指標分別位於哪個下標?此時容量是多少?下一步又應移動哪個指標?
3. 在當前一對隔板中,可以移動較短隔板對應的指標,也可以移動較長隔板對應的指標。哪一種移動仍有可能得到更大的容量?為什麼?
??? success "參考答案"
1. 當前容量為 `min(1, 5) × (4 - 0) = 4`。左側隔板較短,因此移動左指標。
2. 移動左指標後,兩個指標分別位於下標 1 和 4。當前容量為
`min(8, 5) × (4 - 1) = 15`。右側隔板較短,因此下一步移動右指標。
3. 移動較短隔板對應的指標,才仍有可能得到更大的容量。移動較長隔板後,兩塊隔板的距離一定縮短,而容器高度仍受未移動的短板限制,
只可能不變或變小。因此容量不可能超過移動前;只有移動短板,才有機會遇到更高的隔板。
## 程式設計練習
### 分數背包
給定等長陣列 `wgt``val`,其中 `wgt[i] > 0``val[i] >= 0`,背包容量 `cap >= 0`
每種物品只有一件,但允許只裝入其中一部分,
獲得的價值按所裝重量佔該物品總重量的比例計算。請使用貪婪演算法,
並以實數返回背包能夠獲得的最大總價值。
??? tip "解題提示"
1. 先計算每件物品的單位重量價值 val[i] / wgt[i],除法結果要保留小數
2. 單位價值越高的物品越先裝入背包
3. 若剩餘容量小於當前物品重量,就裝入恰好填滿背包的部分並結束
+101
View File
@@ -0,0 +1,101 @@
# 練習
## 知識鞏固
### 發生雜湊衝突後怎樣查詢
一個雜湊表有 5 個桶,雜湊函式為 $h(x)=x \bmod 5$,衝突時把元素依次放進該桶的串列中。
依次插入 `[1, 6, 11, 7]`
<!-- numbered-subquestions -->
1. 寫出 04 號桶中的內容;
2. 查詢 6 時會先進入哪個桶,並依次檢查哪些元素?
3. 根據第 1 問寫出的桶內容,後插入的元素是否覆蓋了先插入的元素?結合這種衝突處理方式說明理由。
??? success "參考答案"
1. 因為 $1\bmod5=6\bmod5=11\bmod5=1$,而 $7\bmod5=2$,各桶為:
```text
0: []
1: [1, 6, 11]
2: [7]
3: []
4: []
```
2. 查詢 6 時先進入 1 號桶,再依次比較 1、6,第二次比較時找到目標。
3. 雜湊值相同只表示它們落入同一個桶,並不表示這些元素相等。鏈式位址把衝突元素都儲存在桶內,
查詢時再逐個比較,因此 1、6、11 不會互相覆蓋。
### 雜湊表擴容後元素去哪兒
一個使用鏈式位址的雜湊表原有 5 個桶,雜湊函式為 $h(x)=x\bmod5$。
`[1, 6, 11]` 都在 1 號桶中。
現在把雜湊表擴容為 7 個桶,雜湊函式相應變為 $h(x)=x\bmod7$
<!-- numbered-subquestions -->
1. 分別計算 1、6、11 的新桶號。
2. 擴容後哪些桶中有元素?
3. 擴容時能否把原來 1 號桶中的串列原樣複製到新的 1 號桶?結合第 1、2 問的結果說明理由。
??? success "參考答案"
1. 新桶號分別為:
- $1\bmod7=1$
- $6\bmod7=6$
- $11\bmod7=4$。
2. 1 號桶儲存 1,4 號桶儲存 11,6 號桶儲存 6。三個鍵不再擠在同一個桶中。
3. 不能原樣複製。桶號由“鍵對桶數取餘”得到。桶數從 5 變成 7 後,同一個鍵的新桶號可能改變,
因此必須重新計算每個鍵的位置。若把舊的 1 號桶原樣複製過去,之後按新公式查詢 6 和 11 時,
會分別前往 6 號桶和 4 號桶,從而找不到它們。
### 刪除 6 後還能找到 11 嗎
一個雜湊表有 5 個位置,索引為 `04`,雜湊函式為 $h(x)=x\bmod5$。
發生衝突時,從雜湊函式算出的索引開始,向右尋找第一個空位。
依次插入 `[1, 6, 11]`
<!-- numbered-subquestions -->
1. 三個數最終分別放在哪個索引?
2. 查詢 11 時,會依次檢查哪些索引?
3. 如果刪除 6 時直接把它的位置改成“從未使用的空位”,而查詢遇到空位就停止,
再查詢 11 時會發生什麼?這個查詢結果是否正確?如果有問題,應怎樣避免?
??? success "參考答案"
1. 1 放在索引 1。6 也對映到索引 1,發生衝突後放在索引 2。
11 同樣從索引 1 開始,依次跳過已佔用的索引 1、2,最終放在索引 3。
2. 查詢 11 時依次檢查索引 `1、2、3`,在索引 3 找到它。
3. 如果把索引 2 改成表示“從未使用”的空位,查詢 11 時檢查索引 1 後就會在索引 2 停止,
從而錯誤地認為 11 不存在。刪除時應留下“已刪除”標記:
查詢遇到該標記時繼續檢查下一個索引(越過索引 4 後回到索引 0),而以後的插入仍可重新使用這個位置。
## 程式設計練習
### 比較兩個字串的字元組成
給定兩個只含小寫英文字母的字串 `s``t`
可以任意調整 `s` 中字元的位置,但不能新增、刪除或替換字元。
請判斷調整後能否得到 `t`;可以則返回 `true`,否則返回 `false`
請使用雜湊表記錄各字母的出現次數,不對字串中的字元排序。
??? tip "解題提示"
1. 兩個字串長度不同,它們的字元組成一定不同
2. 用雜湊表記錄每種字母的數量;掃描 s 時把對應計數加 1
3. 掃描 t 時把對應計數減 1;所有計數最終都為 0,字元組成才相同
[LeetCode](https://leetcode.cn/problems/valid-anagram/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/valid-anagram/solutions/2362065/242-you-xiao-de-zi-mu-yi-wei-ci-ha-xi-bi-cch7/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
+88
View File
@@ -0,0 +1,88 @@
# 練習
## 知識鞏固
### 數字 10 入堆積後怎樣調整
陣列 `[9, 7, 8, 3, 5]` 表示一個大頂堆積。現在將數字 10 入堆積。
<!-- numbered-subquestions -->
1. 先把 10 新增到陣列末尾,它的父節點值是多少?
2. 從新節點開始向上堆積化,寫出每次交換後的陣列。
3. 最終的堆積頂元素是什麼?一共交換了幾次?
??? success "參考答案"
1. 10 新增後的索引為 5,其父節點索引為
$\lfloor(5-1)/2\rfloor=2$,父節點值為 8。
2. 10 大於 8,第一次交換後為 `[9, 7, 10, 3, 5, 8]`
10 又大於父節點 9,第二次交換後為 `[10, 7, 9, 3, 5, 8]`
此時 10 已到達根節點,堆積化結束。
3. 最終堆積頂為 10,共交換 2 次。
### 檢查小頂堆積的父子關係
陣列 `[1, 4, 3, 7, 6, 2]` 表示一棵完全二元樹。小頂堆積要求每個父節點都不大於它的孩子。
對索引 $i$,左、右孩子索引分別為 $2i+1$ 和 $2i+2$。
<!-- numbered-subquestions -->
1. 索引 2 的孩子索引和值分別是什麼?
2. 索引 2 處的節點值為 3,它與孩子節點之間是否違反小頂堆積規則?如果違反,應交換哪兩個元素?
3. 根據第 2 問的判斷:若違反規則,寫出交換後的陣列;若不違反,說明無須交換。最後檢查其餘父子關係。
??? success "參考答案"
1. 索引 2 的左孩子索引是 5、值為 2;右孩子索引是 6,但陣列長度為 6,因此右孩子不存在。
2. 父節點值 3 大於孩子值 2,違反小頂堆積規則,應交換索引 2 和索引 5 的元素。
3. 交換後得到 `[1, 4, 2, 7, 6, 3]`。逐一檢查:
`1 ≤ 4``1 ≤ 2``4 ≤ 7``4 ≤ 6``2 ≤ 3`
所有父節點都不大於自己的孩子,因此現在滿足小頂堆積規則。
### 用小頂堆積保留最大的三個數
要從資料流 `[4, 1, 7, 3, 8]` 中保留最大的 3 個數,可以維護一個大小不超過 3 的小頂堆積。
先把前 3 個數依次放入小頂堆積。堆積滿後,每讀入一個新數:
如果它大於堆積頂,就刪除堆積頂並放入新數;否則堆積保持不變。
請寫出每次讀入數字後堆積中保留的數字和堆積頂。
只需把保留的數字寫成集合,不要求寫出它們在堆積陣列中的排列。
??? success "參考答案"
每次讀入數字後的結果如下:
| 讀入數字 | 保留的數字 | 堆積頂 |
| --- | --- | --- |
| 4 | `{4}` | 4 |
| 1 | `{1, 4}` | 1 |
| 7 | `{1, 4, 7}` | 1 |
| 3 | `{3, 4, 7}` | 3 |
| 8 | `{4, 7, 8}` | 4 |
堆積滿後,堆積頂是當前保留數字中最小的一個。只有新數字大於堆積頂時,
才用新數字替換堆積頂。最終保留的 `{4, 7, 8}` 正是最大的 3 個數。
## 程式設計練習
### 尋找陣列中的第 k 大元素
給定整數陣列 `nums` 和整數 $k$,其中 $1 \le k \le n$,$n$ 是陣列長度。把陣列按從大到小排列後,請返回第 $k$ 個位置上的元素。
重複元素需要分別計數。例如,`[5, 5, 2]` 的第 2 大元素仍是 5。請使用一個大小不超過 $k$ 的小頂堆積完成。
??? tip "解題提示"
1. 第 k 大元素是最大的 k 個數中最小的那個
2. 每個數先入小頂堆積,大小超過 k 時彈出最小值
3. 走訪結束時堆積中保留最大的 k 個數,堆積頂就是答案
[LeetCode](https://leetcode.cn/problems/kth-largest-element-in-an-array/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/kth-largest-element-in-an-array/solutions/2361969/215-shu-zu-zhong-de-di-k-ge-zui-da-yuan-d786p/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
> **說明:** 連結中的題解講解了快速排序和快速選擇,沒有使用堆積;本練習請按照正文 8.3 節使用大小不超過 k 的小頂堆積完成
@@ -0,0 +1,95 @@
# 練習
## 知識鞏固
### 二分搜尋怎樣縮小搜尋區間
在有序陣列 `[2, 5, 8, 12, 16, 23, 38]` 中查詢 16。
使用雙閉區間 `[i, j]`,中點為
$m=i+(j-i)/2$(向下取整)。
請寫出每輪的 `(i, j, m)`、中點元素,以及下一步如何縮小區間,
直到找到目標。
??? success "參考答案"
每輪查詢過程如下:
| 輪次 | `(i, j, m)` | 中點元素 | 下一步 |
| --- | --- | --- | --- |
| 1 | `(0, 6, 3)` | 12 | `12 < 16`,令 `i = 4` |
| 2 | `(4, 6, 5)` | 23 | `23 > 16`,令 `j = 4` |
| 3 | `(4, 4, 4)` | 16 | 找到目標,返回索引 4 |
陣列有序,所以中點值小於目標時可以排除中點及其左側;
中點值大於目標時可以排除中點及其右側。
### 重複元素的左右邊界
在陣列 `[1, 2, 2, 2, 4, 6]` 中查詢數字 2。
一名同學用二分搜尋在索引 2 找到目標後立即返回,並說:
“索引 2 就是數字 2 的左邊界。”
<!-- numbered-subquestions -->
1. 這名同學的說法是否正確?數字 2 的左、右邊界分別是多少?請說明理由。
2. 查詢左邊界時,若中點元素等於目標,接下來應繼續搜尋哪一側?
3. 查詢右邊界時又應繼續搜尋哪一側?只需說明方向,無須寫出完整的查詢過程。
??? success "參考答案"
1. 這名同學的說法不正確。找到一個 2 後立即返回,只能保證找到某一個 2,不能保證找到最左或最右的 2。
本題的左邊界為索引 1,右邊界為索引 3。
2. 查詢左邊界時,即使中點元素等於 2,也要繼續搜尋左側,
例如在雙閉區間寫法中令 `j = m - 1`
3. 查詢右邊界時,中點元素等於 2 後應繼續搜尋右側,
例如令 `i = m + 1`
### 不同資料該選哪種搜尋方法
請在“順序查詢、二分搜尋、雜湊表”中,為下面三個場景選擇合適的方法,並說明理由:
<!-- numbered-subquestions -->
1. 在 $10^7$ 個已經有序且不再變動的整數中反覆查詢,不額外建立其他資料結構;
2. 在頻繁插入、刪除的資料集中反覆判斷某個鍵是否存在,不要求保持有序,也不進行範圍查詢;
3. 在無序陣列中只查詢一次某個值。
??? success "參考答案"
1. 二分搜尋:資料有序且靜態,$O(\log n)$ 且無須額外空間。
2. 雜湊表:當雜湊函式能把鍵較均勻地分散到各桶時,插入、刪除和按鍵查詢的平均時間複雜度都是 $O(1)$。
3. 直接從頭到尾走訪:只查詢一次時,排序或建立雜湊表都要先處理整個陣列,
並不會減少這一次任務的總工作量。
選擇取決於資料是否有序、是否允許建立額外結構、查詢次數以及需要執行哪些操作。
## 程式設計練習
### 有序陣列的二分搜尋
給定一個按嚴格遞增順序排列的整數陣列 `nums` 和目標值 `target`。請使用二分搜尋找到 `target`:若存在,返回它的陣列索引;若不存在,返回 -1。
??? tip "解題提示"
1. 初始區間是 left = 0、right = n - 1,非空條件是 left <= right
2. 用 mid = left + (right - left) // 2 計算中點
3. 若 `nums[mid]` 小於 `target`,就把左邊界移到 `mid + 1`;若 `nums[mid]` 大於 `target`,就把右邊界移到 `mid - 1`;相等時立即返回
[LeetCode](https://leetcode.cn/problems/binary-search/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/binary-search/solutions/1692151/by-jyd-i7xr/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
### 有序陣列的插入位置
給定一個按嚴格遞增順序排列的整數陣列 `nums` 和目標值 `target`
`target` 已在陣列中,返回它的索引;否則,返回把 `target` 插入陣列後仍能保持嚴格遞增順序的位置。答案可能是 0,也可能等於陣列長度。請使用二分搜尋。
??? tip "解題提示"
1. 答案可能是 0,也可能是陣列長度 n
2. 使用雙閉區間時,若 `nums[mid]` 大於等於 `target`,就令 `right = mid - 1`,繼續檢查更靠左的位置;否則令 `left = mid + 1`
3. 迴圈結束時 left 就是插入位置
[LeetCode](https://leetcode.cn/problems/search-insert-position/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
+94
View File
@@ -0,0 +1,94 @@
# 練習
## 知識鞏固
### 選擇排序和泡沫排序的前幾輪
給定陣列 `[4, 2, 5, 1, 3]`,以下都按從小到大排序。
<!-- numbered-subquestions -->
1. 按選擇排序模擬前兩輪,寫出每輪結束後的陣列,並指出此時哪些位置已經確定。
2. 按泡沫排序模擬第一輪,寫出陣列狀態和交換次數,並指出哪個位置已經確定。
??? success "參考答案"
1. 前兩輪為:
| 輪次 | 陣列狀態 | 說明 |
| --- | --- | --- |
| 1 | `[1, 2, 5, 4, 3]` | 最小元素 1 與首位交換 |
| 2 | `[1, 2, 5, 4, 3]` | 數值 2 已位於索引 1,無須交換 |
此時前兩個位置已經確定,後續只需在 `[5, 4, 3]` 中繼續選擇最小元素。
2. 依次比較相鄰元素:4 與 2 交換、4 與 5 不換、5 與 1 交換、5 與 3 交換,
`[2, 4, 1, 3, 5]` ,共 3 次交換;最大元素 5 已移動到陣列末尾,因此最後一個位置已經確定。
### 相同元素的先後次序會變嗎
陣列 $[2_a, 2_b, 1]$ 中,$2_a$ 和 $2_b$ 的數值相同,但用下標標出了原有次序。
<!-- numbered-subquestions -->
1. 寫出選擇排序第一輪結束後的陣列。$2_a$ 和 $2_b$ 的相對次序是否改變?
2. 寫出泡沫排序第一輪結束後的陣列。$2_a$ 和 $2_b$ 的相對次序是否改變?
3. 根據前兩問,說明兩種排序在保持相等元素原有次序方面有什麼不同。
??? success "參考答案"
1. 選擇排序第一輪選出最小元素 1,並與首位 $2_a$ 交換,得
$[1, 2_b, 2_a]$ 。$2_a$ 被換到了 $2_b$ 之後,兩者的相對次序已經改變。
2. 泡沫排序先比較 $2_a$ 和 $2_b$,因為兩者相等,不交換;再比較 $2_b$ 和 1 並交換,
第一輪結束後為 $[2_a, 1, 2_b]$ 。$2_a$ 仍在 $2_b$ 之前,相對次序沒有改變。
3. 這個例子中,選擇排序會改變相等元素的原有次序;泡沫排序只在左邊元素大於右邊元素時
交換相鄰元素。相等元素之間不交換,因此能保持它們原有的先後次序。
### 比較計數排序和基數排序
學校要排序許多固定為 8 位的學號。請回答:
<!-- numbered-subquestions -->
1. 從最低位開始的基數排序需要處理幾輪?
2. 如果把學號直接當作整數使用計數排序,為什麼會準備大量沒有用到的計數位置?
3. 根據前兩問,如果在計數排序和基數排序之間選擇一種處理大量固定為 8 位的學號,你會選擇哪一種?為什麼?
??? success "參考答案"
1. 學號有 8 個數位,因此從最低位到最高位共處理 8 輪;每輪只按 0~9 分組。
2. 直接計數需要為所有可能的 8 位取值預留位置,但實際學生只使用其中很少一部分,
大多數計數位置會一直為 0。
3. 應選擇基數排序。它利用了“位數固定、每位只有 10 種取值”的特點,只需重複 8 輪穩定分組。
計數排序若把整個 8 位學號當作整數下標,則需要為大量實際不會出現的數值預留計數位置。
## 程式設計練習
### 用合併排序排列陣列
給定一個整數陣列 `nums`,請自行實現合併排序,把其中的元素按非遞減順序排列並返回。不要呼叫語言自帶的排序函式。
??? tip "解題提示"
1. 區間長度不超過 1 時,它已經有序
2. 從中點把區間分成兩半,並分別遞迴排序
3. 用兩個指標合併兩個有序半區,再把結果寫回原陣列
[LeetCode](https://leetcode.cn/problems/sort-an-array/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
### 用計數排序排列整數陣列
給定整數陣列 `nums` 和非負整數 $K$,陣列中的每個元素都在 $0$ 到 $K$ 之間。
請實現計數排序,把結果按非遞減順序寫回 `nums` 並返回 `nums`
不要使用元素之間的大小比較來決定順序,也不要呼叫語言自帶的排序函式。
??? tip "解題提示"
1. 因為每個元素都在 0 到 K 之間,可以直接用元素值作為計數陣列的索引
2. 第一遍掃描 nums,令對應位置的計數加 1
3. 再從 0 到 K 掃描計數陣列;數值 x 出現多少次,就向 nums 中連續寫入多少個 x
@@ -0,0 +1,99 @@
# 練習
## 知識鞏固
### 堆疊和佇列會先取出誰
準備一個空堆疊 `S` 和一個空佇列 `Q`,分別對它們執行下面同一組操作:
步驟 1:加入 `A`
步驟 2:加入 `B`
步驟 3:移除一個元素並記錄;
步驟 4:加入 `C`
步驟 5:不斷移除並記錄,直到容器為空。
請分別寫出 `S``Q` 中元素被移除的順序,並用“先入後出”或“先入先出”解釋差異。
??? success "參考答案"
堆疊 `S` 的移除順序是 `B、C、A`:加入 `A、B` 後先彈出最近加入的 `B`,再加入 `C`
此後依次彈出 `C、A`,體現“先入後出”。
佇列 `Q` 的移除順序是 `A、B、C`:加入 `A、B` 後先移除最早加入的 `A`
再加入 `C`,此後依次移除 `B、C`,體現“先入先出”。
### 佇列尾越過陣列末尾怎麼辦
用長度為 5 的環形陣列實現佇列,陣列索引為 `04`
當前 `front = 3``size = 2`,佇列中的 `A、B` 分別位於索引 3、4。
<!-- numbered-subquestions -->
1. 執行“`C` 入列”時,`C` 應放在哪個索引?入列後 `size` 是多少?
2. 接著執行一次出列,彈出哪個元素?新的 `front``size` 分別是多少?
3. 此時從佇列首到佇列尾的邏輯順序是什麼?出列時是否需要移動陣列中的其他元素?為什麼?
??? success "參考答案"
1. 新元素的位置為
`(front + size) % 5 = (3 + 2) % 5 = 0`
所以 `C` 放在索引 0。入列後 `size = 3`
2. 出列彈出當前佇列首 `A`。新的佇列首索引為
`(3 + 1) % 5 = 4`,因此 `front = 4``size = 2`
3. 有效元素的邏輯順序為 `B、C`,其中 `B` 位於索引 4`C` 位於索引 0。
出列時只需移動 `front` 並修改 `size`,環形陣列用取餘讓索引回到開頭,
因此無須把其他元素整體向前移動。
### 雙向佇列的兩端操作
這裡規定:`push_first` 表示從佇列首加入,`push_last` 表示從佇列尾加入,
`pop_first` 表示從佇列首彈出,`pop_last` 表示從佇列尾彈出。
對一個空的雙向佇列 `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. 兩次彈出的元素分別是什麼?
2. 全部操作完成後,從佇列首到佇列尾還剩哪些元素?
3. 檢查這 6 步操作:只允許從佇列尾加入、從佇列首刪除的佇列能否全部完成?如果不能,請指出無法完成的操作;再說明雙向佇列能否完成及其原因。
??? success "參考答案"
前三步後,雙向佇列從佇列首到佇列尾為 `[C, A, B]`
<!-- numbered-subquestions -->
1. `pop_last()` 彈出 `B`;加入 `D` 後佇列為 `[C, A, D]`
`pop_first()` 再彈出 `C`
2. 最後剩下 `[A, D]`
3. 只允許在佇列尾新增、在佇列首刪除的佇列不能完成全部操作:
第 3 步 `push_first(C)` 要求從佇列首加入,第 4 步 `pop_last()` 要求從佇列尾刪除,都超出了這種佇列的操作範圍。
雙向佇列的兩端都可以新增和刪除,因此能夠完成這 6 步操作。
## 程式設計練習
### 檢查括號序列
給定一個只包含 `()``[]``{}` 這三類括號的字串 `s`,請使用堆疊判斷它是否合法。
合法序列須同時滿足:每個右括號都必須與最近一個尚未配對的左括號型別匹配,
並且走訪結束後沒有未配對的左括號。返回布林值表示判斷結果。
??? tip "解題提示"
1. 可以建立“右括號到對應左括號”的對映
2. 遇到右括號時,先檢查堆疊是否為空,再檢查堆疊頂是否匹配
3. 走訪結束後,堆疊也必須為空
[LeetCode](https://leetcode.cn/problems/valid-parentheses/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/valid-parentheses/solutions/9185/valid-parentheses-fu-zhu-zhan-fa-by-jin407891080/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
+126
View File
@@ -0,0 +1,126 @@
# 練習
## 知識鞏固
### 完全、完滿與完美二元樹
下面兩個陣列按層序表示二元樹,`None` 表示空位:
- 樹 A`[1, 2, 3, 4, 5, 6]`
- 樹 B`[1, 2, 3, None, None, 6, 7]`
<!-- numbered-subquestions -->
1. 哪一棵是完全二元樹?
2. 哪一棵是完滿二元樹,即每個非葉節點都有兩個子節點?
3. 兩棵樹中是否有完美二元樹?分別說明理由。
??? success "參考答案"
1. 樹 A 是完全二元樹。它只有最底層未填滿,並且節點從左到右連續排列。
樹 B 不是完全二元樹,因為最底層左側已有空位,右側卻仍有節點。
2. 樹 B 是完滿二元樹:節點 1 和節點 3 各有兩個子節點,其餘節點都是葉節點。
樹 A 不是完滿二元樹,因為節點 3 只有左子節點 6。
3. 兩棵都不是完美二元樹,因為它們的最底層都沒有完全填滿。
### 同一棵樹的三種走訪順序
將陣列 `[1, 2, 3, 4, 5, 6, 7]` 按層序存入一棵完全二元樹。
<!-- numbered-subquestions -->
1. 畫出這棵樹。
2. 寫出它的前序、中序、後序走訪序列。
3. 在中序序列中,根節點 1 左側和右側的兩段序列,分別對應樹的哪一部分?
??? success "參考答案"
1. 這棵樹為:
```text
1
/ \
2 3
/ \ / \
4 5 6 7
```
2. 前序走訪為 `1, 2, 4, 5, 3, 6, 7`
中序走訪為 `4, 2, 5, 1, 6, 3, 7`
後序走訪為 `4, 5, 2, 6, 7, 3, 1`
3. 根節點 1 左側的 `4, 2, 5` 是左子樹的中序走訪序列;
右側的 `6, 3, 7` 是右子樹的中序走訪序列。
### 比較兩棵二元搜尋樹
將下面兩組序列從左到右依次插入空的二元搜尋樹:
- 序列 A`[4, 2, 6, 1, 3, 5, 7]`
- 序列 B`[1, 2, 3, 4, 5, 6, 7]`
<!-- numbered-subquestions -->
1. 分別寫出查詢數字 7 時經過的節點。
2. 若高度按根節點到最遠葉節點經過的邊數計算,兩棵樹的高度分別是多少?
3. 根據前兩問,你認為兩棵樹查詢數字 7 時的效率是否相同?請結合兩棵樹的形狀和查詢路徑說明理由。
??? success "參考答案"
1. 序列 A 建成的樹中,查詢路徑為 `4 → 6 → 7`
序列 B 建成的樹中,查詢路徑為 `1 → 2 → 3 → 4 → 5 → 6 → 7`
2. 第一棵樹的每一層都填滿,高度為 2;第二棵樹只有右孩子,高度為 6。
3. 兩棵樹查詢 7 時的效率不同。插入順序改變了二元搜尋樹的形狀和高度。查詢 7 時,第一棵樹只經過 3 個節點,
第二棵樹則要依次經過 7 個節點;樹越高,最壞情況下需要沿路徑比較的節點就越多。
## 程式設計練習
### 二元樹的最大深度
給定一棵二元樹的根節點 `root`。每個節點都包含一個整數值,以及指向左、右子節點的引用。
把從根節點到最遠葉節點經過的**節點數**稱為二元樹的最大深度。請返回這棵樹的最大深度;空樹的最大深度為 0。
請使用遞迴完成。
??? tip "解題提示"
1. 本題按節點數計算深度:只有一個根節點時,最大深度為 1
2. 讓遞迴函式返回以當前節點為根的子樹的最大深度
3. 空節點返回 0,非空節點返回 max(depth(left), depth(right)) + 1
[LeetCode](https://leetcode.cn/problems/maximum-depth-of-binary-tree/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/maximum-depth-of-binary-tree/solutions/2361697/104-er-cha-shu-de-zui-da-shen-du-hou-xu-txzrx/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
### 按層走訪二元樹
給定一棵二元樹的根節點 `root`。請使用佇列從上到下逐層訪問所有節點,同一層內按照從左到右的順序訪問。
返回一個二維陣列:第一個子陣列儲存根節點所在層的節點值,第二個子陣列儲存下一層的節點值,依此類推。
若二元樹為空,返回空陣列。
??? tip "解題提示"
1. 層序走訪需要先進入的節點先訪問,因此使用佇列
2. 一輪開始時佇列中的節點正好屬於同一層
3. 先記錄佇列長度,再彈出這麼多個節點並加入它們的孩子
[LeetCode](https://leetcode.cn/problems/binary-tree-level-order-traversal/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/binary-tree-level-order-traversal/solutions/2361604/102-er-cha-shu-de-ceng-xu-bian-li-yan-du-dyf7/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
### 二元搜尋樹中的第 k 小元素
一棵二元搜尋樹共有 `n` 個節點,各節點值互不相同。
將所有節點值從小到大排列後,位置從 1 開始編號。
給定根節點 `root` 和滿足 `1 <= k <= n` 的整數 `k`,請返回排在第 `k` 位的節點值。
請在中序走訪過程中直接尋找答案,不先收集全部節點值。
??? tip "解題提示"
1. 二元搜尋樹的中序走訪會按從小到大的順序訪問節點值
2. 中序走訪依次處理左子樹、當前節點和右子樹;訪問當前節點時把計數加 1
3. 當計數第一次等於 k 時,當前節點值就是答案,此後無須繼續走訪
[LeetCode](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/solutions/2361685/230-er-cha-sou-suo-shu-zhong-di-k-xiao-d-n3he/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
-1
View File
@@ -75,7 +75,6 @@ nav:
- 4.1 &nbsp; 陣列: chapter_array_and_linkedlist/array.md
- 4.2 &nbsp; 鏈結串列: chapter_array_and_linkedlist/linked_list.md
- 4.3 &nbsp; 串列: chapter_array_and_linkedlist/list.md
# [status: new]
- 4.4 &nbsp; 記憶體與快取 *: chapter_array_and_linkedlist/ram_and_cache.md
- 4.5 &nbsp; 小結: chapter_array_and_linkedlist/summary.md
- 4.6 &nbsp; 練習: chapter_array_and_linkedlist/exercises.md