完美数列
给定一个正整数数列,和正整数 p,设这个数列中的最大值是 M,最小值是 m,如果 M≤mp,则称这个数列是完美数列。
现在给定参数 p 和一些正整数,请你从中选择尽可能多的数构成一个完美数列。
输入格式:
输入第一行给出两个正整数 N 和 p,其中 N(≤105)是输入的正整数的个数,p(≤109)是给定的参数。第二行给出 N 个正整数,每个数不超过 109。
输出格式:
在一行中输出最多可以选择多少个数可以用它们组成一个完美数列。
输入样例:
10 8 2 3 20 4 5 1 6 7 8 9
输出样例:
8
解题思路和分析:
题目思路很简单,这题难点在于解决超时问题,
- 首先第一点需要进行排序。
- 然后遍历数组,按照题目要求求出满足条件的最长数列,这个也简单,稍微难点的是优化遍历方法,稍后介绍。
先展示一下错误示范,也可以直接翻到最后看最终代码:
错误代码示范(C):
因为我第一次做这道题也是没考虑到各种优化,导致超时了,记录一下我的错误时刻,
#include <stdio.h>
#include <stdlib.h>
int compare(const void * a, const void * b) {
return * (int *) a - * (int *) b;
}
int main() {
int N, p; // 数列长度, 系数
scanf("%d %d", &N, &p);
int num[N]; // 存储数列
int list_max = 0; // 最大完美数列长度
for (int i = 0;i < N;i++) { // 循环读取
scanf("%d", &num[i]);
}
qsort(num, N, sizeof(int), compare); // 排序
for (int i = 0;i < N;i++) {
long min_p = num[i] * p; // 计算 最小值 * 系数 p
int num_size = 1; // 记录数组长度
for (int j = i + 1;j < N;j++) {
if ((long)num[j] <= min_p) {
num_size++; // 满足最小值 m * p >= 最大值 M, 数列长度 + 1
}
}
if (num_size > list_max) {
list_max = num_size; // 更新最大长度
}
}
printf("%d\n", list_max); // 打印结果
return 0;
}
以上代码提交会超时,超时最大原因就是进行了没必要的查询或者计算,所以思路就来了:
- 第一:你会发现从数列中计算最大完美数列长度时,从第一个数到最后一个数一直都在循环判断,所以这就是重复计算了。
- 第二:以上问题解决之后排序算法也需要用更快的方法,我稍后调整使用的是快速排序。
- 第三:其实我还忽略了一点乘法结果超过int的范围,其实我考虑到了,但是没有处理好,后面也有解决。
优化后的代码(C):
既然说了问题和思路,那我接下来就直接给出我的最终代码:
#include <stdio.h>
#include <stdlib.h>
void quicksort(int *arr,int n)
{
if(n<2) return;
int key=arr[0];
int left=0;
int right=n-1;
int which=2; // 2表示右移动,1表示左移动
while(left<right)
{
if(which==2)
{
if(arr[right]>=key) {right--;continue;}
else if(arr[right]<key)
{
arr[left]=arr[right];
left++;
which=1;
continue;
}
}
if(which==1)
{
if(arr[left]<=key) {left++;continue;}
else if(arr[left]>key)
{
arr[right]=arr[left];
right--;
which=2;
continue;
}
}
}
arr[left]=key;
quicksort(arr,left); //左递归
quicksort(arr+left+1,n-left-1); //右递归
}
int main() {
int N;
long p; // 数列长度, 系数
scanf("%d %ld", &N, &p);
int num[N]; // 存储数列
int list_max = 1; // 最大完美数列长度,初始化为2是因为在初始计算num_size = list_max - 1
for (int i = 0;i < N;i++) { // 循环读取
scanf("%d", &num[i]);
}
quicksort(num, N);
for (int i = 0;i < N - list_max;i++) { // 获得最大长度之后,在数列剩余不足最大长度时最没必要计算
for (int j = i + list_max;j < N;j++) {
if (num[j] <= num[i] * p) {
list_max++; // 满足最小值 m * p >= 最大值 M, 数列长度 + 1
} else {
break;
}
}
}
printf("%d\n", list_max); // 打印结果
return 0;
}
以上代码两个大调整:
- 使用更高效的排序算法。
- 优化了遍历过程,减少不必要的遍历,因为题目只要求求最大完美数列,所以小于最大长度时都可以不必计算,其次我还发现只需要使用到一个记录长度的变量就行,其他都是多余的。
提交代码,答案正确,欢迎大佬指导不恰当的地方。