逐个元素地减少问题可能会导致即使是中等大小的数组也会出现堆栈溢出。这可以通过将范围值 first 和 last 添加到递归方法的参数列表并将结果范围(大致)分成两半来生成两个长度相差不超过 1 的子范围来避免。递归地对每个子范围应用此拆分过程,直到您到达包含单个元素的范围,其“总和”是该元素的值。然后对递归调用的结果求和,并继续将累积结果传递回递归树。重复减半减少了对O(log(elements.length)) 而不是O(elements.length) 的递归调用次数。运行时间的递归是T(n) = 2T(n/2) + O(1) => O(n),所以这种方法(就像其他人提出的解决方案一样)以线性时间计算总和。
我已经将实现分解为一个公共前端,它将数组作为其唯一参数,以及一个单独的私有递归,它具有范围信息的附加参数。这对最终用户隐藏了范围簿记。当递归需要可以从数据本身派生的信息,或者涉及只需要预先执行一次的值或类型检查时,这是一个常见且有用的技巧。将这些派生和检查分离到前端可以减少递归调用中完成的工作量,并使面向公众的方法界面更加用户友好。我还设置了返回类型long,因为两个ints 的总和会溢出int 的容量。以下代码:
class Test {
// Public facing front end
public static long sumArray(int [] elements){
int count = elements.length;
if (count == 0) {
return 0L;
} else {
return _sumArray_(elements, 0, count - 1);
}
}
// Private recursive worker-bee to do the actual task.
private static long _sumArray_(int [] elts, int first, int last) {
// When focus is on a single element, return its value
if (last == first) {
return (long) elts[first];
}
// Otherwise find the mid-range index for the current range
int mid = first + (last - first) / 2;
// Sum the sums of the two resulting sub-ranges
return _sumArray_(elts, first, mid) + _sumArray_(elts, mid + 1, last);
}
public static void main(String[] args) {
int [] ary = {5,6,7,1,2,3,4,8,9,10};
System.out.println(sumArray(ary));
}
}
为main 中提供的测试数据生成正确答案 55。
总而言之,这个解决方案:
- 产生正确答案,即使数组包含非常大的
int 值(返回long 以避免整数溢出);
- 示例:使用返回
int 的方法处理 int [] ary = {2147483647,2147483647}; 将产生 -2 而不是 4294967294 的正确答案
- 不需要最终用户记住和提供除数据本身之外的其他参数;
- 与其他建议解决方案的时间复杂度相匹配;和
- 在递归堆栈的大小上支配所有其他解决方案,因此如果将它用于包含数十万个或更多元素的数组,它不会导致堆栈溢出。
- 示例:将大小为
n 的问题划分为大小为1 和n-1 的子问题的方法将在int [] ary = new int[100000]; java.util.Arrays.fill(ary, 42); 等大型数组上生成java.lang.StackOverflowError,同时将问题分成连续的两半处理一个数组100M 个元素没有问题。