【转】【算法学习】最大子串问题

原文自:http://vistb.net/2011/07/max-subarray-problem/

今天遇到了这个问题,与大家再次分享。

今天在看《算法导论》时谈到了最大子串问题,书中共提到了3种算法,时间复杂度依次是O(n^2),O(nlogn)和 O(n)。感觉挺有意思的,写出来分享一下。

定义:最大子串问题(maximum sub-array problem)

最大子串问题,又称为最大连续子串问题,是指给出一个长度为n的整数数组A,然后要求给出其中和值最大的连续子数组。也即,求出MAX(SUM(A[i...j])),其中1 ≤ i ≤ j ≤ n。

解法一:穷举(brute force)

第一种方法,也是最笨的办法,就是穷举。将所有可能的i、j组合穷举,选出其中SUM(A[i...j])最大的即可。很明显,由于可能的组合数有C(n,2)=O(n^2)个,算法的复杂度也为O(n^2)。

解法二:分治法(divide and conquer)

注意到A[1...n]的最大子串A[i..j]可能来自于以下三种情况中的一种:

  • 1 ≤ i ≤ j ≤ n/2,也即最大子串出现在原串的左半部分
  • n/2 ≤ i ≤ j ≤ n,也即最大子串出现在原串的右半部分
  • 1 ≤ i ≤ n/2 ≤ j ≤ n,也即最大子串部分来自原串的最半部分,部分来自原串的右半部分

因此我们可以分别计算这三种情况下的解,然后将这三个解中最好的一个选出作为整个问题的解。

针对前两种情况,其实就相当于把问题的规模缩小了(divide)。把求长度为n的数组的最大子串变为求长度最n/2的数组的最大子串。这个规模变小了的问题的解同样可能有三种情况。对其中的前两种情况,我们依然采取细分的方法。可是,这样的细分会有头吗?当然会的,到了最后,我们会得到一个个长度为1的数组,在这种极限情况下,我们就不必考虑三种情况,因为此时的最大子串显然就是这个长度为1的子串自己(conquer)。

而针对第三种情况,我们采取另一种策略,即不是将问题化为相同的更小规模的问题,而是直接求解。我们首先求出A[i...n/2]使其为A[1...n/2]的最大子串,这里,进行一次遍历即可,时间复杂度为O(n)。同理,我们也可以求出A[n/2...j]使其为A[n/2...n]的最大子串。将这2个最大子串合在一起,就得到了所有会跨越串的中部的子串中的最大子串。

总结起来,假如使用T(n)代表使用此方法求解大小为n的数组的最大子串问题所消耗的时间,则有:

  • T(n) = 2T(n/2) + O(n),n>1
  • T(1) = O(1),n=1

不难发现,此时求解整个问题的时间复杂度为O(nlogn)。

算法的伪代码如下(截取自《算法导论》):

解法三:动态规划(dynamic programming)

时间复杂度为O(nlogn)已经是一个比较理想的结果了,但在书后的习题中还提出了另一种基于动态规划的解决思路,可以将时间复杂度降到O(n)。

具体来说,假如我们已经知道了A[1..j]这个数组的最大子串,那么A[1...(j+1)]的最大子串有可能来自于两种情况,一是其和A[1...j]的最大子串相同,二是A[1...(j+1)]的最大子串具有A[i...(j+1)]的形式,其中1 ≤ i ≤ j+1。针对第二种情况,如果我们已经知道所有尾部为j的子串中的最大子串,我们就可以在常数时间内得到所有尾部为(j+1)的子串中的最大子串。

基于上面的思想,我们只需从串的最左部开始(此时的最大子串就是串的第一个元素),向右逐渐拓展直到处理完整个串,即可得到最大子串。

算法的伪代码如下(自己写的,不一定对):

拓展:K最大子串问题(k maximum sub-array problem)

在一般的最大子串问题的基础上,我们还可以拓展出K最大子串问题,即如何求出前K个最大子串。这是另一类问题了,此处不再多叙。

参考资料:

  • 《算法导论(第三版)》第四章第一节:< Introduction to Algorithms (3ed) > Chapter 4 Section 1

无相关帖子

原文地址:https://www.cnblogs.com/jackyzzy/p/2590483.html