質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

Q&A

解決済

2回答

2032閲覧

ある数になる全ての組み合わせを出力する

masa_engin

総合スコア15

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

0グッド

0クリップ

投稿2022/02/17 08:41

編集2022/02/17 09:35

入力データとして下記があり、

ruby

1candidates = [2,3,6,7], target = 7

このような結果を得たいです。

ruby

1出力: [[2,2,3],[7]]

与えられた候補の中から合計がtargetになる組み合わせを全て出力したいです。
自分なりにプログラムを組んでみましたが、並びが違うだけの同じ数の組み合わせが出力されてしまってうまく行きませんでした。
このアルゴリズム問題の解き方と、考え方がわかる方がいらっしゃいましたら、ご教授いただけますと幸いです。

ruby

1def combination_sum(candidates, target) 2 result = [] 3 (1..target).each do |num| 4 candidates.repeated_combination(num).to_a.each do |pair| 5 if pair.sum == target 6 result.push(pair) 7 end 8 end 9 end 10 result 11end 12 13# [[7],[2,2,3],[2,3,2],[3,2,2]] 14 15 16この場合、[[7],[2,2,3]]なってしまって[[2,2,3],[7]]にならないのと、単純に処理時間がかかってTimeOutしてしまうようです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

masa_engin

2022/02/17 09:33

def combination_sum(candidates, target) result = [] pp (1..target).each do |num| candidates.repeated_combination(num).to_a.each do |pair| if pair.sum == target result.push(pair) end end end result end この場合、[[7],[2,2,3]]なってしまって[[2,2,3],[7]]にならないのと、単純に処理時間がかかってTimeOutしてしまうようです。
BeatStar

2022/02/17 09:56

> ...この場合、[[7],[2,2,3]]なってしまって[[2,2,3],[7]]にならないのと... 追記はここに書かずに質問本文に追記してください。 そこに書かれても見逃してしまいます。 質問は編集できるので編集しましょう。
guest

回答2

0

ベストアンサー

Rubyはほとんど使ったことがないので、コードが拙いのはご容赦ください。あと、もっと良い方法があるかもしれません。

まずは候補を[2,3,6,7]に固定したコードを書いてみましょう。

2,3,6,7のそれぞれの使用回数について、そのすべての組み合わせを考えて、足した結果が target になるものを収集すればよさそうです。
target が 7 の場合、2の使用回数は0~3回のいずれかになるはずです。(4回だと target を超えてしまう)
ここで、最大回数は target を 2 で割った結果となることに注意しましょう。
同様に3の使用回数は0~2回、6の使用回数は0~1回、7の使用回数は0~1回のいずれかになるはずです。

これらをネストしたループで繰り返して、和が target になるものを収集すると考えると、以下のようなコードになります。
([2,2,3]が[7]の前に来るように、ループは逆順に回しています)

ruby

1def combination_sum(target) 2 result = [] 3 (target / 2).downto(0) do |n2| 4 (target / 3).downto(0) do |n3| 5 (target / 6).downto(0) do |n6| 6 (target / 7).downto(0) do |n7| 7 if 2 * n2 + 3 * n3 + 6 * n6 + 7 * n7 == target 8 result << [2] * n2 + [3] * n3 + [6] * n6 + [7] * n7 9 end 10 end 11 end 12 end 13 end 14 result 15end

さて、これを任意の候補でやりたいのですが、候補の数だけネストを深くしなければならないので、簡単にはいきません。
やり方としては、以下の2通りが考えられます。

  1. Array の product を使う
  2. 再帰を使う

まず 1. ですが、Array の product は、複数の配列から要素を選ぶ組み合わせのパターンをすべて列挙できます。
そのため、それぞれの候補の使用回数の配列を渡せば、1つのループで多重ループと同じことができます。
ただ、候補が1つもない場合は対応できないので別途処理する必要があります。
さらに、Array の product はやたらメモリを使うらしく、少し候補が増えると計算できなくなります。

ruby

