LeetCode53. Maximum Subarray (子数组的最大和) 解题思路方法
2017-02-25 16:12
281 查看
最大连续子序列和,非常经典的题。
当我们从头到尾遍历这个数组的时候,对于数组里的一个整数,它有几种选择呢?它只有两种选择: 1、加入之前的SubArray;2. 自己另起一个SubArray。那什么时候会出现这两种情况呢?
如果之前SubArray的总体和大于0的话,我们认为其对后续结果是有贡献的。这种情况下我们选择加入之前的SubArray
如果之前SubArray的总体和为0或者小于0的话,我们认为其对后续结果是没有贡献,甚至是有害的(小于0时)。这种情况下我们选择以这个数字开始,另起一个SubArray。
设状态为{f[j]},表示以{S[j]}结尾的最大连续子序列和,则状态转移方程如下:
f[j]target==max{f[j−1]+S[j],S[j]}, 其中 1≤j≤nmax{f[j]}, 其中 1≤j≤n
自己参考 法1的解法
对于 -2,1,-3,4,-1,2,1,-5,4 试一次就知道
public class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
int sum = nums[n - 1];
int maxSum = sum;
for (int i = n - 2; i >= 0; i--) {
sum = max(nums[i], sum + nums[i]);
maxSum = max(maxSum, sum);
}
return maxSum;
}
int max(int a, int b) {
return a > b ? a : b;
}
}
方法I:动态规划
另sum[i]表示从i开始的最大子串和,则有递推公式:sum[i] = max{A[i], A[i] + sum[i+1]}
因为递推式只用到了后一项,所以在编码实现的时候可以进行状态压缩,用一个变量即可
代码:
时间复杂度O(n),空间复杂度O(1)
方法II:扫描法(姑且这么称呼吧)
这是网上比较流行的一种做法,本质上还是动态规划+状态压缩。参考这篇博文
代码:
时间复杂度O(n),空间复杂度O(1)
方法III:分治法
假设求A[l..r]的最大子串和
首先将其分成两半A[l..m]和A[m+1..r],其中m=(l+r)/2,并分别求递归求出这两半的最大子串和,不妨称为left,right。如下图所示:
A[l..r]的连续子串和可能出现在左半边(即left),或者可能出现在右半边(即right),还可能出现在横跨左右两半的地方(即middle),如下图橙色部分所示:
当然,middle完全有可能覆盖left或right,它可能的范围入下图所示:
那么,如何求middle?貌似没有什么简单的方法,只能从中间向两遍扫,也就是把上图种的范围扫一遍。具体怎么扫呢?见方法I和方法II
是不是突然觉得很坑爹?既然知道最后求middle要扫一遍,还不如一开始就从l到r扫一遍求max得了,还费什么劲儿求left和right呢?求left和right的作用仅限于缩小扫描的范围。
代码:
分析一下时间复杂度,设问题的工作量是T(n),则有T(n) = 2T(n/2) + O(n),解得T(n) = O(nlogn)。看看,效率反而低了不少。
当我们从头到尾遍历这个数组的时候,对于数组里的一个整数,它有几种选择呢?它只有两种选择: 1、加入之前的SubArray;2. 自己另起一个SubArray。那什么时候会出现这两种情况呢?
如果之前SubArray的总体和大于0的话,我们认为其对后续结果是有贡献的。这种情况下我们选择加入之前的SubArray
如果之前SubArray的总体和为0或者小于0的话,我们认为其对后续结果是没有贡献,甚至是有害的(小于0时)。这种情况下我们选择以这个数字开始,另起一个SubArray。
设状态为{f[j]},表示以{S[j]}结尾的最大连续子序列和,则状态转移方程如下:
f[j]target==max{f[j−1]+S[j],S[j]}, 其中 1≤j≤nmax{f[j]}, 其中 1≤j≤n
自己参考 法1的解法
对于 -2,1,-3,4,-1,2,1,-5,4 试一次就知道
public class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
int sum = nums[n - 1];
int maxSum = sum;
for (int i = n - 2; i >= 0; i--) {
sum = max(nums[i], sum + nums[i]);
maxSum = max(maxSum, sum);
}
return maxSum;
}
int max(int a, int b) {
return a > b ? a : b;
}
}
方法I:动态规划
另sum[i]表示从i开始的最大子串和,则有递推公式:sum[i] = max{A[i], A[i] + sum[i+1]}
因为递推式只用到了后一项,所以在编码实现的时候可以进行状态压缩,用一个变量即可
代码:
1 int maxSubArray(int A[], int n) {
2 int subsum = A[n - 1];
3 int maxSum = subsum;
4
5 for (int i = n - 2; i >= 0; i--) {
6 subsum = max(A[i], subsum + A[i]);
7 maxSum = max(maxSum, subsum);
8 }
9
10 return maxSum;
11 }
时间复杂度O(n),空间复杂度O(1)
方法II:扫描法(姑且这么称呼吧)
这是网上比较流行的一种做法,本质上还是动态规划+状态压缩。参考这篇博文
代码:
1 int maxSubArray(int A[], int n) {
2 if (n == 0)
3 return 0;
4
5 int max_ending_here = A[0];
6 int max_so_far = A[0];
7 for(int i = 1; i < n; ++i)
8 {
9 if (max_ending_here < 0)
10 // So far we get negative values, this part has to be dropped
11 max_ending_here = A[i];
12 else
13 // we can accept it, it could grow later
14 max_ending_here += A[i];
15
16 max_so_far = max(max_so_far, max_ending_here);
17 }
18 return max_so_far;
19 }
时间复杂度O(n),空间复杂度O(1)
方法III:分治法
假设求A[l..r]的最大子串和
首先将其分成两半A[l..m]和A[m+1..r],其中m=(l+r)/2,并分别求递归求出这两半的最大子串和,不妨称为left,right。如下图所示:
A[l..r]的连续子串和可能出现在左半边(即left),或者可能出现在右半边(即right),还可能出现在横跨左右两半的地方(即middle),如下图橙色部分所示:
当然,middle完全有可能覆盖left或right,它可能的范围入下图所示:
那么,如何求middle?貌似没有什么简单的方法,只能从中间向两遍扫,也就是把上图种的范围扫一遍。具体怎么扫呢?见方法I和方法II
是不是突然觉得很坑爹?既然知道最后求middle要扫一遍,还不如一开始就从l到r扫一遍求max得了,还费什么劲儿求left和right呢?求left和right的作用仅限于缩小扫描的范围。
代码:
1 int diveNConquer(int A[], int l, int r) {
2 if (l == r)
3 return A[l];
4
5 int m = (l + r) / 2;
6 int left = diveNConquer(A, l, m);
7 int right = diveNConquer(A, m + 1, r);
8 int middle = A[m];
9 for (int i = m - 1, tmp = middle; i >= l; i--) {
10 tmp += A[i];
11 middle = max(middle, tmp);
12 }
13 for (int i = m + 1, tmp = middle; i <= r; i++) {
14 tmp += A[i];
15 middle = max(middle, tmp);
16 }
17
18 return max(middle, max(left, right));
19 }
20
21 int maxSubArray(int A[], int n) {
22 return diveNConquer(A, 0, n - 1);
23 }
分析一下时间复杂度,设问题的工作量是T(n),则有T(n) = 2T(n/2) + O(n),解得T(n) = O(nlogn)。看看,效率反而低了不少。
相关文章推荐
- leetCode 53.Maximum Subarray (子数组的最大和) 解题思路方法
- leetCode 53.Maximum Subarray (子数组的最大和) 解题思路方法
- leetCode 108.Convert Sorted Array to Binary Search Tree(将排序数组转换为BST) 解题思路和方法
- leetCode 81.Search in Rotated Sorted Array II (旋转数组的搜索II) 解题思路和方法
- leetCode 33.Search in Rotated Sorted Array(排序旋转数组的查找) 解题思路和方法
- leetCode 26.Remove Duplicates from Sorted Array(删除数组反复点) 解题思路和方法
- leetCode 26.Remove Duplicates from Sorted Array(删除数组重复点) 解题思路和方法
- leetCode 32.Longest Valid Parentheses (有效的最大括号) 解题思路和方法
- leetCode 85.Maximal Rectangle (最大矩阵) 解题思路和方法
- leetCode 80.Remove Duplicates from Sorted Array II (删除排序数组中的重复II) 解题思路和方法
- leetCode 84.Largest Rectangle in Histogram (最大矩形直方图) 解题思路和方法
- leetCode 88.Merge Sorted Array (合并排序数组) 解题思路和方法
- leetCode 88.Merge Sorted Array (合并排序数组) 解题思路和方法
- leetCode 84.Largest Rectangle in Histogram (最大矩形直方图) 解题思路和方法
- leetCode 104.Maximum Depth of Binary Tree(二叉树最大深度) 解题思路和方法
- leetCode 104.Maximum Depth of Binary Tree(二叉树最大深度) 解题思路和方法
- leetCode 84.Largest Rectangle in Histogram (最大矩形直方图) 解题思路和方法
- leetCode 85.Maximal Rectangle (最大矩阵) 解题思路和方法
- leetCode 31.Next Permutation (下一个字典序排序) 解题思路和方法
- leetCode 30.Substring with Concatenation of All Words (words中所有子串相连) 解题思路和方法