动态规划(Dynamic Programming)
原创
©著作权归作者所有:来自51CTO博客作者wx631a9eff87243的原创作品,请联系作者获取转载授权,否则将追究法律责任
动态规划(Dynamic Programming)
- 动态规划(Dynamic Programming),简称DP
- ② 记忆化搜索(自顶向下)
- ③ 递推(自底向上)
- 直接学习动态规划的概念有点难以理解,先理解透一道例题再学习概念
练习1:找零钱
leetcode_322_零钱兑换:https://leetcode-cn.com/problems/coin-change/
- 假设有25分、20分、5分、1分的硬币,现要找给客户41分的零钱,如何办到硬币个数最少?
- 此前用贪心策略得到的并非是最优解(贪心得到的解是 5 枚硬币:25、5、5、5、1)
- 假设 dp( n ) 是凑到 n 分需要的最少硬币个数:
- 如果第 1 次选择了 25 分的硬币,那么 dp( n ) = dp( n - 25 ) + 1
- 如果第 1 次选择了 20 分的硬币,那么 dp( n ) = dp( n - 20 ) + 1
- 如果第 1 次选择了 5 分的硬币,那么 dp( n ) = dp( n - 5 ) + 1
- 如果第 1 次选择了 1 分的硬币,那么 dp( n ) = dp( n - 1 ) + 1
- 所以 dp( n ) = min { dp( n - 25 ), dp( n - 20 ), dp( n - 5 ), dp( n - 1 ) } + 1
找零钱 - 暴力递归
/**
* 暴力递归(自顶向下的调用, 出现了重叠子问题)
*
* @param n
* @return
*/
static int coins(int n) {
//递归基
if (n < 1) {
return Integer.MAX_VALUE;
}
if (n == 25 || n == 20 || n == 5 || n == 1) {
return 1;
}
// 求出四种取法的最小值
int min1 = Math.min(coins(n - 1), coins(n - 5));
int min2 = Math.min(coins(n - 20), coins(n - 25));
return Math.min(min1, min2) + 1;
}
找零钱 - 记忆化搜索
/**
* 记忆化搜索(自顶向下的调用)
*
* @param n
* @return
*/
static int coins2(int n) {
if (n < 1) {
return -1; //处理非法数据
}
int[] dp = new int[n + 1];
int[] faces = {1, 5, 20, 25}; // 给定的面值数组
for (int face : faces) { // 如果我要凑的钱是20元, 那么我肯定用不到25元面值
if (face > n) {
break; // 用不到的面值不用初始化
}
dp[face] = 1; // 初始化可能用到的面值
}
return coins2(n, dp);
}
static int coins2(int n, int[] dp) {
// 递归基
if (n < 1) {
return Integer.MAX_VALUE;
}
if (dp[n] == 0) { // 记忆化搜索, dp[n] == 0 表示以前没有算过, 那便初始化一下
int min1 = Math.min(coins2(n - 1, dp), coins2(n - 5, dp));
int min2 = Math.min(coins2(n - 20, dp), coins2(n - 25, dp));
dp[n] = Math.min(min1, min2) + 1;
}
return dp[n];
}
找零钱 - 递推
/**
* 递推(自底向上)
*
* @param n
* @return
*/
static int coins3(int n) {
if (n < 1) {
return -1;
}
int[] dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
int min = Integer.MAX_VALUE;
if (i >= 1) {
min = Math.min(dp[i - 1], min);
}
if (i >= 5) {
min = Math.min(dp[i - 5], min);
}
if (i >= 20) {
min = Math.min(dp[i - 20], min);
}
if (i >= 25) {
min = Math.min(dp[i - 25], min);
}
dp[i] = min + 1;
}
return dp[n];
}
/**
* 递推(自底向上)
*
* @param n
* @return
*/
static int coins3(int n) {
if (n < 1) {
return -1;
}
int[] dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
int min = dp[i - 1]; // 由于下面两行是必然执行的, 直接这么写就行了
// if (i >= 1) {
// min = Math.min(dp[i - 1], min);
// }
if (i >= 5) {
min = Math.min(dp[i - 5], min);
}
if (i >= 20) {
min = Math.min(dp[i - 20], min);
}
if (i >= 25) {
min = Math.min(dp[i - 25], min);
}
dp[i] = min + 1;
}
return dp[n];
}
思考题:输出找零钱的具体方案(具体是用了哪些面值的硬币)
static int coins4(int n) {
if (n < 1) {
return -1;
}
int[] dp = new int[n + 1];
// faces[i]是凑够i分时最后选择的那枚硬币的面值
int[] faces = new int[dp.length]; // 存放硬币面值(为了输出)
for (int i = 1; i <= n; i++) {
int min = Integer.MAX_VALUE;
if (i >= 1 && dp[i - 1] < min) {
min = dp[i - 1];
faces[i] = 1;
}
if (i >= 5 && dp[i - 5] < min) {
min = dp[i - 5];
faces[i] = 5;
}
if (i >= 20 && dp[i - 20] < min) {
min = dp[i - 20];
faces[i] = 20;
}
if (i >= 25 && dp[i - 25] < min) {
min = dp[i - 25];
faces[i] = 25;
}
dp[i] = min + 1;
print(faces, i); // 打印凑够面值 1 ~ n 的方案
}
// print(faces, n); // 打印凑够面值 n 的方案
return dp[n];
}
// 打印凑够面值 n 的方案
static void print(int[] faces, int n) {
System.out.print("[" + n + "] = ");
while (n > 0) {
System.out.print(faces[n] + " ");
n -= faces[n];
}
System.out.println();
}
- 尝试打印了 n = 41 的情况,打印出了凑够 1~41 所有面值的情况:
[1] = 1
[2] = 1 1
[3] = 1 1 1
[4] = 1 1 1 1
[5] = 5
[6] = 1 5
[7] = 1 1 5
[8] = 1 1 1 5
[9] = 1 1 1 1 5
[10] = 5 5
[11] = 1 5 5
[12] = 1 1 5 5
[13] = 1 1 1 5 5
[14] = 1 1 1 1 5 5
[15] = 5 5 5
[16] = 1 5 5 5
[17] = 1 1 5 5 5
[18] = 1 1 1 5 5 5
[19] = 1 1 1 1 5 5 5
[20] = 20
[21] = 1 20
[22] = 1 1 20
[23] = 1 1 1 20
[24] = 1 1 1 1 20
[25] = 25
[26] = 1 25
[27] = 1 1 25
[28] = 1 1 1 25
[29] = 1 1 1 1 25
[30] = 5 25
[31] = 1 5 25
[32] = 1 1 5 25
[33] = 1 1 1 5 25
[34] = 1 1 1 1 5 25
[35] = 5 5 25
[36] = 1 5 5 25
[37] = 1 1 5 5 25
[38] = 1 1 1 5 5 25
[39] = 1 1 1 1 5 5 25
[40] = 20 20
[41] = 1 20 20
3
找零钱 - 通用实现
static int coins5(int n, int[] faces) {
if (n < 1 || faces == null || faces.length == 0) {
return -1;
}
int[] dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
int min = Integer.MAX_VALUE;
for (int face : faces) {
// 假如给我的面值是20, 要凑的是15, 则跳过此轮循环
if (i < face) { // 如果给我的面值比我要凑的面值还大, 跳过此轮循环
continue;
}
min = Math.min(dp[i - face], min);
}
dp[i] = min + 1;
}
return dp[n];
}
static int coins5(int n, int[] faces) {
if (n < 1 || faces == null || faces.length == 0) {
return -1;
}
int[] dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
int min = Integer.MAX_VALUE;
for (int face : faces) {
// 假如给我的面值是20, 要凑的是15, 则跳过此轮循环
if (face > i) {
continue; // 如果给我的面值比我要凑的面值还大, 跳过此轮循环
}
// 比如给的面值是{4}, 要凑的是6, 先给出一张4, 再看6-4=2, 是否能凑成
// 2无法凑成, 则跳过此轮循环
int v = dp[i - face];
if (v < 0 || v >= min) {
continue;
}
min = v;
}
// 说明上面的循环中每次都是continue, 要凑的面值比给定的所有面值小
if (min == Integer.MAX_VALUE) {
dp[i] = -1;
} else {
dp[i] = min + 1;
}
}
return dp[n];
}
动态规划(Dynamic Programming)
动态规划的常规步骤
- ① 暴力递归(自顶向下,出现了重叠子问题)
- ② 记忆化搜索(自顶向下)
- ③ 递推(自底向上)
- 比如确定 dp( i ) 和 dp( i - 1 ) 的关系
动态规划的一些概念
维基百科的解释:
Dynamic Programming is a method for solving a complex problem by breaking it down into a collection of simpler subproblems, solving each of those subproblems just once, and storing their solutions.
- ① 将复杂的原问题拆解成若干个简单的子问题
- ② 每个子问题仅仅解决1次,并保存它们的解
- ③ 最后推导出原问题的解
- 可以用动态规划来解决的问题,通常具备2个特点:
- 最优子结构(最优化原理):通过求解子问题的最优解,可以获得原问题的最优解
- 某阶段的状态一旦确定,则此后过程的演变不再受此前各状态及决策的影响(未来与过去无关)
- 在推导后面阶段的状态时,只关心前面阶段的具体状态值,不关心这个状态是怎么一步步推导出来的
有后效性与无后效性
练习2:最大连续子序列和
题目:给定一个长度为 n 的整数序列,求它的最大连续子序列和
比如 –2、1、–3、4、–1、2、1、–5、4 的最大连续子序列和是 4 + (–1) + 2 + 1 = 6
- 假设 dp(i) 是以 nums[i] 结尾的最大连续子序列和(nums是整个序列)
- 以
nums[0]
–2 结尾的最大连续子序列是 –2,所以 dp(0) = –2 - 以
nums[1]
1 结尾的最大连续子序列是 1,所以 dp(1) = 1 - 以
nums[2]
–3 结尾的最大连续子序列是 1、–3,所以 dp(2) = dp(1) + (–3) = –2 - 以
nums[3]
4 结尾的最大连续子序列是 4,所以 dp(3) = 4 - 以
nums[4]
–1 结尾的最大连续子序列是 4、–1,所以 dp(4) = dp(3) + (–1) = 3 - 以
nums[5]
2 结尾的最大连续子序列是 4、–1、2,所以 dp(5) = dp(4) + 2 = 5 - 以
nums[6]
1 结尾的最大连续子序列是 4、–1、2、1,所以 dp(6) = dp(5) + 1 = 6 - 以
nums[7]
–5 结尾的最大连续子序列是 4、–1、2、1、–5,所以 dp(7) = dp(6) + (–5) = 1 - 以
nums[8]
4 结尾的最大连续子序列是 4、–1、2、1、–5、4,所以 dp(8) = dp(7) + 4 = 5
状态转移方程和初始状态:
- 如果 dp(i – 1) ≤ 0,那么 dp(i) = nums[i]
- 如果 dp(i – 1) > 0,那么 dp(i) = dp(i – 1) + nums[i]
- 最大连续子序列和是所有 dp(i) 中的最大值 max { dp(i) },i ∈ [0, nums.length)
最大连续子序列和 – 实现
static int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] dp = new int[nums.length];
dp[0] = nums[0];
int max = dp[0];
for (int i = 1; i < nums.length; i++) {
if (dp[i - 1] <= 0) {
dp[i] = nums[i];
} else {
dp[i] = dp[i - 1] + nums[i];
}
max = Math.max(dp[i], max);
}
return max;
}
最大连续子序列和 – 优化
static int maxSubArray2(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int dp = nums[0];
int max = dp;
for (int i = 1; i < nums.length; i++) {
if (dp <= 0) {
dp = nums[i];
} else {
dp = dp + nums[i];
}
max = Math.max(dp, max);
}
return max;
}
练习3:最长上升子序列(LIS)
最长上升子序列(最长递增子序列,Longest Increasing Subsequence,LIS)
leetcode_300_最长上升子序列: https://leetcode-cn.com/problems/longest-increasing-subsequence/
- 题目:给定一个无序的整数序列,求出它最长上升子序列的长度(要求严格上升)
- 比如 [10, 2, 2, 5, 1, 7, 101, 18] 的最长上升子序列是 [2, 5, 7, 101]、[2, 5, 7, 18],长度是 4
- 假设数组是 nums, [10, 2, 2, 5, 1, 7, 101, 18]
- dp(i) 是以
nums[i]
结尾的最长上升子序列的长度,i ∈ [0, nums.length)
- 以
nums[0]
10 结尾的最长上升子序列是 10,所以 dp(0) = 1
- 以
nums[1]
2 结尾的最长上升子序列是 2,所以 dp(1) = 1 - 以
nums[2]
2 结尾的最长上升子序列是 2,所以 dp(2) = 1 - 以
nums[3]
5 结尾的最长上升子序列是 2、5,所以 dp(3) = dp(1) + 1 = dp(2) + 1 = 2 - 以
nums[4]
1 结尾的最长上升子序列是 1,所以 dp(4) = 1 - 以
nums[5]
7 结尾的最长上升子序列是 2、5、7,所以 dp(5) = dp(3) + 1 = 3 - 以
nums[6]
101 结尾的最长上升子序列是 2、5、7、101,所以 dp(6) = dp(5) + 1 = 4 - 以
nums[7]
18 结尾的最长上升子序列是 2、5、7、18,所以 dp(7) = dp(5) + 1 = 4 - 最长上升子序列的长度是所有 dp(i) 中的最大值 max { dp(i) },i ∈ [0, nums.length)
- 状态转移方程:
- dp(0) = 1
- 所有的 dp(i) 默认都初始化为 1
- 最长上升子序列的长度是所有 dp(i) 中的最大值 max { dp(i) },i ∈ [0, nums.length)
最长上升子序列 – 实现
static int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] dp = new int[nums.length];
int max = dp[0] = 1; // 只有一个元素则长度为1
for (int i = 1; i < dp.length; i++) {
dp[i] = 1; // 默认只有一个元素时长度为1
for (int j = 0; j < i; j++) {
// 无法成为一个上升子序列
if (nums[i] <= nums[j]) {
continue;
}
dp[i] = Math.max(dp[i], dp[j] + 1);
}
max = Math.max(dp[i], max);
}
return max;
}
最长上升子序列 – 二分搜索实现
思路
练习4 – 最长公共子序列(LCS)
最长公共子序列(Longest Common Subsequence,LCS)
leetcode_1143_最长公共子序列:https://leetcode-cn.com/problems/longest-common-subsequence/
- [1, 3, 5, 9, 10] 和 [1, 4, 9, 10] 的最长公共子序列是 [1, 9, 10],长度为 3
- ABCBDAB 和 BDCABA 的最长公共子序列长度是 4,可能是
- ABCBDAB 和 BDCABA > BDAB
- ABCBDAB 和 BDCABA > BDAB
- ABCBDAB 和 BDCABA > BCAB
- ABCBDAB 和 BDCABA > BCBA
思路:
最长公共子序列 – 递归实现
/**
* 递归实现
*
* @param nums1
* @param nums2
* @return
*/
static int longestCommonSubsequence1(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0) {
return 0;
}
if (nums2 == null || nums2.length == 0) {
return 0;
}
return lcs(nums1, nums1.length, nums2, nums2.length);
}
/**
* 求nums1前i个元素和nums2前j个元素的最长公共子序列长度
*
* @param nums1
* @param i
* @param nums2
* @param j
* @return
*/
static int lcs1(int[] nums1, int i, int[] nums2, int j) {
if (i == 0 || j == 0) {
return 0;
}
if (nums1[i - 1] == nums2[j - 1]) {
return lcs1(nums1, i - 1, nums2, j - 1) + 1;
}
return Math.max(lcs1(nums1, i - 1, nums2, j),
lcs1(nums1, i, nums2, j - 1));
}
最长公共子序列 – 非递归实现
/**
* 非递归实现
*
* @param nums1
* @param nums2
* @return
*/
static int longestCommonSubsequence2(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0) {
return 0;
}
if (nums2 == null || nums2.length == 0) {
return 0;
}
int[][] dp = new int[nums1.length + 1][nums2.length + 1];
for (int i = 1; i <= nums1.length; i++) {
for (int j = 1; j <= nums2.length; j++) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[nums1.length][nums2.length];
}
最长公共子序列 – 滚动数组优化
/**
* 非递归实现 - 滚动数组
*
* @param nums1
* @param nums2
* @return
*/
static int longestCommonSubsequence3(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0) {
return 0;
}
if (nums2 == null || nums2.length == 0) {
return 0;
}
int[][] dp = new int[2][nums2.length + 1];
for (int i = 1; i <= nums1.length; i++) {
int row = i & 1; // 相当于 i % 2
int preRow = (i - 1) & 1; // 相当于 (i - 1) % 2
for (int j = 1; j <= nums2.length; j++) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[row][j] = dp[preRow][j - 1] + 1;
} else {
dp[row][j] = Math.max(dp[preRow][j], dp[row][j - 1]);
}
}
}
return dp[nums1.length & 1][nums2.length]; // 相当于 nums1.length % 2
}
最长公共子序列 – 一维数组优化
/**
* 非递归实现 - 一维数组
*
* @param nums1
* @param nums2
* @return
*/
static int longestCommonSubsequence4(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0) {
return 0;
}
if (nums2 == null || nums2.length == 0) {
return 0;
}
int[] dp = new int[nums2.length + 1];
for (int i = 1; i <= nums1.length; i++) {
int cur = 0;
for (int j = 1; j <= nums2.length; j++) {
int leftTop = cur;
cur = dp[j];
if (nums1[i - 1] == nums2[j - 1]) {
dp[j] = leftTop + 1;
} else {
dp[j] = Math.max(dp[j], dp[j - 1]);
}
}
}
return dp[nums2.length];
}
练习5 – 最长公共子串
- 最长公共子串(Longest Common Substring)
- ABCBA 和 BABCA 的最长公共子串是 ABC,长度为 3
最长公共子串 – 实现
static int lcs(String str1, String str2) {
if (str1 == null || str2 == null) {
return 0;
}
char[] nums1 = str1.toCharArray();
char[] nums2 = str2.toCharArray();
if (nums1.length == 0) {
return 0;
}
if (nums2.length == 0) {
return 0;
}
int[][] dp = new int[nums1.length + 1][nums2.length + 1];
int max = 0;
for (int i = 1; i <= nums1.length; i++) {
for (int j = 1; j <= nums2.length; j++) {
if (nums1[i - 1] != nums2[j - 1]) {
continue;
}
dp[i][j] = dp[i - 1][j - 1] + 1;
max = Math.max(dp[i][j], max);
}
}
return max;
}
最长公共子串 – 一维数组优化
/**
* row:行
* col:列
*
* @param str1
* @param str2
* @return
*/
static int lcs2(String str1, String str2) {
if (str1 == null || str2 == null) {
return 0;
}
char[] nums1 = str1.toCharArray();
char[] nums2 = str2.toCharArray();
if (nums1.length == 0) {
return 0;
}
if (nums2.length == 0) {
return 0;
}
char[] rowsChars = nums1, colsChars = nums2;
//以最长的那个字符串作为列
if (nums1.length < nums2.length) {
colsChars = nums1;
rowsChars = nums2;
}
int[] dp = new int[rowsChars.length + 1];
int max = 0;
for (int row = 1; row <= rowsChars.length; row++) {
int cur = 0;
for (int col = 1; col <= colsChars.length; col++) {
int leftTop = cur;
cur = dp[col];
if (nums1[row - 1] != nums2[col - 1]) {
dp[col] = 0;
} else {
dp[col] = leftTop + 1;
max = Math.max(dp[col], max);
}
}
}
return max;
}
练习6 – 0-1背包
0-1背包 – 实现
static int maxValue(int[] values, int[] weights, int capacity) {
if (values == null || values.length == 0) {
return 0;
}
if (weights == null || weights.length == 0) {
return 0;
}
if (values.length != weights.length || capacity <= 0) {
return 0;
}
// 特征方程: dp(i, j) 是最大承重为 j、有前 i 件物品可选时的最大总价值;
int[][] dp = new int[values.length + 1][capacity + 1];
for (int i = 1; i <= values.length; i++) {
for (int j = 1; j <= capacity; j++) {
if (j < weights[i - 1]) { //如果背包的最大承重小于最后一件物品的重量
dp[i][j] = dp[i - 1][j]; //没法选择最后一件物品
} else {
//有两种选择:
//①不选择最后一件物品:dp[i-1][j]
//②选择最后一件物品:values[i - 1] + dp[i - 1][j - weights[i - 1]
dp[i][j] = Math.max(dp[i - 1][j], values[i - 1] + dp[i - 1][j - weights[i - 1]]);
}
}
}
return dp[values.length][capacity];
}
public static void main(String[] args) {
int[] values = {6, 3, 5, 4, 6};
int[] weights = {2, 2, 6, 5, 4};
/**
* dp(3,7) 是最大承重为7,有前3件物品可选时的最大价值
* dp(4,8) 是最大承重为8,有前4件物品可选时的最大价值
* dp(n,capacity) 是最大承重为capacity,有前n件物品可选时的最大价值
*
* i == 5
* j == 10
* ①如果 j < weights[i],dp(i, j) = dp(i - 1, j)
* 如果 j >= weights[i],那么有下面两种情况
* ②如果不选择第i个物品,dp(i, j) = dp(i - 1, j)
* ③如果选择第i个物品,dp(i, j) = values[i - 1] + dp(i - 1,j - weights[i - 1])
* dp(i ,j) = Maths.max( dp(i - 1, j), values[i - 1] + dp(i - 1,j - weights[i - 1]) );
*/
int capacity = 10;
System.out.println(maxValue(values, weights, capacity));
}
0-1背包 – 一维数组
/**
* 一维数组
*
* @param values
* @param weights
* @param capacity
* @return
*/
static int maxValue2(int[] values, int[] weights, int capacity) {
if (values == null || values.length == 0) {
return 0;
}
if (weights == null || weights.length == 0) {
return 0;
}
if (values.length != weights.length || capacity <= 0) {
return 0;
}
int[] dp = new int[capacity + 1];
for (int i = 1; i <= values.length; i++) {
for (int j = capacity; j >= 1; j--) {
if (j < weights[i - 1]) {
continue;
}
dp[j] = Math.max(dp[j], values[i - 1] + dp[j - weights[i - 1]]);
}
}
return dp[capacity];
}
0-1背包 – 一维数组优化
/**
* 一维数组 - 优化
*
* @param values
* @param weights
* @param capacity
* @return
*/
static int maxValue3(int[] values, int[] weights, int capacity) {
if (values == null || values.length == 0) {
return 0;
}
if (weights == null || weights.length == 0) {
return 0;
}
if (values.length != weights.length || capacity <= 0) {
return 0;
}
int[] dp = new int[capacity + 1];
for (int i = 1; i <= values.length; i++) {
for (int j = capacity; j >= weights[i - 1]; j--) {
dp[j] = Math.max(dp[j], values[i - 1] + dp[j - weights[i - 1]]);
}
}
return dp[capacity];
}
0-1背包 – 恰好装满 – 实现
/**
* 01背包问题 - 恰好装满
*
* @param values
* @param weights
* @param capacity
* @return 如果返回-1,代表没法刚好凑到capacity这个容量
*/
static int maxValueExactly(int[] values, int[] weights, int capacity) {
if (values == null || values.length == 0) {
return 0;
}
if (weights == null || weights.length == 0) {
return 0;
}
if (values.length != weights.length || capacity <= 0) {
return 0;
}
int[] dp = new int[capacity + 1];
for (int j = 1; j <= capacity; j++) {
dp[j] = Integer.MIN_VALUE;
}
for (int i = 1; i <= values.length; i++) {
for (int j = capacity; j >= weights[i - 1]; j--) {
dp[j] = Math.max(dp[j], values[i - 1] + dp[j - weights[i - 1]]);
}
}
return dp[capacity] < 0 ? -1 : dp[capacity];
}