[饭后算法系列] 数组中"和非负"的最长子数组
2014-03-21 15:49
204 查看
1. 问题
给定一列数字数组 a, 求这个数组中最长的 "和>=0" 的子数组. (注: "子数组"表示下标必须是连续的. 另一个概念"子序列"则不必连续)
举个例子:
数组 a
= {1, 2, -4, 5, -6, 1}, 最长的和非负的子数组为 {1, 2, -4, 5}, 其他子数组要么和<0, 要么长度<4
2. 暴力法
我们先来看看暴力解法和时间复杂度1. 如果我求出所有的数组前缀和 即P(i) = a[1]到a[i]的和
2. 然后对于数组的所有子数组 a[i..j], 它的和为 P(j) - P(i-1)
第一步预处理的时间复杂度为O(n). 第二步是暴力法的主体, 穷举了所有O(n^2)个子数组, 每个子数组和的计算需要时间O(1). 因此整个算法的时间复杂度就是O(n^2)
由此可见, 任何优化必须使得复杂度小于 O(n^2)
3. 算法优化
算法是一门基于观察的学科, 让我们先从例子入手, 观察一下优化的方法对于数组a
, 用动态规划法从第一个数开始往后遍历
1 | 2 | -4 | 5 | -6 | 1 |
(1,1) | (2,3) | (3,-1) | (4,4) | (5,-2) | (6,-1) |
(1,2) | (2,-2) | (3,3) | (4,-3) | (5,-2) | |
(1,-4) | (2,1) | (3,-5) | (4,-4) | ||
(1,5) | (2,-1) | (3, 0) | |||
(1,-6) | (2,-5) | ||||
(1, 1) |
2. 遍历到第二个数2时, 填写(2,3), 表示前两个数的和为3; 填写(1,2), 表示前一个数(即2自己)的和为2
以此类推, 填完所有6列, 可以看到第4列的(4,4)是和为4>=0, 且长度最长的结果
这个做法仍然需要O(n^2), 有什么办法优化呢?
3.1 计算简化
这个表格的计算是可以简化的:1 | 2 | -4 | 5 | -6 | 1 | |
0 | ||||||
-1 | ||||||
-3 | ||||||
1 | ||||||
-4 | ||||||
2 | ||||||
sum | 1 | 3 | -1 | 4 | -2 | -1 |
假设运算到第k列的时候, 所有以a[k]结尾的子数组和记为b[k] = [sum(1..k), sum(2..k), ..., sum(k..k)]
我只要记录b[k]的变形c[k]即可, c[k] = [0, -sum(1..1), -sum(1..2), ..., -sum(1..k-1)], 要从c[k]转回b[k], 我只要简单的把c中的每个元素值加上sum(1..k)
这样做的好处是: 我从c[k]前进到c[k+1]的时候, 只要简单地在最后加一个元素-sum(1..k), 而不需要修改前面的元素. 这使得前进一步的开销为O(1)
转化后, 我要找b[k]中>=0的元素, 等同于找c[k]中>=-sum(1..k)的元素, 这个查找过程怎么简化呢?
3.2 单调优化
这个过程是可以发现单调性优化的. 为了方便, 我把表格转回最开始的样子:1 | 2 | -4 | 5 | -6 | 1 |
(1,1) | (2,3) | (3,-1) | (4,4) | (5,-2) | (6,-1) |
(1,2) | (2,-2) | (3,3) | (4,-3) | (5,-2) | |
(1,-4) | (2,1) | (3,-5) | (4,-4) | ||
(1,5) | (2,-1) | (3, 0) | |||
(1,-6) | (2,-5) | ||||
(1, 1) |
比如第3列中, (2,-2)的上面有(3,-1). 也就是-4往前加2个数的和为-2, 往前加3个数的和为-1. 如果最终答案包含了-4往前的2个数, 那我一定能够换成-4往前3个数, 总和比原来大了1, 且长度也比原来长
去掉飘灰的这些格子后, 我们发现, 每一列的第二个数(和)是单调递增的.
因为我要找每列第二个数(和)>=0的格子, 由于有了单调性之后, 我就能用二分查找了, 查找的复杂度为O(lgn)
3.3 总结
综合3.1和3.2, 在这个动态规划过程中, 遍历的每一步, 时间复杂度=O(1)+O(lgn), 总共遍历n次, 因此总的时间复杂度为O(nlgn)关键字: 算法, 动态规划, 数组
相关文章推荐
- [饭后算法系列] "头尾移动" 排序列表
- 【白话经典算法系列之十二】数组中只出现1次的两个数字(百度面试题)
- 【啊哈,算法】之十、后缀数组,求最长重复子串
- 算法系列——最长公共子串
- 【数据结构&&算法系列】最大子数组
- 算法系列——数组中只出现一次的数字
- 白话经典算法系列之十二 数组中只出现1次的两个数字(百度面试题)
- 程序员笔试面试算法题系列--数组
- 算法系列——数组中出现次数超过一半的数字(剑指offer)
- 算法题-数组的最长递增子序列
- 实用算法实现-第 8 篇 后缀树和后缀数组 [2 最长公共子串]
- 数组字符串那些经典算法:最大子序列和,最长递增子序列,最长公共子串,最长公共子序列,字符串编辑距离,最长不重复子串,最长回文子串
- 【白话经典算法系列之十五】“一步千里”之数组找数
- 算法系列——Longest Common Prefix最长公共前缀
- 算法系列——调整数组顺序使奇数位于偶数前面
- 每天学习一算法系列(14) (输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字)
- 算法系列——数组中的重复数字
- 实用算法实现-第 8 篇 后缀树和后缀数组 [2 最长公共子串]
- 实用算法实现-第 8 篇 后缀树和后缀数组 [3 两个字符串的最长公共子串]
- 算法总结(5)--Duplicate 数组,链表中重复元素系列