算法时空-最大子数组问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rebornyp/article/details/86014726

最大子数组问题

给定数组,求算其中的最大子数组,要求返回最后的最大子数组的左下标 l 和右下标 r ,以及最大和 s ;


  • 思路1:暴力解决,O(n^2)复杂度,这里具体就不用代码实现;
  • 思路2:使用分治法,通过假定最大子数组处于左边,中间和右边,来最终得出准确的结果;
  • 思路3:使用动态规划算法,最常用的高效算法;

1. 分治法


int main()  
{  
	int a[] = {2,4,-7,5,2,-1,2,-4,3};
	int l, r, sum;
	l = 0, r = 8;
	sum = findMaxSubarray(a, l, r);
	printf("左边元素是a[%d]=%d, 右边元素是a[%d]=%d, 最大和sum=%d\n", l, a[l], r, a[r], sum);
	return 0;  
}  

/*
求算最大子数组,传入数组a指针,保存a最左边下标的l地址,最右边下标的r地址;
*/
int findMaxSubarray(int *a, int &l, int&r) {
	if(l == r) return a[l];
	int mid = l + (r - l) / 2;

	int left_min = l, left_max = mid; // 因为l和r都得中间存值,所以每次递归时候,需要用新的变量去存储;
	int left_num = findMaxSubarray(a, left_min, left_max); // 最大子数组仅存在左半边时;

	int right_min = mid+1, right_max = r; //同上;
	int right_num = findMaxSubarray(a, right_min, right_max);

	int cross_num = findMaxFromMid(a, l, r); // 计算,当最大子数组贯穿中间mid元素时;


	if(left_num >= right_num && left_num >= cross_num) {
		l = left_min;
		r = left_max;
		return left_num;
	}
	else if(right_num >= left_num && right_num >= cross_num) {
		l = right_min;
		r = right_max;
		return right_num;
	}
	else return cross_num;
}


/* 对于数组a[],传入最左边下标l,最右边下标r,计算返回子数组中最大和;
* 但是,这里默认是该子数组计算中,必须包含mid元素!
*/
int findMaxFromMid(int *a, int& l, int& r) {
	if(l == r) return a[l];
	int mid = l + (r - l) / 2;
	int sum = a[mid], left_sum = a[mid], left_index = mid; //sum不从0开始取值,使得left_sum数值不必取负无穷;
	for(int i=mid-1; i>=l; i--) {
		sum += a[i];
		if(sum > left_sum) {
			left_sum = sum; //找到最大和;
			left_index = i; //找到此时的下界;
		}
	}

	sum = a[mid];
	int right_sum = a[mid], right_index = mid;
	for(int i=mid+1; i<=r; i++) {
		sum += a[i];
		if(sum > right_sum) {
			right_sum = sum;
			right_index = i;
		}
	}

	l = left_index; //将求算得到的下界赋给全局变量;下同;
	r = right_index;
	return left_sum + right_sum - a[mid];; //a[mid]计算了两次,故返回最大和时候要减去一个;		
}
  • 结果展示:
    输入数组:[2,4,-7,5,2,-1,2,-4,3];
    输出最大子数组:
    在这里插入图片描述

  • 算法总结:
    时间复杂度T(n) = 2T(n/2)+O(n);根据主定理,最终运行时间复杂度为O(nlog(n));


2. 动态规划

int main() {	
	int a[] = {2,4,-7,5,2,-1,2,-4,3};
	int l = 0, r = 8;
	int sum = findMaxSubarray(a, l, r);
	printf("左边元素是a[%d]=%d, 右边元素是a[%d]=%d, 最大和sum=%d\n", l, a[l], r, a[r], sum);
}

/*
求算最大子数组,传入数组a指针,保存a最左边下标的l地址,最右边下标的r地址;
*/
int findMaxSubarray(int *a, int &l, int &r) {
	if(l == r) return a[l];
	int cur = a[l], total = a[l]; //cur 为当前最大和值,total为全局最大和值
	int left_index = l, right_index = l; //动态记录左右下标
	for(int i=l+1; i<=r; i++) { // O(n)级别
		// 其实下面这一句代码本来应该为 if(cur + a[i] < a[i]),当前最大和值指的是**必须**包含当前a[i]元素
		if(cur < 0) { //那么如果之前的cur拖累的当前a[i],那么久丢掉a[i-1]元素
			cur = a[i];
			left_index = i; // 记录左下标
		}
		else cur += a[i]; // 否则加上当前元素
		if(cur > total) {
			total = cur;
			right_index = i; // 记录右下标
		}
	}
	l = left_index;
	r = right_index;
	return total;
}
  • 算法总结:
    此算法的时间复杂度为O(n)级别,通过找局部最大,然后求全局最大的思路,最终得出结论;

猜你喜欢

转载自blog.csdn.net/rebornyp/article/details/86014726