楼主这篇文章的目的是要带大家梳理一下,有关于求子数组问题。如求子数组的最大和,求最大和的子数组,求最大积的子数组等一系列问题。今天阳光明媚,楼主今天心情很好哦,愿大家开心每一天,哈哈。Are you ready?开始了哦~~~
题目一:求子数组的最大和
题目求子数组的最大和,这里需要注意的一个问题就是,子数组那么便意味着是连续的一段数据。我们可以先写的例子,方便我们注意到要考虑的一些问题。
数组:[1, -2, 3, 5, -3, 2] 应该返回8,最大和的子数组为3,5
数组:[0, -2, 3, 5, -1, 2] 应该返回9,最大和的子数组为3, 5,-1,2
数组:[-1, -2, -3, -4, -5]应该返回-1,最大和的子数组为-1
这里,我们只介绍一种时间复杂度为O(N)的算法来求解。
解法一:分治法
将所给数组(A[0],A[1],...,A[N?1])分成两组,(A[0],A[1],...,A[N2?1])和(A[N2],A[N2+1],...,A[N?1]),分别求出这两段数组各自的最大子段和,则原数组的子数组的最大和可以分为下面三种情况:
- 和(A[0],A[1],...,A[N2?1])相同
- 和(A[N2],A[N2+1],...,A[N?1])相同
- 跨过中间两个元素A[N2?1])和A[N2])。
看下面代码:
#include <iostream>
#include <limits>
using namespace std;
int getMAX3(int a, int b, int c);
int getBiggestSubSum(int *pArray, int low, int high);
int main()
{
int a1[] = {1, -2, 3, 5, -3, 2};
int a2[] = {1, -2, 3, 5, -1, 2};
int a3[] = {-1, -2, -3, -5, -3, -2};
cout << "数组1的子数子的最大和为" << getBiggestSubSum(a1, 0, sizeof(a1) / sizeof(int) - 1) << endl;
cout << "数组2的子数子的最大和为" << getBiggestSubSum(a2, 0, sizeof(a2) / sizeof(int) - 1) << endl;
cout << "数组3的子数子的最大和为" << getBiggestSubSum(a3, 0, sizeof(a3) / sizeof(int) - 1) << endl;
system("pause");
}
int getBiggestSubSum(int *pArray, int low, int high)
{
int middle = (low + high) >> 1; //获取中间节点的下标
int MAX = numeric_limits<int>::min();
if (high == low)
{
//递归到只剩下一个数之后,那么直接返回
return pArray[low];
}
int lMax = numeric_limits<int>::min();
int rMax = numeric_limits<int>::min();
int mMax = numeric_limits<int>::min();
int mMaxTemp = 0;
//求左半部分的最大和
lMax = getBiggestSubSum(pArray, low, middle);
//求右半部分的最大和
rMax = getBiggestSubSum(pArray, middle + 1, high);
//求横跨中间的最大和
for (int i = middle; i <= high; i++)
{
mMaxTemp += pArray[i];
mMax = max(mMax, mMaxTemp);
}
for (int i = middle - 1; i >= 0; i--)
{
mMaxTemp += pArray[i];
mMax = max(mMax, mMaxTemp);
}
//返回三大部分最大和之间的最大值
return getMAX3(rMax, mMax, lMax);
}
int getMAX3(int a, int b, int c)
{
int max = a;
max = (b > max) ? (b):(max);
max = (c > max) ? (c):(max);
return max;
}
算法复杂度:O(NlogN)。
解法二:动态规划法
我们可以考虑数组的第一个元素A[0],以及最大的一段数组(A[i],...,A[j])跟$A[0]的关系,有以下几种情况:
- i = 0 = j,这便意味着,最大和的子数组只由一个A[0]组成;
- 0 = i < j,这便意味着,最大和的子数组是从A[0]开始的一串序列组成;
- 0 < i < j,这便意味着,最大和的子数组和A[0]无关,不是从A[0]开始的。
从上面三种情况可以看出来,我们可以将一个大问题,转化成一个较小的问题。如本题中,我们可以将一个N维数组的子数组最大和问题,转化成N-1维的子数组最大和问题。假设已经知道(A[1],...,A[N?1])子数组的最大和为All[1],还知道包含A[1]的子数组的最大和为Start[1]。那么根据上面的分析可知
All[0]=max{A[0],A[0]+Start[0],All[1]}
上述公式中,第一项表示只有一个A[0]元素构成最大和子数组,第二项表示A[0]和从A[1]开始的序列构成最大和子数组,第三项表示,最大和子数组和A[0]无关。看下面代码:
#include <iostream>
using namespace std;
int getBiggestSubSum(int *pArray, int len);
int main()
{
int a1[] = {1, -2, 3, 5, -3, 2};
int a2[] = {1, -2, 3, 5, -1, 2};
int a3[] = {-1, -2, -3, -5, -3, -2};
cout << "数组1的子数子的最大和为" << getBiggestSubSum(a1, sizeof(a1) / sizeof(int)) << endl;
cout << "数组2的子数子的最大和为" << getBiggestSubSum(a2, sizeof(a2) / sizeof(int)) << endl;
cout << "数组3的子数子的最大和为" << getBiggestSubSum(a3, sizeof(a3) / sizeof(int)) << endl;
system("pause");
}
int getBiggestSubSum(int *pArray, int len)
{
//从数组的最后一个元素开始,当只有一个元素的时候,那么start和all当然都是等于这个数啦
int nStart = pArray[len - 1];
int nAll = pArray[len - 1];
for (int i = len - 2; i >= 0; i--)
{
nStart = max(pArray[i], nStart + pArray[i]);
nAll = max(nAll, nStart);
}
return nAll;
}
运行结果:
数组1的子数子的最大和为8
数组2的子数子的最大和为9
数组3的子数子的最大和为-1
请按任意键继续. . .
算法复杂度:O(NlogN)
题目二:求子数组的最大和并输出相应的子数组
题目一只管求出子数组的最大和,不管子数组是什么。而这个题目中,我们需要求解子数组序列的起始位置和结束位置。其实和上面差不多,只是需要记录位置而已,看代码,在题目一的解法二上面修改的。
#include <iostream>
using namespace std;
int getBiggestSubSum(int *pArray, int len, int *startIndex, int *endIndex);
void printMaxSubArray(int *pArray, int start, int end);
int main()
{
int a1[] = {1, -2, 3, 5, -3, 2};
int a2[] = {1, -2, 3, 5, -1, 2};
int a3[] = {-1, -2, -3, -5, -3, -2};
int start = 0;
int end = 0;
cout << "数组1的子数子的最大和为" << getBiggestSubSum(a1, sizeof(a1) / sizeof(int), &start, &end) << endl;
cout << "和最大的子序列为" << endl;
printMaxSubArray(a1, start, end);
cout << "数组2的子数子的最大和为" << getBiggestSubSum(a2, sizeof(a2) / sizeof(int), &start, &end) << endl;
cout << "和最大的子序列为" << endl;
printMaxSubArray(a2, start, end);
cout << "数组3的子数子的最大和为" << getBiggestSubSum(a3, sizeof(a3) / sizeof(int), &start, &end) << endl;
cout << "和最大的子序列为" << endl;
printMaxSubArray(a3, start, end);
system("pause");
}
int getBiggestSubSum(int *pArray, int len, int *startIndex, int *endIndex)
{
//从数组的最后一个元素开始,当只有一个元素的时候,那么start和all当然都是等于这个数啦
int nStart = pArray[len - 1];
int nAll = pArray[len - 1];
*startIndex = len - 1;
*endIndex = len - 1;
for (int i = len - 2; i >= 0; i--)
{
if (pArray[i] > nStart + pArray[i])
{
nStart = pArray[i];
if (nStart > nAll)
{
//如果仅仅包含pArray[i]的子序列获得最大和的话,那么startIndex和endIndex和nAll都需要更新
*startIndex = i;
*endIndex = i;
nAll = pArray[i];
}
else
{
//还是原来的子序列和最大,那startIndex和endIndex都不变了
}
}
else
{
nStart = nStart + pArray[i];
if (nStart > nAll)
{
//如果包含a[i],a[i+1],...a[j]获得最大的和的话,那么要更新startIndex和nAll
nAll = nStart;
*startIndex = i;
}
else
{
//还是原来的子序列和最大,那startIndex和endIndex都不变了
}
}
}
return nAll;
}
void printMaxSubArray(int *pArray, int start, int end)
{
for (int i = start; i <= end; i++)
{
cout << pArray[i] << " ";
}
cout << endl;
}
时间复杂度同样是O(N)。
题目三:求子数组的最大积
这里必须要重述一下题目,和题目一以及题目二是不一样的!!!!
给定一个长度为N的整数数组,只允许用乘法,不能用除法,计算任意N-1个数的组合乘积中的最大的一组,并写出算法的时间复杂度。
这里的子数组,是任意N-1个数的组合,并不一定是连续的N-1的子序列。
解法一
其实这道题,腾讯某年笔试题的加试题就有这道题,感兴趣的同学可以看这个链接2012年腾讯实习生笔试附加题
题目简介:已知数组a[n],求数组b[n].要求:b[i]=a[0]a[1]……*a[n-1]/a[i],不能用除法。a.时间复杂度O(n),空间复杂度O(1)。 b.除了迭代器i,不允许使用任何其它变量(包括栈临时变量等),看,是不是差不多?
这里我们同样可以使用前累乘后累乘的办法把所有的b[i]求出来,然后遍历一遍,求出最大的积。看代码:
#include <iostream>
using namespace std;
void getBiggestMultipleResults(int *pArray, int *pResultsArray, int len);
int main()
{
int a[] = {1, 2, 3, 4};
int len = sizeof(a) / sizeof(int);
int *b = new int[len]();
int result = b[0];
//求出所有的b[i]
getBiggestMultipleResults(a, b, len);
for (int i = 0; i < len; i++)
{
result = max(b[i], result);
}
cout << result << endl;
delete []b;
system("pause");
}
void getBiggestMultipleResults(int *pArray, int *pResultsArray, int len)
{
pResultsArray[0] = 1;
//前累乘
for (int i = 1; i < len; i++)
{
pResultsArray[i] = pResultsArray[i - 1] * pArray[i - 1];
}
//后累乘
for (int i = len - 2; i > 0; i--)
{
//把pResultsArray[0]作为临时变量
pResultsArray[0] *= pArray[i + 1];
pResultsArray[i] *= pResultsArray[0];
}
pResultsArray[0] *= pArray[1];
}
解法二:数学分析法
看完这个数学分析的方法,不禁感叹,还是要数学好啊!不信,你看吧。
假设N个证书的乘积为P, 针对P的正负性进行分析:
1. P=0
那么,数组中至少有1个0。除去这个0之外,其他N-1个数的乘积为Q,根据Q的正负性进行分析:
- Q = 0
结果返回0.
分析:Q=0说明Q数组里面还有1个0,加上前面P数组中的1个0,我们可以断定,任意N-1个数的乘积只能为0,那么结果返回0.
- Q > 0
结果返回Q
分析:如果以0替换此时Q数组里面任意一个数的话,那么结果都是0,所以最后结果为Q
- Q < 0
结果返回0
分析:如果以0替换此时Q数组里面的任意一个,则结果为0,0比负数大,所以结果返回0
2. P > 0
从数组中去除一个绝对值最小的正整数,这样得到剩下的PN?1就是最大的。
3. P< 0
从数组中去除一个绝对值最小的负整数,剩下的PN?1就是最大的。
这样的解法,你服不服?反正我服了。
还有一些题目没有时间更新了,因为要汇报啊。预告一下,这个还会有更新那个什么,“最大递增子序列”,“最大的group个数”,后续会更新上来。今天就到这儿位置喽,祝大家晚上愉快,good night哦。(~ o ~)~zZ