数据结构与算法分析第二章12题

问题描述:
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值