1. 程式人生 > >演算法學習之尋找最長等差數列

演算法學習之尋找最長等差數列

  最長等差數列就是在一個數組中,組成等差數列的最長的那一個,首先我們對陣列排序,然後我們一般會先想到暴力法從第一個開始迴圈遍歷整個陣列,時間複雜度O(N^3),下面給出虛擬碼

               int i,j,k,len = 0
               for(i = 0; i < n; i++)
                     for(j = i+1; j < n; j++)
                    {
                        int delta = a[j] - a[i];
                        len = 2;
                        int tmp = a[j];
                        for(k = j+1; k < n; k++)
                       {
                            if(a[k] - tmp== delta)
                          {
                               len++;
                               tmp = a[k];
                          }
                       }
                      求出最大的len
                    }


第二種方法用動態規劃,我們可以利用等差數列的性質來做,當等差數列長度為奇數的時候,比如相鄰3個數,aiajak

則2*aj = ai+ak;只要任何一個關於它對稱的兩個數相加都等於它的2倍,有了這個性質我們就可以利用動態規劃來做

dp[i][j] = d,表示以a[i]a[j]為前兩個元素的等差數列長度為d,我們再來推狀態轉移方程,

 如果ai+ak<2*aj,說明ak - aj < aj - ai dp[i][j]的值不能確定,k接著往後走

如果ai+ak>2*aj,說明ak-aj > aj -ai,此時dp[i][j]的值為2,因為k不能往後走了,再往後走二者差距更大,所以a[i]a[j]開頭的等差數列為2,然後再i--,開始尋找下個ai aj開頭的等差數列

如果ai+ak==2*aj,則說明這三個數構成等差數列,此時dp[i][j]=dp[j][k]+1;也就是說以ajak開頭的等差數列前加上了一個ai,狀態轉移確定好後,下面開始寫程式碼

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct result
{
    int max_len;
    int i;
    int j;
};
int max(int a,int b)
{
    return a > b ? a : b;
}
void find_llap(int *arry,int n)
{
    int dp[50][50]; //dp[i][j]代表以arry[i]和arry[j]為頭兩個元素的等差數列,此題規模小,規模大的時候i,j可以做hash,就可以節省空間
    int i,j,k;
    struct result result;
    memset(dp,0,sizeof(dp));
    for(i = 0; i < n; i++)
    {
       dp[i][n] = 2; //因為以arry[n]為第二個元素的等差數列長度一定為2
    }
    if(n < 2)
    {
        for(i = 0; i < 2; i++)
            printf("%d ",arry[i]);
        return;
    }
    result.max_len = 0;
    for(j = n-1; j >= 1; j--)
    {
        i = j-1;k = j+1;
        while(i >= 0 && k <= n)
        {
            if(arry[i]+arry[k] > arry[j]<<1) //如果和ai+ak>2aj,說明ak-aj>aj-ai,這個構造不成等差數列,則k以後的更不行,因為陣列是升序
             {
                 dp[i][j] = 2;                //所以此步驟直接賦值為2,及只有ai aj構造成等差數列
                 i--;
             }
            else if(arry[i]+arry[k] < arry[j]<<1)
            {
                k++;       //如果ai+ak<2aj,說明ak-aj<aj-ai,所以以ai aj開頭的等差數列,往後遍歷,可能還有新的元素符合公差,所以k++
            }
            else
            {
                /*如果ai+ak=2aj,則正好構成等差數列,直接將前一個子問題的解+1即可*/
                dp[i][j] = dp[j][k]+1;
                result.max_len = max(result.max_len,dp[i][j]);
                if(result.max_len == dp[i][j])
                {
                    result.i = i;
                    result.j = j;
                }
                i--;k++;
            }
         }
         if(result.max_len == 0)
         {
             result.max_len = 2;
             result.i = 0;
             result.j = 1;
         }
         while(i >= 0) /*這個迴圈是因為k已經到最後了,所以以這個j為第二個元素的所有數列都是2*/
         {
             dp[i][j] = 2;
             i--;
         }
     }
     int delta = arry[result.j] - arry[result.i];
     int last  = arry[result.i]+(result.max_len-1)*delta;
     for(i = arry[result.i]; i <= last; i+=delta)
        printf("%d ",i);

}

int main()
{
    int n;
    int arry[100];
    scanf("%d",&n);
    int i;
    for(i = 0; i < n; i++)
      scanf("%d",&arry[i]);
    find_llap(arry,n-1);
    return 0;
}


  依據這個性質,還有一種動態規劃,dp[i][j]代表等差數列最後兩個元素,然後從前往後遍歷,得到的結果一樣