# 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 方法1: 暴力法
/**
* 方法1 暴力法
* 直接求解 A[i, ...j] 的和
* 0 <= i < n
* i <= j < n
* 时间复杂度 O(n^3)
*/
function maxSubArray(arr) {
let len = arr.length
let maxSum = arr[0]
let currSum
let pos = [0, 0]
for (var i = 0; i < len; i++) {
for (var j = i; j < len; j++) {
currSum = 0
for (var k = i; k <= j; k++) {
currSum += arr[k]
}
if (currSum > maxSum) {
pos = [i, j]
maxSum = currSum
}
}
}
console.log(pos) // 开始结束位置
return maxSum
}
var arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
console.log(maxSubArray(arr))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 方法2: 分治法
/**
* 方法2 分治法
* 将数组重中间分开,那么最大子数组要么完全在左半边数组,要么完全在右半边数组,要么跨立在分界点上
* 完全在左数组、右数组递归解决
* 跨立在分界点上:实际上是左数组的最大后缀和右数组的最大前缀的和。因此从分界点向前扫,向后扫即可
* 时间复杂度 O(nlogn)
*/
function maxAddSub(arr, from, to) {
if (from === to) {
return arr[from]
}
let middle = Math.floor((from + to) / 2)
let m1 = maxAddSub(arr, from, middle)
let m2 = maxAddSub(arr, middle + 1, to)
let i
let left = arr[middle]
let now = arr[middle]
for (let i = middle - 1; i >= from; i--) {
now += arr[i]
left = Math.max(now, left)
}
let right = arr[middle + 1]
now = arr[middle + 1]
for (let i = middle + 2; i <= to; i++) {
now += arr[i]
right = Math.max(now, right)
}
let m3 = left + right
return Math.max(m1, m2, m3)
}
var arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
console.log(maxAddSub(arr, 0, arr.length - 1))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
分治法时间复杂度
# 方法3: 分析法(逻辑推理的算法应用)
/**
* 分析法(逻辑推理的算法应用)
* 前缀和 p[i] = a[0] + a[1] + ... + a[i]
* s[i, j] = p[j] - p[i - 1](定义p[-1] = 0)
* 算法过程
* 1. 求 i 前缀 p[i]:
* 遍历i: 0 <= i <= n - 1
* p[i] = p[i - 1] + A[i]
* 2. 计算 p[i] - p[j]
* 遍历i: 0 <= i <= n-1 ,求最小值 m
* m 的初始值取0(p[-1]=0),然后遍历 p[0, ..., i-1] 更新m
* p[i] - m 即为以 a[i] 结尾的数组中最大的子数组
* 3. 在第2步中,可顺手记录 p[i] - m 的最大值
* 为什么
* 时间复杂度 O(n)
*
* 进一步分析:
* 记 S[i] 为以 A[i] 结尾的数组中和最大的子数组
* 则 S[i + 1] = max(S[i] + A[i + 1], A[i + 1])
* S[0] = A[0]
* 遍历i: 0 <= i <= n-1
* 动态规划: 最优子问题
* 时间复杂度: O(n)
*/
function maxSub(a) {
var result = a[0]
var sum = a[0]
var pos = [0, 0]
for (let i = 1; i < arr.length; i++) {
if (sum > 0) {
sum += a[i]
} else {
sum = a[i]
pos[0] = i // 和最大子数组的 开头的肯定是正数(如果都为负数,那就是当前遍历的负数),而且往后在加 sum 肯定也是正的
}
if (sum > result) {
result = sum
pos[1] = i
}
}
console.log(pos)
return result
}
var arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
console.log(maxSub(arr))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47