链接:https://www.nowcoder.com/questionTerminal/7f24eb7266ce4b0792ce8721d6259800
来源:牛客网
给定一个有n个正整数的数组A和一个整数sum,求选择数组A中部分数字和为sum的方案数。
当两种选取方案有一个数字的下标不一样,我们就认为是不同的组成方案。
输入描述:
输入为两行:
第一行为两个正整数n(1 ≤ n ≤ 1000),sum(1 ≤ sum ≤ 1000)
第二行为n个正整数Ai,以空格隔开。
输出描述:
输出所求的方案数
示例1
输入
5 15 5 5 10 2 3
输出
4
我刚开始做这道题首先想到的是递归,但是最后测了以下发现递归的时间复杂度太大了,只能通过50%。所以这道题正确的解法应该是使用动态规划。
动态规划也挺烧脑的,这里的思路是:创建二维数组dp[][],依次计算数组arr[]中的元素可以组成0-sum之间数字的可能情况。比如数组元素为5 5 10 2 3,首先计算5在0-sum之间的可能情况,其中5可以组合成5,其他都不可以,所以dp[1][5] = 1;接着加入下一个元素5,这时可以组成的5的可能情况就是两种,分别是第一个5和第二个5,而且这时还可以组成10,所以dp[2][5] = 2,dp[2][10] = 1;以此类推就可以算出来当加入元素有5 5 10 2 3时可以组成0-sum之间数字的可能情况,当然最终要求解的只是组成sum的情况数,所以输出dp[n][sum]就可以了。
使用5 5 10 2 3来举例,二维图如下:
这里的dp[1][5] = 1,就是因为现在只有1个5。而dp[2][5] = 2是因为有两个5,并且这两个5可以组成10,所以dp[2][10] = 1;
可以发现一个规律,
dp[i][j]表示用前i个值组成和为j的方法数
当j < arrp[i]时,dp[i][j] = dp[i -1][j]
当j >= arr[i]时,dp[i][j] = dp[i - 1][j] + dp[i - 1][j - arr[i]]
这是为什么呢?是因为当j < arrp[i]时,这时就算加入新的元素,但是新的元素比要组成的j大,所以情况就和没有加入新的元素时一样。而当j >= arr[i]时,这时候新加入的元素就有用了,情况也就发生了改变,比如现在新加入了2,那么要组成15,就等于之前可以组成15的情况+现在加入2后可以组成15的情况,加入2后可以组成15的情况就可等同于之前可以组成13的情况,因为13 + 2= 15,有多少种13的组成情况就有多少种15的组成情况。核心思路就是上面的过程。
这里还有一些小细节:
比如,第一列为1,是因为dp[i][0],每个元素都可以满足0,而第一行为0,是为了之后的动态规划做准备。
程序如下:
import java.util.Scanner;
/**
* @author abaka
* @date 2019/7/7 14:37
*/
public class AddEqualSum {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int sum = in.nextInt();
int[] arr = new int[n + 1];
long[][] dp = new long[n + 1][sum + 1];
for (int i = 0; i <= n;i++){
dp[i][0] = 1;
}
for (int j = 1; j <= sum; j++){
dp[0][j] = 0;
}
for (int i = 1; i <= n; i++){
arr[i] = in.nextInt();
for (int j = 0; j <= sum; j++){
if (j >= arr[i])
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - arr[i]];
else
dp[i][j] = dp[i - 1][j];
}
}
System.out.println(dp[n][sum]);
}
//下面这是一种优化方法,将二维数组的动态规划优化为一维数组,
//关键点是在内层for循环中,这里是从后向前循环的,所以可以使用一维数组。
//因为j < arr[i]的情况和之前的情况一样,所以就直接使用一维数组中之前所保存的数
// public static void main(String[] args) {
// Scanner in = new Scanner(System.in);
// int n = in.nextInt();
// int sum = in.nextInt();
// int[] value = new int[n];
// long[] dp = new long[sum + 1];
// dp[0] = 1;
// for (int i = 0; i < n; i++){
// value[i] = in.nextInt();
// for (int j = sum; j >= 0; j--){
// if (j >= value[i]){
// dp[j] += dp[j - value[i]];
// }
// }
// }
// System.out.println(dp[sum]);
// }
}