题目分析
这道题乍看起来比较复杂,不容易想到什么比较好的应对策略,至少我没有想到。 所以没有办法,朴素的思想蛮力硬干。
目标是找出那些“关键数”,稍加分析可以知道,这实际上就是指删除那些会出现在其他数的卡拉兹序列(这里称一个数按卡拉兹猜想的规则进行运算,一直到1所产生的序列)的数。
因此就可以想出这样一种办法:
从第一个数出发,求出它的卡拉兹序列,然后对整个数组除当前数外的所有元素进行遍历,找出所有出现在卡拉兹序列中的数,删除(同时也“降低”了后面的搜索规模)。之后再移到下一个数,重复前面的工作,循环直到遍历完数组。
可以看到这样需要频繁“删除”数组中的元素,那真用数组的话势必需要频繁移位,显然这样很复杂,用链表似乎也有些浪费。所以一种替代方案是另外创建一个bool型数组来表征对应的数组中元素是否还有效,若被删除则将对应序号的bool值修改为false即可。
在计算卡拉兹序列时,由于长度未知,又不想开太大的数组浪费空间,可以采用动态扩容的手段。
最后遍历完“删除”掉所有的非关键元素,题目还要求我们按从大到小的顺序输出,于是还需要遍历一遍数组,找出那些尚存的关键元素,另用一个数组保存,这里为了方便,由于题目已经告诉我们不会超过100个元素,所以直接开了大小为100的int数组来保存,之后再排序一遍输出即可。
优化策略
再多注意一下题目的条件,可以发现题目告诉我们元素大小不会超过100,那么我们之前每次计算的卡拉兹序列中也不用保存超过100的元素,这样可以缩小数组大小,减少遍历和比较时间。
整个题目按这个思路干下来,还是有些繁琐,最后排序为了简单少写点代码就用了插入排序。不过相比于前面比较和删除元素的复杂度,排序剩下那几个元素的时间几乎可以忽略不计了,所以不用写性能很好的排序算法,简单正确就行。
源代码
#include <stdio.h>
const int DEFAULT_SIZE=30; //设置储存卡拉兹序列数组默认大小为30,满后再进行扩容
void expand(int *&arr,int len); //对数组扩容
int* findPath(int num,int &len); //计算卡拉兹猜想过程数
void insertSort(int *arr,int len); //插入排序
int main()
{
int caseNumber;
scanf("%d",&caseNumber);
getchar();
int *value=new int[caseNumber]; //储存元素值
bool *isValid=new bool[caseNumber]; //判断元素是否还有效
for(int i=0;i<caseNumber;++i){ //读取数据
scanf("%d",&value[i]);
isValid[i]=true; //初始化为true
}
for(int i=0;i<caseNumber;++i){
if(!isValid[i]) continue;
int len=0;
int* path=findPath(value[i],len); //计算当前有效元素的卡拉兹路径元素
for(int j=0;j<caseNumber;++j){ //对除当前元素外所有元素进行遍历,删除被覆盖元素
if(!isValid[j]||j==i) continue;
for(int k=0;k<len;++k){
if(path[k]==value[j]){ //发现路径上的元素,删除
isValid[j]=false;
break;
}
}
}
delete []path; //释放当前路径数组的空间
}
int keys[100]; //剩余数个数不超过100
int count=0;
for(int i=0;i<caseNumber;++i){
if(!isValid[i]) continue;
keys[count++]=value[i]; //记录剩下的关键数
}
insertSort(keys,count);
for(int i=0;i<count-1;++i)
printf("%d ",keys[i]);
printf("%d",keys[count-1]);
delete []value;
delete []isValid;
return 0;
}
void expand(int *&arr,int len)
{
int *old=arr;
arr=new int[len*2]; //扩容为原来两倍
for(int i=0;i<len;++i)
arr[i]=old[i];
delete []old; //释放原空间
}
int* findPath(int num,int &len)
{
len=1;
int curSize=1;
int capacity=DEFAULT_SIZE;
int * path=new int [capacity];
path[0]=num;
while(num!=1){
len++;
if(num%2==0) num>>=1;
else num=(3*num+1)>>1;
if(num<101) path[curSize++]=num; //仅当所得数值小于101时放入数组
if(curSize==capacity){ //若数组满,扩容
capacity<<=1;
expand(path,curSize);
}
}
return path;
}
void insertSort(int *arr,int len)
{
for(int i=1;i<len;++i){
int curOrder=i;
int pre=i-1;
while(arr[curOrder]>arr[pre]){
int tmp=arr[curOrder];
arr[curOrder--]=arr[pre];
arr[pre--]=tmp;
if(curOrder==0) break;
}
}
}