剑指offer
Contents
- 题目
- 思路分析
- 方法1:暴力搜索
- 方法2:分析规律得到启发式的算法
- 方法3:动态规划
- 解答
- 解法1:暴力搜索
- 解法2:观察规律
- 解法3:动态规划
题目
输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
提示:
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100
思路分析
方法1:暴力搜索
两层循环,遍历所有可能的连续子数组,比较得出他们当中最大的连续子数组和。时间超限。面试最好别这么写。
方法2:分析规律得到启发式的算法
可以从头到尾逐渐计算连续和,分析求最大连续和的过程,如下图。
[图片]
发现我们可以用两个变量来保存“累加的数组和”及“最大的数组和”两个数字,在遍历数组的过程中不断更新这两个变量。
循环结束即可得到最大连续子数组和。
方法3:动态规划
- 状态定义:
dp[i]
是以nums[i]
作为结尾的连续子数组的最大和。 - 状态转移:
dp[i] = dp[i - 1] + nums[i] (dp[i - 1] > 0)
dp[i] = nums[i] (dp[i - 1] <= 0)
其实这个思路跟方法2写出的代码一摸一样,只不过感觉这样分析更符合“套路”,更通用一些。
解答
解法1:暴力搜索
class Solution {
public int maxSubArray(int[] nums) {
int max = nums[0];
int sum = nums[0];
for(int i = 0;i <= nums.length - 1;i++){
//进入第二层循环之前,sum需要重置,因为这时开始算另一个连续子数组了
sum = 0;
for(int j = i; j <= nums.length - 1;j++){
//计算从nums[i]开始的所有连续和,作为最大值的候选
sum += nums[j];
max = Math.max(max, sum);//更新max
}
}
return max;
}
}
超出时间限制。
解法2:观察规律
class Solution {
public int maxSubArray(int[] nums) {
int tmpSum = nums[0];//保存当前的连续和(可能是最大连续和)
int maxSum = nums[0];//保存结果
for(int num:nums){
//如果加上之前的连续还没有当前的num大,就可以抛弃前面的tmpSum了
tmpSum = Math.max(tmpSum + num,num);
maxSum = Math.max(tmpSum, maxSum);
}
return maxSum;
}
}
解法3:动态规划
class Solution {
public int maxSubArray(int[] nums) {
int res = nums[0];//存储结果
int cur = nums[0];//存储dp[i]
int pre = 0;//存储dp[i-1]
for(int num:nums){
//当dp[i-1] > 0:dp[i] = dp[i-1] + num
//当dp[i-1] <= 0:dp[i] = num
cur = num + Math.max(pre, 0);
res = Math.max(res ,cur);
//状态更新,本轮循环的cur,是下轮循环的cur
pre = cur;
}
return res;
}
}