名企笔试:Google面试题-目标和

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
 * 
 */

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值