私は迷っています。バックトラック/再帰アプローチに頭を悩ませているようです。階乗のような単純な再帰問題がどのように機能するかを理解しているので、手動で追跡することもできます。しかし、それがバックトラックの問題になると、私はとても迷っています。頑張ります。 5時間経ちましたが、フォーラムやスタック交換に関するさまざまなアプローチを読んでいますが、何もクリックしていません。
たとえば、[1,2,3,4]
の配列があり、宛先の値が5
であるとします。目的の値に等しいすべての可能な組み合わせを見つけようとしています。
私はこの問題をさらに詳しく分析して、自分が理解しやすいようにしました。最初に、配列の可能なすべての組み合わせを見つけて、それらを別の関数に渡し、その配列の合計が宛先値と等しいかどうかをチェックして、それを出力するだけです。
誰かがこれに取り組む方法をアドバイスできますか?私は頭の中でこれをはっきりと想像し、想像できるようにしたいコード/答えを探していません。
サイズn
の配列を取ります。あそこ2ん この配列の可能なサブ配列。サイズ4のサンプル配列を見てみましょう:[1, 2, 3, 4]
。 2つあります4 サブ配列。
空のセットのサブ配列([]
)は0です番目 1つ(0000
)。 [1]
のサブ配列は2番目のもの(0001
)であり、[2]
のサブ配列は2番目のものです...(0010
)およびサブ配列[1, 2]
は3番目(0011
)です。これがどこに向かっているのかがわかるはずです。
これには再帰は必要ありません。サブ配列のセットは、nのバイナリ表現です番目 0から2の範囲の値ん -1。
この実現により、特定の配列のすべてのサブ配列の生成は、整数を反復し、特定の要素がサブ配列内にあるかどうかのビットフィールドとしてそれらを見るという簡単な問題になるはずです。
すべてのkの kの組み合わせの数も参照 ウィキペディア。
これはおそらくCや類似の言語でそれを行うための正しい方法であることを指摘します。再帰、リスト、バックトラックを強制し、そうでなければ、明確で理解可能な答えを持つ単純な反復問題になることを試みるのは、最善のアプローチではないかもしれません。
これに対する他の提案された回答はすぐに関数型プログラミングの例とソリューションになることに注意してください。関数型プログラミングは素晴らしいです。ただし、このようなプログラミングのパラダイムを強制しようとするのに適していない言語では、LISPでCコードを作成するのと同じです。問題に取り組むための最良の方法ではない可能性があります。
さらに、この質問で示唆されている問題は次のとおりであることを指摘しておきます。
たとえば、[1,2,3,4]の配列があり、宛先の値が5であるとします。宛先の値と等しいすべての可能な組み合わせを見つけようとしています。
これは サブセット合計問題 として知られているよく知られた問題であり、それを解決するためのいくつかのアプローチがあります...配列のすべてのサブセットを生成することは、より高速なアプローチには必要ありません(または望まれさえしません)それを解決するために。
合計して1 :: 2 :: 3 :: 4 :: []
になる5
のサブリストのセットを再帰的に列挙したいとします。 (::
は、リスト構築のOCaml表記です。)リスト処理の再帰的なステップは、リストの先頭1
と末尾2 :: 3 :: 4 :: []
を分割することです—セットをこれらの用語?
1 :: 2 :: 3 :: 4 :: []
には、合計で5
となる2種類のサブリストがあります。
1
で始まり、合計が4 = 5 - 1
になるリストが続くリストで構成されます。2 :: 3 :: 4 :: []
から5
までのサブリストで構成されます。素晴らしい! partition
関数int list -> int -> int list list
を呼び出して、必要なすべてのサブリストを見つける場合、上記の説明は、リストのpartition lst
を定義する方法を説明しただけですlst
of length n
長さn-1
のリストの定義が与えられています。
OCamlで関数を書くのは簡単です:
let rec partition lst w =
if w = 0 then
[[]]
else
match lst with
| [] -> []
| hd :: tl -> firstkind hd tl (w - hd) @ partition tl w
and firstkind hd tl w =
List.map (fun lst -> hd :: lst) (partition tl w)
この演習では、繰り返しを必要としないことを前提としています。たとえば、[1..4]の組み合わせは次のとおりです。
_[[1],[1,2],[1,2,3],[1,2,3,4],[1,2,4],[1,3],[1,3,4],
[1,4],[2],[2,3],[2,3,4],[2,4],[3],[3,4],[4]]
_
そうでない場合、プロセスはすべての再帰関数で実際に同じです。基本ケースを理解し、小さな問題の観点から大きな問題を理解します。
リストを取り、リストのリストを返す関数subarrays
を作成します。すべての組み合わせに対して1つなので、次のようになります。
_subarrays :: [a] -> [[a]]
_
ベースケースは通常非常に簡単です。この場合、空のリストの部分配列は空のリストなので、次のようになります。
_subarrays [] = []
_
リスト関数の再帰的なステップは、ほとんどの場合、「ヘッド」と呼ばれる最初の要素(ここではx
と表記)と、「テール」と呼ばれる残りの部分(ここではxs
と表記)に分割します。尾の正しい答えはすでにわかっていると想定し、それを使用して頭の答えを作成します。ここで、記号combos
を尾の答えに割り当てます。
_subarrays (x:xs) = let combos = subarrays xs
_
これまでのところ、これは比較的定型的なコードであり、ほぼすべての再帰関数で何らかの形で見つかります。トリッキーな部分は、リストを小さくするために解決したことです。別のレイヤーを追加するにはどうすればよいですか?
例として、combos
に[2,3,4]のすべてのソリューションが含まれていて、_1
_を含むソリューションも含まれているリストを作成しているとします。この場合、3つのことのいずれかを行って、より大きなリストを作成できます。
1
_を単独で使用するだけです。 (_[x]
_)combos
)1
_を追加します。 (map (x:) combos
)次に、これらすべての可能性を含むリストを返します。
_[x] : map (x:) combos ++ combos
_
これですべてです。以下は、合計テスト関数を含むHaskellの完全なプログラムです。
_subarrays :: [a] -> [[a]]
subarrays [] = []
subarrays (x:xs) = let combos = subarrays xs
in [x] : map (x:) combos ++ combos
combosSumTo :: Int -> [Int] -> [[Int]]
combosSumTo x = filter (\y -> sum y == x) . subarrays
_
私がどのように3つのことを思いついたのかに関しては、練習して、テストして繰り返す必要があります。初めてこれを書いたとき、誤って_[x]
_の部分を忘れてしまいましたが、テストでそれが表示されました。トリックはあなたの頭の中のスタックを再帰しようとしないことです。再帰呼び出しがリストの末尾の正しい答えを与えると仮定してください。