题目描述
给你一个整数数组
nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。
示例 1:输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:输入:nums = [1] 输出:1
示例 3:输入:nums = [5,4,-1,7,8] 输出:23
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
进阶:如果你已经实现复杂度为
O(n)
的解法,尝试使用更为精妙的 分治法 求解。
解题思路
这个问题可以通过动态规划和分治法两种方法解决。
动态规划法:动态规划法的核心思想是使用一个变量 currentMax
来记录当前子数组的最大和,然后更新全局最大和 globalMax
。
具体步骤:
- 初始化
globalMax
和currentMax
为数组的第一个元素。 - 从数组的第二个元素开始,遍历每个元素:更新
currentMax
为Math.max(currentElement, currentMax + currentElement)
。即决定是继续扩展当前子数组,还是从当前元素重新开始子数组;更新globalMax
为Math.max(globalMax, currentMax)
。 - 返回
globalMax
作为最终结果。
这种方法的时间复杂度为 O(n),空间复杂度为 O(1)。
分治法:分治法利用递归将数组分为左右两部分,并结合三个部分来找到最大和:
- 左半部分的最大子数组和。
- 右半部分的最大子数组和。
- 跨越中间的最大子数组和。
具体步骤:
- 将数组分成左右两部分。
- 递归地计算左半部分和右半部分的最大子数组和。
- 计算跨越中间的最大子数组和。首先从中间向左扩展,找到左侧最大和,然后从中间向右扩展,找到右侧最大和,最后求和得到跨越中间的最大子数组和。
- 返回三个值中的最大值。
这种方法的时间复杂度为 O(n log n),空间复杂度为 O(log n)。
复杂度分析
动态规划法
- 时间复杂度:O(n)。我们遍历一次数组,每个元素进行常数时间的操作。
- 空间复杂度:O(1)。我们只使用了常数空间来存储变量。
分治法
-
时间复杂度:O(n log n)。每次分割数组都需要 O(n) 时间来计算跨越中间的子数组和,并且总共有 O(log n) 层递归。
-
空间复杂度:O(log n)。递归调用栈的深度为 O(log n)。
代码实现
package org.zyf.javabasic.letcode.hot100.ordinaryarray;
/**
* @program: zyfboot-javabasic
* @description: 最大子数组和
* @author: zhangyanfeng
* @create: 2024-08-21 22:04
**/
public class MaxSubArraySolution {
public int maxSubArray1(int[] nums) {
if (nums == null || nums.length == 0) {
throw new IllegalArgumentException("Array cannot be empty");
}
// 初始化动态规划变量
int globalMax = nums[0];
int currentMax = nums[0];
for (int i = 1; i < nums.length; i++) {
currentMax = Math.max(nums[i], currentMax + nums[i]);
globalMax = Math.max(globalMax, currentMax);
}
return globalMax;
}
public int maxSubArray2(int[] nums) {
if (nums == null || nums.length == 0) {
throw new IllegalArgumentException("Array cannot be empty");
}
return maxSubArray(nums, 0, nums.length - 1);
}
private int maxSubArray(int[] nums, int left, int right) {
if (left == right) {
return nums[left];
}
int mid = (left + right) / 2;
// 计算左半部分、右半部分以及跨越中间的最大子数组和
int leftMax = maxSubArray(nums, left, mid);
int rightMax = maxSubArray(nums, mid + 1, right);
int crossMax = maxCrossingSubArray(nums, left, mid, right);
// 返回三个部分中的最大值
return Math.max(Math.max(leftMax, rightMax), crossMax);
}
private int maxCrossingSubArray(int[] nums, int left, int mid, int right) {
int leftSum = Integer.MIN_VALUE;
int sum = 0;
// 计算跨越中间的最大子数组和(从中间向左)
for (int i = mid; i >= left; i--) {
sum += nums[i];
if (sum > leftSum) {
leftSum = sum;
}
}
int rightSum = Integer.MIN_VALUE;
sum = 0;
// 计算跨越中间的最大子数组和(从中间向右)
for (int i = mid + 1; i <= right; i++) {
sum += nums[i];
if (sum > rightSum) {
rightSum = sum;
}
}
// 返回跨越中间的最大子数组和
return leftSum + rightSum;
}
public static void main(String[] args) {
MaxSubArraySolution solution = new MaxSubArraySolution();
// 测试用例 1
int[] nums1 = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
System.out.println(solution.maxSubArray1(nums1)); // 输出: 6
// 测试用例 2
int[] nums2 = {1};
System.out.println(solution.maxSubArray1(nums2)); // 输出: 1
// 测试用例 3
int[] nums3 = {5, 4, -1, 7, 8};
System.out.println(solution.maxSubArray1(nums3)); // 输出: 23
}
}
具体可参考:https://zyfcodes.blog.csdn.net/article/details/141401712