Pythonで合計がkになる部分集合の個数を数えるプログラム
数値のリスト nums と整数 k が与えられたとき、リストの要素から作れる部分集合(サブセット)のうち、合計がちょうど k になるものの個数を求めます。答えが非常に大きくなる可能性があるため、結果は 109 + 7 で割った余りを返します。
たとえば、入力が nums = [2, 3, 4, 5, 7]、k = 7 の場合、出力は 3 になります。[2, 5]、[3, 4]、[7] の 3 つの部分集合が条件を満たすためです。
解法のアプローチ:動的計画法(DP)
この問題はナップサック問題と同じ構造を持っており、動的計画法を使うことで効率的に解けます。dp[j] を「合計が j になる部分集合の個数」と定義し、各要素を順番に処理しながら DP テーブルを更新していきます。
アルゴリズムの手順
- サイズ (k + 1) のリスト dp を作成し、すべて 0 で初期化します。
- dp[0] := 1 とします。何も選ばない空の部分集合で合計 0 が作れるのは 1 通りだからです。
- m := 10^9 + 7 とします(剰余を取るための定数)。
- i を 0 から nums の長さ - 1 まで繰り返します。
- j を k から 0 まで 1 ずつ減らしながら繰り返します。
- nums[i] <= j の場合:
- dp[j] := dp[j] + dp[j - nums[i]]
- dp[j] := dp[j] mod m
- nums[i] <= j の場合:
- j を k から 0 まで 1 ずつ減らしながら繰り返します。
- 最後に dp[k] mod m を返します。
なぜ j を降順にループするのか?
内側のループを k から 0 に向かって降順に回すのが重要なポイントです。もし昇順に処理すると、同じ要素を何度も使ってしまうため正しい答えが得られません。降順にすることで、各要素がそれぞれ 1 回だけ使われることが保証されます。
実装例
class Solution:
def solve(self, nums, k):
dp = [0] * (k + 1)
dp[0] = 1
m = int(1e9 + 7)
for i in range(len(nums)):
for j in range(k, -1, -1):
if nums[i] <= j:
dp[j] += dp[j - nums[i]]
dp[j] %= m
return dp[k] % m
ob = Solution()
nums = [2, 3, 4, 5, 7]
k = 7
print(ob.solve(nums, k))
入力
[2, 3, 4, 5, 7], 7
出力
3
計算量
時間計算量は O(n × k)、空間計算量は O(k) です(n は nums の要素数)。すべての部分集合を列挙する O(2n) の総当たり法と比べて、大幅に効率化できることがわかります。
-
Pythonでリストの累積和(累積合計)を求める方法
この記事では、リストの累積和(累積合計)を求める問題の解決策について詳しく解説します。問題文あるリストが与えられたとき、各要素までの累積和を格納した新しいリストを作成する必要があります。例えば、[10, 20, 30, 40, 50] というリストが与えられた場合、出力は [10, 30, 60, 100, 150] となります。これは、各位置でそれ以前の要素をすべて足し合わせた値です。実装例それでは、実際の実装を見ていきましょう。# 累積和を求める関数 def Cumulative(l): new = [] cumsum = 0 for element in l:
-
Pythonで配列の反転数(転倒数)をカウントする方法
はじめに この記事では、配列内の反転(インバージョン)をカウントする問題とその解決策について詳しく解説します。 問題定義 問題: リストが与えられたとき、その中に含まれる反転の数をカウントして表示します。 反転数とは、配列を昇順にソートされた状態にするために必要な入れ替え(スワップ)の回数を表す指標です。具体的には、i < j かつ arr[i] > arr[j] を満たす要素のペア(i, j)の総数として定義されます。 実装例 # 反転数をカウントする関数 def InvCount(arr, n): inv_count = 0 for i in range(n