Skip to content

Commit de7f06a

Browse files
committed
53
1 parent 9aa9d81 commit de7f06a

File tree

2 files changed

+265
-1
lines changed

2 files changed

+265
-1
lines changed

SUMMARY.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,5 @@
5252
* [49. Group Anagrams](leetCode-49-Group-Anagrams.md)
5353
* [50. Pow(x, n)](leetCode-50-Pow.md)
5454
* [51. N-Queens](leetCode-51-N-Queens.md)
55-
* [52. N-Queens II](leetCode-52-N-QueensII.md)
55+
* [52. N-Queens II](leetCode-52-N-QueensII.md)
56+
* [53. Maximum Subarray](leetCode-53-Maximum-Subarray.md)

leetCode-53-Maximum-Subarray.md

+263
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# 题目描述(简单难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/53.jpg)
4+
5+
给一个数组,找出一个连续的子数组,长度任意,和最大。
6+
7+
# 解法一 动态规划思路一
8+
9+
用一个二维数组 dp\[ i \] \[ len \] 表示从下标 i 开始,长度为 len 的子数组的元素和。
10+
11+
这样长度是 len + 1 的子数组就可以通过长度是 len 的子数组去求,也就是下边的递推式,
12+
13+
dp \[ i \] \[ len + 1 \] = dp\[ i \] \[ len \] + nums [ i + len - 1 ]
14+
15+
当然,和[第 5 题](https://leetcode.windliang.cc/leetCode-5-Longest-Palindromic-Substring.html)一样,考虑到求 i + 1 的情况的时候,我们只需要 i 时候的情况,所有我们其实没必要用一个二维数组,直接用一维数组就可以了。
16+
17+
```java
18+
public int maxSubArray(int[] nums) {
19+
int n = nums.length;
20+
int[] dp = new int[n];
21+
int max = Integer.MIN_VALUE;
22+
for (int len = 1; len <= n; len++) {
23+
for (int i = 0; i <= n - len; i++) {
24+
//直接覆盖掉前边对应的情况就行
25+
dp[i] = dp[i] + nums[i + len - 1];
26+
//更新 max
27+
if (dp[i] > max) {
28+
max = dp[i];
29+
}
30+
}
31+
}
32+
return max;
33+
}
34+
```
35+
36+
时间复杂度:O(n²)。
37+
38+
空间复杂度:O(n)。
39+
40+
# 解法二 动态规划思路二
41+
42+
参考[这里](https://leetcode.com/problems/maximum-subarray/discuss/20193/DP-solution-and-some-thoughts)
43+
44+
用一个一维数组 dp [ i ] 表示以下标 i 结尾的子数组的元素的最大的和,也就是这个子数组最后一个元素是下边为 i 的元素,并且这个子数组是所有以 i 结尾的子数组中,和最大的。
45+
46+
这样的话就有两种情况,
47+
48+
* 如果 dp [ i - 1 ] < 0,那么 dp [ i ] = nums [ i ]
49+
* 如果 dp [ i - 1 ] >= 0,那么 dp [ i ] = dp [ i - 1 ] + nums [ i ]
50+
51+
```java
52+
public int maxSubArray(int[] nums) {
53+
int n = nums.length;
54+
int[] dp = new int[n];
55+
int max = nums[0];
56+
dp[0] = nums[0];
57+
for (int i = 1; i < n; i++) {
58+
//两种情况更新 dp[i]
59+
if (dp[i - 1] < 0) {
60+
dp[i] = nums[i];
61+
} else {
62+
dp[i] = dp[i - 1] + nums[i];
63+
}
64+
//更新 max
65+
max = Math.max(max, dp[i]);
66+
}
67+
return max;
68+
}
69+
```
70+
71+
时间复杂度: O(n)。
72+
73+
空间复杂度:O(n)。
74+
75+
当然,和以前一样,我们注意到更新 i 的情况的时候只用到 i - 1 的时候,所以我们不需要数组,只需要两个变量。
76+
77+
```java
78+
public int maxSubArray(int[] nums) {
79+
int n = nums.length;
80+
//两个变量即可
81+
int[] dp = new int[2];
82+
int max = nums[0];
83+
dp[0] = nums[0];
84+
for (int i = 1; i < n; i++) {
85+
//利用求余,轮换两个变量
86+
if (dp[(i - 1) % 2] < 0) {
87+
dp[i % 2] = nums[i];
88+
} else {
89+
dp[i % 2] = dp[(i - 1) % 2] + nums[i];
90+
}
91+
max = Math.max(max, dp[i % 2]);
92+
}
93+
return max;
94+
}
95+
```
96+
97+
时间复杂度: O(n)。
98+
99+
空间复杂度:O(1)。
100+
101+
再粗暴点,直接用一个变量就可以了。
102+
103+
```java
104+
public int maxSubArray(int[] nums) {
105+
int n = nums.length;
106+
int dp = nums[0];
107+
int max = nums[0];
108+
for (int i = 1; i < n; i++) {
109+
if (dp < 0) {
110+
dp = nums[i];
111+
} else {
112+
dp= dp + nums[i];
113+
}
114+
max = Math.max(max, dp);
115+
}
116+
return max;
117+
}
118+
```
119+
120+
而对于
121+
122+
```java
123+
if (dp < 0) {
124+
dp = nums[i];
125+
} else {
126+
dp= dp + nums[i];
127+
}
128+
```
129+
130+
其实也可以这样理解,
131+
132+
```java
133+
dp= Math.max(dp + nums[i],nums[i]);
134+
```
135+
136+
然后就变成了[这里](https://leetcode.com/problems/maximum-subarray/discuss/20211/Accepted-O(n)-solution-in-java)提到的算法。
137+
138+
# 解法三 折半
139+
140+
题目最后说
141+
142+
> If you have figured out the O(*n*) solution, try coding another solution using the divide and conquer approach, which is more subtle.
143+
144+
[这里](If you have figured out the O(*n*) solution, try coding another solution using the divide and conquer approach, which is more subtle.)找到了种解法,分享下。
145+
146+
假设我们有了一个函数 int getSubMax(int start, int end, int[] nums) ,可以得到 num [ start, end ) (左包右不包) 中子数组最大值。
147+
148+
如果, start == end,那么 getSubMax 直接返回 nums [ start ] 就可以了。
149+
150+
```java
151+
if (start == end) {
152+
return nums[start];
153+
}
154+
```
155+
156+
然后对问题进行分解。
157+
158+
先找一个 mid , mid = ( start + end ) / 2。
159+
160+
然后,对于我们要找的和最大的子数组有两种情况。
161+
162+
* mid 不在我们要找的子数组中
163+
164+
这样的话,子数组的最大值要么是 mid 左半部分数组的子数组产生,要么是右边的产生,最大值的可以利用 getSubMax 求出来。
165+
166+
```java
167+
int leftMax = getSubMax(start, mid, nums);
168+
int rightMax = getSubMax(mid + 1, end, nums);
169+
```
170+
171+
* mid 在我们要找的子数组中
172+
173+
这样的话,我们可以分别从 mid 左边扩展,和右边扩展,找出两边和最大的时候,然后加起来就可以了。当然如果,左边或者右边最大的都小于 0 ,我们就不加了。
174+
175+
```java
176+
int containsMidMax = getContainMidMax(start, end, mid, nums);
177+
private int getContainMidMax(int start, int end, int mid, int[] nums) {
178+
int containsMidLeftMax = 0; //初始化为 0 ,防止最大的值也小于 0
179+
//找左边最大
180+
if (mid > 0) {
181+
int sum = 0;
182+
for (int i = mid - 1; i >= 0; i--) {
183+
sum += nums[i];
184+
if (sum > containsMidLeftMax) {
185+
containsMidLeftMax = sum;
186+
}
187+
}
188+
189+
}
190+
int containsMidRightMax = 0;
191+
//找右边最大
192+
if (mid < end) {
193+
int sum = 0;
194+
for (int i = mid + 1; i <= end; i++) {
195+
sum += nums[i];
196+
if (sum > containsMidRightMax) {
197+
containsMidRightMax = sum;
198+
}
199+
}
200+
}
201+
return containsMidLeftMax + nums[mid] + containsMidRightMax;
202+
}
203+
```
204+
205+
最后,我们只需要返回这三个中最大的值就可以了。
206+
207+
综上,递归出口,问题分解就都有了。
208+
209+
```java
210+
public int maxSubArray(int[] nums) {
211+
return getSubMax(0, nums.length - 1, nums);
212+
}
213+
214+
private int getSubMax(int start, int end, int[] nums) {
215+
//递归出口
216+
if (start == end) {
217+
return nums[start];
218+
}
219+
int mid = (start + end) / 2;
220+
//要找的数组不包含 mid,然后得到左边和右边最大的值
221+
int leftMax = getSubMax(start, mid, nums);
222+
int rightMax = getSubMax(mid + 1, end, nums);
223+
//要找的数组包含 mid
224+
int containsMidMax = getContainMidMax(start, end, mid, nums);
225+
//返回它们 3 个中最大的
226+
return Math.max(containsMidMax, Math.max(leftMax, rightMax));
227+
}
228+
229+
private int getContainMidMax(int start, int end, int mid, int[] nums) {
230+
int containsMidLeftMax = 0; //初始化为 0 ,防止最大的值也小于 0
231+
//找左边最大
232+
if (mid > 0) {
233+
int sum = 0;
234+
for (int i = mid - 1; i >= 0; i--) {
235+
sum += nums[i];
236+
if (sum > containsMidLeftMax) {
237+
containsMidLeftMax = sum;
238+
}
239+
}
240+
241+
}
242+
int containsMidRightMax = 0;
243+
//找右边最大
244+
if (mid < end) {
245+
int sum = 0;
246+
for (int i = mid + 1; i <= end; i++) {
247+
sum += nums[i];
248+
if (sum > containsMidRightMax) {
249+
containsMidRightMax = sum;
250+
}
251+
}
252+
}
253+
return containsMidLeftMax + nums[mid] + containsMidRightMax;
254+
}
255+
```
256+
257+
时间复杂度:O(n log ( n ))。由于 getContainMidMax 这个函数耗费了 O(n)。所以时间复杂度反而相比之前的算法变大了。
258+
259+
空间复杂度:
260+
261+
#
262+
263+
解法一和解法二的动态规划,只是在定义的时候一个表示以 i 开头的子数组,一个表示以 i 结尾的子数组,却造成了时间复杂度的差异。问题就是解法一中求出了太多的没必要的和,不如解法二直接,只保存最大的和。解法三,一半一半的求,从而使问题分解,也是经常遇到的思想。

0 commit comments

Comments
 (0)