最大连续子数组的三种解决方案

一、暴力求解

遍历整个数组,从数组第一个元素一一和后面的元素进行相加不断扩充序列,将结果值进行比较,直到得到第一轮的最大值,在从数组第二个元素一一和后面的元素进行相加不断扩充序列和第一轮的最大值进行比较,重复执行以上步骤直到最后一个元素,这样边得到最大子数组。算法的复杂度为O(n^2).
代码实现如下:

import java.util.Arrays;
public class ForthTopicTest {
	static int start= 0, end = 0,possible_start=0;   //弄两个值来存放最大子数组的开始、结束的索引
	public static void main(String[] args) {
		int[] a = { 13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12};   //初始化一个数组
		int[] max_arr = max_arr(a);
		System.out.println(Arrays.toString(max_arr));  //输出结果。
	}

	public static int[] max_arr(int[] array) {
		/*
		 * 最大子连续数组,其中算法复杂度为O(n^2),暴力破解。
		 */
		int max_sum = Integer.MIN_VALUE;		//最大值初始化
		for (int i = 0; i < array.length; i++) {
			int sum = array[i];
			for (int j = i ; j < array.length; j++) {
				if (sum > max_sum) {
					max_sum = sum;
					possible_start=i;						//最大子数组的开始位置的索引
					end= j;						//最大子数组的结束未知的索引
				}
				if(j<array.length-1){				
				sum += array[j+1];}
			}
		}
		start=possible_start;   
		if(end==start){System.out.println("没有最大连续子数组") ; return null;}
		int ans[] = new int[end- start + 1];
		for (int s = 0; s < ans.length; s++) {
			ans[s] = array[start++];
		}
		return ans;
	}
	}

结果为:[18, 20, -7, 12]

分析:暴力求解虽然能得到结果,但是由于算法复杂度过高,对于数据量比较大的情况下,时间比较长,为了将算法复杂度降低,考虑使用归并排序来实现。

二、归并排序实现

思考:对于一个数组,将其使用归并排序来进行求解的时候,必然会被拆分成多个子数组,对于一个完整的数组,将其拆分成两个子数组的时候,最大子数组可能的位置只有三种情况:一、在左边的子数组中。二、在右边的子数组中。三、一部分在左边,一部分在右边。是左右两部分的拼接起来的一段数组。我们只能分别求出这三种情况下的最大子数组的值,对其进行比较,挑选出最大的那个值,就是最大子数组。

代码实现如下:

import java.util.Arrays; 
public class Get_max_array {
	 /*
	  * 这个归并排序方式求解最大连续子数组。
	  */
		public static void main(String[] args) {
			int[] a = { 13, -25, 20, -3, -16, 2,-1, 20, -7, 12, -5, -22, 15,  7};
			int[] s = getMax_subarray(a, 0, 13);
			System.out.println(Arrays.toString(s));
		}
	 
	
		public static int[] getMax_subarray(int[] A, int start, int end) {
			if (start == end) { // 递归到最后一步执行这步骤
				int[] result = { start, end, A[start] };
				return result;
			} else {
				int mid = (int) Math.floor((start + end) / 2); // 获取中间值
				int[] left = new int[3]; // 保存左边部分返回值
				int[] right = new int[3]; // 保存右边部分返回值
				int[] cross = new int[3]; // 返回交叉部分返回值
				left = getMax_subarray(A, start, mid);
				right = getMax_subarray(A, mid + 1, end);
				cross = getMaxCrossMid(A, start, end, mid);
				//取出最大子数组。
				if (left[2] >= right[2] && left[2] >= cross[2]) {   
					return left;
				} else if (right[2] >= left[2] && right[2] >= cross[2]) {
					return right;
				} else {
					return cross;
				}
			}
		}
	 
		public static int[] getMaxCrossMid(int[] A, int start, int end, int mid) {
			int leftSum = Integer.MIN_VALUE;
			int sum_left= 0; // 保存左边子数组的和
			int left = 0; 		// 记录子数组的左边的位置
			for (int i = mid; i >= start; i--) {
				sum_left = sum_left + A[i];
				if (sum_left > leftSum) { // 证明所加数字为正数,那么符合条件(因为最大子数组内正数越多指定越大)
					leftSum = sum_left;
					left = i;
				}
			}
	 
			int rightSum = Integer.MIN_VALUE;
			int sum_right = 0;   //保存右边子数组的和
			int right = 0; // 记录子数组的右边位置
			for (int i = mid + 1; i <= end; i++) {
				sum_right = sum_right + A[i];
				if (sum_right > rightSum) {
					rightSum = sum_right;
					right = i;
				}
			}
	 
			int[] result = new int[3];
			result[0] = left;
			result[1] = right;
			result[2] = leftSum + rightSum;
			return result;
		}		
	}

结果为:[2, 9, 27]

分析:使用归并排序的解决方案运行时,算法复杂度在O(nlogn),这个算法相比暴力求解的方式复杂度下降,从而提升了程序的效率,继续思考是否存在还有算法复杂度更低的算法?开始思考,最大子数组的特点,最大子数组一定是从大于0的数字开始,并且以大于0的数字结束。否则这个连续子数组可以去掉这个值,子数组的和可以更大。于是,在这里引出了算法复杂度为O(n)的线性时间的排序算法。

线性时间排序

思路:从第一个大于0的值开始进行最大子数组进行计算(上面的分析说了最大子数组的开始一定是大于0的值),并将这个值用个变量max_sum来保存,用于存放最大子数组的和,于是将当前位置的后面的值进行相加,来扩充子数组,如果扩充的值大于先前的最大子数组的和,将这个值的位置用另外变量right来存储.直到扩充的序列小于等于0的时候,重新开始寻找子数组,操作的过程和前面的一样,依次执行直到最后一个值就行了,这样的话,只需要将数组里面的值每个都取出来依次进行操作就可以了。因此复杂度在O(n).

代码实现如下:

import java.util.Arrays;

public class Linear_max_subarray {
	public static void main(String[] args) {
		int[] a = { 13, -3, -25, 20, -3, -16, -23, 18, -7, 12, -5, -22,-20, 15, -4, 7};
		int[] max_subarray = max_subarray(a);
		System.out.println(Arrays.toString(max_subarray));
	}
	public static int [] max_subarray(int a[]) {
            int possiable_left=0;
            int left=0;
            int right=0;
            int sum=0;
            int max_sum=0;
            int valueIndex=0;
            boolean IfCreateNewArray=true;
            int maxNum=Integer.MIN_VALUE;
            for(int i=0;i<a.length;i++) {
                sum+=a[i];
                
                if(sum<=0) {IfCreateNewArray=true;sum=0;}
                if(a[i]>=0&& IfCreateNewArray) {
                    possiable_left=i;
                    IfCreateNewArray=false;
                    
                }
                if(a[i]>maxNum) {
                    maxNum=a[i];
                    valueIndex=i;
                }
                if(sum>max_sum) {
                    left=possiable_left;
                    max_sum=sum;
                    right=i;
                    
                }
            }

            int sub_arr[]=new int [3];
           
            
            if(maxNum<=0) {
                sub_arr[0]=valueIndex;
                sub_arr[1]=valueIndex;
                sub_arr[2]=maxNum;
            } else {
                sub_arr[0]=left;
                sub_arr[1]=right;
                sub_arr[2]=max_sum;
            }
            return sub_arr;
        }
}

结果为:[7, 9, 23]

结语:以上代码均通过java个人测试,如有问题,欢迎在下面进行回复。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值