mirror of
https://github.com/krahets/hello-algo.git
synced 2026-11-02 03:05:24 +08:00
* Retranslate the complete Japanese online edition from Chinese * Fit Japanese homepage labels and localize heading permalinks * Point Japanese contribution instructions to the Japanese preview * Replace locale conditionals with multilingual UI tables
4.7 KiB
4.7 KiB
練習問題
知識の確認
分割統治に適した作業
ある生徒が「まず半分に分け、それぞれを解き、結果を結合する」方法で次の作業を行おうとしています。 それぞれを「分割統治に適している」「分割統治はできるが総作業量は減らない」「二つを独立に解けない」のいずれかに分類し、理由を説明してください。
- 未整列配列をソートします。
- 配列の最大値を求めます。
- 一連のスタック操作
push(x)、pop()を順に実行し、各pop()で得た要素を出力します。
??? success "解答例"
1. 適しています。半分に分割し、それぞれを独立にソートして、$O(n)$ 時間で結合します。これはマージソートそのものです。
2. 分割統治はできますが、総作業量は減りません。左右の半分を合わせると、全 $n$ 要素を調べる必要があり、
直接走査する場合と同じ $O(n)$ 時間です。
3. 二つを独立には解けません。後半開始時のスタックの内容は前半の実行結果に依存するため、
互いの結果を知らずに独立して完了することはできません。
高速べき乗で計算を減らす仕組み
次の再帰関数は、分割統治で x^n を計算します。
[file]{fast_power}-[class]{}-[func]{fast_pow}
x = 3、n = 5 として、この関数で計算します。
- 再帰呼び出しで、引数
nは順にどの値になりますか。 - 最も深い呼び出しから戻るとき、各呼び出しは順にどの値を返しますか。
- 乗算の両側で同じ部分問題を一回ずつ呼び出さず、先に再帰の結果を
halfに保存するのはなぜですか。
??? 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. 乗算の両側で同じ部分問題を呼び出すと、二回の再帰でまったく同じ計算を行います。
先に結果を `half` に保存すれば、各層での再帰は一回だけとなり、再帰の深さは約 $\log n$ です。
二回呼び出すと、大量の重複計算が生じます。
走査列を左右の部分木に分ける
重複するノードのない二分木の行きがけ順と通りがけ順の走査列は、次のとおりです。
- 行きがけ順:
[A, B, D, E, C] - 通りがけ順:
[D, B, E, A, C]
根の層での分割だけを行ってください。それ以上再帰したり、木全体を描いたりする必要はありません。
- 根ノードはどれですか。
- 左右の部分木は、通りがけ順の列のどの部分に対応しますか。
- 左右の部分木は、行きがけ順の列のどの部分に対応しますか。根の直接の子はどれですか。
??? 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 の場合も含めて x^0=1 とします。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{ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }