Description

有一個犯罪集團,共 n 個成員。有多個犯罪計劃,第 i 個計劃需要 group[i] 個成員,能獲利 profit[i]。同一個成員不能參與多個計劃。回傳能獲利至少 minProfit 的方案數(對 10^9+7 取餘)。

Example:

Input: n = 5, minProfit = 3, group = [2,2], profit = [2,3] Output: 2

Intuition#

三維 0/1 背包:維度為「計劃」、「人數」、「利潤」,計算滿足最低利潤的方案數。

  • dp[j][k] 表示使用 j 個成員、獲利恰好為 k 的方案數
  • 利潤維度上限為 minProfit(超過都算作 minProfit,因為只要 >= 就行)
  • 最後答案為 dp[0..n][minProfit] 的總和

Approaches#

1: 3D DP#

  • 概念: dp[i][j][k] 表示考慮前 i 個計劃、用 j 個人、獲利為 k 的方案數。
  • 時間複雜度: O(plans * n * minProfit)
  • 空間複雜度: O(plans * n * minProfit)
class Solution {
    fun profitableSchemes(n: Int, minProfit: Int, group: IntArray, profit: IntArray): Int {
        val MOD = 1_000_000_007L
        val plans = group.size
        val dp = Array(plans + 1) { Array(n + 1) { LongArray(minProfit + 1) } }
        for (j in 0..n) dp[0][j][0] = 1

        for (i in 1..plans) {
            val g = group[i - 1]
            val p = profit[i - 1]
            for (j in 0..n) {
                for (k in 0..minProfit) {
                    dp[i][j][k] = dp[i - 1][j][k]
                    if (j >= g) {
                        val prevK = maxOf(0, k - p)
                        dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j - g][prevK]) % MOD
                    }
                }
            }
        }

        var result = 0L
        for (j in 0..n) result = (result + dp[plans][j][minProfit]) % MOD
        return result.toInt()
    }
}

⭐2: 2D DP(空間優化)#

  • 概念: 壓縮掉計劃維度,逆序遍歷人數和利潤。利潤超過 minProfit 的統一歸到 minProfit
  • 時間複雜度: O(plans * n * minProfit)
  • 空間複雜度: O(n * minProfit)
class Solution {
    fun profitableSchemes(n: Int, minProfit: Int, group: IntArray, profit: IntArray): Int {
        val MOD = 1_000_000_007L
        val dp = Array(n + 1) { LongArray(minProfit + 1) }
        for (j in 0..n) dp[j][0] = 1

        for (i in group.indices) {
            val g = group[i]
            val p = profit[i]
            for (j in n downTo g) {
                for (k in minProfit downTo 0) {
                    val newK = minOf(k + p, minProfit)
                    dp[j][newK] = (dp[j][newK] + dp[j - g][k]) % MOD
                }
            }
        }
        return dp[n][minProfit].toInt()
    }
}

🔑 Takeaways#

  • Pattern: 多維 0/1 背包 — 人數和利潤兩個限制
  • 關鍵技巧: 利潤超過目標的統一歸類為 minProfit,避免陣列過大;逆序遍歷是 0/1 背包的標準技巧