LeetCode - 0/1 Knapsack Problems
Medium Hard
0/1 背包基本概念
0/1 背包的核心是:每個物品只能選一次
- 每個
num、每個stone、每個字串、每個 crime 都只能使用一次 - 所以更新 dp 時,容量通常要從大到小更新
- 這樣可以避免同一個物品在同一輪被重複使用
常見寫法:
1 | |
我的另一篇文章有解說各種背包問題的思路,底家 XD:
Algorithm - DP Knapsack416. Partition Equal Subset Sum
題目
Given an integer array nums, return true if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or false otherwise.
Example 1
Input: nums = [1,5,11,5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].
Example 2
Input: nums = [1,2,3,5]
Output: false
Explanation: The array cannot be partitioned into equal sum subsets.
Constraints
1 <= nums.length <= 2001 <= nums[i] <= 100
解題思路
這題是 0/1 背包
每個數字只能被選一次
先計算總和
total- 如果
total是奇數,不可能分成兩個相等 subset - 直接回傳
false
- 如果
目標變成:
- 能不能從
nums裡選一些數字,使總和剛好是total / 2
- 能不能從
DP 定義:
dp[currSum]:是否可以湊出currSum
初始化:
dp[0] = true- 因為什麼都不選,sum 一定為
0
狀態轉移:
- 每個
num只能用一次 - 所以
currSum要從大到小更新 dp[currSum] = dp[currSum] || dp[currSum - num]
- 每個
最後答案為:
dp[total]
Solution Code
Time Complexity
O(n × target)
Space Complexity
O(target)
1 | |
494. Target Sum
題目
You are given an integer array nums and an integer target.
You want to build an expression out of nums by adding one of the symbols '+' and '-' before each integer in nums and then concatenate all the integers.
Return the number of different expressions that you can build, which evaluates to target.
Example 1
Input: nums = [1,1,1,1,1], target = 3
Output: 5
Explanation: There are 5 ways to assign symbols to make the sum of nums be target 3.
Example 2
Input: nums = [1], target = 1
Output: 1
Constraints
1 <= nums.length <= 200 <= nums[i] <= 10000 <= sum(nums[i]) <= 1000-1000 <= target <= 1000
解題思路
這題每個數字都只能用一次
每個數字有兩種選擇:加上去或減掉
先計算
totalSum- 如果
abs(target) > totalSum - 代表不可能湊出 target,直接回傳
0
- 如果
因為 sum 可能是負數
- 所以用 offset 的方式
- 實際存取時用
sum + totalSum
DP 定義:
dp[i][sum + totalSum]- 代表使用到第
i個數字時,得到 sum 的方法數
初始化:
- 第一個數字可以加,也可以減
dp[0][totalSum + nums[0]] += 1dp[0][totalSum - nums[0]] += 1
狀態轉移:
- 枚舉前一層所有可能的 sum
- 如果以前有更新過
- 可以選擇加上
nums[i] - 也可以選擇減掉
nums[i]
- 可以選擇加上
最後答案為:
dp[nums.size()-1][target + totalSum]
Solution Code
Time Complexity
O(n × totalSum)
Space Complexity
O(n × totalSum)
1 | |
474. Ones and Zeroes
題目
You are given an array of binary strings strs and two integers m and n.
Return the size of the largest subset of strs such that there are at most m 0‘s and n 1‘s in the subset.
A set x is a subset of a set y if all elements of x are also elements of y.
Example 1
Input: strs = ["10","0001","111001","1","0"], m = 5, n = 3
Output: 4
Explanation: The largest subset with at most 5 0‘s and 3 1‘s is {"10", "0001", "1", "0"}, so the answer is 4.
Example 2
Input: strs = ["10","0","1"], m = 1, n = 1
Output: 2
Explanation: The largest subset is {"0", "1"}, so the answer is 2.
Constraints
1 <= strs.length <= 6001 <= strs[i].length <= 100strs[i]consists only of digits'0'and'1'.1 <= m, n <= 100
解題思路
這題是二維 0/1 背包
每個字串只能選一次
背包容量有兩個維度:
- 最多可以使用
m個0 - 最多可以使用
n個1
- 最多可以使用
DP 定義:
dp[i][j]:最多使用i個0和j個1,可以選到的最大 subset size
對每個字串:
- 先計算這個字串裡有幾個
0 - 再計算這個字串裡有幾個
1
- 先計算這個字串裡有幾個
因為每個字串只能選一次
- 所以
i和j都要從大到小更新
- 所以
狀態轉移:
- 不選目前字串:維持
dp[i][j] - 選目前字串:
dp[i-zeronum][j-onenum] + 1 - 因此:
dp[i][j] = max(dp[i][j], dp[i-zeronum][j-onenum] + 1)
- 不選目前字串:維持
最後答案為:
dp[m][n]
Solution Code
Time Complexity
O(strs.length × m × n)
Space Complexity
O(m × n)
1 | |
1049. Last Stone Weight II
題目
You are given an array of integers stones where stones[i] is the weight of the ith stone.
We are playing a game with the stones. On each turn, we choose any two stones and smash them together.
Suppose the stones have weights x and y with x <= y. The result of this smash is:
- If
x == y, both stones are destroyed - If
x != y, the stone of weightxis destroyed, and the stone of weightyhas new weighty - x
At the end of the game, there is at most one stone left.
Return the smallest possible weight of the left stone. If there are no stones left, return 0.
Example 1
Input: stones = [2,7,4,1,8,1]
Output: 1
Example 2
Input: stones = [31,26,33,21,40]
Output: 5
Constraints
1 <= stones.length <= 301 <= stones[i] <= 100
解題思路
這題可以轉成 0/1 背包
目標是把 stones 分成兩堆,讓兩堆重量差最小
設全部石頭總重量為
total如果其中一堆重量是
sum另一堆就是
total - sum最後剩下的重量就是:
abs(total - sum - sum)所以問題變成:
- 找一堆石頭
- 重量盡量接近
total / 2
DP 定義:
dp[j]:容量為j時,可以裝到的最大石頭重量
每顆石頭只能用一次
- 所以是 0/1 背包
j要從大到小更新
狀態轉移:
dp[j] = max(dp[j], dp[j - stone] + stone)
最後答案為:
total - 2 * dp[target]
Solution Code
Time Complexity
O(n × total)
Space Complexity
O(total)
1 | |
2915. Length of the Longest Subsequence That Sums to Target
題目
You are given a 0-indexed array of integers nums, and an integer target.
Return the length of the longest subsequence of nums that sums up to target. If no such subsequence exists, return -1.
A subsequence is an array that can be derived from another array by deleting some or no elements without changing the order of the remaining elements.
Example 1
Input: nums = [1,2,3,4,5], target = 9
Output: 3
Example 2
Input: nums = [4,1,3,2,1,5], target = 7
Output: 4
Example 3
Input: nums = [1,1,5,4,5], target = 3
Output: -1
Constraints
1 <= nums.length <= 10001 <= nums[i] <= 10001 <= target <= 1000
解題思路
這題是 0/1 背包
每個數字只能選一次,目標是湊出 target
DP 定義:
dp[j]:總和為j時,最長 subsequence 長度
初始化:
dp[0] = 0- 其他設成
INT_MIN - 代表目前還湊不出來
對每個
num- 因為每個數字只能選一次
- 所以
j要從大到小更新
狀態轉移:
- 選目前的
num dp[j] = max(dp[j], dp[j - num] + 1)
- 選目前的
最後:
- 如果
dp[target] > 0,回傳dp[target] - 否則回傳
-1
- 如果
Solution Code
Time Complexity
O(n × target)
Space Complexity
O(target)
1 | |
2787. Ways to Express an Integer as Sum of Powers
題目
Given two positive integers n and x.
Return the number of ways n can be expressed as the sum of the xth power of unique positive integers, in other words, the number of sets of unique integers [n1, n2, ..., nk] where n = n1^x + n2^x + ... + nk^x.
Since the result can be very large, return it modulo
Example 1
Input: n = 10, x = 2
Output: 1
Explanation: We can express n as 3^2 + 1^2 = 10.
Example 2
Input: n = 4, x = 1
Output: 2
Constraints
1 <= n <= 3001 <= x <= 5
解題思路
這題是 0/1 背包
每個 positive integer 的 x 次方只能使用一次
先枚舉每個數字
i- 計算
i^x - 如果
i^x超過n,後面就不用考慮
- 計算
DP 定義:
dp[j]:湊出總和j的方法數
初始化:
dp[0] = 1- 代表湊出 0 有一種方式,就是什麼都不選
因為每個
i^x只能選一次- 所以
j要從大到小更新
- 所以
狀態轉移:
- 這題是求方法數,所以用
+= dp[j] = dp[j] + dp[j - nx]
- 這題是求方法數,所以用
最後答案為:
dp[n]
Solution Code
Time Complexity
O(n × n)
Space Complexity
O(n)
1 | |
879. Profitable Schemes
題目
There is a group of n members, and a list of various crimes they could commit. The ith crime generates a profit[i] and requires group[i] members to participate in it. If a member participates in one crime, that member can’t participate in another crime.
Let’s call a profitable scheme any subset of these crimes that generates at least minProfit profit, and the total number of members participating in that subset of crimes is at most n.
Return the number of schemes that can be chosen. Since the answer may be very large, return it modulo
Example 1
Input: n = 5, minProfit = 3, group = [2,2], profit = [2,3]
Output: 2
Example 2
Input: n = 10, minProfit = 5, group = [2,3,5], profit = [6,7,8]
Output: 7
Constraints
1 <= n <= 1000 <= minProfit <= 1001 <= group.length <= 1001 <= group[i] <= 100profit.length == group.length0 <= profit[i] <= 100
解題思路
這題是二維 0/1 背包
每個 crime 只能選一次
背包容量有兩個維度:
- 使用的人數
g - 達到的 profit
p
- 使用的人數
DP 定義:
dp[g][p]:用g個人,達到 profit 至少p的方案數
初始化:
dp[0][0] = 1- 代表不選任何 crime,也是一種方案
對每個 crime:
members = group[i]earn = profit[i]
因為每個 crime 只能選一次
- 所以
g要從大到小更新 p也從大到小更新
- 所以
profit 最多只需要記到
minProfit- 超過
minProfit的都壓成minProfit newProfit = min(minProfit, p + earn)
- 超過
狀態轉移:
- 這題是找方法數,所以用
+= dp[g][newProfit] += dp[g - members][p]
- 這題是找方法數,所以用
最後把所有
dp[g][minProfit]加起來- 因為人數只要不超過
n都可以
- 因為人數只要不超過
Solution Code
Time Complexity
O(crimes × n × minProfit)
Space Complexity
O(n × minProfit)
1 | |
Conclusion
0/1 背包的重點是每個物品只能使用一次
如果是一般一維容量
- 通常用一維
dp - 容量從大到小更新
- 通常用一維
如果有兩種容量限制
- 例如
0的數量和1的數量 - 或是人數和 profit
- 就會變成二維背包
- 例如
如果是求可不可行
- 常用
bool dp
- 常用
如果是求最大值
- 常用
max
- 常用
如果是求方法數
- 常用
+=
- 常用
最重要的是更新方向:
0/1 背包為了避免同一個物品被重複使用,容量要從大到小更新