我的LeetCode代码仓:https://github.com/617076674/LeetCode
原题链接:https://leetcode-cn.com/problems/maximum-subarray/description/
题目描述:
知识点:
思路一:暴力解法(在LeetCode中提交会超时)
暴力解法的思路很简单,遍历所有可能的连续子数组,寻求其最大和。
时间复杂度是O(n ^ 3),其中n为nums数组的长度。空间复杂度是O(1)。
JAVA代码:
public class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
int result = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int sum = 0;
for (int k = i; k <= j; k++) {
sum += nums[k];
}
result = Math.max(result, sum);
}
}
return result;
}
}
思路二:对暴力解法的改进
在思路一的最内层循环中,我们每次计算sum值时,都是从索引i一直加到索引j,事实上,计算sum时我们完全可以利用上一步的结果,即从索引i一直加到索引j可以变成从索引i一直加到索引j - 1的和再加上索引j。这就是本思路的由来。
时间复杂度是O(n ^ 2),其中n为nums数组的长度。空间复杂度为O(1)。
JAVA代码:
public class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
int result = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
int sum = 0;
for (int j = i; j < n; j++) {
sum += nums[j];
result = Math.max(result, sum);
}
}
return result;
}
}
LeetCode解题报告:
思路三:分治算法
对于数组中索引在[left, right]范围内的元素,其具有最大和的连续子数组可能在左半边,也可能在右半边,也可能横跨左右两边。
递归终止条件:
如果left == right,在此范围内只有一个元素,直接返回该元素的值即可。
递归过程:
(1)递归调用函数求出左半边的最大和leftResult。
(2)递归调用函数求出右半边的最大和rightResult。
(3)求解横跨左右两边的最大和midResult。此时需要以mid为起点,向左和向右分别遍历数组,求出左半边最大和midLeftResult以及右半边最大和midRightResult,将这两者相加即得midResult。
时间复杂度是O(nlogn)级别的,其中n为nums数组的长度。空间复杂度即递归深度是O(logn)级别的。
JAVA代码:
public class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
int result = maxSubArray(nums, 0, n - 1);
return result;
}
private int maxSubArray(int[] nums, int left, int right) {
if(left == right) {
return nums[left];
}
int mid = left + (right - left) / 2;
int leftResult = maxSubArray(nums, left, mid);
int rightResult = maxSubArray(nums, mid + 1, right);
int leftSum = 0;
int midLeftResult = Integer.MIN_VALUE;
for (int i = mid; i >= left; i--) {
leftSum += nums[i];
midLeftResult = Math.max(midLeftResult, leftSum);
}
int rightSum = 0;
int midRightResult = Integer.MIN_VALUE;
for (int i = mid + 1; i <= right; i++) {
rightSum += nums[i];
midRightResult = Math.max(midRightResult, rightSum);
}
int midResult = midLeftResult + midRightResult;
return Math.max(leftResult, Math.max(rightResult, midResult));
}
}
LeetCode解题报告:
思路四:“在线算法”
对于nums中索引0 ~ n - 1,假设满足和最大的子串的左右索引分别是i和j,那么对于不论什么k(i <= k <= j),我们有nums中左右索引分别是i和k的子序列的和大于0。
因为假设存在k使得左右索引分别是i和k的子序列的和小于0。那么我们就有左右索引分别是k + 1和j的子序列的和大于左右索引分别是i和j的和,这与我们假设的array[i...j]就是array中和最大子串矛盾。
基于上述规律,我们可以一次扫描nums数组就求出具有最大和的连续子数组的最大和。关键就在于一旦出现sum < 0的情况,我们直接令sum = 0,即舍弃前面的所有的元素,从下一个元素i + 1开始计算起。因为一旦出现sum < 0的情况,说明前面的所有元素不该出现在具有最大和的连续子数组里,因为从索引i + 1开始算起肯定比算上i及到上一个起始索引之间的索引对应的nums数组的值的总和要大,因为i及到上一个起始索引之间的索引对应的nums数组的值的总和的sum < 0。
时间复杂度是O(n)级别的,其中n为nums数组的长度。空间复杂度是O(1)级别的。
JAVA代码:
public class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
int result = Integer.MIN_VALUE;
int sum = 0;
for (int i = 0; i < n; i++) {
sum += nums[i];
result = Math.max(result, sum);
if(sum < 0) {
sum = 0;
}
}
return result;
}
}
LeetCode解题报告: