leetcode_动态规划



原题来自LeetCode,由“待字闺中”微信公众号转发。这里是我自己看了原贴之后的思考和解答。

以下是链接:
http://mp.weixin.qq.com/s?__biz=MjM5ODIzNDQ3Mw==&mid=200559586&idx=1&sn=7d7e7ed20f92e437d0924403e8ac2a37&key=8ab9b131eb859e5c90bbb23362befef4c63a339f83182b3957aeb4fd0f56f40d6ddcbe11c7532434e5ba90ae7875bfaa&ascene=1&uin=Mjg3NDAzMjYzNw%3D%3D&devicetype=webwx&version=70000001&pass_ticket=4VRhlhj84%2FF9OzkaBX%2Ftm6Kb3BSudOA1s%2FIPMvTT5P0XVMEQVlXwF3rUt4Z4Gg%2Fh

题目如下:给定一个整型数组,至少有一个元素,请计算子数组最大乘积是多少?子数组必须是原数组中连续的一串数字构成的数组。整数可正可负。

例如:给定数组 [2, 3, -2, 4]。经过计算,得到最大乘积为6。子数组为[2,3]。

根据原贴的解题思路,这道题可以用动态规划来解,但难度在哪里呢?负负得正,就可恶在这里。那这个题目怎么办?O(n^2)么?是否要计算保存之前的所有状态?不必如此,其实就多了数组中的元素为负情况的考虑。因为负负得正,我们除了考虑当前的最大值之外,还需要考虑当前的最小值。

原因是,如果最小值是负的,而且当前是负的,那么乘积之后是正数,很可能就大于当前最大的。

所以遍历一遍,计算以每个元素结尾的最大值最小值,最后找到全局的最大值。就是我们要的结果了。

根据原贴的代码,我自己重新实现了一遍,如下:

class DynProg01
{
    public static void main(String[] args)
    {
        System.out.println(new Integer(solve(new int[] { 1, 2, 3 })).toString());
        System.out.println(new Integer(solve(new int[] { 1, -2, 3 })).toString());
        System.out.println(new Integer(solve(new int[] { 1, -2, 3, -4 })).toString());
        System.out.println(new Integer(solve(new int[] { 1, -2, -7, -4 })).toString());
        System.out.println(new Integer(solve(new int[] { 1, -2, -5, -4 })).toString());
        System.out.println(new Integer(solve(new int[] { 3, 4, -1 })).toString());
    }

    public static int solve(int[] array)
    {
        int maxVal = array[0];
        int minVal = array[0];
        int result = array[0];
        for (int i = 1; i < array.length; i++) {
            int tmpMax = maxVal * array[i];
            int tmpMin = minVal * array[i];
            maxVal = max3(tmpMax, tmpMin, array[i]);
            minVal = min3(tmpMax, tmpMin, array[i]);
            System.out.println("maxVal: " + (new Integer(maxVal)).toString() + "; minVal: " + (new Integer(minVal)).toString());
            result = max2(maxVal, result);
        }
        return result;
    }

    private static int max3(int a, int b, int c)
    {
        return max2(max2(a, b), c);
    }

    public static int max2(int a, int b)
    {
        if (a < b) return b;
        return a;
    }

    private static int min3(int a, int b, int c)
    {
        return min2(min2(a, b), c);
    }

    public static int min2(int a, int b)
    {
        if (a < b) return a;
        return b;
    }
}

代码没有经过简化,所以把我的测试数据也都附上了。对于所有这些例子,运行下来得到的结果都是正确的。它为什么正确呢?

上述代码里面,max2和max3函数都是相当平凡的,它们就是从两个或三个元素中选取最大值。关键在于solve函数。它里面的那个maxVal、minVal、tmpMax、tmpMin和result这些变量分别表示什么。

我昨晚上思考了许久,想明白了。对于下标i来说,maxVal和minVal代表的是数组从元素0到元素i-i之间,包含元素i-1的子数组中能得到的最大乘积和最小乘积。result则代表从元素0到元素i-1之间,乘积最大的子数组的乘积。tmpMax和tmpMin则是将maxVal、minVal和元素i相乘得到的临时结果,用于后面的比较来得到新的maxVal和minVal。

现在需要用一下数学归纳法。假设对于n的情况,maxVal和minVal还有result的值都满足了上述条件,对于n+1的情况又如何呢?现在的情况是,新增了元素n。首先,考虑maxVal。maxVal要么就是元素n,要么就是包含元素n在内的子数组的乘积。前者的情况显然在这里得到了考虑,而事实上,在我们题设的整数情况下却不会发生,因为整数的绝对值总是大于等于1,所以之前的乘积的绝对值总是大于等于1,同时考虑maxVal和minVal的结果就会让新的maxVal大于等于元素n。但它对于浮点数的情况是有必要的。对于包含元素n在内的子数组的乘积的情况,maxVal表示的是可能得到的最大乘积,其他子数组的乘积已经不可能比这个更大了,如果考虑当前值为负的情况,minVal就能起到作用,所以新的maxVal是正确的。

同理可证新的minVal是正确的。那么result呢?需要注意result只是从元素0到元素n-1之间的最大乘积子数组的乘积,它未必要包含元素n-1。那很简单,它只需要从它自己和新的maxVal之间取较大值即可。

这样的动态规划,让我联想起了字符串查找用的KMP算法。因为KMP算法也要计算最大匹配的真前缀的位置,也要用到数学归纳法。下面是我当年对KMP算法的理解和解释:

http://www.fandecheng.com/archives/167

上面说过,现在这个动态规划算法也适用于浮点数。我试了[2, 0.5, 3],这个算法也是正确的。

还有,原贴中提到的一道求和题。大致是:给定一个整型数组,请计算加起来的和最大的子数组的和是多少?子数组必须是原数组中连续的一串数字构成的数组。整数可正可负。

这道题更简单些,因为我们只要一路求最大和就可以了。关键问题是遇到负数怎么处理。其实也一样,用上面的maxVal和result的思路。如果元素n是一个负数,maxVal加上它将让maxVal变小,但我们要求的是包含这个元素的子数组中的最大和,所以还是必须加上它的。只要之前的maxVal本身是正的就行。如果之前的maxVal本身就是负的,那我们把它抛弃,只取元素n。而result则将成为和最大的子数组,其结尾不必是元素n。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值