44
55## 目次(Table of Contents)
66
7- - [ 概要] ( #overview )
8- - [ アルゴリズム要点 TL;DR] ( #tldr )
9- - [ 図解] ( #figures )
10- - [ 正しさのスケッチ] ( #correctness )
11- - [ 計算量] ( #complexity )
12- - [ Python 実装] ( #impl )
13- - [ CPython 最適化ポイント] ( #cpython )
14- - [ エッジケースと検証観点] ( #edgecases )
15- - [ FAQ] ( #faq )
7+ - [ Overview] ( #overview )
8+ - [ Algorithm] ( #algorithm )
9+ - [ Complexity] ( #complexity )
10+ - [ Implementation] ( #implementation )
11+ - [ Optimization] ( #optimization )
1612
1713---
1814
19- < h2 id = " overview " >概要</ h2 >
15+ ## Overview
2016
2117### 問題要約
2218
3733
3834---
3935
40- <h2 id =" tldr " >アルゴリズム要点 TL;DR</h2 >
36+ ## Algorithm
37+
38+ ### アルゴリズム要点 TL;DR
4139
4240- ** 戦略** : ` nums1 ` の末尾から走査する ** 3 ポインタ法** (後ろからマージ)
4341- ** データ構造** : 追加配列不要。` nums1 ` 自体をバッファとして再利用
4442- ** 時間計算量** : O(m + n) — 各要素をちょうど 1 回参照・書き込み
45- - ** 空間計算量** : O(1) — 追加ヒープアロケーションなし
46- (残余コピーのスライス代入は定数サイズの一時オブジェクト 1 個のみ)
43+ - ** 空間計算量** : O(n) — スライス代入による一時オブジェクト生成の最悪ケース
4744- ** 核心の不変条件** : 書き込みポインタ ` k ` は常に読み取りポインタ ` i ` 以上
4845 → 未処理の ` nums1 ` 要素を上書きしない
4946- ** 後ろから書く理由** : 前から比較すると ` nums1 ` の有効要素を上書きしてしまうため逆順が安全
5047
51- ---
52-
53- <h2 id =" figures " >図解</h2 >
48+ ### 図解
5449
55- ### フローチャート
50+ #### フローチャート
5651
5752``` mermaid
5853flowchart TD
@@ -75,9 +70,7 @@ flowchart TD
7570
7671> 後ろから比較して大きい方を末尾(` k ` )に書き込む。どちらかが尽きたら ` nums2 ` の残余を先頭スライスへ一括コピー。
7772
78- ---
79-
80- ### データフロー図(ステップ別状態遷移)
73+ #### データフロー図(ステップ別状態遷移)
8174
8275``` mermaid
8376graph LR
@@ -113,11 +106,9 @@ graph LR
113106
114107> 各ステップで大きい方の値が ` k ` の位置に書き込まれ、対応するポインタと ` k ` が 1 ずつ減少する。
115108
116- ---
109+ ### 正しさのスケッチ
117110
118- <h2 id =" correctness " >正しさのスケッチ</h2 >
119-
120- ### 不変条件
111+ #### 不変条件
121112
122113書き込みポインタ ` k ` と ` nums1 ` の読み取りポインタ ` i ` について、以下が常に成立する:
123114
@@ -130,44 +121,46 @@ k - i = (m + n - 1 - step_count) - (m - 1 - i_decrements)
130121` i ` が減少するとき ` k - i ` は変わらず、` j ` が減少するとき ` k - i ` は 1 増加する。
131122よって ` k ≥ i ` が保持され、** 未処理の ` nums1 ` 要素を上書きしない** 。
132123
133- ### 網羅性
124+ #### 網羅性
134125
135126- ` while ` ループ終了後、` i < 0 ` または ` j < 0 ` のどちらかが成立
136127- ` j < 0 ` の場合:` nums2 ` は全て書き込み済み、` nums1 ` の残余は正しい位置にある(移動不要)
137128- ` j ≥ 0 ` の場合:` nums2[:j+1] ` を ` nums1[:j+1] ` にスライス代入して完了
138129
139- ### 基底条件(エッジケース)
130+ #### 基底条件(エッジケース)
140131
141132| 条件 | 処理 |
142133| -------- | --------------------------------------------- |
143134| ` n == 0 ` | 早期リターン、` nums1 ` はそのまま |
144135| ` m == 0 ` | ` while ` の ` i >= 0 ` が即 False → 残余コピーへ |
145136
146- ### 終了性
137+ #### 終了性
147138
148139` i ` と ` j ` はループごとに必ず 1 以上減少し、非負整数であるため有限ステップで終了する。
149140
150141---
151142
152- < h2 id = " complexity " >計算量</ h2 >
143+ ## Complexity
153144
154145| 観点 | 計算量 | 説明 |
155146| ---------- | -------- | ------------------------------------------ |
156147| 時間計算量 | O(m + n) | 各要素を最大 1 回比較・書き込み |
157- | 空間計算量 | O(1 ) | スライス代入の一時オブジェクトは定数サイズ |
148+ | 空間計算量 | O(n ) | スライス代入による一時オブジェクト生成による最悪ケース |
158149
159150### In-place vs Pure 比較
160151
161152| 実装方針 | 時間 | 空間 | 備考 |
162153| ----------------------------- | ----------------- | -------- | --------------------------------------- |
163- | 本実装(後ろから 3 ポインタ) | O(m+n) | ** O(1 )** | ✅ 最適 |
154+ | 本実装(後ろから 3 ポインタ) | O(m+n) | ** O(n )** | スライス代入の空間コストを含む |
164155| 前から + 一時バッファ | O(m+n) | O(m) | ` nums1[:m] ` を ` list() ` でコピー |
165156| ` nums1 + nums2 ` 後 ` .sort() ` | O((m+n) log(m+n)) | O(1) | Timsort は C 実装で高速だが計算量は劣る |
166157| ` heapq.merge ` + スライス代入 | O(m+n) | O(m+n) | 一時リストが発生 |
167158
168159---
169160
170- <h2 id =" impl " >Python 実装</h2 >
161+ ## Implementation
162+
163+ ### Python 実装
171164
172165``` python
173166from __future__ import annotations
@@ -180,7 +173,7 @@ class Solution:
180173 """
181174 2 つのソート済み配列を nums1 にインプレースでマージする。
182175
183- 後ろから走査する 3 ポインタ法により O(m+n) 時間・O(1) 追加空間を実現 。
176+ 後ろから走査する 3 ポインタ法により O(m+n) 時間・O(n) 空間(スライス代入による最悪ケース)を実現 。
184177
185178 不変条件: k >= i が常に成立するため nums1 の未処理要素を上書きしない。
186179 初期値 k - i = n >= 0 が各イテレーションで保持される。
@@ -195,7 +188,7 @@ class Solution:
195188 None(nums1 を破壊的に変更)
196189
197190 Time Complexity: O(m + n)
198- Space Complexity: O(1 )
191+ Space Complexity: O(n )
199192 """
200193 # ── エッジケース: nums2 が空なら nums1 はそのまま ──────────────────
201194 if n == 0 :
@@ -225,51 +218,7 @@ class Solution:
225218 nums1[: j + 1 ] = nums2[: j + 1 ]
226219```
227220
228- ---
229-
230- <h2 id =" cpython " >CPython 最適化ポイント</h2 >
231-
232- ### 1. スライス代入による残余コピーの高速化
233-
234- ``` python
235- nums1[: j + 1 ] = nums2[: j + 1 ]
236- ```
237-
238- CPython の ` list.__setitem__ ` スライス版は内部で ` list_ass_slice ` を呼び出し、
239- ` memmove ` 相当の C レイヤーのメモリコピーが走る。
240- 逐次 ` while j >= 0: nums1[k] = nums2[j]; j -= 1; k -= 1 ` より
241- ** インタープリタのバイトコードディスパッチ回数を大幅に削減** できる。
242-
243- ### 2. LOAD_FAST によるローカル変数の高速アクセス
244-
245- | アクセス種別 | バイトコード | 速度 |
246- | ----------------------------------------------- | ------------- | -------------------- |
247- | ローカル変数(` i ` , ` j ` , ` k ` , ` nums1 ` , ` nums2 ` ) | ` LOAD_FAST ` | 高速(辞書探索なし) |
248- | グローバル変数 | ` LOAD_GLOBAL ` | やや低速 |
249- | 属性アクセス(` self.xxx ` ) | ` LOAD_ATTR ` | 最も低速 |
250-
251- 本実装では全カウンタがメソッドのローカルスコープに収まるため、
252- ` LOAD_FAST ` が全アクセスに適用される。
253-
254- ### 3. 早期リターンによる不要な処理の排除
255-
256- ``` python
257- if n == 0 :
258- return
259- ```
260-
261- ` n == 0 ` の場合はループに入らず即リターン。
262- 条件チェックのオーバーヘッドが極めて小さい。
263-
264- ### 4. 型ヒントとランタイムの分離
265-
266- ` from __future__ import annotations ` を使用することで、
267- 型アノテーションの評価が ** 遅延評価(文字列化)** になり、
268- 実行時のインポートオーバーヘッドを最小化する。
269-
270- ---
271-
272- <h2 id =" edgecases " >エッジケースと検証観点</h2 >
221+ ### エッジケースと検証観点
273222
274223| ケース | 入力例 | 期待出力 | 対応箇所 |
275224| ------------------------------------- | ------------------------------------------- | -------------- | -------------------------------------- |
@@ -281,15 +230,13 @@ if n == 0:
281230| 負の数を含む | ` nums1=[-3,-1,0,0], m=2, nums2=[-2,0], n=2 ` | ` [-3,-2,-1,0] ` | 整数比較なので問題なし |
282231| 最大制約 | ` m=n=100 ` | 正しくマージ | O(m+n) で余裕 |
283232
284- ### 静的解析観点(Pylance strict mode)
233+ #### 静的解析観点(Pylance strict mode)
285234
286235- ` List[int] ` 型ヒントにより ` nums1[k] ` への ` int ` 代入が型安全
287236- ` -> None ` 明示により暗黙的な ` None ` 返却が警告なし
288237- ` from __future__ import annotations ` で前方参照の問題を回避
289238
290- ---
291-
292- <h2 id =" faq " >FAQ</h2 >
239+ ### FAQ
293240
294241** Q1. なぜ前からではなく後ろからマージするのか?**
295242
@@ -305,8 +252,8 @@ if n == 0:
305252** Q3. スライス代入 ` nums1[:j+1] = nums2[:j+1] ` で一時オブジェクトは発生しないのか?**
306253
307254右辺 ` nums2[:j+1] ` がリストスライスとして一時的に生成される(O(j) サイズ)。
308- ただし ` j ` は最大でも ` n-1 ` であり定数スケールのため、実質 O(1) の追加コスト 。
309- 逐次代入に比べてバイトコードのオーバーヘッドを削減できるため、実用上は高速 。
255+ ` j ` は最大で ` n-1 ` となるため、最悪ケースでは O(n) の追加空間を消費する 。
256+ 厳密な O(1) が必要な場合は while ループによる各要素コピーが推奨されるが、 逐次代入に比べてバイトコードのオーバーヘッドを削減できるため、Python 実用上はスライスが高速 。
310257
311258** Q4. ` .sort() ` を使う方法ではダメなのか?**
312259
@@ -336,3 +283,47 @@ O(m+n) だが一時リストが O(m+n) の追加空間を消費する。
336283
337284` nums1[i] >= nums2[j] ` の条件により、等値の場合は ` nums1 ` 側が優先される。
338285問題仕様上、安定性(相対順序の保持)は要求されていないため、どちらでも正解。
286+
287+ ---
288+
289+ ## Optimization
290+
291+ ### CPython 最適化ポイント
292+
293+ #### 1. スライス代入による残余コピーの高速化
294+
295+ ``` python
296+ nums1[: j + 1 ] = nums2[: j + 1 ]
297+ ```
298+
299+ CPython の ` list.__setitem__ ` スライス版は内部で ` list_ass_slice ` を呼び出し、
300+ ` memmove ` 相当の C レイヤーのメモリコピーが走る。
301+ 逐次 ` while j >= 0: nums1[k] = nums2[j]; j -= 1; k -= 1 ` より
302+ ** インタープリタのバイトコードディスパッチ回数を大幅に削減** できる。
303+
304+ #### 2. LOAD_FAST によるローカル変数の高速アクセス
305+
306+ | アクセス種別 | バイトコード | 速度 |
307+ | ----------------------------------------------- | ------------- | -------------------- |
308+ | ローカル変数(` i ` , ` j ` , ` k ` , ` nums1 ` , ` nums2 ` ) | ` LOAD_FAST ` | 高速(辞書探索なし) |
309+ | グローバル変数 | ` LOAD_GLOBAL ` | やや低速 |
310+ | 属性アクセス(` self.xxx ` ) | ` LOAD_ATTR ` | 最も低速 |
311+
312+ 本実装では全カウンタがメソッドのローカルスコープに収まるため、
313+ ` LOAD_FAST ` が全アクセスに適用される。
314+
315+ #### 3. 早期リターンによる不要な処理の排除
316+
317+ ``` python
318+ if n == 0 :
319+ return
320+ ```
321+
322+ ` n == 0 ` の場合はループに入らず即リターン。
323+ 条件チェックのオーバーヘッドが極めて小さい。
324+
325+ #### 4. 型ヒントとランタイムの分離
326+
327+ ` from __future__ import annotations ` を使用することで、
328+ 型アノテーションの評価が ** 遅延評価(文字列化)** になり、
329+ 実行時のインポートオーバーヘッドを最小化する。
0 commit comments