最大连续子数组问题之一维数组

今年做了现代程序设计课的助教,邹老师第一节课为了考察学生编程水平,就留了下面这道题作为课堂作业,为了当好助教,在此稍作总结

问题:求解一维数组中的任何连续子数组的和的最大值,此题是各种面试、算法课中的经典问题,本文将对目前遇到的各种解法做个实践与归纳

输入:长度为n的数组num[0...n-1]

输出:连续子数组和的最大值

解法与思路:

1、O(n3)解法

  考虑数组num[]的子数组个数,对于子数组num[i...j],其中0≤i≤j<n,子数组共有n+n-1+n-2+...+2+1=n(n+1)/2个。对于每个子数组再求和,并记录其最大值,子数组的平均长度为O(n),所以算法复杂度为O(n3),具体的代码实现max_subseq_n3.py如下所示

 1 #!/usr/bin/python
 2 
 3 '''
 4 便于实验,在num.txt中每行一个[-10000,10000]的随机整数,共10000个
 5 '''
 6 f = open("num.txt", "r")
 7 num = []
 8 for line in f.readlines():
 9     num.append(int(line))
10 
11 n = len(num)
12 max_so_far = min(num)
13 
14 for i in range(0, n):
15     for j in range(i, n):
16         sum = 0
17         for k in range(i, j + 1):
18             sum += num[k]
19         max_so_far = max(sum, max_so_far)
20 
21 print max_so_far

使用time ./max_subseq_n3.py进行计时,得到结果如下(这个时间太长,明天再贴了。。。):

2、O(n2)解法

  注意到sum(num[i...j]) = sum(num[i...j-1]) + num[j],可以在计算子数组num[i...j]的和时利用上一个子数组num[i...j-1]的结果,这样便可减少子数组和的计算,使算法减少一层循环,时间复杂度降低到O(n2),具体的代码实现max_subseq_n2.py如下所示

 1 #!/usr/bin/python
 2 
 3 f = open("num.txt", "r")
 4 num = []
 5 for line in f.readlines():
 6     num.append(int(line))
 7 
 8 n = len(num)
 9 max_so_far = min(num)
10 
11 for i in range(0, n):
12     sum = 0
13     for j in range(i, n):
14         sum = sum + num[j]
15         max_so_far = max(sum, max_so_far)
16 
17 print max_so_far

使用time ./max_subseq_n2.py进行计时,得到结果如下:

3、O(nlogn)解法

  使用分治法,要解决规模为n的问题,可以递归的解决两个规模近为n/2的子问题,然后对它们的答案进行合并以得到整个问题的答案。将数组num[0...n-1]划分为两个大小近似为n/2的子数组a和b

  然后递归的找出数组a和b的最大子数组的和为ma和mb

  合并子问题的答案时可以发现,原问题的最大子数组也可能跨越a和b的边界,设其和为mc

  所以可得原问题的解为max(ma, mb, mc),具体的代码实现max_subseq_nlogn.py如下所示

 1 #!/usr/bin/python
 2 
 3 f = open("num.txt", "r")
 4 num = []
 5 for line in f.readlines():
 6     num.append(int(line))
 7 n = len(num)
 8 
 9 def maxsum(start, end):
10     if start > end:
11         return 0
12     if start == end:
13         return max(0, num[start])
14     mid = (start + end) / 2
15     smax = sum = 0
16     for i in range(mid, -1, -1):
17         sum += num[i]
18         smax = max(smax, sum)
19     emax = sum = 0
20     for i in range(mid + 1, end):
21         sum += num[i]
22         emax = max(emax, sum)
23     return max(smax + emax, maxsum(start, mid), maxsum(mid + 1, end))
24 
25 print maxsum(0, n-1)

使用time ./max_subseq_nlogn.py进行计时,得到结果如下:

4、O(n)解法

  从数组下标0开始扫描至到i,假设已经知道num[0...i-1]的最大子数组的和为max_so_far,那么此时在有了下标i后,最大子数组要么包含num[i]并以num[i]结尾,要么不包含num[i],假设以num[i]结尾的最大子数组的和为max_ending_here,则max_so_far = max(max_so_far, max_ending_here)。此时,问题转换为如何求解max_ending_here

  可以知道在扫描num[i]前已经知道了num[0...i-1]的已num[i-1]结尾的最大子数组的和为max_ending_here,则在扫描num[i]后,max_ending_here将变为以num[i]结尾的最大子数组的和,所以max_ending_here = max(max_ending_here + num[i], num[i]),这样再根据上式便可以求解出max_so_far

  具体的代码实现max_subseq_n.py如下所示

 1 #!/usr/bin/python
 2 
 3 f = open("num.txt", "r")
 4 num = []
 5 for line in f.readlines():
 6     num.append(int(line))
 7 
 8 n = len(num)
 9 max_so_far = 0
10 max_ending_here = 0
11 if max(num) < 0
12         max_so_far = min(num)
13         max_ending_here = min(num)
14 
15 for i in range(0, n):
16      max_ending_here = max(max_ending_here + num[i], num[i])
17      max_so_far = max(max_so_far, max_ending_here)
18 
19 print max_so_far
注意:max_so_far和max_ending_here应初始化为0,但当全部元素小于0时,应初始化为数组最小值

使用time ./max_subseq_n.py进行计时,得到结果如下:

 

总结,根据上面的运行时间统计,就可以看出算法时间复杂度的影响了,当然这里的时间包含了读入num.txt的时间,算法真实运行时间并未实测,有兴趣的同学可以尝试一下

思考:max_so_far和max_ending_here初始化为数组中元素的最大值,岂不更好~~

posted @ 2013-09-11 21:35  infraio  阅读(1292)  评论(0编辑  收藏  举报