Files
hello-algo/zh-hant/docs/chapter_divide_and_conquer/exercises.md
T
2026-07-19 07:19:42 +08:00

99 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 練習
## 知識鞏固
### 哪些任務適合分治
一位同學想用“先分成兩半,分別解決,再合併結果”的方法完成以下任務。
請分別判斷它們是“適合分治”“可以分治,但不會減少總工作量”還是“兩半不能獨立解決”,並說明理由。
<!-- 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" }
> **說明:** 連結中的題解使用迭代位運算,與本練習指定的遞迴分治方法不同;請根據本題的解題提示完成遞迴版本