求数列中连续数值组成的等差数列最大长度_最长等差数列

本文介绍了一种动态规划方法来解决在已排序的数字集合中找到最长等差数列的问题。首先通过一个简单的算法找到是否存在三个元素组成等差数列,然后通过创建二维表L填充和更新等差数列的长度,最终实现O(n^2)时间复杂度的解决方案。
摘要由CSDN通过智能技术生成

本课程是从少年编程网转载的课程,目标是向中学生详细介绍计算机比赛涉及的编程语言,数据结构和算法。编程学习最好使用计算机,请登陆 www.3dian14.org (免费注册,免费学习)。

给定一组数字,在其中找到最长等差数列的长度。

我们看两个例子:

例1)

输入:set [] = {1,7,10,15,27,29}

输出 = 3

最长的等差数列为{1,15,29},3个数字之间依次之间的等差值为14

例2)

set [] = {5,10,15,20,25,30}

输出= 6

最长的等差数列为{5,10,15,20,25,30},63个数字之间依次之间的等差值为5

为简单起见,我们假设给定的给定的数字的集合已排序(我们总可以先对集合进行排序,不影响我们讨论)。

4661bf91d2f4e295dc79fa37a75273ca.png

算法分析

很容易想到以下的简单解决方案:

1)将集合中的每对数字作为视为等差数列中的前两个元素,然后依次检查排序集中的其余元素,找出能与这对数字组合成等差数列的其它数字,这样得到一个候选的等差数列。

2)接着再检查下一对数字,再次得到对应的候选等差数列。

3)最后再从这些候选等差数列中选择长度最长的。

但是上面的方案中我们需要检查所有数字对,共需要运行O(n^2)嵌套循环。在嵌套循环中,我们需要运行第三个循环,该循环线性查找候选等差数列中的更多元素,因此此过程需要O(n^3)时间。

这时候,动态规划又一次显示了威力。使用动态规划可以在O(n^2)时间内解决此问题。

为了讲解如何采用动态规划解决此问题,我们首先讨论以下简单问题的解决方案。

给定一个已排序的数字集合set,请判断其中是否存在三个元素可以组成等差数列。如果存在,则输出true, 否则输出false。注意:如果我们可以找到的等差数列包含3个以上的元素,则答案也为true。

要找到这三个元素,我们首先将一个元素固定为中间元素,然后搜索其他两个元素(一个位于较小,位于左侧;而另一个较大,位于右侧)。我们从第二个元素开始,并将每个元素固定为中间元素。

为了使某个元素set[j]位于等差数列的中间,必须存在元素set[i]和set[k],使得set [i] + set [k] = 2 * set [j],其中 0<= i

如何有效地找到给定 j 的 i 和 k?我们可以使用以下简单算法在线性时间内找到i和k。

1)初始化i为j-1和k为j+1

2)当i>=0且j<=n-1时执行以下操作:

         如果set [i] + set [k]等于2 * set [j],那么返回true,结束。

         如果set [i] + set [k]> 2 * set [j],则将i减1。

         否则,如果set [i] + set [k] <2 * set [j],则将k加1。

3)如果在第二步中都没有找到合适的i和k, 返回false。

以下是针对较简单问题的上述算法的C++实现。请您输入并运行它。

#include

using namespace std;

//假设 set[0..n-1]已经排序了。

//该函数判别是否存在一个3个数组成等差数列,如果存在返回true,否则返回false

bool arithmeticThree(int set[], int n) 

//set[j]将作为中间位置的元素

