问题描述:
1、求最小子序列和
2、求最小正子序列和
3、求最大子序列乘积
分析:
1、求最小子序列和其实和求最大子序列和是一样一样的。
最高效的O(N)的时间复杂度算法思想是:
假设有N个元素的数组,最大子序列和为从i到j。那么想想【i,j】是否可以继续向左右扩展呢?如果不能,制约条件是什么?对于最大子序列而言,如果可以向左扩展到i’,即[i’,i],那因为[i,j]位最大子序列,所以[i’,i]的序列和一定为负,而i’事实上可以扩展至0。
这样就产生了该算法:从前向后遍历,每次更新sum,如果当前sum>maxsum,更新sum,如果sum<0,那么表示前面的这些数据实际上是不可以被后面的数据扩展的。此时全部丢弃,使sum=0,继续遍历。
代码如下:
//求最大子序列和
int getMaxSumSubSequence(int *nums) {
int maxSum = 0;
int tmpSum = 0;
for (int i = 0; i < SIZE; i++) {
tmpSum += nums[i];
if (tmpSum > maxSum)
maxSum = tmpSum;
if (tmpSum < 0)
tmpSum = 0;
}
return maxSum;
}
//求最小子序列和
int getMinSumSubSequence(int *nums) {
int minSum = nums[0];
int tmpSum = 0;
for (int i = 0; i < SIZE; i++) {
tmpSum += nums[i];
if (tmpSum < minSum)
minSum = tmpSum;
if (tmpSum > 0)
tmpSum = 0;
}
return minSum;
}
2、求最小正子序列和
首先实际问的是子序列的和为正的最小值,而并非序列中的每一个元素为正(事实上,这样就太简单了,直接找最小的正元素即可)。
这个问题,我只能想到O(n2)的解法,即遍历所有的序列,然后将大于0的元素里面找到最小的那个。
//求最小正子序列和
int getPositiveMinSumSubSequence(int *nums) {
int minSum = INT_MAX;
int tmpSum = 0;
for (int i = 0; i < SIZE; i++) {
tmpSum = 0;
for (int j = i; j < SIZE; j++) {
tmpSum += nums[j];
if (tmpSum < 0)
continue;
else if (tmpSum < minSum)
minSum = tmpSum;
}
}
return minSum;
}
这道题实际上是有O(nlogn)的解法。思想是,并非所有的子序列都是要求的,因为要求是找到正子序列。如何保证得到所有的正子序列呢?
有种做法是:维护一个sum数组,sum[i] = 元素0到i的累加。
然后根据sum进行从小到大排序,排好序后,如果前面元素的索引小于后面元素的索引,那么之间的差就是一个候选子序列,而且只需要和相邻元素比较即可。因为更远的元素的差会更大一些。
但是但是。。。如果直接这样进行的话会有bug,因为有些地方没考虑到。比如如果直接从0到该元素的序列为要找的子序列,也即sum数组中值为正数,且小于minsum的值。
那为了与代码风格统一,可以强行加入一个元素,值为0,索引为-1。这样就将在该值之后的元素(大于0的)考虑到了。而且该元素以前的元素是无法借助该元素的,因为该元素的下标为-1。
代码如下:
struct Node {
int val;
int index;
};
int cmp(const void* node1,const void* node2){
struct Node inNode1 = *(struct Node*)node1;
struct Node inNode2 = *(struct Node*)node2;
return inNode1.val - inNode2.val;
}
int getPositiveMinSumSubSequence2(int* nums) {
struct Node nodes[SIZE+1];
int total = 0;
int minsum = INT_MAX;
nodes[0].val = 0;
nodes[0].index = -1;
for (int i = 0; i < SIZE; i++) {
total += nums[i];
nodes[i+1].val = total;
nodes[i+1].index = i;
}
qsort(nodes, SIZE+1, sizeof(struct Node), cmp);
for (int i = 0; i < SIZE; i++) {
struct Node now = nodes[i];
struct Node next = nodes[i + 1];
if (now.index < next.index) {//只有当前节点的下标小于下一个节点的下标才有意义
int val = next.val - now.val;
if (val < minsum)
minsum = val;
}
}
return minsum;
}
3、求最大子序列乘积
乘积运算与加减运算不同,乘积运算需要考虑正负,事实上,对于当前点而言,最大值可以由两个值产生,一个是最大值与该值相乘,一个是最小值与该值相乘。所以就意味着要同时维护着到当前值的最大值和最小值。
对于某一点而言,最大值和最小值分别有三个来源,一个是之前的最大值与该点的乘积,一个是之前的最小值与该点的乘积,一个就是当前点。
代码如下:
//求最大子序列乘积
int getMaxMultiSubSequence(int *nums) {
int result = 0;
int tmpMax = nums[0];
int tmpMin = nums[0];
for (int i = 1; i < SIZE; i++) {
int now1 = tmpMax*nums[i];
int now2 = tmpMin*nums[i];
tmpMax = std::max(std::max(now1, now2), nums[i]);
tmpMin = std::min(std::min(now1, now2), nums[i]);
if (tmpMax > result)
result = tmpMax;
}
return result;
}