动态规划:最大乘积子串

    给定一个数组,求其中乘积最大的子串的乘积。如数组[2,3,-2,4],最大乘积为2*3=6.

    分析:最简单的方法是设置一个二维数组p[i][j]表示从 i 到 j 这个子串的乘积,然后求p中的最大值。但这种方法时间复杂度和空间复杂度都比较高。

    计算子串的乘积,如果中间包含了0,那么整个子串的乘积就是0。因此,需要以0为分界点,把数组分成几个子串,再分别求最大乘积。计算子串的最大乘积的方法有以下几种方法:

    方法一:因为数组中都是整数,所以子串越长,绝对值越大,因此可以先计算子串中负数的总数,如果总数为偶数,那么所有元素的乘积是最大的正数;如果负数的总数为奇数,使p1 = 从第一个到最后一个负数之前元素的乘积, p2 = 第一个负数后的元素开始到最后一个元素的乘积,p1和p2之间的最大值就是这个子串的最大值。

    public int maxProduct(int[] nums) {
        if(nums.length == 0)
            return 0;
        List<Integer> zeros = new LinkedList<Integer>();
        zeros.add(nums.length);
        zeros.add(-1);
        for(int i = 0; i < nums.length; i++){ //倒序插入0的位置
            if(nums[i] == 0)
                zeros.add(1, i);
        }
        if(zeros.size() == 2)
    			return product(nums, 0, nums.length - 1); //没有0
        List<Integer> products = new ArrayList<Integer>();
        for(int i = 0; i < zeros.size() - 1; i++){ //各子串的最大乘积
            products.add(product(nums, zeros.get(i + 1) + 1, zeros.get(i) - 1));
        }
        int max = Integer.MIN_VALUE;
        for(int p : products)
            if(p > max)
                max = p;
        return Math.max(max, 0); 
    }
    public int product(int[] nums, int start, int end){
        if(end < start) return 0; 
        if(end == start) return nums[start];
        int count = 0;
        int first = -1; //第一个负数的位置
        int last = -1; //最后一个负数的位置
        int max = nums[start];
        for(int i = start; i <= end; i++){
            if(nums[i] < 0){
                count++;
                if(first == -1) first = i;
                last = i;
            }
        }
        if(count % 2 == 0){ //偶数
            for(int i = start + 1; i <= end; i++){
                max *= nums[i];
            }
            return max;
        }  
        else{ //奇数
            int p1 = 1, p2 = 1;
            for(int i = start; i < last; i++)
                p1 *= nums[i];
            for(int i = first + 1; i <= end; i++)
                p2 *= nums[i];
            return Math.max(p1, p2);
        }
    }
    方法二:维持两个变量:最大值和最小值,如果乘以一个负数,最大值将会变成最小值,最小值变成最大值。对于每一个元素,其之前的子串的最大值和最小值要么是其自身,要么是之前的最大值、最小值乘以该元素。
    public int maxProduct(int[] nums) {
        int max = Integer.MIN_VALUE, imax = 1, imin = 1;
        for(int i=0; i<nums.length; i++){
            if(nums[i] < 0){ int tmp = imax; imax = imin; imin = tmp;}
            //实际上imax从第一个正数开始累计,imin从第一个负数开始累计,遇到负数就交换
            //如果有偶数个负数,会交换回从第一个开始计数的imax
            //如果有奇数个负数,在最后一个奇数前是偶数个,imax还是从第一个开始计数的imax
            //在最后一个奇数处再交换,imax变成从第一个负数后面的开始计数的imin,一直累计到最后
            //因此两个imax和上面的方法的结果是一样的
            imax = Math.max(imax*nums[i], nums[i]); 
            imin = Math.min(imin*nums[i], nums[i]);
     
            max = Math.max(max, imax);
        }
        return max;
    }




    

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值