问题描述
求数组中连续和最大的值,例如数组:{1, 2, 3, 4, -5, 6, 7, 8 , 9 ,-10 ,-12}
/**
* 求数组中连续和中最大的值
*/
public class Demo {
public static void main(String[] args) {
int[] arr = {
1, 2, 3, 4, -5, 6, 7, 8 , 9 ,-10 ,-12};
}
}
方式一:
计算出所有可能的情况,留下最大值
实现思路:将数组拆分出所有可能的子列,使用三层循环的方式去求每一个子列中连续和的值,第一层循环确定子列起始的位置【i】,第二层确定子列终止的位置【j】,第三层循环就是计算子列【i ~ j】的和,如果当前子列和大于之前的子列和,则更新最大值。
public static int maxSubseqSum1(int[] arr) {
int n = arr.length;
int maxSum = 0; // 定义最大值为0
for (int i = 0; i < n; i++) {
// i 是子列左端位置
for (int j = i; j < n; j++) {
// j 是子列右端位置
int thisSum = 0; // thisSum 是从 arr[i] 到 arr[j] 的子列和
for (int k = i; k <= j; k++) {
thisSum += arr[k];
}
if (thisSum > maxSum) {
// 如果得到该子列和更大,则更新结果
maxSum = thisSum;
}
}
}
return maxSum;
}
这个算法的复杂度是 T(N)=O(N^3)
,因为有三层嵌套的 for 循环,如果每一层循环都是从 0~N ,那么乘起来就是 N 的三次方
方式二:
方式一使用三层嵌套循环求每一个子列的和,其实第三个 for 循环可以去除掉,这个循环做的事就是将 i ~ j 中间的数字求和,比如你第二个循环 j = 100,j++ 之后 j =101,那么你计算 i ~ j 之和的时候是将 i ~ 101 的数据重新求和方便还是在 i ~ 100 和 的基础上加上 arr[101] 的值方便点?对于相同的 i 不同的 j 只需要在前一项的基础上多加一个 arr[j] 的值就可以了。
实现思路:将 【i ~ j】求和的值放在第一个循环下,第二个循环求子列的和,如果得到该子列和更大,则更新结果。
public static int maxSubseqSum2(int[] arr) {
int n = arr.length;
int maxSum = 0;
for (int i = 0; i < n; i++) {
// i 是子列左端位置
int thisSum = 0; // thisSum 是从 arr[i] 到 arr[j] 的子列和
for (int j = i; j < n; j++) {
// j 是子列右端位置
thisSum += arr[j];
// 对于相同的 i,不同的 j,只要 j-1 次循环的基础上累加 1 项即可
if (thisSum > maxSum) {
// 如果得到该子列和更大,则更新结果
maxSum = thisSum;
}
}
}
return maxSum;
}
这个算法的复杂度是 T(N)=O(N^2)
,假如 N =100000 时,该算法就已经比算法一效率高了十万倍。
方式三:
采用分而治之(二分法)的思想去求解。
分而治之:分而治之的思想可以用于解决很多问题,大概的思路就是把一个比较大的复杂的问题切分成小的块,然后分头去解决他们,最后再把结果合并起来,就是“分而治之”。
实现思路:这个算法第一步就是先“分”,也就是把这个数组从中间一分为二,然后递归地去解决左右两边的问题,递归的去解决左边的问题,我们会得到左边的一个最大子列和,反之亦然,跨越边界的最大子列和。
public static int maxSubseqSum3(int[] arr) {
return divideAndConquer(arr,0,arr.length-1);
}
/**
* 返回 3 个整数中的最大值
*/
public static int max(int a,int b,int c) {
return a>b?(Math.max(a, c)):(Math.max(b, c));
}
/**
* 分治法求 List[left] 到 List[right] 的最大子列和
*/
public static int divideAndConquer(int[] arr,int left, int right) {
int maxLeftSum,maxRightSum; //存放左右子问题的解
int maxLeftBorderSum,maxRightBorderSum; //存放跨分界线的结果
int leftBorderSum,rightBorderSum;
int center,i;
// 递归的终止条件,子列只有1个数字
if (left==right) {
//如果大于0则返回 arr[left] 否则返回 0
return Math.max(arr[left], 0);
}
//下面是 “分” 的过程
center = (left+right)/2;
//找到中分点
//递归求得两边子列的最大和
maxLeftSum = divideAndConquer(arr,left,center);
maxRightSum = divideAndConquer(arr,center+1,right);
//下面求跨分界线的最大和
maxLeftBorderSum = 0;
leftBorderSum = 0;
for (i=center;i>=left;i--) {
//从中线向左扫描
leftBorderSum += arr[i];
if (leftBorderSum>maxLeftBorderSum) {
maxLeftBorderSum = leftBorderSum;
}
}
maxRightBorderSum = 0;
rightBorderSum = 0;
for (i=center+1;i<=right;i++) {
//从中线向右边扫描
rightBorderSum += arr[i];
if (rightBorderSum>maxRightBorderSum) {
maxRightBorderSum = rightBorderSum;
}
}
//获取治的结果
return max(maxLeftSum,maxRightSum,maxLeftBorderSum+maxRightBorderSum);
}
这个算法的复杂度是 T(N)=O(NlogN)
。
方式四:
在线处理,“在线” 的意思是指每输入一个数据就进行及时处理,在任何一个地方终止输入,算法都能正确给出当前的解。
实现思路:一个集合中的元素累加,如果当前子列和为负,则不可能使后面的部分和增大,抛弃之
public static int maxSubseqSum4(int[] arr) {
int n = arr.length;
int maxSum = 0;
int thisSum = 0;
for (int i = 0; i < n; i++) {
// i 是子列左端位置
thisSum += arr[i]; // 向右累加
if ( thisSum > maxSum) {
maxSum = thisSum; // 发现更大的结果则更新当前结果
} else if (thisSum < 0 ) {
// 如果当前子列和为负
thisSum = 0; // 则不可能使后面的部分和增大,抛弃之
}
}
return maxSum;
}
这个算法的复杂度是 T(N)=O(N)