问题:对给定义数组A,其长度为n,找出数组A中的最大子数组,例如数组A={-23,18,20,-7,12},则最大子数组为{18,20,-7,12}。
应用:比如一段时间内股票如何低买高卖,获取最大利润。
解题思路:
(1)蛮力法(brute-force ):通过二重循环计算每个子数字和,同时进行比较,最后的结果是最大子数组和及其开始和结束的坐标,即从数组中选择一个数A(i),然后计算以A(i)开始的所有子数组的和,计算的次数为(n-i),选择的次数为n。该方法的时间复杂度为O(n2)。
(2)分治法(Divide-Conquer ):用分治策略来求解。假设要寻找数组A[low,high]的最大子数组,将数组分为规模相同的两部分,中间的位置假设为mid。数组A所有的连续子数组A[i..j]所处的位置必然是以下三种情况之一:
a. 完全位于子数组A[low,mid]中,因此low≤i≤j≤mid
b.完全位于子数组A[mid+1,high]中,因此mid+1≤i≤j≤high
c.跨越了中间元素,因此low≤i≤mid<j≤high。
因此数组A的最大子数组所处的位置必然是这三种情况的一种。参考归并排序中递归思想,递归地求解A[low,mid]和A[mid+1,high]中的最大子数组,然后计算跨越中间元素的最大子数组,剩下的问题就是找出这三个最大子数组中的最大子数组。时间复杂度为O(nlogn2)。
代码实现如下:
- #include <stdio.h>
- #include <string.h>
- #ifndef INT_MIN
- #define INT_MIN (-((int)(~0U>>1)) - 1)
- #endif
- struct subarray {
- int start;
- int end;
- int sum;
- };
- void find_max_cross_subarray(int *a, int low, int mid, int high, void *p)
- {
- struct subarray *sa = (typeof(sa))p;
- int max_left, max_right;
- int left_sum, right_sum;
- int sum;
- int i;
- left_sum = INT_MIN;
- sum = 0;
- for (i = mid; i >= low; --i) {
- sum += a[i];
- if (sum > left_sum) {
- max_left = i;
- left_sum = sum;
- }
- }
- right_sum = INT_MIN;
- sum = 0;
- for (i = mid + 1; i <= high; ++i) {
- sum += a[i];
- if (sum > right_sum) {
- max_right = i;
- right_sum = sum;
- }
- }
- sa->start = max_left;
- sa->end = max_right;
- sa->sum = left_sum + right_sum;
- }
- void find_max_subarray(int *a, int low, int high, void *p)
- {
- struct subarray *sa = (typeof(sa))p;
- struct subarray *left_sa, __left_sa;
- struct subarray *right_sa, __right_sa;
- struct subarray *cross_sa, __cross_sa;
- struct subarray *tmp;
- int mid = (low + high) / 2;
- if (low > high) {
- fprintf(stderr, "Invalid argument.\n");
- return;
- }
- memset(sa, 0, sizeof(*sa));
- if (high == low) {
- sa->sum = a[low];
- sa->start = sa->end = low;
- return;
- }
- left_sa = &__left_sa;
- right_sa = &__right_sa;
- cross_sa = &__cross_sa;
- find_max_subarray(a, low, mid, left_sa);
- find_max_subarray(a, mid + 1, high, right_sa);
- find_max_cross_subarray(a, low, mid, high, cross_sa);
- if ((left_sa->sum >= right_sa->sum) &&
- (left_sa->sum >= cross_sa->sum)) {
- tmp = left_sa;
- } else if ((right_sa->sum >= left_sa->sum) &&
- (right_sa->sum >= cross_sa->sum)) {
- tmp = right_sa;
- } else {
- tmp = cross_sa;
- }
- memcpy(sa, tmp, sizeof(*sa));
- }
- int main(void)
- {
- int source[] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7};
- struct subarray sa;
- find_max_subarray(source, 0, sizeof(source) / sizeof(source[0]) - 1, &sa);
- printf("Max sum: %d, start: %d, end: %d.\n", sa.sum, sa.start, sa.end);
- return 0;
- }
(3)动态规划法(Dynamic Programming):从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知A[1..j]的最大子数组基于如下性质将解扩展为A[1..j+1]的最大子数组:A[1..j+1]的最大子数组要么是A[1..j]的最大子数组,要么是某个子数组A[i..j+1](1≤i≤j+1)。在已知A[1..j]的最大子数组的情况下,可以在线性时间内找出形如A[i..j+1]的最大子数组。该算法的时间复杂度为因此该算法的时间复杂度为Θ(n)。
代码实现如下所示:
- #include <stdio.h>
- #include <string.h>
- struct subarray {
- int start;
- int end;
- int sum;
- };
- #define max(__x, __y) ((__x) > (__y) ? (__x) : (__y))
- static void max_sumarray(int *a, int len, void *p)
- {
- struct subarray *sa = (typeof(sa))p;
- int i;
- int max_sum, prev, tmp;
- int start, end;
- if (!sa || (len <= 0)) {
- fprintf(stderr, "Invalid argument.\n");
- return;
- }
- memset(sa, 0, sizeof(*sa));
- max_sum = a[0];
- prev = a[0];
- start = end = 0;
- for (i = 1; i < len; ++i) {
- prev = max(a[i], prev + a[i]);
- if (prev < max_sum) {
- if (prev == a[i]) {
- start = i;
- }
- continue;
- }
- max_sum = prev;
- if (prev == a[i]) {
- sa->start = sa->end = i;
- } else {
- sa->start = start;
- sa->end = i;
- }
- }
- sa->sum = max_sum;
- }
- int main(void)
- {
- int source[] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7};
- struct subarray sa;
- max_sumarray(source, sizeof(source) / sizeof(source[0]), &sa);
- printf("Max sum: %d, start: %d, end: %d.\n", sa.sum, sa.start, sa.end);
- return 0;
- }
各种方法分析:
方法(1)简单直观,但是效率及其低下;方法(2)时间复杂度为O(nlogn2),为解决问题的一种方法,递归消耗的堆栈比较多,空间开销比较大,但不是效率最佳;方法(3)时间复杂度O(n),效率最佳,可能理解起来想对比较难。在实际编码中有限选择第(3)方法。