6. Z 字形变换
1.模拟遍历
-
模拟题目中给出的Z字转换规则,直接得出结果;
-
事先计算清楚结果有几行几列,方便直接使用下标遍历:
行数:即是题目中给出的numRows;
列数:可以将Z字转换分成一个个操作相同的周期。这里注意,如果将一个完整的“Z”视为一个周期,分析起来比较麻烦,真正的周期是“Z”少了下面一个横的形状。
不难看出,每个周期内的元素C=numRows(直线部分)+numRows-2(斜线部分);
因此周期数T=s.size()/周期内元素个数C;
每个周期有numRows-1列;
因此*列数为(numRows-1)s.size()/T;
以下图以numRows=4为例:
-
vector的构造:
vector的构造可以不规定大小,随着插入动态的增长:vector<string>Z_result;
也可以规定初始大小,可以按照下标直接访问:vector<string>Z_result(n);
在规定初始大小基础上,第二个参数还能初始化值,vector<string> Z_result(numRows, string(numCols, 0));
在这里填入了numRows个,numCols长度,初始值全是0的string,便于我们后面可以直接用二维坐标操作。
class Solution { public: string convert(string s, int numRows) { string result; int n=s.size(); if(numRows==1){//这种情况会展开成一行,直接返回,否则下面num_a_cycle计算会出现除0错误 return s; } int num_a_cycle=numRows+numRows-2;//每个周期内的节点数 int cycles=n/num_a_cycle+1;//总共有几个周期 int numCols=cycles*(numRows-1);//每一个周期有numRows-1列 vector<string> Z_result(numRows, string(numCols, 0)); int ind_X=0,ind_Y=0;//ind_Y代表纵向的坐标,ind_X代表横向的坐标 for(int i=0;i<n;i++){ Z_result[ind_Y][ind_X]=s[i]; if(i%num_a_cycle<numRows-1){//这里属于Z字型的横部分 ind_Y++; }else{//这里属于Z字型的斜部分 ind_X++; ind_Y--; } } // 可以用这部分代码输出Z字转换后的结果查看 // for(int i=0;i<numRows;i++){ // for(int j=0;j<numCols;j++){ // cout<<Z_result[i][j]; // } // cout<<endl; // } for(int i=0;i<numRows;i++){ for(int j=0;j<numCols;j++){ if(Z_result[i][j]){ result+=Z_result[i][j]; } } } return result; } };
2.对应规律
-
根据上面周期规律的分析,其实我们可以分析出Z转换后的矩阵和S中下标的对应规律;
-
不难发现,每个周期第一行和最后一行有一个元素,其他行都是两个;
-
假设当前在第T个周期,每个周期有元素C,这两个数的计算方式在方法1中。
对于第一行(我们的row从0开始编号)来说,下标都是每个周期的第一个元素,对应的下标就是0+T*C;
(我们不妨将当前在第几个周期T也从0开始编号);
那么第二行呢?第二行可能会有两个元素,我们先不讨论第二个元素,只看第一个:
下标都是每个周期的第二个元素,对应的下标是1+T*C;
同理可以推到第numRows行,对应的下标是 (numRows-1)+T*C;
-
我们再看其他行的第二个位置,其实可以根据改行第一个元素推理:
第2行:第一个位置,是该周期正数第2个元素,第二个位置,是该周期倒数第1个元素;
第3行:第一个位置,是该周期正数第3个元素,第二个位置,是该周期倒数第2个元素;
第i行:第一个位置,是该周期正数第i个元素,第二个位置,是该周期倒数第i-1个元素;
其实第二个位置和行数存在一定关系的,下标是T*C-i;
下图将元素序号和周期内元素个数进行了取余操作,便于理解周期性质。
class Solution { public: string convert(string s, int numRows) { string result; int n=s.size(); if(numRows==1){//这种情况会展开成一行,直接返回,否则下面num_a_cycle计算会出现除0错误 return s; } int num_a_cycle=numRows+numRows-2;//每个周期内的节点数 int cycles=n/num_a_cycle+1;//总共有几个周期 int numCols=cycles*(numRows-1);//每一个周期有numRows-1列 for(int i=0;i<numRows;i++){//i代表行数,进行遍历 int ind=i; if(i==0||i==numRows-1){//第一行和最后一行,每周期只有一个元素,在s中的坐标是行数的整数倍数 while(ind<n){ result+=s[ind]; ind+=num_a_cycle; } }else{//其他行一次处理两个元素 int T_cnt=1; while(ind<n){ result+=s[ind];//第一个元素仍然是行数的整数倍数 int pos2=num_a_cycle*T_cnt-i; if(pos2<n){//第二个元素需要判断是否超界 result+=s[pos2]; } ind+=num_a_cycle; T_cnt++; } } } return result; } };