最大子数组问题

本文详细阐述了最大子数组问题的三种求解方法:暴力求解、分治法和顺序扫描法。通过代码实例,展示了每种方法的时间复杂度及核心逻辑,旨在帮助开发者高效解决最大子数组问题。

最大子数组问题就是求数组中最大的非空连续子数组。比如13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7的最大子数组就是18, 20, -7, 12。下面分别用三种方法来求解此问题。

定义全局最大子数组结构体:

//最大子数组结构
struct  MaxSubArray
{
	int max_left; //最大子数组左边界
	int max_right;//最大子数组右边界
	int max_sum;//最大子数组和
	MaxSubArray(int maxLeft, int maxRight, int maxSum)
		:max_left(maxLeft), max_right(maxRight), max_sum(maxSum){}
};

方法一:暴力求解,时间复杂度O(n2)

此方法是求出每一个子数组的和,然后取最大值,从而得出结果。

代码如下:

暴力解法,求出每个子数组的和,取最大值
MaxSubArray findMaxSubarray(int A[], int n)
{
	int max_left = 0, max_right = 0, max_sum = A[0];
	int sum = 0;
	for (int i=0; i<n; i++)//每个i开始的子数组
	{
		sum = 0; //保存子数组A[i:j-1]的和;
		for (int j=i; j<n; j++)//产生A[i:j]的子数组
		{
			if ((sum += A[j]) > max_sum)  //更新
			{
				max_sum = sum;
				max_left = i;
				max_right = j;
			}
		}
	}
	return MaxSubArray(max_left, max_right, max_sum);
}

方法二:分治求解,时间复杂度O(nlgn)

我们可以这样理解此问题,在数组A[low:mid:high]中,最大子数组要么存在于A[low:mid],要么存在于A[mid+1:high],要么跨越mid存在于A[i:mid:j],其中(low<=i<mid, mid<j<=high)。因此,只要取以上三种情况的最大值即可得出答案。

代码如下:

//求跨越数组A[low:high]中间位置mid的最大子数组
MaxSubArray findMaxCrossSubArray(int A[], int low, int mid, int high)
{
 int max_left=mid, left_sum = A[mid];
 int sum = 0;
 for (int i=mid; i>=low; i--)
 {
  if((sum+=A[i]) > left_sum)
  {
   left_sum = sum;
   max_left = i;
  }
 }

 int max_right = mid+1, right_sum = A[mid+1];
 sum = 0;
 for (int j=mid+1; j<=high; j++)
 {
  if ((sum += A[j]) > right_sum)
  {
   right_sum = sum;
   max_right = j;
  }
 }
 return MaxSubArray(max_left, max_right, left_sum+right_sum);
}

//分治法求A[low:high]的最大子数组
MaxSubArray findMaxSubarray(int A[], int low, int high)
{
 if(low>=high) //少于一个元素
  return MaxSubArray(low, low, A[low]);
 else
 {
  int mid = (high+low)/2;
  MaxSubArray left = findMaxSubarray(A, low, mid); //求左边的最大子数组
  MaxSubArray right = findMaxSubarray(A, mid+1, high); //求右边的最大子数组
  MaxSubArray cross = findMaxCrossSubArray(A, low, mid, high);//求跨越mid的最大子数组
  if(left.max_sum >= right.max_sum && left.max_sum >= cross.max_sum)
   return left;
  else if(right.max_sum >= left.max_sum && right.max_sum >= cross.max_sum)
   return right;
  else
   return cross;
 }
}

方法三:顺序扫描,时间复杂度O(n)

此方法的原理就是:从数组的左边界开始,从左至右记录到目前为止已经处理的最大子数组;已知A[1:j]的最大子数组,那么A[1:j+1]的最大子数组要么就是A[1:j]的最大子数组,要么就是A[i:j+1](1=<i<=j+1)。具体实现如下:

在数组A[1:n]中,初始时,设最大子数组的左边界max_left右边界max_right都为1,和max_sum=A[1];

sum累加i(初始为1)到j+1的和(1=<i<=n),如果sum小于0,则新的最大子数组的新左边界应从j+2开始,i置j+2,sum置为A[j+2];

如果sum大于A[1:j]的最大子数组的和max_sum,则更新最大子数组的边界,即max_left=i,max_right=j+1,同时更新max_sum=sum。

代码如下:

MaxSubArray findMaxSubarray(int A[], int n)
{
 int max_left = 0, max_right = 0, max_sum = A[0];
 int i = 0, sum = 0;
 for (int j=0; j<n; j++)
 {
  if (sum < 0)
  {
   sum = A[i];//A[max_left:i-1]之和小于0,不应再考虑,重新从i开始
   i = j;
  }
  else
   sum += A[j]; 
  if(max_sum < sum) //新的和,更新右边界
  {
   max_left = i;
   max_right = j;
   max_sum = sum;
  }
 }
 return MaxSubArray(max_left, max_right, max_sum);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值