最大子数组问题

问题基于《算法导论》第四章 分治策略中最大收益问题


问题原型:

假如你能获取股票未来的行情,怎么计算出什么时候买入,什么时候卖出才能获得最大收益。


首先分析数据后认为在最低处购买向后找到最高点,或者在最高处卖出向前找到价格最低的点。对比这两个点大小。

书中给出了反例,证明该方法并不能准确找出最大收益方案,反例如下:

天数01234
价格10117106
变化 1-43-4
该例子中并不能以之前两种猜想获取最大收益


暴力求解方案

遍历所有买入,卖出可能性进行比较。这种方案当然可行但是其运行时间为Ω(n^2),C++代码如下:

int x[10] = { 0, 2, -4, -1, 3, 2, -5, 8, -1, 3 };

void normal()
{
	int start = 0;
	int end = 0;
	float shouyi = 0.0f;
	float temp = 0.0f;
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			if (i < j)
			{
				for (int m = 0; m < j - i+1; m++)temp += x[m + i];
				if (temp > shouyi)
				{
					shouyi = temp;
					start = i;
					end = j;
				}
				temp = 0;
			}
		}
	}
	cout << "start:" << start << endl << "end:" << end << endl << "shouyi:" << shouyi << endl;
}


可以输出结果

start=7

end=9

shouyi=10

这种方案当然不是最优解。


最大子数组方案

不再考虑输入数组,而是关心价格变化,此时,只需要找到一个起始为i终止为j的子数组,其和最大即可。
但如果任然用简单的循环方式也并不能简化运算,此时可以将数组拆分为两段。
原数组x
02-4-132-58-13

子数组1
02-4-13
start---mid
子数组2
2-58-13
mid--end

此时有三种可能:
1.最大子数组在子数组1范围内:start<=i<j<=mid
2.最大子数组在子数组2范围内:mid<=i<j<=end
3.最大子数组需要穿过子数组1,2:start<=i<=mid<=j<=end

在例子中 第一种情况需要循环25次
void findleft(int x[],int mid,int length)//在左边子数组
{
	int start = 0;
	int end = 0;
	int temp = 0;
	int shouyi = 0;

	for (int i = 0; i < mid; i++)
	{
		for (int j = 0; j < mid; j++)
		{
			if (i < j)
			{ 
				for (int m = 0; m < j - i+1; m++)temp += x[i + m];
				if (temp > shouyi)
				{
					shouyi = temp;
					start = i;
					end = j;
				}
				temp = 0;
			}
		}
	}
	cout << "left" << endl;
	cout << "start:" << start << endl << "end:" << end << endl << "shouyi:" << shouyi << endl;
}
寻找右半段子数组也需要25次:
void findright(int x[],int mid,int length)//在右边子数组
{
	int start = 0;//起始数组下标
	int end = 0;//终止数组下标
	int temp = 0;//临时存储收益值
	int shouyi = 0;//最大收益值

	for (int i = mid; i < length; i++)
	{
		for (int j = mid; j < length; j++)
		{
			if (i < j)
			{
				for (int m = 0; m < j - i+1; m++)temp += x[i + m];
				if (temp > shouyi)
				{
					shouyi = temp;
					start = i;
					end = j;
				}
				temp = 0;
			}
		}
	}
	cout << "right" << endl;
	cout << "start:" << start << endl << "end:" << end << endl << "shouyi:" << shouyi << endl;
}

对于第三种情况因为多了必须穿过中间点的限制,所以也需要25次循环
void findcross(int x[],int mid,int length)//越过中心点
{
	int start = 0;//起始数组下标
	int end = 0;//终止数组下标
	int temp=0;//临时存储收益值
	int shouyi = 0;//最大收益值

	for (int i = 0; i < mid; i++)
	{
		for (int j = mid; j < length; j++)
		{
			for (int m = 0; m < j - i+1; m++)
				temp += x[i + m];
			if (temp > shouyi)
			{
				shouyi = temp;
				start = i;
				end = j;
			}
			temp = 0;
		}
	}
	cout << "cross" << endl;
	cout << "start:" << start << endl << "end:" << end << endl << "shouyi:" << shouyi << endl;
}



总循环数为75(3/4*n^2),而暴力破解需要100(n^2)


最大子数组是连续的若干数组元素,若其和是最大的,那么这个子数组就称为该数组的最大子数组,它是很多问题的抽象,比如购买股票,把相邻两天的股价之差作为数组元素,求在连续的某个时间段内买入股票的最佳时间和卖出股票的最佳时间就可以抽象为计算最大子数组问题 [^3]。 求解最大子数组问题有分治法和贪心算法(这里结合动态规划思想的变种贪心算法,即Kadane算法)等方法: - **分治法**:将数组 `X[1…n]` 分为 `X[1…n/2]` 和 `X[n/2 +1…n]`,递归求解子问题,包括数组 `X[1…n/2]` 的最大子数组 `S1`、数组 `X[n/2+1…n]` 的最大子数组 `S2`,然后计算跨中点的最大子数组 `S3`,数组 `X` 的最大子数组之和 `Smax = max{S1, S2, S3}`,时间复杂度为 $O(nlogn)$ [^5]。 ```python def find_max_crossing_subarray(arr, low, mid, high): left_sum = float('-inf') sum_val = 0 max_left = mid for i in range(mid, low - 1, -1): sum_val += arr[i] if sum_val > left_sum: left_sum = sum_val max_left = i right_sum = float('-inf') sum_val = 0 max_right = mid + 1 for j in range(mid + 1, high + 1): sum_val += arr[j] if sum_val > right_sum: right_sum = sum_val max_right = j return max_left, max_right, left_sum + right_sum def find_max_subarray(arr, low, high): if high == low: return low, high, arr[low] else: mid = (low + high) // 2 left_low, left_high, left_sum = find_max_subarray(arr, low, mid) right_low, right_high, right_sum = find_max_subarray(arr, mid + 1, high) cross_low, cross_high, cross_sum = find_max_crossing_subarray(arr, low, mid, high) if left_sum >= right_sum and left_sum >= cross_sum: return left_low, left_high, left_sum elif right_sum >= left_sum and right_sum >= cross_sum: return right_low, right_high, right_sum else: return cross_low, cross_high, cross_sum arr = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7] low, high, max_sum = find_max_subarray(arr, 0, len(arr) - 1) print(f"最大子数组的起始下标: {low}, 结束下标: {high}, 最大和: {max_sum}") ``` - **Kadane算法**:从数组左边界开始,由左到右处理,记录到目前为止已处理过的最大子数组。若已知 `A[1...j]` 的最大子数组,`A[1...j+1]` 的最大子数组要么是 `A[1...j]` 的最大子数组,要么是某个子数组 `A[i...j+1]` 的最大子数组(`1 <= i <= j+1`),在已知 `A[1...j]` 的最大子数组的情况下,可以在线性时间内找出形如 `A[i...j+1]` 的最大子数组,时间复杂度为 $O(n)$ [^1]。 ```python def max_subarray_sum(arr): max_ending_here = max_so_far = arr[0] for num in arr[1:]: max_ending_here = max(num, max_ending_here + num) max_so_far = max(max_so_far, max_ending_here) return max_so_far arr = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7] print(f"最大子数组和: {max_subarray_sum(arr)}") ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值