1. 程式人生 > >PAT 乙級 1030 完美數列

PAT 乙級 1030 完美數列

這裡寫圖片描述

分析題目,解題需要如下步驟

  1. 分割數列(這裡的分割時任意分割沒有必要連續)
  2. 找出分割的數列的兩個最值
  3. 判斷是否符合題目條件
  4. 若符合條件則和當前已經記錄的最大的符合條件的數列的長度比較,若大於已經記錄的長度則更新最大長度。
  5. 若還有未判斷過的數列則重複第一步,反之結束。

很明顯,解題需要用到類似窮舉的方法。但是PAT判分是有時間限制的,又遇到了如下問題:

  • 如何快速地計算出數列的長度
  • 如何快速地尋找到最大值和最小值

不解決這兩個問題是無法得到滿分的。所以我們可以先對數列進行排序(本文以升序排列為例),這樣兩個最值就是兩端,長度則可以通過下標直接計算。
之後的過程如圖(圖中的例子是PAT的樣例):
這裡寫圖片描述

  1. 定義兩個指標(非C語言指標,能夠指示位置即可,本文中為下標)left=0,right。
  2. right=n-1(n為所有數字的個數)。
  3. 判斷A[right]<=p×A[left]是否成立,若成立則更新Max=Length(A[left···right]所限定的子列長度Length),跳轉到第六步。
  4. right–;
  5. 判斷當前A[left···right]所限定的子列長度Length>Max是否成立,若成立,則轉到第三步。
  6. left++,right=n-1;
  7. 判斷當前A[left···right]所限定的子列長度Length是否大於Max,若大於,則跳轉到第三步,反之則結束演算法,輸出結果。

虛擬碼如下:

Perfect series()
{
    input N and p;
    input array A;
    //進行升序排序
    sort(A);
    Max=0;
    for(left=0;left<n;left++)
    {
        //如果本次迴圈開始時被分割的數列長度就小於Max,則直接結束迴圈。
        if(n-1-left+1<=Max)
            break;
        for(right=n-1;right-left+1>Max;right--)
        {
            if
(A[right]<=A[left]*p; { //若符合條件則直接更新Max並跳出內層迴圈,因為隨著right的減小,當前數列的長度也在減少。 max=right-left+1; break; } } } print(Max); }

C++程式碼如下:

#include <iostream>
#include <stdio.h>
#include <algorithm>

using namespace std;

int main()
{
    unsigned int n, p;
    unsigned int max = 0;
    scanf("%u%u", &n, &p);
    if (n == 0)
    {
        printf("0");
        return 0;
    }
    unsigned int* q = new unsigned int[n];

    for (unsigned int i = 0; i < n; i++)
        scanf("%u", &q[i]);

    sort(q, q + n);;
    unsigned int left = 0, right = 0;
    for (left = 0; left < n; left++)
    {
        if (n - 1 - left + 1 > max)
            break;
        for (right = n - 1; right - left + 1 > max; right--)
        {
            if (q[right] <= p * q[left])
            {
                max = right - left + 1;
                break;
            }
        }
    }
    printf("%d", max);
    system("pause");
}

可是直接提交到PAT上判分發現第四個測試點超時了,想了半天沒想到什麼辦法,就試著修改了一下程式碼,之前的程式碼是right指標從右邊開始向左邊移動,這次從left+Max的位置向右移動,程式碼如下:

#include <iostream>
#include <stdio.h>
#include <algorithm>

using namespace std;

int main()
{
    unsigned int n, p;
    unsigned int max = 0;
    scanf("%u%u", &n, &p);
    if (n == 0)
    {
        printf("0");
        return 0;
    }
    unsigned int* q = new unsigned int[n];

    for (unsigned int i = 0; i < n; i++)
        scanf("%u", &q[i]);

    sort(q, q + n);;
    unsigned int left = 0, right = 0;
    for (left = 0; left < n; left++)
    {
        if(n-1-left+1<=max)
            break;
        for (right=left+max;right<n;right++)
        {
            if (q[right] <= p * q[left])
            {
                max = right - left + 1;
            }
            else
                break;
        }
    }
    printf("%d", max);
    system("pause");
}

這次就滿分了,難道是第二個程式碼更快?

兩種程式碼時間複雜度分析:

  • 第一種:right指標從最右邊開始,若沒有遇到符合條件的就向左移動,若符合條件則直接直接結束內層迴圈(因為若right繼續移動只會讓數列的長度變得更短,而我們要求最長的數列長度)。若A[left···right]所限定的子列長度小於Max的也結束內層迴圈。平均情況下,right指標可能移動到任何位置,所以演算法的時間複雜度為O(n^2)。
  • 第二種:right指標從left+Max開始,若遇到一個不符合條件的則結束迴圈(具體為何可以參考第一種方法的說明)。平均情況下right也可能移動到任何位置,所以演算法的時間複雜度同樣也是O(n^2)。
  • 綜上所述,我覺得可能是PAT測試用例正好用第二種方法比較快,因為二者的時間複雜度相同,實際上平均情況下時間複雜度的常係數也是一樣的。如果大家發現了其他原因還請留言一下,我們討論一下。