私は再帰を理解し、ツリーの問題を解決している間、それが便利で直感的であると思いますが、他の多くの問題では、再帰によって戸惑うことはありません。最近、私は次の問題を解決していました:
コインの金種のリストを指定して、金額を変更するさまざまな方法を数える再帰関数を記述します。
頑張ってグーグルで試した後、私は次の解決策を思いつきました詳細について考えるとめまいがします。
function isEmpty(coins) {
return coins.length === 0;
}
function allButFirst(coins) {
return coins.slice(1);
}
function countChange(money, coins) {
if (money === 0) return 1
else if (money > 0 && !isEmpty(coins)) {
return countChange(money - coins[0], coins) +
countChange(money, allButFirst(coins));
}
else // money < 0
return 0;
}
console.clear();
console.log(countChange(4, [1, 2])); // 3 (1+1+1+1), (1+1+2), (2+2)
私の知る限り、再帰的ソリューションの最初のステップは、基礎となる構造が再帰的構造であるかどうかをチェックすることです。私の場合はそうです。しかし、私はメンタルモデルのようなある種の原理や、そのような解決策を設計するのを助けることができる何か、私が間違った方向に行くのを止めることができる何かがうまくいくでしょうか?
ここには2つの問題があります。
これら両方を組み合わせると、かなり混乱します。したがって、これらの問題を分離しておく必要があります。
探しているソリューションの特定の呼び出しまたはレベルの結果は、順列の要件により、単一の回答ではなく、一連の回答または配列の配列であることを最初に認識しておく必要があります。
順列部分を除算した場合、結果の答えは金種ごとのコインの数のセットであるため、単一の最良の変化の答えでは、ペアのセットである結果タイプがあります。その後、順列を考慮に入れると、それらのセットの配列ができます。
したがって、ソリューションは、状況に対しておそらく最良の回答(変更の量)を作成し、別の回答が表示されるように状況を並べ替える必要があります。これらは最終的にコードの異なる部分になると私は考えます。たとえば、ツリーを(順番に)トラバースする場合は、次のように開始します。(1)左にトラバースし、(2)自分で作業し、(3)右にトラバースします。これらの各ステップは、コード内で互いに区別されるため、明確に表示されます。
最後に、順列は各タイプのコインの量を制限する必要があります。それ以外の場合、すべての順列にヒットすることはないと思います。したがって、入力では、金額と、数量の両方または無限の数量を記述する必要があります。次に、順列は最初に数量を制限し、次に金種のみを制限することができます(これは当然、数量からゼロに制限されるようになります)。
つまり、このような問題が発生すると予想される結果のタイプまたは形状を真剣に検査する必要があり、提供する入力のタイプまたは形状についても同様です。
(また、C#またはJavaを使用している場合は、適切な型について言語とコンパイラーから適切なヘルプが得られますが、型の支援が制限されている言語で作業しています。)
再帰アルゴリズムを作成する方法は次のとおりです...
最初に、最も単純なケースを解決する関数を記述します。次に、次の最も単純なケースを解決するために、最初にそれを最も単純なケースに減らし、次に最も単純なケースに対して関数を再度呼び出します。
この問題について、コインが1、5、10、25の金種であると仮定します。
したがって、最初に0に変更を加えることができるすべての方法を返し、他の状況ではエラーを出力する関数を記述します。 (もちろん、0の変更は、コインの空の配列になります。)
次に、関数を変更して、1、2、3、最大4まで変更できるようにします(いずれの場合も、ソリューションは1つだけで、送信された金額の合計を表す1の配列が1つあります。
次に、最初の興味深いもの... 5に変更します。ここで、関数は2つの配列を生成する必要があります。 1つは[5]を含み、もう1つは[1] + makeChange(4)を含みます。
次の興味深いもの... 10の変更を行います。これで、[[10]、[5] + makeChange(5)、[1] + makeChange(9)]が返されます。
等々...