# 最大子列和
给定 K 个整数组成的序列 {
# 数据特点
数据1:与样例等价,测试基本正确性;
数据2:
数据3:
数据4:
数据5:
# 输入
第一行给出正整数 K (
第二行给出 K 个整数,中间以空格分隔。
# 输出
在一行中输出最大子列和。如果序列中所有整数皆为负数,则输出 0.
# 输入输出样例
Input
6
-2 11 -4 13 -5 -2
2
Output
20
# 解法分析
# 暴力穷举
算法利用三层循环分别确定子列左端点、右端点和子列内的遍历。
每次更新子列时便重新计算当前列的和,遍历累加判断是否需要更新最大和。
时间复杂度:
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int arr[] = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = in.nextInt();
}
int maxSum = 0;
int currentSum = 0;
// 暴力穷举法 (类似 nested loop join)
for(int i = 0; i < n; i++) { // i 为子列左端
for(int j = i; j < n; j++) { // j 为子列右端
currentSum = 0; // currentSum 为当前子列和,每次更新子列时清零
for(int k = i; k <= j; k++) { // k 为累加指针,从 i 到 j 逐个累加
currentSum += arr[k];
if (currentSum > maxSum) {
maxSum = currentSum;
}
}
}
}
System.out.println(maxSum);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 分部累加
算法利用两层循环分别确定子列左端和右端,在遍历时便进行累加判断操作。
对于每个相同的左端,每次更新一个右端。利用上一次的和来判断当前状态是否需要更新最大和。
时间复杂度:
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int arr[] = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = in.nextInt();
}
int maxSum = 0;
int currentSum = 0;
// 分部穷举法 (类似 page oriented loop join)
for (int i = 0; i < n; i++) {
currentSum = 0;
for (int j = i; j < n; j++) {
currentSum += arr[j];
if (currentSum > maxSum) {
maxSum = currentSum;
}
}
}
System.out.println(maxSum);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 递归分治
算法利用递归思想,把问题分解成三块:中线平分后左子列最大和;中线平分后右子列最大和;跨越中线的最大和。比较三个数值得出最终结果。
对于跨越中线的子列和计算方法为:
- 1)从中线向左遍历累加,直到遇到会让子列和下降的元素前停止。
- 2)从中线向右遍历累加,直到遇到会让子列和下降的元素前停止。
- 3)将上述结果相加得出最终结果。
时间复杂度:
记时间复杂度为
则有:
将
将
继续展开到 k 阶有:
展开 k 步后,函数 T 的自变量变为 1,即:
(即当递推到终止条件时,元素只有一个)
此时可认为:
则有:
则:
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int arr[] = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = in.nextInt();
}
// 递归分治法
System.out.println(divideAndConquer(arr, 0, n - 1));
}
// 分治法求从 arr[left] 到 arr[right] 的最大子列和
public static int divideAndConquer(int[] arr, int left, int right) {
// 左右子问题结果
int maxLeftSum;
int maxRightSum;
// 跨界问题结果
int maxLeftBorderSum;
int maxRightBorderSum;
// 变量
int leftBorderSum;
int rightBorderSum;
int center;
// 子列只有一个元素;递归的终止条件
if (left == right) {
if(arr[left] > 0) {
return arr[left];
} else {
return 0;
}
}
// 拆分数组
center = (left + right) >> 1;
// 递归求解
maxLeftSum = divideAndConquer(arr, left, center);
maxRightSum = divideAndConquer(arr, center + 1, right);
// 求跨界结果
maxLeftBorderSum = 0;
leftBorderSum = 0;
for (int i = center; i >= left; i--) { // 从中线向左扫描
leftBorderSum += arr[i];
if (leftBorderSum > maxLeftBorderSum) {
maxLeftBorderSum = leftBorderSum;
}
}
maxRightBorderSum = 0;
rightBorderSum = 0;
for (int i = center + 1; i <= right; i++) { // 从中线向右扫描
rightBorderSum += arr[i];
if (rightBorderSum > maxRightBorderSum) {
maxRightBorderSum = rightBorderSum;
}
}
// 比较返回结果
return max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);
}
public static int max3(int a, int b, int c) {
return a > b ? (a > c ? a : c) : (b > c ? b : c);
}
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 在线处理
算法遍历全部输入,期间计算当前子列和并判断是否更新最终结果。
每读入一个数字,累加到上一次的结果上,进行比较是否需要更细结果;
但若当前子列和为负,则证明当前子列不可能使得下一个元素加入后的子列更大,则重新计数。
时间复杂度:
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int arr[] = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = in.nextInt();
}
int maxSum = 0;
int currentSum = 0;
// 在线处理法
for (int i = 0; i < n; i++) {
currentSum += arr[i]; // 向右累加
if (currentSum > maxSum) {
maxSum = currentSum; // 当发现累加使得结果更大,更新结果
} else if (currentSum < 0) { // 当发现当前和为负,则无法使得后续新增元素子列和更大,清零
currentSum = 0;
}
}
System.out.println(maxSum);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
← 算法 数组、链表、栈和队列相关的题目 →