これは、整数分割問題または部分集合和問題に似ていますが、厳密には同じではありません。これは整数パーティションの問題ではありません。整数パーティションでは数値の繰り返しが許可されているためです (ここでは、範囲内の各数値の 1 回の出現のみを許可しています)。
これは部分集合和問題 (動的計画法によって多かれ少なかれ効率的に解くことができる) に似ていますが、解法は、与えられた数に追加される可能性のある数の部分集合をすべて生成するように適応させる必要があります。その問題の元の定式化のように、サブセットは 1 つだけです。Scheme を使用して動的プログラミング ソリューションを実装することは可能ですが、可変テーブルの実装にマトリックス ライブラリなどを使用しない限り、少し面倒です。
別の可能な解決策を次に示します。今回は、範囲の累乗セット[1, n]
を生成し、各サブセットを順番にチェックして、合計が期待値に追加されるかどうかを確認します。ただし、これは依然として力ずくのアプローチです。
; helper procedure for generating a list of numbers in the range [start, end]
(define (range start end)
(let loop ((acc '())
(i end))
(if (< i start)
acc
(loop (cons i acc) (sub1 i)))))
; helper procedure for generating the power set of a given list
(define (powerset set)
(if (null? set)
'(())
(let ((rest (powerset (cdr set))))
(append (map (lambda (element) (cons (car set) element))
rest)
rest))))
; the solution is simple using the above procedures
(define (find n sum)
(filter (lambda (s) (= sum (apply + s)))
(powerset (range 1 n))))
; test it, it works!
(find 10 10)
=> '((1 2 3 4) (1 2 7) (1 3 6) (1 4 5) (1 9) (2 3 5) (2 8) (3 7) (4 6) (10))
アップデート
前のソリューションは正しい結果を生成しますが、一部のサブセットのみに関心がある場合でも、パワー セットのリスト全体を生成するため、メモリの使用効率が低下します。Racket Scheme では、次のように遅延シーケンスを使用すると、必要に応じて値のみを生成することができます (ただし、注意してください - 最初のソリューションの方が高速です!)。
; it's the same power set algorithm, but using lazy streams
(define (powerset set)
(if (stream-empty? set)
(stream '())
(let ((rest (powerset (stream-rest set))))
(stream-append
(stream-map (lambda (e) (cons (stream-first set) e))
rest)
rest))))
; same algorithm as before, but using lazy streams
(define (find n sum)
(stream-filter (lambda (s) (= sum (apply + s)))
(powerset (in-range 1 (add1 n)))))
; convert the resulting stream into a list, for displaying purposes
(stream->list (find 10 10))
=> '((1 2 3 4) (1 2 7) (1 3 6) (1 4 5) (1 9) (2 3 5) (2 8) (3 7) (4 6) (10))