编程之美4:那些常被考到的关于数组的最大子数组问题

楼主这篇文章的目的是要带大家梳理一下,有关于求子数组问题。如求子数组的最大和,求最大和的子数组,求最大积的子数组等一系列问题。今天阳光明媚,楼主今天心情很好哦,愿大家开心每一天,哈哈。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

时间: 2024-08-06 00:55:46

编程之美4:那些常被考到的关于数组的最大子数组问题的相关文章

编程之美2.14 求数组的子数组之和的最大值

问题描述: 一个有N个整数元素的一维数组(A[0], A[1], A[2],...,A[n-1]),这个数组当然有很多子数组,那么子数组之和的最大值是什么呢? 解法: 1. 暴力解法-------O(N^3) 2. 改进版暴力解法-------O(N^2) *3. 分治算法-------O(NlogN)(暂时未去实现) 4. 数组间关系法-------O(N) 具体思路和代码: 1.暴力解法 思路:Sum[i,...,j]为数组第i个元素到第j个元素的和,遍历所有可能的Sum[i,...,j].

编程之美2.17 数组循环移位

问题描述: 设计一个算法,把一个含有N元素的数组循环左移或者右移K位. 解决方法: 1. 暴力解法------O(KN) 2. 颠倒位置------O(N) 具体思路和代码: 1. 暴力解法------O(KN) 思路:循环K次,每次移动一位 代码: 1 //右移 2 void s1(int A[], int n, int k) 3 { 4 k = k % n; 5 for(int i = 0; i < k; i++) 6 { 7 int t = A[n-1]; 8 for(int j = n-

编程之美leetcode之编辑距离

Edit Distance Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.) You have the following 3 operations permitted on a word: a) Insert a character b) Delete a char

编程之美2.17之数组循环移位

题目描述:设计一个算法,把一个含有N个元素的数组循环右移K位,要求算法的时间复杂度位O(Log2N),且只允许使用两个附加变量. 什么意思呢,就是说如果输入序列为:abcd1234,右移2位即变为34abcd12.唯一的要求就是使用两个附加变量. 其实这道题编程珠玑上面也出现过,书中给出的一种符合题意的解法是巧妙地进行翻转.以把abcd1234右移4位为例: 第一步:翻转1234,abcd1234---->abcd4321 第二步:翻转abcd,abcd4321---->dcba4321 第三

编程之美2.3: 寻找发帖水王

题目:传说,Tango有一大"水王",他不但喜欢发帖,还会回复其他ID发的帖子,发帖数目超过帖子总数的一半,如果你有一个当前论坛上所有帖子的列表,其中帖子作者的ID也在表中,你能快速找到这个传说中的Tango水王吗? 解题思路:由于水王的发帖数目超过一半,当每次删除两个不同ID的帖子时,水王占得帖子数目仍然大于剩下帖子的一半,重复整个过程,将ID列表中的ID总数降低,转化为更小的问题,从而得到最后水王的ID. #include <iostream> #include <

编程之美2.13 子数组最大乘积

问题描述: 给定一个长度为N的整数数组,只允许用乘法,不能用除法,计算任意(N-1)个数的组合乘积中最大的一组,并写出算法的时间复杂度. 解法: 1.暴力解法------O(n^2) 2.前后缀法------O(n) 3.统计法--------O(n) 具体思路和代码: 1.暴力解法: 思路:利用两层循环,依次删掉一个,其余的做乘法,计算出最大的. 代码: 1 int s1(int A[], int n) 2 { 3 int s = 1; 4 int max; 5 for(int i = 1;

编程之美2.1 求二进制中1的个数

最近一段的时间,一直在看编程之美之类的算法书籍,刚开始看编程之美,感觉到难度太大,有时候也不愿意去翻动这本书,不过,经过一段时间的修炼,我也彻底的喜欢上这本书了, 书中的算法涉及到很多方面,树,链表,位运算,数组,hash表应用等等. 由于最近事情也忙得差不多了,我重新写了一遍编程之美中的算法,在这里记录下来,以便以后阅读方便. 第一道题从2.1写起,这道题目难度不是很大,首先,给出这个题目的函数声明: /*2.1 求二进制中1的个数*/ int DutCountOf1InBin_1(unsig

Java 编程之美:并发极速赛车平台出租编程高级篇

借用 Java 并发极速赛车平台出租haozbbs.comQ1446595067 编程实践中的话:编写正确的程序并不容易,而编写正常的并发程序就更难了. 相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的. 并发编程相比 Java 中其他知识点学习起来门槛相对较高,学习起来比较费劲,从而导致很多人望而却步: 而无论是职场面试和高并发高流量的系统的实现却都还离不开并发编程,从而导致能够真正掌握并发编程的人才成为市场比较迫

【目录】编程之美

编程之美 2.1 二进制数中1的个数 2.2 阶乘 2.4 1的数目 2.5 寻找最大的k个数 2.6 精确表达浮点数 2.7求最大公约数 2.8 找符合条件的整数 2.10 求数组中最大的数和最小的数 2.12快速寻找满足条件的两个数 2.13 子数组的最大乘积 2.14 求数组的子数组之和的最大值 2.15 子数组之和的最大值(二维) 2.16 求数组的最大递增子序列 2.17 数组循环位移 2.18 数组分割 2.19 区间重合判断 2.20程序理解和时间分析 2.21 只考加法的面试题

编程之美5:求数组中最长递增子序列

最近楼楼被男朋友带着玩dota,有点上瘾,终于在昨天晚上作出了一个重大的决定,shift+delete删掉warIII文件夹,从此退出dota的明争暗斗.不过最近看男票已经将战场从11转到了topcoder,嗯,这是个好现象,希望楼楼也能跟着玩儿起来. 理想是美好的,唉,可是楼主还在编程之美的初级阶段啊.话不多说了,希望自己加油加油再加油!!(^o^)/~ 今天要看的一道题目是求数组中最长递增子序列. 题目简介: 写一个时间复杂度尽可能低的程序,求一个一维数组(N)个元素中的最长递增子序列的长度