求一个最大连续子数组的和
最近在研究研究算法,打算把学习的些许东西记录下来,一来。巩固学习的东西,而来记录下来后面也方便查找。网上关于这些基础算法的文章很多,仅记录学习过程。
1、直接暴力求解,两重循环,求得结果,时间复杂度θ(n2);记得之前一次找工作的笔试题中就有这个问题,当时看到就蒙了,完全不会(算法太差,尴尬 -_-‘|),就写了一个最笨的方法,暴力求解(当然笔试结果可想而知),算法是这样的:
public static void getMaxSubArr1(SubArrElement el, int[] arr) { int currSum = 0; for(int i=0; i<arr.length; i++) { currSum = arr[i]; for(int j=i+1; j<arr.length; j++) { currSum += arr[j]; if(el.sum <= currSum) { el.start = i; el.end = j; el.sum = currSum; } } } }
2、在算法导论的第4章就讲到用分治策略求解最大连续子数组和问题,分治策略:将一个问题先分解问一些子问题,子问题的形式不发生变化,与原问题形式一致,只是规模变小了,递归求解子问题,最后将子问题的解在组合成原问题的解,该方法求解时间复杂度θ(nlgn),我写了一个如下:
/** * 通过分治法一个最大连续子数组和 * 有三种情况: * 1、最大连续子数组都在中点的左边 * 2、最大连续子数组都在中点的右边 * 3、穿过了中点,一部分在左边,一部分在右边 * @param el * @param arr */ public static void getMaxSubArr(SubArrElement el, int[] arr, int left, int right) { if(left == right) { el.start = el.end = left; el.sum = arr[left]; return ; } SubArrElement leftEl = new SubArrElement(); SubArrElement rightEl = new SubArrElement(); SubArrElement midEl = new SubArrElement(); int mid = (right + left) / 2; //在左边的情况 getMaxSubArr(leftEl, arr, left, mid); //在右边的情况 getMaxSubArr(rightEl, arr, mid+1, right); //穿过中点的情况 getMaxSubArrCrossingMid(midEl, arr, left, mid, right); //在左边 if(leftEl.sum >= midEl.sum && leftEl.sum >= rightEl.sum) { el.sum = leftEl.sum; el.start = leftEl.start; el.end = leftEl.end; return; } else if(rightEl.sum >= midEl.sum && rightEl.sum >= midEl.sum) { el.sum = rightEl.sum; el.start = rightEl.start; el.end = rightEl.end; return; } else { el.sum = midEl.sum; el.start = midEl.start; el.end = midEl.end; return; } } /** * 计算穿过中点的情况 * @param el * @param arr * @param left * @param mid * @param right */ private static void getMaxSubArrCrossingMid(SubArrElement el, int[] arr, int left, int mid, int right) { int leftSum = 0, rightSum = 0, tmpSum = 0; for(int i=mid; i>left; i--) { tmpSum += arr[i]; if(tmpSum > leftSum) { leftSum = tmpSum; el.start = i; } } tmpSum = 0; for(int i=mid+1; i<right; i++) { tmpSum += arr[i]; if(tmpSum > rightSum) { rightSum = tmpSum; el.end = i; } } el.sum = leftSum + rightSum; }
3、用线性方法求解该问题,可从左至右遍历数组并求和,以currSum记录当前A[1...i]的和,sum记录最大子数组的和,当sum比currSum时,交换并记录开始和结束为止,若currSum小于0,那么代表currSum加上第i个数反而比之前的和数都小了,此时应当重置当前的起始位置currStart、结束位置currEnd和currSum的只,最后再判断sum的直,如果为0,那么说明所有子数组和均小于等于0;此时返回数组中的最大者即可,时间复杂度仅为(n):
/** * 线性求一个最大连续子数组和 * @param el * @param arr */ public static void getMaxSubArr(SubArrElement el, int[] arr) { int currStart = 0, currEnd = 0; int currSum = 0; for(int i=0; i<arr.length; i++) { currSum += arr[i]; //当最大值小于当前已计算最大的连续字数组值,交换 if(el.sum < currSum) { el.sum = currSum; //记录当前的结束位置 currEnd = i; //并确定当前最大子数组时的起始位置 el.start = currStart; el.end = currEnd; } //当计算值小于0时,重置当前起点 if(currSum <= 0) { currSum = 0; currStart = currEnd = i+1; } } //如果遍历完数组,最大值还是为0,那么取数组中最大者返回 if(el.sum == 0) { el.sum = arr[0]; for(int j=1; j<arr.length; j++) { if(arr[j] > el.sum) { el.sum = arr[j]; } } } }
以前学东西,看书都不会做笔记,当时似乎懂了,可不多长时间,就都抛之脑后,没有任何效果;俗话说:好记性不如烂笔头(何况记性还不好~_~'),所以决定之后学习的点点滴滴,记录下来为好。