在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
第一次写这方面的blog.自己也是初次接触相关知识,写的有不妥的地方十分欢迎大家指正~ 这是浙大PAT上的一道算法题(据说是浙大04年研究生复试题),题目是这样的:
翻译成中文就是,在程序第一行输入一个正整数N,第二行输入一个含有N个整数的序列,从中取连续的任意n个数(当然不超过N),让这n个数的和最大.接着要输出这个和的值,以及这个片段的首个和末尾数字.如果有多个连续片段的和一样大,则取下标最小的.如果全为负数,则定义其最大和为0,首个和末尾数分别是序列的首个和末尾数; 这个只是一个数学问题,不需要特别高深的编程技巧,所以我只说一下我的思路(文尾也会有源码). 既然是求连续最大和,我们首先可以肯定的说: 1.我不要负数(但不能说不要0!后面会解释). 2.我要尽可能多的连续正数. 根据题目要求,我们要输出头尾的数,显然我们需要一个表来保存我们输入的数.仅仅是数吗?当然不是!我们还要判断下标的大小,所以还需要保存他们的标号.于是你可能就想到了数组这种东西:直接int sequence[N]然后一个一个塞进去,简单!但这样的话,写出来的程序恐怕就是好几个for循环套來套去,然后出现O(n3)甚至O(n4)这种比较尴尬的事情--别忘了题目还有时间限制(200ms),我们要在各个方面都尽量加快程序的效率.首先要用指针操作代替使用数组索引(具体原因参见这里),malloc或许是一个不错的选择,但malloc出来的空间没有清零(我这个算法不清零的话,算到最后会gg..),所以改成calloc.这里还要构造一个结构体: 1 typedef struct
2 {
3 int right_indices; //右下标
4 int left_indices; //左下标
5 int sum; //当前的数
6 } cluster;
这个结构体除了放输入的数,还能放一个左下标,一个右下标,说白了就是题目中的那个边界. 接下来,我们看一看上面的sample:
很好,一目了然知道最大和是10了~(注意,所有进行过处理而形成的序列都用cluster的结构体来保存!!!!)接下来的问题是如何再程序里面实现这个功能.先请看这个部分的代码 1 int main(void)
2 {
3 int flag=2;//1:positive; -1:negative; 0:undefined
4 int K=0,i=0,temp=0,combine_count=0,n_count=0,original_count=0;
5 int current_max=0;
6 cluster* numbers=NULL;
7 int* original=NULL;
8 int final_left,final_right;
9 scanf("%d",&K);
10 original=(int *)calloc(K+2,sizeof(int));
11 numbers=(cluster *)calloc(K+2,sizeof(cluster));
解释一下代码的意思:flag是一个标志变量,保存上一个数的状态,其值有 2/1/0/-1;其中2是初始状态,1为正,0为0,-1为负.temp是一个交换变量,存放当前读入的数.K是要读入的数的数量,i我就不说了.original_count为原始数据计数器.combine_count是合并后的计数器.original是一个用于保存原始数据的int数组. 首先我们从缓冲区一个一个读数进original: 12 for (i=0;i<K;i++)
13 {
14 scanf("%d",&temp);
15 *(original+(++original_count))=temp;
用++original_count是为了让用*(numbers+i)直接访问第i个数,以免出错. 16 switch (flag)
17 {
18 default:if(temp > 0) //default = 2;
19 {
20 numbers->sum=temp;
21 numbers->left_indices=original_count;;
22 flag = 1;
23 }
24 else if (temp < 0) n_count++;
25 else if (temp == 0)
26 {
27 numbers->left_indices=original_count;
28 flag = 0;
29 }
30 break;
每次读入一个数据后会接着进行判断,只有在遇到第一个正数时才会写入cluster,并把标志更新为1(正数)然后更新最小下标,遇到负数会让负数计数器n_count加1,遇到0会将标志变为0,也会更新最小下标!这个很关键,因为假设输入了"0 0 0 1 2 3",那么最小下标应从第一个0算起而不是1算起(虽然他们的和都是6,但题目要求输出最小的下标!) 31 case 1:if(temp>0)
32 {
33 ((numbers+combine_count)->sum)+=temp;
34 (numbers+combine_count)->right_indices=original_count;
35 }
36 else if (temp<0)
37 {
38 n_count++;
39 (numbers+combine_count)->right_indices=original_count-1;
40 (numbers+(++combine_count))->sum=temp;
41 flag = -1;
42 }
43 else if ( temp == 0)
44 {
45 (numbers+combine_count)->right_indices=original_count-1;
46 flag = 0;
47 }
48 break;
49 case -1:if(temp < 0)
50 {
51 (numbers+combine_count)->sum+=temp;
52 n_count++;
53 }
54 else if(temp == 0)
55 {
56 (numbers+combine_count)->left_indices=original_count;
57 }
58 else if (temp > 0)
59 {
60 ((numbers+(++combine_count))->sum)=temp;
61 (numbers+combine_count)->left_indices=original_count;
62 flag=1;
63 }
64 break;
65 case 0:if(temp > 0)
66 {
67 ((numbers+combine_count)->sum)+=temp;
68 (numbers+combine_count)->right_indices=original_count;
69 flag=1;
70 }
71 else if (temp < 0)
72 {
73 n_count++;
74 }
75 else if ( temp == 0) ;
76 break;
终于在cluster中写入第一个数了(它绝对是正数),此后的事情是这样的: 1.如果读入的数和上一个数同号(当然不考虑0啦),就把新的数加到旧的数上,如果遇到符号相反的,就让numbers指针加一,再把这个数写下去.这里又分几种情况: a)正变负,会顺便更新最大下标(最后那个正数的序号写到right_indices) b)负变正,会更新最小下标(正在输入的那个数的序号写到left_indices) c)正变0,不更新下标!!! d)负变0,更新下一个cluster的最小下标!!!(虽然等下可能又是负数) 2.如果上一个数是0. e)0变正:不更新最小下标,但是要把正数写到新的cluster中 f)0变负:负数计数器加一 此时已经可以做第一次判断,如果负数计数器(n_count)等于输入的数的数量,则直接输出"0 0": if (n_count==K)
{
printf("0 %d %d",*(original+1),*(original+original_count));
return 0;
}
以上便是在读入数据过程中对它们的第一步操作,这是一个动态处理过程,输入完成则处理也完成了.最后numbers结构数组中会是如下的结构(original则原封不动地保留了每一个数.): "+ - + - + - + - + ..."(+代表正数,+代表负数,每个正数都含有两个下标,负数储存下标的空间是空的) nice!看起来更有规律了不是吗~接下来进行下一步的处理: i=0;
while (i<=combine_count)
{
temp=((numbers+i)->sum)+((numbers+i+1)->sum);
if (temp>=0)
{
{
if ((numbers+i+2)->sum != 0)
{
((numbers+i+2)->sum)+=temp;
((numbers+i+2)->left_indices)=((numbers+i)->left_indices);
}
else if ((numbers+i+2)->sum == 0)
{
((numbers+i+2)->sum)+=temp;
((numbers+i+2)->left_indices)=((numbers+i)->left_indices);
((numbers+i+2)->right_indices)=((numbers+i)->right_indices);
}
}
if (current_max<((numbers+i+2)->sum))
{
current_max=((numbers+i+2)->sum);
final_left=((numbers+i+2)->left_indices);
final_right=((numbers+i+2)->right_indices);
}
}
else if (temp == 0)
{
;
}
i+=2;
}
我们把 "+ - + - + - + - + ..."分组为:"[+ - ][+ - ][+ - ][+ - ][+ ..."有落单也没关系,然后把每组内的正数加到负数上,正数的下标都是偶数,每次循环结尾让i加2就可以使指针对准正数.因为是正负间隔,所以一旦[+ -]中的和大于0,那么就可以将这个和再加到下一组的正数上!这样保证了跨越负数能够得到补偿!画个图就是这样: [+1 -1 ][+2 -2 ][+3 -3 ][+4 -4 ] -> [+1 -1][+s -2][+3 -3 ][+4 -4 ] 其中+s=(+1 ) + (-1 )+ (+2 ) 如果某组内的和小于0,则将之前累积的那个正数暂时存入current_max中,然后再下一组中继续上面的累加形式,遇到小于0就拿出来与current_max比,如果大就更新current_max.当然,这个过程中会更新下标,具体的更新规则看上面的代码.这样以后,我们只遍历了一次序列,就找到了和最大且下标和最小的一串连续的数,至于他们的下标,在遍历的时候每次更新current_max就会把其对应的下标写入final_left和final_right.(取最后一次累加时下一组的正数的最大下标,取前一组和中的最小下标). 做到这里,基本就结束了,但是某些特殊情况会导致bug,如果输入的数据过少,则会导致即使程序遍历结束,current_max还是为0,此时就要手动将numbers第一个正数赋给current_max,此外还有一种情况会下标颠倒(映像中是最大和只有一个数的时候...?有条件的做个测试然后告诉我吧 T T). if (current_max == 0 )
{
current_max=(numbers->sum);
final_left=((numbers)->left_indices);
final_right=((numbers)->right_indices);
}
if (final_left>final_right) final_right=final_left;
之所以要单独处理这种情况另一方面也是我写的判断逻辑不够完善以至需要加两个收尾的操作...最后直接打印结果即可,有任何建议欢迎提出~(完)
P.S 下方附PTA运行结果
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论