题目描述
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?(子向量的长度至少是1)
往往最直观的方法都不是最简单的方法,所以当我们看到这题时第一想法是枚举数组的想法是不优的算法。因此我们需要找寻更快的方法。
解法一:举例分析数组的规律
下面以输入的数组为{1,-2,3,10,-4,7,2,-5}为例:
- 初始化和为0,然后从头到尾逐个累加示例数组中的每个数字就好了。
- 第一步加上第一个数字1,此时和为1。
- 第二步加上第二个数字-2,此时和为1。
- 第三步加上第三个数字3,此时和为3 。发现由于此前累加的和是-1,小于0,那如果-1加上3,得到的和是2,比3本身小。也就是说,从第一个数字开始的子数组的和会小于从第三个数字开始的子数组的和。因此,我们不用考虑从第一个数字开始的子数组,之前累加的和也被抛弃了。
- 接下来,从第三个数字开始重新开始累加,此时得到的和为3 。第四步加10,得到的和为13.
- 第五步加上-4,和为9 。我们发现,由于-4是一个负数,因此累加的和13保存了下来,因为它有可能是最大的子数组的和。
- 第六步加上数字7,和为16,此时的和比之前的最大的和13要大,因此把最大的子数组和更新为16 。
- 第七步加上2,和为18,更新最大子数组和。
所以最终子数组的和为18 ,对应的数组是{3,10,-4,7,2}。
步骤 | 操作 | 累加的子数组和 | 最大的子数组和 |
---|---|---|---|
1 | 加1 | 1 | 1 |
2 | 减2 | -1 | 1 |
3 | 抛弃前面的和-1,加3 | 3 | 3 |
4 | 加10 | 13 | 13 |
5 | 加-4 | 9 | 13 |
6 | 加7 | 16 | 16 |
7 | 加2 | 18 | 18 |
8 | 加-5 | 13 | 18 |
由以上过程,可看出整个过程的思路,然后通过C#实现,参考代码如下:
using System;
namespace 连续子数组的最大和
{
class Program
{
static void Main(string[] args)
{
int[] num1 = new int[8] { 1, -2, 3, 10, -4, 7, 2, -5 };
int[] num2 = new int[] { -2, -8, -1, -5, -9 };
Solution s = new Solution();
Console.WriteLine(s.FindGreatestSumOfSubArray(num1));
Console.WriteLine(s.FindGreatestSumOfSubArray(num2));
}
}
class Solution
{
public bool isInvalidInput = true; //输入是否合法,不合法返回false;
public int FindGreatestSumOfSubArray(int[] array)
{
if(array==null||array.Length<1)
{
isInvalidInput = false;
return 0;
}
isInvalidInput = false;
int sum = 0, greatSum = array[0];
for(int i=0;i<array.Length;i++)
{
//如果和为负数,那么令和重新开始计算,起始值为当前值
if (sum <= 0)
sum = array[i];
//如果和为正数,那么继续加当前值
else
sum += array[i];
//如果当前和大于最大和,那么更新最大和
if (sum > greatSum)
greatSum = sum;
}
return greatSum;
}
}
}
解法二:动态规划法
如果用函数f(i)表示以第i个数字结尾的子数组的最大和,那么我们需要求出max[f(i)],其中0<=i < n 。可以用以下公式求f(i):
这个公式的意义:当以第i-1个数字结尾的子数组中所有数字的和小于0时,如果吧这个负数和第i个数累加,则得到的记过比第i个数字本身小,所以这种情况下以第i个数字结尾的子数组就是第i个数字本身。如果以第i-1个数字结尾的子数组中所有数字的和大于0,则与第i个数字累加就得到以第i个数字结尾的子数组中所有数字的和。
虽然通常我们用递归的方法分析动态规划的问题,但最终都会基于循环去编码。上述公式,和第一种方法的代码一致,递归公式中f(i)对应的变量是sum(当前和),而max[f(i)]就是greatSum(最大和)。因此可以说这两种方法都是异曲同工。
以下为递归的参考代码:
class Solution
{
public bool isInvalidInput = true; //输入是否合法,不合法返回false;
public int FindGreatestSumOfSubArray(int[] array)
{
if (array == null || array.Length < 1)
{
isInvalidInput = false;
return 0;
}
int greatSum = array[0];
FindGreatestSumCore(ref array, 0, out greatSum);
return greatSum;
}
private int FindGreatestSumCore(ref int[] array, int index,out int greatSum)
{
if (index == array.Length - 1)
{
greatSum = array[index];
return array[index];
}
int sum = FindGreatestSumCore(ref array, index + 1,out greatSum) + array[index];
if (sum < 0)
sum = array[index];
if (sum > greatSum)
greatSum = sum;
return sum;
}
}