天天看点

Day30:连续子数组的最大和

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
时间限制: C/C++ 1秒,其他语言2秒 空间限制: C/C++32M,其他语言64M

背景知识介绍

  在做题前,首先给大家介绍一种非常典型的算法——动态规划算法。在维基百科中,动态规划算法是这样解释的:动态规划(英语:Dynamic programming,简称DP)是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量:一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。

  动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。

  最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。无后效性。即子问题的解一旦确定,就不再改变,不受在这之后、包含它的更大的问题的求解决策影响。子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

具体实现一

  本题是动态规划算法的典型应用,根据上述对动态规划的解释,我们可以用函数f(i)表示以第i个数字结尾的子数组的最大和,那么我们需要求出max[f(i)],其中0 <= i <= n。我们可以用如下递归公式求f(i):

f(i) = pData[i] i = 0或者f(i-1) <= 0

f(i) = f(i-1) + pData[i] i 不等于 0并且f(i-1) > 0

  我们解释一下这个递归公式所表示的含义:当以第I-1个数字结尾的子数组中所有数字的和小于0时,如果把这个负数与第i个数累加,则得到的结果比第i个数字本身还要小,所以这种情况下以第I个数字结尾的子数组就是第I个数字本身。如果以第I-1个数字结尾的子数组中所有数字的和大于0,则与第i个数字累加就得到以第i个数字结尾的子数组中所有数字的和。

  不过需要我们注意的是通常我们用递归的方式分析动态规划的问题,但最终都会基于循环去编码。接下来我们用java将其实现。

代码效果图如图所示:

Day30:连续子数组的最大和

  我们加上Main()在本地IDE运行,查看一下运行结果,具体实现如下:

代码测试效果图如下:

Day30:连续子数组的最大和

具体实现二

  除了我们上述用动态规划的思路做出来,其实这道题有很强的规律性,因此,我们可以通过举例分析数组的规律来把此题做出来。具体的步骤如下:

  从头到尾逐个累加数组中的每个数字,初始化和为0;

  定义两个变量:“累加子数组和”和“最大子数组和”,第一步把数组中的第一个数字赋值给他们,然后从第二个数字开始累加,累加值放入“累加子数组和”。

  1.如果当前“累加子数组和”小于0,那抛弃前面的“累加子数组和”,从下一个数字开始重新累加,“最大子数组和”的值不更新。

  2.如果当前“累加子数组和”大于0,再让当前“累加子数组和”和当前的“最大子数组和”进行比较。

    如果当前“累加子数组和”大于当前“最大子数组和”,则更新“最大子数组和”的值为“累加子数组和”的值。

    如果当前“累加子数组和”小于当前“最大子数组和”,“最大子数组和”的值不更新。

  3.再加入数组中的下一个值,“累加子数组和”进入下一轮的累加,“最大子数组和”也进入下一轮的更新。知道数组中所有值都累加完毕。

  这样进行一次累加的遍历之后,就可以得到最终的最大累加和,时间复杂度是O(n)。根据以上的思路,我们可以进行一次演示,例如{1, -2, 3, 10, -4, 7, 2, -5}。具体过程如下:

Day30:连续子数组的最大和

我们分别用java和python实现如下,不过java是从前往后遍历,而python是从后往前遍历,结果是一致的:

Day30:连续子数组的最大和
Day30:连续子数组的最大和

  根据测试代码运行结果可知和我们之前与练习寻找最大子数组的结果是一致的。接下来我们利用python将其思路按照从后往前进行遍历,实现代码如下:

Day30:连续子数组的最大和

  本道题主要是通过数组考察动态规划思想,动态规划思想在数据结构与算法中也是特别的重要的,因此在做本题之前,我们首先给大家介绍了动态规划算法的基础知识,接下来通过一个递归式将其实现,不过尽管是递归,但是在实际写代码时候,还是将递归转换为迭代,这样大大减少了时间复杂度,大大提高了运行速度。并且我们还从举例找规律的角度出发来完成该题,通过找规律,并且举例子按照给定的思路走了一遍,并且分别用java和python将其实现。通过测试代码发现和我们举例子走的结果完全符合。因此,我们在做题的时候,应该多次尝试各种方法,扩展自己的思维,写出优质的代码。总之,我们要继续加油,争取早日找到工作,Good Luck!!!

[1] kongmin_123

[2] CC‘s World

[3] 动态规划算法