diff --git a/docs/chapter_array_and_linkedlist/exercises.md b/docs/chapter_array_and_linkedlist/exercises.md index 8ceb25861..2a794b214 100644 --- a/docs/chapter_array_and_linkedlist/exercises.md +++ b/docs/chapter_array_and_linkedlist/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_backtracking/exercises.md b/docs/chapter_backtracking/exercises.md index 7dcc74d01..e69225bc7 100644 --- a/docs/chapter_backtracking/exercises.md +++ b/docs/chapter_backtracking/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_computational_complexity/exercises.md b/docs/chapter_computational_complexity/exercises.md index 546e98b4b..d51e8f531 100644 --- a/docs/chapter_computational_complexity/exercises.md +++ b/docs/chapter_computational_complexity/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_data_structure/exercises.md b/docs/chapter_data_structure/exercises.md index 9f9db798a..1b74aeb96 100644 --- a/docs/chapter_data_structure/exercises.md +++ b/docs/chapter_data_structure/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_divide_and_conquer/exercises.md b/docs/chapter_divide_and_conquer/exercises.md index 54ee993ec..a65691dc2 100644 --- a/docs/chapter_divide_and_conquer/exercises.md +++ b/docs/chapter_divide_and_conquer/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_dynamic_programming/exercises.md b/docs/chapter_dynamic_programming/exercises.md index 6d750f2bb..ce89d5a42 100644 --- a/docs/chapter_dynamic_programming/exercises.md +++ b/docs/chapter_dynamic_programming/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_graph/exercises.md b/docs/chapter_graph/exercises.md index 9569cbd2e..27ceba537 100644 --- a/docs/chapter_graph/exercises.md +++ b/docs/chapter_graph/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_greedy/exercises.md b/docs/chapter_greedy/exercises.md index 75aa4783c..33f61f70c 100644 --- a/docs/chapter_greedy/exercises.md +++ b/docs/chapter_greedy/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_hashing/exercises.md b/docs/chapter_hashing/exercises.md index 669f1a9fb..9749c13bf 100644 --- a/docs/chapter_hashing/exercises.md +++ b/docs/chapter_hashing/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_heap/exercises.md b/docs/chapter_heap/exercises.md index 464d1d6a5..b32753e81 100644 --- a/docs/chapter_heap/exercises.md +++ b/docs/chapter_heap/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_searching/exercises.md b/docs/chapter_searching/exercises.md index 3dad9f407..fa57b0144 100644 --- a/docs/chapter_searching/exercises.md +++ b/docs/chapter_searching/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_sorting/exercises.md b/docs/chapter_sorting/exercises.md index ea76d3351..832f17a90 100644 --- a/docs/chapter_sorting/exercises.md +++ b/docs/chapter_sorting/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_stack_and_queue/exercises.md b/docs/chapter_stack_and_queue/exercises.md index 4c37d1f6f..76bc6255c 100644 --- a/docs/chapter_stack_and_queue/exercises.md +++ b/docs/chapter_stack_and_queue/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/docs/chapter_tree/exercises.md b/docs/chapter_tree/exercises.md index 2dc5fb73a..d94a29311 100644 --- a/docs/chapter_tree/exercises.md +++ b/docs/chapter_tree/exercises.md @@ -1,5 +1,3 @@ - - # 练习 ## 知识巩固 diff --git a/en/docs/chapter_array_and_linkedlist/exercises.md b/en/docs/chapter_array_and_linkedlist/exercises.md index d8649cd81..1eb142c45 100644 --- a/en/docs/chapter_array_and_linkedlist/exercises.md +++ b/en/docs/chapter_array_and_linkedlist/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_backtracking/exercises.md b/en/docs/chapter_backtracking/exercises.md index 03a79282c..1ae3334e3 100644 --- a/en/docs/chapter_backtracking/exercises.md +++ b/en/docs/chapter_backtracking/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_computational_complexity/exercises.md b/en/docs/chapter_computational_complexity/exercises.md index 7ca4137c0..ff6c9f08d 100644 --- a/en/docs/chapter_computational_complexity/exercises.md +++ b/en/docs/chapter_computational_complexity/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_data_structure/exercises.md b/en/docs/chapter_data_structure/exercises.md index 5f338c69b..5e611cd53 100644 --- a/en/docs/chapter_data_structure/exercises.md +++ b/en/docs/chapter_data_structure/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_divide_and_conquer/exercises.md b/en/docs/chapter_divide_and_conquer/exercises.md index 296bf79f1..2ca23045f 100644 --- a/en/docs/chapter_divide_and_conquer/exercises.md +++ b/en/docs/chapter_divide_and_conquer/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_dynamic_programming/exercises.md b/en/docs/chapter_dynamic_programming/exercises.md index a43a54111..079943d2c 100644 --- a/en/docs/chapter_dynamic_programming/exercises.md +++ b/en/docs/chapter_dynamic_programming/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_graph/exercises.md b/en/docs/chapter_graph/exercises.md index a34261595..8a443b2c5 100644 --- a/en/docs/chapter_graph/exercises.md +++ b/en/docs/chapter_graph/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_greedy/exercises.md b/en/docs/chapter_greedy/exercises.md index 05fc2d1a9..661c89f62 100644 --- a/en/docs/chapter_greedy/exercises.md +++ b/en/docs/chapter_greedy/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_hashing/exercises.md b/en/docs/chapter_hashing/exercises.md index cab1851a3..78c6fdb65 100644 --- a/en/docs/chapter_hashing/exercises.md +++ b/en/docs/chapter_hashing/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_heap/exercises.md b/en/docs/chapter_heap/exercises.md index 1320fd489..8f7376b3a 100644 --- a/en/docs/chapter_heap/exercises.md +++ b/en/docs/chapter_heap/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_searching/exercises.md b/en/docs/chapter_searching/exercises.md index f1b01891b..d0e0b4635 100644 --- a/en/docs/chapter_searching/exercises.md +++ b/en/docs/chapter_searching/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_sorting/exercises.md b/en/docs/chapter_sorting/exercises.md index 41d7349fd..5702b1551 100644 --- a/en/docs/chapter_sorting/exercises.md +++ b/en/docs/chapter_sorting/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_stack_and_queue/exercises.md b/en/docs/chapter_stack_and_queue/exercises.md index 5c0d85d5e..79cc72f05 100644 --- a/en/docs/chapter_stack_and_queue/exercises.md +++ b/en/docs/chapter_stack_and_queue/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/en/docs/chapter_tree/exercises.md b/en/docs/chapter_tree/exercises.md index a417ffd09..004fc05c6 100644 --- a/en/docs/chapter_tree/exercises.md +++ b/en/docs/chapter_tree/exercises.md @@ -1,5 +1,3 @@ - - # Exercises ## Concept Review diff --git a/ja/docs/chapter_array_and_linkedlist/exercises.md b/ja/docs/chapter_array_and_linkedlist/exercises.md index 3feddb6f2..51020d699 100644 --- a/ja/docs/chapter_array_and_linkedlist/exercises.md +++ b/ja/docs/chapter_array_and_linkedlist/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_backtracking/exercises.md b/ja/docs/chapter_backtracking/exercises.md index a791faf1e..acb762db6 100644 --- a/ja/docs/chapter_backtracking/exercises.md +++ b/ja/docs/chapter_backtracking/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_computational_complexity/exercises.md b/ja/docs/chapter_computational_complexity/exercises.md index 97a5b52de..968f757fc 100644 --- a/ja/docs/chapter_computational_complexity/exercises.md +++ b/ja/docs/chapter_computational_complexity/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_data_structure/exercises.md b/ja/docs/chapter_data_structure/exercises.md index a72636418..aa86239e2 100644 --- a/ja/docs/chapter_data_structure/exercises.md +++ b/ja/docs/chapter_data_structure/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_divide_and_conquer/exercises.md b/ja/docs/chapter_divide_and_conquer/exercises.md index 5b21185b5..e7b469539 100644 --- a/ja/docs/chapter_divide_and_conquer/exercises.md +++ b/ja/docs/chapter_divide_and_conquer/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_dynamic_programming/exercises.md b/ja/docs/chapter_dynamic_programming/exercises.md index 1fd967aad..bce48929d 100644 --- a/ja/docs/chapter_dynamic_programming/exercises.md +++ b/ja/docs/chapter_dynamic_programming/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_graph/exercises.md b/ja/docs/chapter_graph/exercises.md index 37e6fba39..3ec87b130 100644 --- a/ja/docs/chapter_graph/exercises.md +++ b/ja/docs/chapter_graph/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_greedy/exercises.md b/ja/docs/chapter_greedy/exercises.md index 97710d938..c0a1146ed 100644 --- a/ja/docs/chapter_greedy/exercises.md +++ b/ja/docs/chapter_greedy/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_hashing/exercises.md b/ja/docs/chapter_hashing/exercises.md index e7069e7cf..884a18915 100644 --- a/ja/docs/chapter_hashing/exercises.md +++ b/ja/docs/chapter_hashing/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_heap/exercises.md b/ja/docs/chapter_heap/exercises.md index 5b587158c..69ef9f4bf 100644 --- a/ja/docs/chapter_heap/exercises.md +++ b/ja/docs/chapter_heap/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_searching/exercises.md b/ja/docs/chapter_searching/exercises.md index 3aef2d258..02d287249 100644 --- a/ja/docs/chapter_searching/exercises.md +++ b/ja/docs/chapter_searching/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_sorting/exercises.md b/ja/docs/chapter_sorting/exercises.md index ee66ef87b..deaea26d6 100644 --- a/ja/docs/chapter_sorting/exercises.md +++ b/ja/docs/chapter_sorting/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_stack_and_queue/exercises.md b/ja/docs/chapter_stack_and_queue/exercises.md index 400c944bd..2ed0340e2 100644 --- a/ja/docs/chapter_stack_and_queue/exercises.md +++ b/ja/docs/chapter_stack_and_queue/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ja/docs/chapter_tree/exercises.md b/ja/docs/chapter_tree/exercises.md index 0ff24467b..fce0f746f 100644 --- a/ja/docs/chapter_tree/exercises.md +++ b/ja/docs/chapter_tree/exercises.md @@ -1,5 +1,3 @@ - - # 演習 ## 確認問題 diff --git a/ru/docs/chapter_array_and_linkedlist/exercises.md b/ru/docs/chapter_array_and_linkedlist/exercises.md index 30d2a79f4..5dcf682f4 100644 --- a/ru/docs/chapter_array_and_linkedlist/exercises.md +++ b/ru/docs/chapter_array_and_linkedlist/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_backtracking/exercises.md b/ru/docs/chapter_backtracking/exercises.md index ca52a7f3d..aa5762a03 100644 --- a/ru/docs/chapter_backtracking/exercises.md +++ b/ru/docs/chapter_backtracking/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_computational_complexity/exercises.md b/ru/docs/chapter_computational_complexity/exercises.md index 1c510a9ec..a88f2f9d8 100644 --- a/ru/docs/chapter_computational_complexity/exercises.md +++ b/ru/docs/chapter_computational_complexity/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_data_structure/exercises.md b/ru/docs/chapter_data_structure/exercises.md index fb5bed6a9..20700519b 100644 --- a/ru/docs/chapter_data_structure/exercises.md +++ b/ru/docs/chapter_data_structure/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_divide_and_conquer/exercises.md b/ru/docs/chapter_divide_and_conquer/exercises.md index 52a2c309b..9b6c7efa5 100644 --- a/ru/docs/chapter_divide_and_conquer/exercises.md +++ b/ru/docs/chapter_divide_and_conquer/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_dynamic_programming/exercises.md b/ru/docs/chapter_dynamic_programming/exercises.md index 3cd07cd86..37dd4112d 100644 --- a/ru/docs/chapter_dynamic_programming/exercises.md +++ b/ru/docs/chapter_dynamic_programming/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_graph/exercises.md b/ru/docs/chapter_graph/exercises.md index 81aaaf482..1f44c7654 100644 --- a/ru/docs/chapter_graph/exercises.md +++ b/ru/docs/chapter_graph/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_greedy/exercises.md b/ru/docs/chapter_greedy/exercises.md index b12306baa..3fb73c514 100644 --- a/ru/docs/chapter_greedy/exercises.md +++ b/ru/docs/chapter_greedy/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_hashing/exercises.md b/ru/docs/chapter_hashing/exercises.md index cd3dbab13..ab579cfaf 100644 --- a/ru/docs/chapter_hashing/exercises.md +++ b/ru/docs/chapter_hashing/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_heap/exercises.md b/ru/docs/chapter_heap/exercises.md index dff8aeccd..de63896cc 100644 --- a/ru/docs/chapter_heap/exercises.md +++ b/ru/docs/chapter_heap/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_searching/exercises.md b/ru/docs/chapter_searching/exercises.md index 1e81a5192..cc956c591 100644 --- a/ru/docs/chapter_searching/exercises.md +++ b/ru/docs/chapter_searching/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_sorting/exercises.md b/ru/docs/chapter_sorting/exercises.md index ce2288118..7aecb09e2 100644 --- a/ru/docs/chapter_sorting/exercises.md +++ b/ru/docs/chapter_sorting/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_stack_and_queue/exercises.md b/ru/docs/chapter_stack_and_queue/exercises.md index 097c63bc1..f2b6d43b1 100644 --- a/ru/docs/chapter_stack_and_queue/exercises.md +++ b/ru/docs/chapter_stack_and_queue/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/ru/docs/chapter_tree/exercises.md b/ru/docs/chapter_tree/exercises.md index 52bed7950..fe013355d 100644 --- a/ru/docs/chapter_tree/exercises.md +++ b/ru/docs/chapter_tree/exercises.md @@ -1,5 +1,3 @@ - - # Упражнения ## Вопросы для самопроверки diff --git a/zh-hant/docs/chapter_array_and_linkedlist/exercises.md b/zh-hant/docs/chapter_array_and_linkedlist/exercises.md new file mode 100644 index 000000000..50e712251 --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/exercises.md @@ -0,0 +1,106 @@ +# 練習 + +## 知識鞏固 + +### 陣列和鏈結串列怎樣找到元素 + +陣列和單向鏈結串列中都按順序儲存了 `[A, B, C, D, E]`。現在要讀取第 4 個元素 `D`: + + + +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` 的引用。 + + + +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 倍。 + + + +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" } diff --git a/zh-hant/docs/chapter_backtracking/exercises.md b/zh-hant/docs/chapter_backtracking/exercises.md new file mode 100644 index 000000000..f2d1d99b3 --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/exercises.md @@ -0,0 +1,89 @@ +# 練習 + +## 知識鞏固 + +### 這段排列演算法會漏掉結果嗎 + +一個回溯演算法按 `1、2、3` 的順序嘗試生成全部排列。每次選擇數字 `x` 時,它會: + +1. 把 `x` 加到當前路徑末尾; +2. 把 `x` 標記為“已使用”; +3. 遞迴填寫下一個位置。 + +遞迴返回後,同學只從路徑末尾刪除了 `x`,然後繼續嘗試下一個數字。 + + + +1. 演算法首先得到哪個排列?它還能得到全部 6 個排列嗎? +2. 遞迴返回上一層前,只刪除路徑末尾的數字是否足夠?如果不夠,還需要做什麼?說明理由。 + +??? success "參考答案" + + 1. 它首先得到 `[1, 2, 3]`,但無法得到全部排列。雖然返回時路徑變短了, + 數字 1、2、3 的標記仍都是“已使用”,後續分支便沒有可選數字。 + + 2. 不夠。刪除路徑末尾的 `x` 後,還必須把 `x` 重新標記為“未使用”。 + 當前路徑和已使用標記共同描述搜尋狀態;選擇時修改了兩處,回退時也必須把兩處都恢復, + 其他分支才能再次選擇 `x`。 + +### 數字的選擇順序重要嗎 + +給定排好序的陣列 `[2, 3, 5]` 和目標值 5,每個數可以重複選擇。 +演算法規定每條搜尋路徑中的數字只能按從小到大的順序出現。 + + + +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 行放置下一個皇后。 + + + +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" } + +> **說明:** 連結題解透過交換陣列元素,把已選元素依次放到陣列前部;本練習使用布林陣列記錄每個元素是否已選。兩種方法都能避免同一元素被重複選擇,但程式碼結構不同 diff --git a/zh-hant/docs/chapter_computational_complexity/exercises.md b/zh-hant/docs/chapter_computational_complexity/exercises.md new file mode 100644 index 000000000..04144e968 --- /dev/null +++ b/zh-hant/docs/chapter_computational_complexity/exercises.md @@ -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) +``` + + + +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` 中的元素全部反轉,有兩種做法: + + + +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" } diff --git a/zh-hant/docs/chapter_data_structure/exercises.md b/zh-hant/docs/chapter_data_structure/exercises.md new file mode 100644 index 000000000..5c4336099 --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/exercises.md @@ -0,0 +1,86 @@ +# 練習 + +## 知識鞏固 + +### 生活場景中的資料關係 + +請根據資料之間的關係,為下面三個場景選擇“線性結構、樹形結構、網狀結構”中的一種,並說明理由: + + + +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` 的位置。 + + + +1. 哪個方案屬於連續空間儲存?哪個屬於分散空間儲存? +2. 兩個方案分別更接近陣列還是鏈結串列? +3. 方案乙的記憶體格編號沒有按大小排列,為什麼仍能表示 `A → B → C` 的邏輯順序? + +??? success "參考答案" + + 1. 方案甲使用連續的記憶體格,屬於連續空間儲存;方案乙的節點分散在不同位置,屬於分散空間儲存。 + + 2. 方案甲更接近陣列,方案乙更接近鏈結串列。 + + 3. 邏輯順序由節點之間記錄的連線關係決定,而不是由記憶體格編號的大小決定。 + 從 `A` 記錄的位置可以找到 `B`,再從 `B` 記錄的位置找到 `C`,所以仍能依次訪問 `A、B、C`。 + + 這也說明邏輯結構和物理結構是觀察同一組資料的兩個不同角度。 + +### 作業記錄中的資料型別與結構 + +某學習小組按座位順序記錄 4 名同學是否交了作業,得到: + +`[true, false, true, true]` + + + +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" } diff --git a/zh-hant/docs/chapter_divide_and_conquer/exercises.md b/zh-hant/docs/chapter_divide_and_conquer/exercises.md new file mode 100644 index 000000000..66cb1d99e --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/exercises.md @@ -0,0 +1,98 @@ +# 練習 + +## 知識鞏固 + +### 哪些任務適合分治 + +一位同學想用“先分成兩半,分別解決,再合併結果”的方法完成以下任務。 +請分別判斷它們是“適合分治”“可以分治,但不會減少總工作量”還是“兩半不能獨立解決”,並說明理由。 + + + +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)`: + + + +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]` + +只完成根節點這一層的拆分,不必繼續遞迴,也不必畫出整棵樹: + + + +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" } + +> **說明:** 連結中的題解使用迭代位運算,與本練習指定的遞迴分治方法不同;請根據本題的解題提示完成遞迴版本 diff --git a/zh-hant/docs/chapter_dynamic_programming/exercises.md b/zh-hant/docs/chapter_dynamic_programming/exercises.md new file mode 100644 index 000000000..3deaa7cd3 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/exercises.md @@ -0,0 +1,113 @@ +# 練習 + +## 知識鞏固 + +### 什麼時候適合使用動態規劃 + +一位同學說:“只要能寫出遞推式,就應該使用動態規劃。” +請判斷下面三個任務分別更適合使用動態規劃、回溯,還是使用迴圈或數學公式而不建立 `dp` 表,並寫出一個關鍵理由。 + + + +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`: + + + +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`。 + + + +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. 容量必須從大到小更新,避免在同一輪中重複選擇當前物品 diff --git a/zh-hant/docs/chapter_graph/exercises.md b/zh-hant/docs/chapter_graph/exercises.md new file mode 100644 index 000000000..47778c252 --- /dev/null +++ b/zh-hant/docs/chapter_graph/exercises.md @@ -0,0 +1,103 @@ +# 練習 + +## 知識鞏固 + +### 用兩種方式表示同一張圖 + +一個無向圖有 4 個頂點 `A、B、C、D`,邊為 +`A-B、A-C、B-C、C-D`。 + + + +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 出發,並規定遇到多個未訪問鄰接頂點時按字母順序選擇: + + + +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`。 + + + +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" } diff --git a/zh-hant/docs/chapter_greedy/exercises.md b/zh-hant/docs/chapter_greedy/exercises.md new file mode 100644 index 000000000..6db51a2b0 --- /dev/null +++ b/zh-hant/docs/chapter_greedy/exercises.md @@ -0,0 +1,83 @@ +# 練習 + +## 知識鞏固 + +### 每次選最大硬幣一定最好嗎 + +硬幣面值為 `[1, 7, 10]`,目標金額為 14。 + + + +1. 按“每次取不超過剩餘金額的最大面值”執行,寫出所取硬幣; +2. 是否存在使用更少硬幣的方案?如果存在,請寫出一種;如果不存在,請說明理由; +3. 這能否說明該貪婪策略對任意硬幣面值都正確? + +??? success "參考答案" + + 1. 貪婪依次選擇 `10 + 1 + 1 + 1 + 1`,共 5 枚硬幣。 + + 2. 存在使用更少硬幣的方案:`7 + 7`,只需 2 枚硬幣。 + + 3. 不能。這個反例說明,對任意硬幣面值,每次選擇當前能取的最大面值並不一定得到最少硬幣數; + 眼前最大的選擇可能破壞後續更好的組合。 + +### 背包應該先裝哪件物品 + +一個容量為 4 千克的背包中可以裝入以下物品,物品允許只取一部分, +所取價值與重量成正比: + +- 物品 A:重量 4 千克,價值 20; +- 物品 B:重量 3 千克,價值 18。 + + + +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]`,用首尾雙指標尋找最大容量。 +容量等於“兩塊隔板中較短者的高度 × 兩塊隔板的下標距離”。 + + + +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. 若剩餘容量小於當前物品重量,就裝入恰好填滿背包的部分並結束 diff --git a/zh-hant/docs/chapter_hashing/exercises.md b/zh-hant/docs/chapter_hashing/exercises.md new file mode 100644 index 000000000..c04bc51fe --- /dev/null +++ b/zh-hant/docs/chapter_hashing/exercises.md @@ -0,0 +1,101 @@ +# 練習 + +## 知識鞏固 + +### 發生雜湊衝突後怎樣查詢 + +一個雜湊表有 5 個桶,雜湊函式為 $h(x)=x \bmod 5$,衝突時把元素依次放進該桶的串列中。 +依次插入 `[1, 6, 11, 7]`: + + + +1. 寫出 0~4 號桶中的內容; +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$: + + + +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 個位置,索引為 `0~4`,雜湊函式為 $h(x)=x\bmod5$。 +發生衝突時,從雜湊函式算出的索引開始,向右尋找第一個空位。 + +依次插入 `[1, 6, 11]`: + + + +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" } diff --git a/zh-hant/docs/chapter_heap/exercises.md b/zh-hant/docs/chapter_heap/exercises.md new file mode 100644 index 000000000..d813d429a --- /dev/null +++ b/zh-hant/docs/chapter_heap/exercises.md @@ -0,0 +1,88 @@ +# 練習 + +## 知識鞏固 + +### 數字 10 入堆積後怎樣調整 + +陣列 `[9, 7, 8, 3, 5]` 表示一個大頂堆積。現在將數字 10 入堆積。 + + + +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$。 + + + +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 的小頂堆積完成 diff --git a/zh-hant/docs/chapter_searching/exercises.md b/zh-hant/docs/chapter_searching/exercises.md new file mode 100644 index 000000000..b0a1887e0 --- /dev/null +++ b/zh-hant/docs/chapter_searching/exercises.md @@ -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 的左邊界。” + + + +1. 這名同學的說法是否正確?數字 2 的左、右邊界分別是多少?請說明理由。 +2. 查詢左邊界時,若中點元素等於目標,接下來應繼續搜尋哪一側? +3. 查詢右邊界時又應繼續搜尋哪一側?只需說明方向,無須寫出完整的查詢過程。 + +??? success "參考答案" + + 1. 這名同學的說法不正確。找到一個 2 後立即返回,只能保證找到某一個 2,不能保證找到最左或最右的 2。 + 本題的左邊界為索引 1,右邊界為索引 3。 + + 2. 查詢左邊界時,即使中點元素等於 2,也要繼續搜尋左側, + 例如在雙閉區間寫法中令 `j = m - 1`。 + + 3. 查詢右邊界時,中點元素等於 2 後應繼續搜尋右側, + 例如令 `i = m + 1`。 + +### 不同資料該選哪種搜尋方法 + +請在“順序查詢、二分搜尋、雜湊表”中,為下面三個場景選擇合適的方法,並說明理由: + + + +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" } diff --git a/zh-hant/docs/chapter_sorting/exercises.md b/zh-hant/docs/chapter_sorting/exercises.md new file mode 100644 index 000000000..41eb179ea --- /dev/null +++ b/zh-hant/docs/chapter_sorting/exercises.md @@ -0,0 +1,94 @@ +# 練習 + +## 知識鞏固 + +### 選擇排序和泡沫排序的前幾輪 + +給定陣列 `[4, 2, 5, 1, 3]`,以下都按從小到大排序。 + + + +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$ 的數值相同,但用下標標出了原有次序。 + + + +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 位的學號。請回答: + + + +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 diff --git a/zh-hant/docs/chapter_stack_and_queue/exercises.md b/zh-hant/docs/chapter_stack_and_queue/exercises.md new file mode 100644 index 000000000..242ee524d --- /dev/null +++ b/zh-hant/docs/chapter_stack_and_queue/exercises.md @@ -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 的環形陣列實現佇列,陣列索引為 `0~4`。 +當前 `front = 3`、`size = 2`,佇列中的 `A、B` 分別位於索引 3、4。 + + + +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()` + + + +1. 兩次彈出的元素分別是什麼? +2. 全部操作完成後,從佇列首到佇列尾還剩哪些元素? +3. 檢查這 6 步操作:只允許從佇列尾加入、從佇列首刪除的佇列能否全部完成?如果不能,請指出無法完成的操作;再說明雙向佇列能否完成及其原因。 + +??? success "參考答案" + + 前三步後,雙向佇列從佇列首到佇列尾為 `[C, A, B]`。 + + + + 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" } diff --git a/zh-hant/docs/chapter_tree/exercises.md b/zh-hant/docs/chapter_tree/exercises.md new file mode 100644 index 000000000..60a5af28a --- /dev/null +++ b/zh-hant/docs/chapter_tree/exercises.md @@ -0,0 +1,126 @@ +# 練習 + +## 知識鞏固 + +### 完全、完滿與完美二元樹 + +下面兩個陣列按層序表示二元樹,`None` 表示空位: + +- 樹 A:`[1, 2, 3, 4, 5, 6]` +- 樹 B:`[1, 2, 3, None, None, 6, 7]` + + + +1. 哪一棵是完全二元樹? +2. 哪一棵是完滿二元樹,即每個非葉節點都有兩個子節點? +3. 兩棵樹中是否有完美二元樹?分別說明理由。 + +??? success "參考答案" + + 1. 樹 A 是完全二元樹。它只有最底層未填滿,並且節點從左到右連續排列。 + 樹 B 不是完全二元樹,因為最底層左側已有空位,右側卻仍有節點。 + + 2. 樹 B 是完滿二元樹:節點 1 和節點 3 各有兩個子節點,其餘節點都是葉節點。 + 樹 A 不是完滿二元樹,因為節點 3 只有左子節點 6。 + + 3. 兩棵都不是完美二元樹,因為它們的最底層都沒有完全填滿。 + +### 同一棵樹的三種走訪順序 + +將陣列 `[1, 2, 3, 4, 5, 6, 7]` 按層序存入一棵完全二元樹。 + + + +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]` + + + +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" } diff --git a/zh-hant/mkdocs.yml b/zh-hant/mkdocs.yml index d7130209c..c3a010e15 100644 --- a/zh-hant/mkdocs.yml +++ b/zh-hant/mkdocs.yml @@ -75,7 +75,6 @@ nav: - 4.1   陣列: chapter_array_and_linkedlist/array.md - 4.2   鏈結串列: chapter_array_and_linkedlist/linked_list.md - 4.3   串列: chapter_array_and_linkedlist/list.md - # [status: new] - 4.4   記憶體與快取 *: chapter_array_and_linkedlist/ram_and_cache.md - 4.5   小結: chapter_array_and_linkedlist/summary.md - 4.6   練習: chapter_array_and_linkedlist/exercises.md