アルゴリズム概要

問題定義

多次元配列 arr と深さ n を受け取り、指定された深さまで平坦化した配列を返します。平坦化は現在のネスト深度が n 未満の場合にのみ実行されます。最初の配列の要素は深度 0 とみなされます。

入出力例

Example 1:

入力: arr = [1, 2, 3, [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]], n = 0
出力: [1, 2, 3, [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]]
説明: n=0 の場合、平坦化されません

Example 2:

入力: arr = [1, 2, 3, [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]], n = 1
出力: [1, 2, 3, 4, 5, 6, 7, 8, [9, 10, 11], 12, 13, 14, 15]
説明: 深度0のサブ配列のみ平坦化されます

制約条件

アルゴリズム戦略

✅ 主要アプローチ

  • 再帰的な深さ優先探索
  • クロージャで結果配列を共有
  • 1要素ずつpushで効率化
  • 型安全な再帰型定義

⚡ 最適化ポイント

  • スプレッド演算子を排除
  • concat()を使わない
  • 配列の再生成を回避
  • 定数時間push操作

性能

🚀 Runtime: 80ms (84.88%) | Memory: 76.02MB (85.12%)

ステップバイステップ解説

TypeScript実装

type MultiDimensionalArray = (number | MultiDimensionalArray)[];

/**
 * 多次元配列を指定された深さまで平坦化する
 *
 * @param arr - 平坦化する多次元配列
 * @param n - 平坦化する深さ(0の場合は平坦化しない)
 * @returns 平坦化された配列
 *
 * @complexity
 * Time: O(N) - N は全要素数
 * Space: O(N + D) - N は結果配列、D はコールスタック深度
 *
 * @example
 * flat([1, 2, [3, 4]], 1) // [1, 2, 3, 4]
 * flat([1, [2, [3]]], 1) // [1, 2, [3]]
 */
var flat = function (arr: MultiDimensionalArray, n: number): MultiDimensionalArray {
    // 結果配列を初期化(クロージャで共有)
    const result: MultiDimensionalArray = [];

    /**
     * 内部再帰関数:配列を深さ制限付きで平坦化
     * @param items - 処理対象の配列
     * @param depth - 残りの平坦化可能深度
     */
    function flatten(items: MultiDimensionalArray, depth: number): void {
        for (const item of items) {
            // 配列かつ深度制限内の場合、再帰的に展開
            if (Array.isArray(item) && depth > 0) {
                flatten(item, depth - 1);
            } else {
                // プリミティブまたは深度制限到達の配列をそのまま追加
                result.push(item);
            }
        }
    }

    // 初期呼び出し
    flatten(arr, n);

    return result;
};

実装のポイント

1. クロージャの活用

外部でresultを宣言し、内部関数から参照することで配列の再生成を回避

2. 型安全性

再帰型定義により任意深度の配列を表現し、コンパイル時に型エラーを検出

3. イミュータブル

元の配列を変更せず、新しい結果配列を構築するPure function

4. エッジケース

n=0、空配列、深い入れ子などを正しく処理

フローチャート

Flatten Deeply Nested Array Flowchart A flowchart diagram showing the algorithm flow for flattening a deeply nested array with depth control 開始 flatten(arr, n) 結果配列を初期化 result = [] 内部flatten関数を定義 function flatten(items, depth) 各要素を走査 for (const item of items) 終了 要素あり 配列 かつ depth > 0? はい 再帰呼び出し flatten(item, depth - 1) 次の要素へ いいえ 直接追加 result.push(item) 次の要素へ resultを返す return result 終了

📊 フローの説明

1

初期化:結果配列を空配列で初期化し、内部flatten関数を定義します

2

ループ処理:各要素についてループで走査します(for...of)

3

分岐判定:配列かつdepth > 0の場合は再帰的に展開、そうでなければ直接追加

4

ループバック:紫の破線矢印は次の要素への遷移を示します

5

完了:すべての要素を処理後、結果配列を返して終了します

🎨 色分けルール

緑:開始/終了/成功パス
青:処理ステップ
オレンジ:条件分岐
紫:ループバック

計算量分析

⏱️ 時間計算量

O(N)

N = 配列内の全要素数(プリミティブ値 + サブ配列の総数)
各要素を1回ずつ訪問し、配列判定とpush()はO(1)のため全体でO(N)

💾 空間計算量

O(N + D)

結果配列: O(N) - 全要素を格納
コールスタック: O(D) - 最大深度までの再帰呼び出し(D ≤ 1000)

アプローチ比較

実装方式 時間計算量 空間計算量 可読性 性能(実測)
最適化再帰版(推奨) O(N) O(N + D) ★★★★★ 80ms (84.88%)
スプレッド再帰版 O(N) O(N + D) ★★★★☆ 156ms (38.84%)
reduce版 O(N²) O(N² + D) ★★★☆☆ TLE
スタック反復版 O(N) O(N + D) ★★★☆☆ 100ms (73.26%)

💡 最適化の考察

  • スプレッド演算子...は大規模配列で非効率(内部コピーのコスト)
  • concat()は毎回新配列を生成しO(N²)に劣化
  • 最適化再帰版は可読性と性能を両立
  • クロージャによる配列共有がメモリ効率の鍵