PAT 乙級 1030 完美數列
阿新 • • 發佈:2019-02-02
分析題目,解題需要如下步驟
- 分割數列(這裡的分割時任意分割沒有必要連續)
- 找出分割的數列的兩個最值
- 判斷是否符合題目條件
- 若符合條件則和當前已經記錄的最大的符合條件的數列的長度比較,若大於已經記錄的長度則更新最大長度。
- 若還有未判斷過的數列則重複第一步,反之結束。
很明顯,解題需要用到類似窮舉的方法。但是PAT判分是有時間限制的,又遇到了如下問題:
- 如何快速地計算出數列的長度
- 如何快速地尋找到最大值和最小值
不解決這兩個問題是無法得到滿分的。所以我們可以先對數列進行排序(本文以升序排列為例),這樣兩個最值就是兩端,長度則可以通過下標直接計算。
之後的過程如圖(圖中的例子是PAT的樣例):
- 定義兩個指標(非C語言指標,能夠指示位置即可,本文中為下標)left=0,right。
- right=n-1(n為所有數字的個數)。
- 判斷A[right]<=p×A[left]是否成立,若成立則更新Max=Length(A[left···right]所限定的子列長度Length),跳轉到第六步。
- right–;
- 判斷當前A[left···right]所限定的子列長度Length>Max是否成立,若成立,則轉到第三步。
- left++,right=n-1;
- 判斷當前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測試用例正好用第二種方法比較快,因為二者的時間複雜度相同,實際上平均情況下時間複雜度的常係數也是一樣的。如果大家發現了其他原因還請留言一下,我們討論一下。