求两个子数组最大的累加和

来自牛客网左程云算法第二堂课第一题

问题:给定一个数组,其中当然有很多的子数组,在所有两个子数组的组合中,找到相加和最大的一组,要求两个子数组无重合的部分。最后返回累加和。

要求: 时间复杂度达到 O(N)

解法:我们很容易想到将整个数组分成两个部分,然后分别求这两个部分子数组的最大累加和。将结果累加起来。那么将数组分成不重合两部分(相邻的两部分)有N-1种分法,根据算法原型就是leetcode 53. Maximum Subarray 子数组最大和,求子数组最大和的时间复杂度是O(N),整体的时间复杂度达到了O(N*N)。那么如何将时间复杂度降到O(N)呢?分析我们的算法原型,其中的max会保存遍历到当前位置的子数组的最大和,我们可以将这个值保存起来,这样我们就可以用O(1)的时间复杂度得到当前子数组的最大累加和。整体的时间复杂度也是O(N)级别的。由于我们要将数组分成两部分,预处理时,我们需要计算一遍从右到左的子数组最大和,这样右边的部分我们也可以直接拿到它的子数组最大累加和,左边的部分我们不用数组保存,因为我们是从左到右遍历的,只用一个变量保存即可。这是我们用空间换时间的一个方法。额外空间复杂度为O(N)。

public int maxTwoSubArray(int[] nums) {
		if (nums == null || nums.length == 0) {
			return 0;
		}
		int[] h = new int[nums.length];
		h[h.length - 1] = nums[nums.length - 1];
		int cur = h[h.length - 1];
		for (int i = h.length - 2; i >= 0; i--) {
			cur += nums[i];
			h[i] = Math.max(cur, h[i + 1]);
			if (cur < 0) {
				cur = 0;
			}
		}
		int max = Integer.MIN_VALUE;
		int lMax = Integer.MIN_VALUE;
		cur = 0;
		for (int i = 0; i < nums.length - 1; i++) {
			cur += nums[i];
			lMax = Math.max(lMax, cur);
			max = Math.max(max, lMax + h[i + 1]);
			if (cur < 0) {
				cur = 0;
			}
			System.out.println(lMax + " " + h[i+1] + " " + i + " " + max);
		}
		return max;
	}


### 分治法解决最大数组和问题 #### 什么是最大数组和问题? 最大数组和问题是寻找一个整数组中的连续子数组,使其元素之和达到最大值。此问题可以通过多种方法解决,其中分治法是一种经典的方法之一。 --- #### 分治法的核心思想 分治法通过将原问题分解成更小的子问题来逐步解决问题。对于最大数组和问题,可以将其划分为三个部分: 1. **左半边的最大数组**:完全位于中间位置左侧的部分。 2. **右半边的最大数组**:完全位于中间位置右侧的部分。 3. **跨越中间位置的最大数组**:包含中间位置及其侧的一部分。 最终的结果将是上述三种情况中的最大值[^1]。 --- #### C语言实现代码示例 以下是基于分治法最大数组和问题的C语言实现: ```c #include <stdio.h> #include <limits.h> // 找到跨过中间点的最大数组和 int findMaxCrossingSubarray(int arr[], int low, int mid, int high) { int leftSum = INT_MIN; int sum = 0; // 向左扩展计算左边的最大和 for (int i = mid; i >= low; i--) { sum += arr[i]; if (sum > leftSum) { leftSum = sum; } } int rightSum = INT_MIN; sum = 0; // 向右扩展计算右边的最大和 for (int j = mid + 1; j <= high; j++) { sum += arr[j]; if (sum > rightSum) { rightSum = sum; } } return leftSum + rightSum; } // 主函实现分治法 int maxSubArrayRecursive(int arr[], int low, int high) { if (low == high) { // 只有一个元素的情况 return arr[low]; } else { int mid = (low + high) / 2; int leftSum = maxSubArrayRecursive(arr, low, mid); // 左侧最大数组和 int rightSum = maxSubArrayRecursive(arr, mid + 1, high); // 右侧最大数组和 int crossSum = findMaxCrossingSubarray(arr, low, mid, high); // 跨越中间的最大数组和 if (leftSum >= rightSum && leftSum >= crossSum) { return leftSum; } else if (rightSum >= leftSum && rightSum >= crossSum) { return rightSum; } else { return crossSum; } } } // 接口函调用 int maxSubArray(int arr[], int size) { return maxSubArrayRecursive(arr, 0, size - 1); } ``` --- #### 代码解释 1. 函 `findMaxCrossingSubarray` 计算跨越中间位置的最大数组和。它分别向左和向右遍历数组,记录下最大累加和并返回者的总和。 2. 函 `maxSubArrayRecursive` 是递归实现的主要逻辑。当数组只有一个元素时,直接返回该元素;否则,继续划分左右两个数组,并比较它们各自的最大数组和以及跨越中间位置的最大数组和。 3. 函 `maxSubArray` 提供了一个简单的接口,方便外部调用。 --- #### 时间复杂度分析 分治法的时间复杂度为 \(O(n \log n)\),因为每次都将数组分成半处理,并且在合并阶段需要线性时间扫描整个数组以找到跨越中间位置的最大数组和。 --- #### 动态规划对比 虽然分治法能够有效解决问题,但在实际应用中,Kadane 算法通常更为高效,因为它仅需一次遍历即可完成任务,时间复杂度为 \(O(n)\)[^2]。然而,在学习算法设计模式时,理解分治法仍然非常重要。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值