大致思路:
笔者刷代码主要还是为了锻炼代码能力,好应对考研复试和工作,所以会使用PTA(目标学校要求)和力扣进行刷题,主要使用C与python,以周为单位进行计划和总结,考虑到以前的知识遗忘许多,所以可能还需要穿插着进行网课学习。
时间:
诸多杂事打扰,还有因为我本身有点计划狂,开始的有点晚12/31晚上才开始刷,惭愧啊,效率较低,毕竟也不止有着一件事,所以付出时间有限,排版不太精美,读者见谅(<(_ _)>)。
PTA基础刷题:
因为好久没刷题,写代码了,就从基础开始:
果然生疏了,慢慢练吧!
6-1:简单题:简单利用for循环,和使用C语言的printf()打印就可,唯一的遗漏是居然忘了printf的换行符是“/n”,也算是收获
6-2:多项式求值f(x)=∑i=0n(a[i]×^i)也是很容易写出来了
double f(int n,double a[],double x){
double f=0;
double m=1;
for(int i=0;i<=n;i++){
for (int j=0;j<i;j++){
m*=x;
}
f+=a[i]*m;
m=1;
}
return f;
}
毫无疑问--错了!习惯了408考试的暴力解了,时间复杂度太高!
所以,再仔细思考:既然是数学题,就从数学角度思考,让我想起以前在计算机组成红皮书上看到的一个解法,仔细搜了一下还是个著名算法:秦九韶算法:P(x)=(...((anx)+an−1)x+...)x+a0从高次项到底次方项计算,将最高次方项系数作为初值,类似递归的想法,每次将上一次的结果乘x加上新的临近的底次方的系数,然后依次到0次方项!
double f(int n,double a[],double x){
double result=a[n];
for(int i=n-1;i>=0;i--){
result=result*x+a[i];
}
return result;
}
简洁而高效啊!(实际上,实现过程中还是犯了许多错误:第一次把系数整倒了,把a[0]当成最高次方项处理了,第二次for循环多写了一次从n到0了,/(ㄒoㄒ)/~~
,第三次终于对了,都快哭了┭┮﹏┭┮,不过解出来很是蛮爽的(●'◡'●)这也是为数不多的代码乐趣)
6-3:简单求和 6-4:求平均值:都很简单,使用一个for循环就能出来的事就不赘述了
6-5:求最大值:我直接一个冒泡排序,简简单单解决了,果然算法学习是程序猿必修课啊。
6-6:求链表结点的阶乘:
刚开始以为很简单,求L所指的结点的数据部分的值的阶乘呗,简单使用个for循环不就解决了
int FactorialSum( List L ){
if(L==NULL){return -1;}
int sum=1;
int n=L->Data;
if (n==0){return 1;}
for(int i=1;i<n+1;i++){
sum*=i;
}
return sum;
}
结果:
好吧,是我想简单了,再看一眼测试案例
好家伙:846=5!+3!+6!原来是求整个链表所有结点的数据的阶乘之和啊!
是我大意了,上面还提示我的程序不能通过空链表,可是我已经做L=NULL的措施了,难道,他不想让我返回-1作为这个情况的值?嗯,试一下改成0:
我擦,还真是,哈哈哈,被自己的粗心气笑了!
重新改:仔细看看错误:
1.sum初值应该为0
2.因为是多个阶乘和,所以要依次处理结点数值,需要每次重置n,并利用一个中间变量记录单个结点的阶乘,命名为jie,每次计算完后将其加在sum上:
int FactorialSum( List L ){
if(L==NULL){return 0;}
int sum=0;
struct Node *p=L;
int jie=0;
while(p!=NULL){
int n=p->Data;
jie=1;
for(int i=1;i<n+1;i++){
jie*=i;
}
sum+=jie;
p=p->Next;
}
return sum;
}
然后,结果就是:
o( ̄▽ ̄)ブ
6-7:统计某类完全平方数:本题要求实现一个函数,判断任一给定整数N是否满足条件:它是完全平方数,又至少有两位数字相同,如144、676等。
首先,笔者怀着对不起数学老师的心情复习一下完全平方数:确定了就是可以作为一个数的平方结果的数
所以,不能为负,为负的数可以直接排除了。
然后,判断N是否为完全平方数:将N开平方,结果赋值给n,如果N不是完全平方数,开平方时结果就会有取舍,所以n的平方和就会和N不一样,以此判断N是否为完全平方数。
最后,判断第二个条件:数字N中至少有两个数相同,如果没学过算法的话,还真被你难住了:利用空间换时间的策略--定义一个长度10数组,记录各个数字(0-9)在N中的出现次数,利用除法和阶乘依次取各位的数。
int IsTheNumber ( const int N ){
if(N<0)return 0;
int n=(int)sqrt(N);
if(n*n!=N)return 0;
int N1=N;
//使用计数法,计算0-9各个数字出现的次数,一旦出现2就跳出,以空间换时间
int count[10]={0};
int dig,s2=0;
while(N1>0){
dig=N1%10;//依次统计各个位置上的数字
count[dig]++;
if(count[dig]>=2){
s2=1;
break;//一旦出现2就跳出
}
N1=N1/10;//因为要依次处理,所以,已经处理的低位数字需要消掉
}
if(s2==1){return 1;}
return 0;
}
再满足第二个条件的时候,我也在想有没有不那么浪费空间的方法,短时间没想到,隔网上搜搜,嘿,还真有大神有妙招:本质上计算机存储数字时是二进制的形式,如十进制的8就表示为二进制的100,所以一个变量,如int型变量,本身就可以当作一个数组,这个时候将该数的二进制形式的底10为当作0-9的出现与否,当0-9出现时,就令对应位置为1,然后提前判断:给对应位置是否已经被置为1,如果是,那么说明这个数已经是第二次出现,那么就可以判断第二个条件满足,若不是,则继续循环处理高位数字!
int IsTheNumber ( const int N ){
if(N<0)return 0;
int n=(int)sqrt(N);
if(n*n!=N)return 0;
int N1=N;
//使用计数法,计算0-9各个数字出现的次数,一旦出现2就跳出,以空间换时间
int count=0;
int dig,s2=0;
while(N1>0){
dig=N1%10;//依次统计各个位置上的数字
if (count & (1 << dig)) {
return 1;
}
// 置第digit位为1
count |= (1 << dig);
N1=N1/10;//因为要依次处理,所以,已经处理的低位数字需要消掉
}
return 0;
}
but,这个方法只可以处理2次及以上的情况,如果想处理多次,其实比较难,因为涉及到把一个数二进制表示均匀分割成许多部分,以此表示多次,但是,不得不说,太过复杂了得不偿失!还是建议采用第一种数组的方法,有时候最简单的方法就是最有效的方法1 6-8 简单阶乘计算:本题要求实现一个计算非负整数阶乘的简单函数---简简单单一个for循环解决了 6-9 统计个位数字:本题要求实现一个函数,可统计任一整数中某个位数出现的次数。例如-21252中,2出现了3次,则该函数应该返回3。 (不对劲啊,哈哈哈,6-8和6-9都是做6-6的基础,这出的题顺序反了吧,先打BOSS后刷小怪啊,😄。) 不过这个6-9也是有坑的:1.当出现负数时,进行取余数时可能得出一个负数,而我们统计的只是数字,不带符号,所以要对待统计的数取其绝对值(就是负数取反)2.统计的数为0时要特殊处理。
int Count_Digit ( const int N, const int D ){
int count=0;
int n=N,s;
if(n<0)n=-n;
if(n==0&&D==0)count=1;
while(n!=0){
s=n%10;
if(s==D)count++;
n=n/10;
}
return count;
}
6-10:本题要求实现一个打印非负整数阶乘的函数。 好家伙,还是在一个系列,那不简简单单,我直接给出答案:
void Print_Factorial ( const int N ){
if(N<0||N>1000){printf("Invalid input");return ;}
int jie=1;
for(int i=1;i<=N;i++){
jie*=i;
}
printf("%d",jie);
}
结果:错了!啊不,部分错误
果然不能大意啊,要认真对待每一题啊。于是我左思右想,我的逻辑哪里错了,想不通啊(挠头--条条青丝掉落)上个厕所,我忽然灵感爆发:好像输入大于12时就不行,等一下是不是我的载体--int不行?当输入变大时,int表示的范围不够了,实践一下,把int换为double
结果:
对啊,double无法保证精度,还不如原来的int,那怎么办?
有问题,自己解决不了,绝不死磕,咱就别人那里取取经呗,不丢人(😏)
于是,咱就去豆包老师那里学习了,哎嘿,获得一个好思路:题目本身只让我们输出所给数字的阶乘,但是人家也没要求必须是个数字啊--而且,目前也没有啥类型的变量能够表示这样的结果,那咱用传统的思路,也就是咱现实中算乘法的思路:
把要求的输出看作是一个数被1到N(输入)乘了个遍所得到的数字,在现实中我们计算乘法时也是一位位的算,那咱就把要求的输出分解为一个个单独的数字(0-9)然后化为一个很大的数组存储(一个数组元素储存一个数位上的数字),然后一位位的计算阶乘,对于每个位上的结果--保留结果的个位作为这个位置上的数,把十位及以上的数作为进位,传递给上一层,如果进位的数很大,也没事,以此取模,再把进位的数剩余的高位接着向上传递呗!就跟套娃似的。
(这个进位数字的处理看似复杂,其实很简单:具体原理就像一个很长的队列,有人从队尾给出一个装满饼干的袋子,接过袋子的人拿出饼干直到自己吃饱,如果饼干还有剩余就往前传)
(整个过程的处理,就像计算机组成里介绍的加法器那样运行,毕竟,它的本质也是模仿人类的运算逻辑。)
正式开始写:
整个逻辑就是:将最终结果和整个计算过程放在一个数组里,这里就设为A[3000]--肯定够了,然后从A[0]开始利用循环依次阶乘,利用取余得到每次乘法的保留位,利用除法除以10得到单个位置上的进位数字,然后在下次计算隔壁的高位数的阶乘时,把上次得到的进位加上,然后再取余,再除以10得到新的进位数字!
然而,要考虑的是,计算过程中,我们是A[0]依次向高位计算的,一开始数组全是0,A[0]=1,但是计算过程中通过进位,A[]中的高位1、2、3...N都会变为非0,我们没必要每次都计算数组中所有位置的乘积,因为0乘以任何数都是0嘛!所以我们需要通过一个数位指示变量(len)指引我们,告诉我们目前得到的阶乘结果是几位数!
那么一开始len肯定是1,毕竟是从个位数计算(常识嘛),然后什么时候增加呢?那就是在上面计算过程中的阶乘的那个循环结束时,如果进位还大于0,也就是说进位还有富裕,那么就可以利用这个富裕的进位数字向高位那些0去开拓了!
最终,经过整理,我写出来是这样滴:
void Print_Factorial ( const int N ){
if(N<0||N>1000){printf("Invalid input\n");return ;}
int A[3000]={0};
A[0]=1;//做阶乘的准备
int len=1;//用作记录计算的位数
int jin=0;//进位数字
for(int i=2;i<=N;i++){//这个循环就是为了计算当前所有的非零数的阶乘中的一环,比如都乘以3
jin=0;//每次循环时将进位置为0,避免误差
for(int j=0;j<len;j++){
int chengji=A[j]*i+jin;//计算单个位置上数字的乘积
//由小学知识:单个位置上乘积为两个乘数相乘加上地位进位
A[j]=chengji%10;//保留个位数字作为该位置的数
jin=chengji/10;//更新进位数字
}
while(jin>0){//这个循环就是:当进位还大于0==>也就是说出现新的高位非零数字
A[len]=jin%10;
jin=jin/10;
len++;
}
}
for(int z=len-1;z>=0;z--){
printf("%d",A[z]);//为了符合人类习惯,还是要从高向底输出的
}
printf("\n");//这行纯属强迫症
}
不过!这居然还只是简单题!OMG!看来是我太弱了呀,还得练!不过,今天也就这样了,一天就磨出来这一题┭┮﹏┭┮相信各位读者肯定比我更牛,能秒了这题✪ ω ✪
6-11:
好家伙,这不撞我枪口上了吗,熟读考研408数据机构排序算法的我,直接就是一个快速排序,给A[]排个序,然后返回中间数,不就是中位数吗,(●'◡'●)
But,我又高兴早了:程序运行超时了!,看来是我被刷题的模式害了,太过轻敌了!
那怎么办呢?我想来想去,还是感觉快速排序应该可以,我们已知:快速排序每次排序的过程中都至少能确认一个元素的最终位置,那么如果我在确认了这个最终位置后,加一个判断逻辑,判断这个最终位置是否刚好是中间位置,不就可以找到中位数了吗!说干就干:
int Partition(ElementType A[],int left,int right){//划分,或者说“分治”
ElementType pivot=A[left];
while(left<right){
while(left<right&&A[right]>pivot)right--;
A[left]=A[right];
while(left<right&&A[left]<pivot)left++;
A[right]=A[left];
}
A[left]=pivot;
return left;
}
ElementType Quick(ElementType A[],int l,int r,int k){//快速排序
if(l==r)return A[l];
int mid=Partition(A,l,r);
if(mid==k)return A[mid];
else if(mid<k){
return Quick(A,mid+1,r,k);
}
else
return Quick(A,l,mid-1,k);
}
ElementType Median( ElementType A[], int N ){
int k=(N+1)/2-1;
return Quick(A,0,N-1,k);
}
然而,还是没有全对
那么,问题出在哪里?可以看到还是有部分是对的,那是不是我的中位数指针K定的有些毛病?
找中位数,已知N个元素,理所当然的,中间位置就是(N+1)/2,但是这是数组啊!从0开始的数组啊!所以,我取得是(N+1)/2,这个逻辑应该是对的啊,算了,改改再说:设为N/2吧
int Partition(ElementType A[],int left,int right){
ElementType pivot = A[left];
while(left<right){
while(left<right&&A[right]>pivot)right--;
A[left]=A[right];
while(left<right&&A[left]<pivot)left++;
A[right]=A[left];
}
A[left]=pivot;
return left;
}
ElementType Quick(ElementType A[],int l,int r,int k){
if(l==r)return A[l];
int mid=Partition(A,l,r);
if(mid==k)return A[mid];
else if(mid<k){
return Quick(A,mid+1,r,k);
}
else
return Quick(A,l,mid-1,k);
}
ElementType Median( ElementType A[], int N ){
int k=N/2;
return Quick(A,0,N-1,k);
}
什么!!!居然对了!哈哈哈,看来真是因为中位数位置没有定对
知道答案后,再看看,感觉都清晰了一点(有点拿着答案讲题的高中老师的感觉,哈哈哈),我反思原来的错是因为:我原来取得中位数位置是(N+1)/2-1,当N=5时,就是取数组A[2],没啥问题,所以原来的奇数测试用例对了,当N=6时,由于C语言向下取整,取A[2],还是数组中第三个元素,这个地方就比较暧昧了——当数组有偶数个元素时,中间位置有两个值,我这种取法,默认取左边的那个,但是这个题目看来是默认取右边的那个,所以,当测试用例为偶数时,我错了。这次,我选择甩锅给出题方,(●ˇ∀ˇ●)。