最大子数组和(动态规划、分治法):
此题解法仅为为个人理解,如有其他思路或解法欢迎来探讨!
力扣链接:连续子数组的最大和
提高篇链接:最大子数组和(连续子数组的最大和)—— 提高篇
题目:
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
示例:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
分析:
这道题打眼一看就是一道考察动态规划的题(当然也可以分治与其他算法,但这里最优解是使用动态规划,所以下面的解析都以动态分析来解释)。
我们先分析题目,题目要求输入一串连续多个整数组成一个子数组,返回连续子数组的最大和 。从示例可以看出当输入为[-2,1,-3,4,-1,2,1,-5,4]
时,其中连续子数组的最大和为6
,子数组是[4,-1,2,1]
。要求输出的仅是连续子数组的最大和,而不用输出对应的子数组。
在动态规划中,我们要想办法完成以下三个关键目标:
- 建立状态转移方程
- 缓存并复用以往结果
- 按顺序从小往大算
这里,我们找到状态转移方程:dp[i] = max(dp[i-1] + nums[i], nums[i])
,
其中dp[i]
表示以索引i
为结束点基准时的子数组的最大值。
那么初始状态:dp[0]= nums[0]
但对于这道题来说,并不需要缓存所有结果,我们只需要缓存上一次的最大值即可,所以不必开辟一个新数组,只要新建一个存放上一次结果的变量pre
即可。
由此可以开始编写代码:
代码:
先看上面的分析!自己理解思考一下,不要急着看代码!
动态规划:
时间复杂度:O(n);空间复杂度:O(1)
public int maxSubArray(int[] nums) {
int pre = 0, result = nums[0];
for (int i = 0; i < nums.length; i++) {
pre = Math.max(pre + nums[i], nums[i]);//将数组中num[i] 与 num[i]+前几位数和的较大值 进行比较,得到当前i位时,数组之和的较大值
result = Math.max(result, pre);//把所得和的较大值 与 和的最大值进行比较,存入当前最大值
}
return result;
}
最终在力扣得到的运行效率:
这里,我再添上分治法与我自己写的另一种方法:
分治法(具体讲解提高篇有):
提高篇链接:最大子数组和(连续子数组的最大和)—— 提高篇
时间复杂度:O(nlogn);空间复杂度:O(1)
连续子数组的最大和主要由以下这三部分子区间里元素的最大和得到:
左子区间[left, mid]
右子区间[left, mid]
跨左右区间,即 nums[mid]
与 nums[mid + 1]
一定会被选取
public int maxSubArray(int[] nums) {
return maxSubArrayPart(nums, 0, nums.length-1);
}
public int maxSubArrayPart(int[] nums, int left, int right){
if (left == right) return nums[left];//找到最后仅剩一位,返回本身
int mid = (left+right)/2;//找中值二分
return Math.max(maxSubArrayPart(nums,left,mid),Math.max(maxSubArrayPart(nums,mid+1,right),maxSubArrayAll(nums,left,mid,right)));
}
//计算跨左右区间
public int maxSubArrayAll(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];
leftSum = Math.max(sum,leftSum);
}
sum = 0;
//右区间最大值
int rightSum=Integer.MIN_VALUE;
for(int i = mid + 1; i <=right;i++){
sum+=nums[i];
rightSum = Math.max(rightSum, sum);
}
return leftSum+rightSum;
}
在力扣得到的运行效率:
我自己的另一种写法:
public int maxSubArray(int[] nums) {
int startIndex = 0;//记录子数组的起始下标
int endIndex = 0;//记录子数组的结束下标
int sum = 0;//存放和
int temp = 0;//临时和,存放当前数组临时的和
int tempStartIndex = -1;//临时起始下标
for (int i = 0 ; i < nums.length ; i++){
if (temp < 0) //如果temp < 0;则要对temp重新赋值
{
temp = nums[i]; //对temp重新赋值
tempStartIndex = i; //暂时记录子数组(和最大)的新的起始位置(要看后续的sum 和 temp是否发生交换)
}
else
{
temp += nums[i]; //temp >= 0;算出当前的临时最大和
}
if (sum < temp) //如果此时 sum < temp;则表示此时的子数组和大于之前的子数组和
{
sum = temp; //将temp赋值给sum
startIndex = tempStartIndex; //存入之前保存的临时起始下标,记录子数组的起始下标
endIndex = i; //记录子数组的结束下标
}
}
if (endIndex - startIndex == 0){
return Arrays.stream(nums).max().getAsInt();//全为负数时,找到最大值
}
return sum;
}
在力扣得到的运行效率:
提高篇链接:最大子数组和(连续子数组的最大和)—— 提高篇