Problem:
* 给你一个非负的数组数列a1,a2,…,an和一个期望值S。你可以为每一个整数赋值一个新的符号,
* 符号只能从+和-中选择。计算有多少种组合可以另赋过符号的所有数的和等于S。
* 输入样例:nums=[1,1,1,1,1],S=3
* 输出:5
分析:
所有分析见代码注释:
一组数据的算法时间对比:
/**
* 时间对比结果
* 30
* nums = {1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1}
* S = 2
* dfs使用时间:5648
* answer = 145422675
* dfsplus使用时间:2643
* answer = 145422675
* dp使用时间:0
* answer = 145422675
*
*/
Code:
package google;
import java.util.Date;
import java.util.Scanner;
/**
* @author xiaoran
* Time: 2017-09-18 19:04
* Problem:
* 给你一个非负的数组数列a1,a2,...,an和一个期望值S。你可以为每一个整数赋值一个新的符号,
* 符号只能从+和-中选择。计算有多少种组合可以另赋过符号的所有数的和等于S。
* 输入样例:nums=[1,1,1,1,1],S=3
* 输出:5
*/
public class Google001 {
static int answer = 0;
/**
* 先使用暴力搜索,再进行剪枝。
* 对于第i个值,有2种选择,
* 第1种是选择赋值为+使用;第2种是选择赋值为-使用,
*
* 参数:
* nums:输入的原始的数组
* i:第i的值
* curSum:当值的和的值
* S:期望的目标的和
*/
public static void dfs(int[] nums,int i,int curSum,int S){
int len = nums.length;
// System.out.println(len);
if(i == len){//注意长度是第一满足的条件
if(curSum == S){
answer++;
}
return ;
}
//第1种
dfs(nums,i+1,curSum+nums[i],S);
//第2种
dfs(nums,i+1,curSum-nums[i],S);
}
public static int[] getSum(int[] nums){
int len = nums.length;
int[] sum = new int[len];
sum[len-1] = nums[len-1];
for(int i=len-2;i>=0;i--){
sum[i] = sum[i+1] + nums[i];
}
return sum;
}
/**
*
* @param nums:输入的原始数组
* @param i:第i的位置
* @param curSum:当前的值
* @param S:期望的额值
* @param sum[]:后缀和的数组
* 在基础的dfs的基础上添加剪枝,降低时间
* 要明白什么是剪枝,搜索树在还没有到达叶节点的时候,就知道这条路是不通的,
* 于是提前结束,降低时间的消耗。我们就是要判断当前的值curSum是否会在后面达到S。
* sum[i]:从i-nums.length的和。
* 对于第i的位置时,如果curSum+sum[i] < S。则表示后面的全是正的也不能达到S。
* if curSum - sum[i] < S。全是负的也不行就要剪枝。
* 两种情况合并得到的S-curSum > Math.abs(sum[i])则进行剪枝
*
*/
public static void dfsplus(int[] nums,int i,int curSum,int S,int[] sum){
int len = nums.length;
// System.out.println(len);
if(i == len){//注意长度是第一满足的条件
if(curSum == S){
answer++;
}
return ;
}
//判断是否需要剪枝
if(Math.abs(S - curSum) <= sum[i]){
//第1种
dfsplus(nums,i+1,curSum+nums[i],S,sum);
//第2种
dfsplus(nums,i+1,curSum-nums[i],S,sum);
}
}
/**
* @param args
* dfs是一种暴力的方法,剪枝只是一种手段,并不能使其从根本上降低时间。
* dp:动态规划问题就很厉害。
* dp[i,j]:表示前i个值组成j的种类的个数。
* 最后的结果就是dp[nums.length-1,S].
* 中间计算使用两层循环i和j。i:[0,nums.length],j:[0,S]
* 好像我们已经解决问题了,但是在运行的过程中j可能出现负数的情况。
* 如果我们对所有的j都加上sum,就可以解决这个问题,对应的结果就是
* sum:数组的和。
* 最后的结果就是dp[nums.length-1,S+sum]
* dp转化方程:
* dp[i,j+sum] 来源于两种情况dp[i-1,j+sum+nums[i]]和dp[i-1,j+sum-nums[i]
*
*/
public static int findSways(int[] nums,int S){
int ans = 0,sum = 0;
for(int a: nums){
sum+=a;
}
if(sum < Math.abs(S)) return 0;
// System.out.println(sum<<1);
int dp[][] = new int[nums.length][sum<<1 + 1];
//dp起点需要注意的是nums[0]==0的情况,+nums[0],-nums[0]
if(0==nums[0]) dp[0][nums[0]+sum] = 2;
else{
dp[0][sum-nums[0]]=1;
dp[0][sum+nums[0]]=1;
}
for(int i=1;i<nums.length;i++){
for(int j=0;j<=sum<<1;j++){
if(j-nums[i]>=0) dp[i][j] += dp[i-1][j-nums[i]];
if(j+nums[i]<=sum<<1) dp[i][j] += dp[i-1][j+nums[i]];
}
}
return dp[nums.length-1][S+sum];
}
public static void main(String[] args) {
int n;
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
n = sc.nextInt();
int nums[] = new int[n];
for(int i=0;i<n;i++){
nums[i] = sc.nextInt();
}
int S = sc.nextInt();
answer = 0;
//测试运行时间
long start = new Date().getTime();
dfs(nums,0,0,S);
long end = new Date().getTime();
System.out.println("dfs使用时间:"+ (end - start));
System.out.println(answer);
answer = 0;
start = new Date().getTime();
int[] sum = getSum(nums);
dfsplus(nums,0,0,S,sum);
end = new Date().getTime();
System.out.println("dfsplus使用时间:"+ (end - start));
System.out.println(answer);
start = new Date().getTime();
int ans = findSways(nums,S);
end = new Date().getTime();
System.out.println("dp使用时间:"+ (end - start));
System.out.println(ans);
}
}
}
/**
* 时间对比结果
* 30
* nums = {1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1}
* S = 2
* dfs使用时间:5648
* answer = 145422675
* dfsplus使用时间:2643
* answer = 145422675
* dp使用时间:0
* answer = 145422675
*
*/