JavaScript
 Computer >> コンピューター >  >> プログラミング >> JavaScript

【JavaScript】バックトラックで階段の上り方を全パターン列挙する方法

問題設定

n段の階段を上ることを考えます。せっかくなので、運動も兼ねて一段ずつ歩くのではなく、ジャンプして登ることにしましょう。

1回のジャンプで進めるのは最大k段まで。ここではkは階段の段数に関係なく、常に1か2であるとします。

課題は、この階段を上り切るために取り得るすべてのジャンプ系列を、ソート済みの配列として返すことです。


入出力例

たとえば n = 4、k = 2 のとき:

climbingStaircase(4, 2)

期待される出力は以下のとおりです。

[[1, 1, 1, 1], [1, 1, 2], [1, 2, 1], [2, 1, 1], [2, 2]]

[1, 1, 1, 1] は毎回1段ジャンプするパターン、[2, 2] は毎回2段ジャンプするパターンを意味します。この場合、合計5通りの上り方が存在します。


バックトラックでの実装方法

すべてのパターンを列挙したい場合は、バックトラック(巻き戻し探索)が定番のアプローチです。各ステップで「1段ジャンプ」「2段ジャンプ」の選択肢を順に試し、残りの段数がちょうど0になった時点でその経路を答えとして記録します。行き詰まったら、直前の選択を取り消して別の分岐へ進みます。

const climbingStaircase = (n, k) => {
  const result = [];
  const path = [];

  const backtrack = (remaining) => {
    // 残り0段 → 完成した経路を保存
    if (remaining === 0) {
      result.push([...path]);
      return;
    }
    // 段数を超えていたら打ち切り
    if (remaining < 0) return;

    for (let step = 1; step <= k; step++) {
      path.push(step);             // 選ぶ
      backtrack(remaining - step); // 探索を進める
      path.pop();                  // 選択を戻す(バックトラック)
    }
  };

  backtrack(n);
  return result;
};

console.log(climbingStaircase(4, 2));

実行結果

[
  [1, 1, 1, 1],
  [1, 1, 2],
  [1, 2, 1],
  [2, 1, 1],
  [2, 2]
]

出力は辞書順にソートされており、要件どおりの結果になっています。ただし計算量は解の総数に比例して増えるため、nが大きい場合は処理時間に注意が必要です。


補足:メモ化で「通り数」だけを数える

パターンの列挙ではなく、「何通りあるのか」というだけが必要なケースもあります。その場合は、メモ化(キャッシュ)付きの再帰で効率よく計算できます。f(n) = f(n-1) + f(n-2) という漸化式に基づく、シンプルな動的計画法です。

const n = 4;

const countWays = (n) => {
  const memo = new Map();
  const recur = (left) => {
    if (left === 1) return 1;
    if (left === 2) return 2;
    if (memo.has(left)) return memo.get(left);
    memo.set(left, recur(left - 1) + recur(left - 2));
    return memo.get(left);
  };
  return recur(n);
};

console.log(countWays(n));

実行結果

5

コンソールには 5 と表示され、先ほど列挙したパターンの総数と一致していることがわかります。


まとめ

  • すべての上り方を列挙したい → バックトラック(DFS+選択の巻き戻し)
  • 上り方の個数だけ知りたい → メモ化再帰・動的計画法

目的に応じてこの2つの手法を使い分けることで、階段上り問題は柔軟かつ効率的に解くことができます。

  1. JavaScriptで非同期ループを実装する方法をわかりやすく解説

    JavaScriptでは、async/awaitとPromiseを組み合わせることで、各処理の完了を待ちながら繰り返しを実行する「非同期ループ」を実装できます。通常のループ内で時間のかかる処理(API通信やタイマー処理など)を順番に実行したい場合に非常に便利です。以下は、JavaScriptで非同期ループを実装するコード例です。コード例<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="vi

  2. 【JavaScript入門】Symbolを使ってオブジェクトごとに一意のIDを作成する方法

    はじめにJavaScriptでオブジェクトごとに一意のIDを作成したい場合、Symbol()を使うのが最も簡単かつ確実な方法です。Symbolは呼び出されるたびに必ず新しい一意の値を生成するため、たとえ同じ説明文字列を渡しても、二度と同じ値にはなりません。以下は、各オブジェクトに対して一意のIDを作成するサンプルコードです。実装例<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewp