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测试用例正好用第二种方法比较快,因为二者的时间复杂度相同,实际上平均情况下时间复杂度的常系数也是一样的。如果大家发现了其他原因还请留言一下,我们讨论一下。

猜你喜欢

转载自blog.csdn.net/KongMing07/article/details/80225591
今日推荐