问题:
设有一个数组 a[1],a[2],a[3],\ldots,a[n]
求这个数组的最长子数组,这个子数组元素和为 0
例如,对于数组 \{1,3,8,-11,0,-7,12\}
其和为 0 的最长子数组为 \{3,8,-11,0\}
方法一:
既然所求的是最大子数组,我们不妨从长往短找,以便减少查找次数
对于数组 a[1],a[2],a[3],\ldots,a[n]
- 其最长子数组为其本身,所以先计算
a[1]+a[2]+\cdots+a[n]
判断其是否为 0 - 其次长子数组为 a[1],a[2]…,a[n-1] 和 a[2],a[3]…,a[n]
分别计算这两个子数组和 - 依次减少数组长度,直到找到最长子数组
需要注意的是:
- 若找到长为k的最长子数组,不能立刻停止计算
而要继续计算完剩下长度为k的子数组是否和为0,防止最长子数组不唯一 - 分析该种计算方法,在最坏情况下(即最长子数组不存在),需要的计算步骤为:
1+2+3+\cdots+n=\Theta(n^2)
即时间复杂度是数组长度的平方
这种算法相信是大家都能想到的,思想是“暴力枚举”
那么有没有一种方法可以简化寻找次数呢?
方法二:
对于数组 a[1],a[2],\ldots,a[n] 我们想到:能不能通过分治的方法简化运算呢?
我们可以将 a[1],a[2],\ldots,a[n] 分为 a[1],a[2],\ldots,a[n/2] 和 a[n/2+1],\ldots,a[n] 两个部分
再分别求两部分的和为 0 的最长子数组
再找到我们刻意忽略的跨过 a[n/2] 和 a[n/2+1] 的最长和为0子数组
最后取较大者
这种计算方法需要以下步骤:
- 将 a[1],a[2],\ldots,a[n] 分为 a[1],a[2],\ldots,a[n/2] 和 a[n/2+1],\ldots,a[n]
- 找到 a[1],a[2],\ldots,a[n/2] 和 a[n/2+1],\ldots,a[n] 的和为 0 的最长子数组
- 找到跨过 a[n/2] 和 a[n/2+1] 的最长和为 0 子数组
- 取最大者
注:
- 若 n/2 仍较大,可继续把 a[1],a[2],\ldots,a[n/2] 再分下去
- 事实上,真正需要计算的只有步骤3,即其实只需要计算如何“合并”即(在此不予证明)
该种算法时间复杂度比枚举稍有优化,为 \Theta(n\log n)
方法3:
还能继续简化计算步骤么?可以的:
如果我们令
S_n=a[1]+a[2]+\cdots+a[n]
即
\begin{align} S_1 &= a[1]\\ S_2 &= a[1]+a[2]\\ S_3 &= a[1]+a[2]+a[3]\\ &\cdots \end{align}
那么我们可以得到一个明显的结论:
如果 S_i = S_j ,那么 a[i+1]+a[i+2]+\cdots+a[j] = 0
如:
S_1=1 \quad S_2=5 \quad S_3=8 \quad S_4=1
则 a[2]+a[3]+a[4]=0
用这种算法,我们只需要对 a[n] 的数组进行一次加法运算并记录(甚至不需要记录)相同 S_i 出现的位置
就可以很快的计算出和为 0 的最长子数组