yuanran0 2019-10-27
之前在其他博客看到了,但是算法的关键部分完全看不懂为什么要这么做,直到最近上算法课,才终于知道到底怎么来的。
问题描述:
给出一个数组,求其最大连续子数组和
例:数组{1,2,3,4,-5,10,-1,-1}的最大连续子数组和是子数组{1,2,3,4,-5,10}的和15
算法过程:
这个算法能从零直接想出来的人是真的厉害,我并不可以,所以我直接描述一下这个算法是怎么算的,而不描述怎么想到的了
首先我们把原来数组记做a,然后最关键的一步,我们需要一个等长的数组b,b[j]的含义是以下一系列和中的最大值
a[0] + a[1] + a[2] + a[3] + … + a[j] a[1] + a[2] + a[3] + … + a[j] a[2] + a[3] + … + a[j] a[3] + … + a[j] …… a[j]
那么显然可以知道的是,b数组中的最大值,就是我们想要的a的最大连续子数组和。
接下来就是要得到数组b,初始化的话,b[0]应该为0和a[0]之中的较大者,下面探讨b[j]和b[j+1]之间的关系:
b[j+1]只有两种取法,第一种是b[j]+a[j+1],注意b[j]本身就是最大值,所以不用再去考虑b[j]内部的情况了,第二种是a[j+1],所以
b[j+1] = max{ b[j]+a[j+1], a[j+1] }
OK,到此这个算法其实就算完成了,下面是直接根据这个思路打的代码:
#include <stdio.h> #include <stdlib.h> int main() { int a[] = {1,2,3,4,-5,10,-1,-1}; // 原数组 int* b = (int*)malloc(sizeof(a)); // 数组b,和原数组等长 // 初始化 b[0]= a[0] > 0 ? a[0] : 0; // 遍历构造,注意i的边界 int length = sizeof(a) / sizeof(int); // 数组长 for (int i = 0; i < length - 1; i++) { b[i+1] = b[i] + a[i+1] > a[i+1] ? b[i] + a[i+1] : a[i+1]; } // 然后找出b中的最大值 int max = b[0]; for (int i = 1; i < length; i++) if (b[i] > max) max = b[i]; printf("max=%i\n", max); return 0; }
然后导致我一直看不懂的地方来了,对上面这段代码进行编程上的优化,优化主要有两个地方,
首先在上面的分析中,b[n+1]只和当前遍历到的a[n+1]和前一个b[n]直接相关,所以其实并不需要保存整个数组b,换用一个变量就可以了,其次是最大值,也是可以一遍遍历一边改的
于是就有了目前网络上流传最多的以下这种代码:
#include <stdio.h> #include <stdlib.h> int main() { int a[] = {1,2,3,4,-5,10,-1,-1}; // 原数组 int max = 0; int b = 0; int length = sizeof(a) / sizeof(int); // 长度 for (int i = 0; i < length; i++) { b = b + a[i] > a[i] ? b + a[i] : a[i]; max = max > b ? max : b; } printf("max=%i\n", max); return 0; }
显然这个算法是o(n)时间复杂度的,讲真最近学动态规划基本o(n^3)起步。动态规划两个基本要素,首先最优子结构性质,全局最大值基于n个子问题b的最优解,而且可以看到b[j+1]是基于b[j]给出的,也就是一个问题是基于他的子问题的最优解来解决的,其次显然子问题是有交叉的,求解b[j]需要b[j-1],递归一下就需要b[j-2],由此就出现了交叉。
PS:LeetCode上题号53 最大子序和就是这个问题,但是貌似子数组长度不为0,所以初始化会有一点变化。