最大子数组问题

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

问题原型:

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


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

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

天数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)

点赞