//依次检查set中第2个元素直到倒数最后第2个位置的元素

    for (int j=1; j

    { 

// i和j初始化为j的前后位置

        int i = j-1, k = j+1; 

//检查是否存在i和k,使得set[i],set[j],set[k]依次组成等差数列

        while (i >= 0 && k <= n-1) 

        { 

            if (set[i] + set[k] == 2*set[j]) 

                return true; 

            (set[i] + set[k] < 2*set[j])? k++ : i--; 

        } 

    } 

    return false; 

//主程序

int main()

{

    int set1[] = {1, 7, 10, 15, 27, 29};

    int n1 = sizeof(set1)/sizeof(set1[0]);

    arithmeticThree(set1, n1)? cout << "Yes\n" : cout << "No\n";

    int set2[] = {1, 7, 10, 15, 27, 28};

    int n2 = sizeof(set2)/sizeof(set2[0]);

    arithmeticThree(set2, n2)? cout << "Yes\n" : cout << "No\n";

    return 0;

}

上述的算法只是解决了判别一个排好序的集合中是否存在3个或3个以上的元素可组成等差数列的问题。如何将上述解决方案扩展到原始问题?

上面的函数返回一个布尔值。原始问题的要求输出是最长等差数列的长度,它是一个整数值。如果给定集合具有两个或更多个元素,则答案的值至少为2(想想看为什么?)。

动态规划算法的精髓是将以前计算得到的中间值保留,以备后续程序查询使用,而无需重复计算。

因此可以创建一个二维表L[n][n]。该表中的条目L[i][j](j>i)存储了一个中间值,它是将set[i]和set[j]作为等差数列的前两个元素时得到的候选等差数列的长度。

该表的最后一列始终为2(想想看为什么)。表格的其余部分从右下到左上填充。要填充表格的其余部分,首先固定j(等差数列中的第二个元素),然后针对这个固定的j,依次搜索i和k。如果发现i和k可以使得set[i],set[j]和set[k]形成等差数列(采用上面讲述的算法),则将L[i][j]的值设置为L[j][k]+1。

请注意,L[j][k]的值必须先填充,然后循环才能从右到左遍历。

924cb3bd987573c79040ab76a1c9db97.png

算法实现

以下是动态编程算法的实现示例。请您试运行该程序并比对结果。

//用C++寻找排好序的数字集中最长的等差数列长度的实现例子

#include  

using namespace std; 

//该函数返回给定数字集中存在的最长等差数列的长度

int lenghtOfLongestAP(int set[], int n) 

    if (n <= 2) return n; 

//创建一个表L,并使得每个元素初始化为2。 

//L[i][j](j>i)存储了一个中间值,它是将se[i]和set[j]作为等差数列的前两个元素时得到的候选等差数列的长度

    int L[n][n]; 

    int llap = 2; // 初始化结果为2

//将表L中最后一列的元素初始化为值2

    for (int i = 0; i < n; i++) 

        L[i][n-1] = 2; 

//逐步试探将元素j作为等差数列中的第二个元素

    for (int j=n-2; j>=1; j--) 

    { 

        //i和k是元素j的前后位置,依次搜索

        int i = j-1, k = j+1; 

        while (i >= 0 && k <= n-1) 

        { 

            if (set[i] + set[k] < 2*set[j]) 

                k++; 

            //在改变i之前,将 L[i][j] 设置为2

            else if (set[i] + set[k] > 2*set[j]) 

                { L[i][j] = 2, i--; } 

            else

            { 

           //找到i和k, 注意,i和j位置的元素是等差数列的前两个元素

           //那么[i][j]的值是L[j][k]的值+1

            //当然应该先计算 L[j][k] ,这就是为什么我们从右下向左上填充的原因

                L[i][j] = L[j][k] + 1; 

            //取较大的值保留,作为结果

                llap = max(llap, L[i][j]); 

              //改变i和k, 从右下向左上填充

                i--; k++; 

            } 

        } 

        //如果k>n-1, 那么剩下的j列中的元素都是2

        while (i >= 0) 

        { 

            L[i][j] = 2; 

            i--; 

        } 

    } 

    return llap; 

//主程序

int main() 

    int set1[] = {1, 7, 10, 13, 14, 19}; 

    int n1 = sizeof(set1)/sizeof(set1[0]); 

    cout << lenghtOfLongestAP(set1, n1) << endl; 

    int set2[] = {1, 7, 10, 15, 27, 29}; 

    int n2 = sizeof(set2)/sizeof(set2[0]); 

    cout << lenghtOfLongestAP(set2, n2) << endl; 

    int set3[] = {2, 4, 6, 8, 10}; 

    int n3 = sizeof(set3)/sizeof(set3[0]); 

    cout << lenghtOfLongestAP(set3, n3) << endl; 

    return 0; 

结果为 4  3  5

8e6c43f1a05f5e18cccdd96e60b0a8db.png

算法优化

很显然,上述动态规划算法的时间复杂度为 O(n^2),空间复杂度为: O(n^2)。如果程序编写得当,我们可以把空间复杂度降低为O(n)。

参看下面的例子。

#include  

using namespace std; 

int Solution(vector A) 

    int ans = 2; 

    int n = A.size(); 

    if (n <= 2) 

        return n; 

    vector llap(n, 2); 

    sort(A.begin(), A.end()); 

    for (int j = n - 2; j >= 0; j--) 

    { 

        int i = j - 1; 

        int k = j + 1; 

        while (i >= 0 && k < n) 

        { 

            if (A[i] + A[k] == 2 * A[j]) 

            { 

                llap[j] = max(llap[k] + 1, llap[j]); 

                ans = max(ans, llap[j]); 

                i -= 1; 

                k += 1; 

            } 

            else if (A[i] + A[k] < 2 * A[j]) 

                k += 1; 

            else

                i -= 1; 

        } 

    } 

    return ans; 

int main() 

    vector a({ 9, 4, 7, 2, 10 }); 

    cout << Solution(a) << endl; 

    return 0; 

请您运行上面的程序,并验证结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值