这个问题如果简化的话需要比较另类的思路,题目的意思是先求hamming距离,然后提取出hamming距离最小的序列,如果满足该条件的序列数大于1,则求字典序最小的序列。这里注意,字典序最小序列可能是输入序列,但也可能是根据输入序列重新生成的序列。
常规思路:(输入三个序列,AGCAT、ATCGG、GATTA)
1)先遍历求出每个序列对应的hamming距离,具体是通过三个循环实现,第一个循环选择被研究序列,例如选择AGCAT,这里声明一个hamming距离,第二个循环选择比较序列,例如ATCGG,这里声明一个局部求和值h,第三个循环按位比较,不匹配h值加一,再加到hamming距离中完成一个序列的hamming距离计算,计算完毕后将距离存储到数组中,数组编号同字符数组行编号一致。
2)找最值,找出hamming距离最小值对应的int数组编号,如果最小值只有一个,那直接输出序列和hamming距离,如果大于一则存储该hamming距离对应的全部字符串的行编号,在这些字符串中找字典序最小。
3)字典序最小:用两个循环表示,大循环按列,小循环按行,以上述序列为例,AGCAT和ATCGG的hamming距离最小都是8。首先,第一列第一行是A,第一列第二行也是A,比较大小发现A是最小的,那输出这个字符;随后,第二列第一行是G,第二列第二行是T,比较发现G是最小的,输出字符G,以此类推,每个小循环会输出一个字符,而所有的这些字符需要被存储起来生成一个新的序列,为什么?因为字典序最小的序列可能在输入中不存在,比如本例字典序最小为AGCAG,这个hamming距离没有被算过,所以还得重算一遍hamming距离求最小值,最后输出这个序列和最小值。
常规思路最难受的一点就是复杂,求hamming的过程被重复了两次,整体算法的时间复杂度可以计算一下,不过实践中时间倒不是什么问题,主要是bug,整体思路复杂后bug奇多,这可不是好事,常规思路对于大输入环境也不友好,所以需要简化算法。
简化思路:(输入三个序列,AGCAT、ATCGG、GATTA)
1)按列为大循环,按行为小循环,统计各个字符每列出现的个数,例如第一列第一行A出现了一次,第一列第二行A出现了一次,第一列第三行G出现了一次。
2)在按列的大循环中找到每一列的最大重复的字符和重复次数,最小重复字符和重复次数,例如第一列A重复了2次,最多,G重复了1次,最小。
3)计算hamming距离,例如A重复的最多,重复了2次,那hamming距离就是3-2=1了
那如果各出现一个,比如G一次,T一次,A一次怎么办,这个优先级设置就很有意思了,之前设定计数器flag,如果出现A,flag[0]++,出现C,flag[1]++,求最大重复次数的循环是从0开始的,所以如果所有元素出现一次,那min和max会锁定在A上,优先级的问题解决。最后按列逐字输出即可。
我把代码放出来让大家看看两种算法的复杂程度:
常规算法:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
char aa[60][10000];
int bb[60];
int cc[60000];
char dd[60000];
int flag=0;
int min=0;
void oper(int m,int n)
{
for(int i=0;i<m;i++)
{ int sum=0;
for(int j=0;j<m;j++)
{ int ham=0;
if(!strcmp(aa[i],aa[j])){ham=0;}
if(strcmp(aa[i],aa[j])!=0)
{
for(int k=0;k<n;k++)
{
if(aa[i][k]!=aa[j][k]) {ham=ham+1;}
if(aa[i][k]==aa[j][k]) {continue;}
}
}
sum=sum+ham;
bb[i]=sum;
}
}
min=10000;
flag=0;
for(int i=0;i<m;i++)
{
if(bb[i]==min) {flag=flag+1;cc[flag]=i;}
if(bb[i]<min) {min=bb[i];flag=0;cc[flag]=i;}
}
}
int main()
{
int m=0;int n=0;int t=0;
scanf("%d",&t);
while(t--)
{
memset(bb,0,sizeof(bb));
scanf("%d%d",&m,&n);
for(int i=0;i<m;i++)
{
scanf("%s",aa[i]);printf("%d\n",i);
}
oper(m,n);
int gg=1;
if(flag==0) {printf("%s",aa[cc[flag]]);}
if(flag!=0)
{ for(int j=0;j<n&≫j++)
{
char miin='T';int minmin=0;
for(int i=0;i<=flag&≫i++)
{
if(aa[cc[i]][j]-'A'<=miin-'A') {miin=aa[cc[i]][j];minmin=cc[i];}
}
dd[j]=aa[minmin][j];
printf("%c",aa[minmin][j]);
}
}
printf("\n");
if(flag!=0)
{
strcpy(aa[m],dd);
m=m+1;
oper(m,n);
}
printf("%d\n",min);
}
return 0;
}
简化算法:
#include <stdio.h>
#include <string.h>
#include <limits.h>
int main()
{ char B[4];int n=0;
strcpy(B,"ACGT");
scanf("%d",&n);
while(n--)
{int x=0;int y=0;
scanf("%d",&x);
scanf("%d",&y);
char A[x][y+1];
char C[y+1];memset(C,0,sizeof(C));
int hamming=0;
for (int i=0;i<x;i++)
{scanf("%s",A[i]);}
for (int j=0;j<y;j++)
{ int flag[4];memset(flag,0,sizeof(flag));int max=INT_MIN;int min=INT_MAX;int d=0;int e=0;
for (int i=0;i<x;i++)
{ if (A[i][j]=='A')
{flag[0]++;}
if (A[i][j]=='C')
{flag[1]++;}
if (A[i][j]=='G')
{flag[2]++;}
if (A[i][j]=='T')
{flag[3]++;}
}
for (int k=0;k<4;k++)
{ if (flag[k]>max)
{max=flag[k];d=k;}
if (flag[k]<min&&flag[k]>0)
{min=flag[k];e=k;}
}
if (min==max)
{d=e;}
hamming=hamming+(x-max);
C[j]=B[d];
} printf("%s\n",C);
printf("%d\n",hamming);
}
return 0;
}
很显然第二个AC了,前一个内存问题太多WA了,改了很多遍还是WA,所以不能算法太复杂。