1def combination_sum(candidates, target) 2 return (target == 0) ? [[]] : [] if candidates.empty? 3 result = [] 4 as = candidates.map{|c| (target / c).downto(0).to_a} 5 a = as.shift 6 a.product(*as) do |ns| 7 if candidates.zip(ns).map{|c, n| c * n}.sum == target 8 result << candidates.zip(ns).map{|c, n| [c] * n}.sum([]) 9 end 10 end 11 result 12end

次に2.の再帰を使う方法についてですが、以下のページが詳しいです。

よくやる再帰関数の書き方 〜 n 重 for 文を機械的に 〜

こちらのページに従うと、以下のようなコードになります。(面倒なのでグローバル変数を使っています)

ruby

1def combination_sum(candidates, target) 2 $candidates = candidates 3 $target = target 4 $result = [] 5 dfs([]) 6 $result 7end 8 9def dfs(ns) 10 if ns.size == $candidates.size 11 if $candidates.zip(ns).map{|c, n| c * n}.sum == $target 12 $result << $candidates.zip(ns).map{|c, n| [c] * n}.sum([]) 13 end 14 return 15 end 16 ($target / $candidates[ns.size]).downto(0) do |n| 17 ns.push(n) 18 dfs(ns) 19 ns.pop 20 end 21end

以下は以前の回答。

再帰を使う場合は以下のようになります。
候補から合計がtargetになる組み合わせを求める際には、候補の先頭の値を使う場合と使わない場合の2つに場合分けできます。
使う場合については、先頭の値の分だけ減らしたtargetを目標とした組み合わせを求めておいて、それらの前に先頭の値を追加すれば求められます。問題の例なら、combination_sum([2,3,6,7],7-2)を求めて([[2,3]])、それぞれの先頭に2を追加します([[2,2,3]])。
使わない場合については、単純に先頭の値を除いた候補からtargetを目標とした組み合わせを求めればよいです。問題の例なら、combination_sum([3,6,7],7)を求めます([[7]])。
あとは、再帰なので終了条件を適当に決めてあげれば、以下のように書けます。

ruby

1def combination_sum(candidates, target) 2 if target == 0 3 [[]] 4 elsif target < 0 || candidates.empty? 5 [] 6 else 7 head, *tail = candidates 8 combination_sum(candidates, target - head).map{|s| [head] + s} + combination_sum(tail, target) 9 end 10end

上記をボトムアップにしたり計算順を逆転させたりすると、動的計画法での解になります。
面倒なので、以下では一次元の表を上書きする形としています。

ruby

1def combination_sum(candidates, target) 2 dp = [[[]]] + [[]] * target 3 candidates.each do |c| 4 (0..target - c).each do |i| 5 dp[i + c] += dp[i].map{|s| s + [c]} 6 end 7 end 8 dp[-1] 9end

過去に同じ問題に回答していたので、リンクを貼っておきます。(ただし言語はPython)
combination sumを可読性の高いコードで計算する方法

投稿2022/02/17 20:59

編集2022/04/14 13:45
actorbug

総合スコア2224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

これを見る限り、repeated_combinationを使えばいいんだと思います。

permutationは順列、つまり { 1, 2 } と { 2, 1 } は別のものとして認識して計算する。数学だと 3P2 とか。
combinationは組み合わせ、{ 1, 2 } と { 2, 1 } は同じものとして重複しない計算。数学だと 3C2 とか。


...この場合、[[7],[2,2,3]]なってしまって[[2,2,3],[7]]にならない

必ず [[7],[2,2,3]] ですか? 単に組み合わせを求めるなら順番は関係ないはずです。もしかして競技プログラミングとかそういうのでしょうか?
それならちゃんとそういう定義を書くべきです。

もし単純に[7]から始めたいのなら、「配列の要素数でソートする」とかが考えられます。

単純に処理時間がかかってTimeOutしてしまう

それならrepeated_combination系を使わなければいいです。ざっと見ただけなのでわかりませんが、repeated_ が付いていない方はどうでしょうか?
計算量を意識して考えてみてください。

私の考えでは、「動的計画法」か「bit全探索」辺りでやる…ですかね。

「bit全探索 部分和」とかで検索してみてください。

投稿2022/02/17 09:07

編集2022/02/17 10:05
BeatStar

総合スコア4958

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問