题目描述

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时,排列如下:
L C I R
E T O E S I I G
E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“LCIRETOESIIGEDHN”。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:

输入: s = “LEETCODEISHIRING”, numRows = 3
输出: “LCIRETOESIIGEDHN”
示例 2:
输入: s = “LEETCODEISHIRING”, numRows = 4
输出: “LDREOEIIECIHNTSG”
解释:
L D R
E O E I I
E C I H N
T S G

题目分析

由题可知,这道题的目的是给定一个字符串以及排列的行数r,将字符串的所有字符按照r行的Z字形(应该是N字形)排列,并将排列后的字符串按行遍历输出。什么意思呢?比如说给定一个字符串“0123456789”,如果r=3,那么排列后的结果就应该是:
Z字形变换算法分析及优化(O(k*n)到O(n))
按行遍历结果即是 048 13579 26 …
如果r=4,那么排列后的结果即是:
Z字形变换算法分析及优化(O(k*n)到O(n))
按行遍历结果即是06 157 248 39…
如果把“10”看做字符,那么整个排列情况将是如下所示:
Z字形变换算法分析及优化(O(k*n)到O(n))
那么该怎么分析呢?由上面的排列情况可以知道,整个“Z字形”可以看作是上图中很多斜着的“V”字拼接而成,仔细观察可以发现,当n=3时,第一行的数据其所对应的原字符串中相应字符的下标刚好都是4的倍数,当n=4时,第一行的数据其所对应的原字符串中相应字符的下标刚好都是6的倍数,由此可以推断,当n=k时,第一行的数据其所对应的原字符串中相应字符的下标刚好都是2n-2的倍数,这样就能确定第一行的字符在原字符串中的下标了,那么第二行、第三行……第n行呢?可以发现,第二行的数据刚好就是其减1或者加1就是2n-2的倍数了,比如图中的1,7,13…减去1;5,11加上1后即是2n-2的倍数……第n行就是其相应下标减去或加上n-1后即是2n-2的倍数了,根据这个原理即可写出相应程序如下:

class Solution {
public:
    string convert(string s, int numRows) {
        int len=s.size();
        string res;
        int temp=2*numRows-2;
        if(!temp)return s;
        for(int i=0;i<numRows;i++)
        {
            for(int j=0;j<len;j++)
            {
                if((i+j)%temp ==0 || (j-i)%temp ==0)res+=s[j];
            }
        }
        return res;
    }
};

运行结果如下图所示:
Z字形变换算法分析及优化(O(k*n)到O(n))
可以看出这种算法是非常耗时的,分析一下可以知道算法对于每一层字符进行相应下标寻找时都会对整个字符串进行遍历,算法复杂度为O(k*N),其中k为Z字排列的行数。很明显有很多遍历是重复的没用必要的,因此就应该思考如何去优化这个算法。
实际上,上述算法并没有很好利用到周期性的特点,比如说遍历第一层时,遍历完‘0’后本来应当直接遍历‘6’,结果却把‘1’~‘5’都遍历了,为了对其进行优化,那么就应当遍历完‘0’后直接遍历‘6’,即遍历的步长应设为6;同样的,到了第二层时,遍历步长也应当设为6,但是这样一来遍历完‘1’后本来应当遍历‘5’,而实际上却遍历到了‘7’,那么该如何在遍历完‘1’后遍历‘5’呢,其实很简单,可以发现‘7’和‘5’是关于转折点对称的,即是各自与‘6’的距离是相同的,如果将‘7’对应的下标除余周期6,得到的就是‘7’的下标与‘6’所在下标的差值,再用‘7’的下标去减去这个差值的两倍,就可以得到‘5’的下标了。第n层以此类推。
不过这里还需要考虑到边界条件,比如说,遍历到‘0’、‘6’以及’3’、'9’时,如果按照上述方法,那么就会对同一个字符遍历两次,因此当遍历到这些Z字转折点时,就不需要去寻找其相应的“对称点”了。
由此优化算法如下:

class Solution {
public:
    string convert(string s, int numRows) {
        int len=s.size();
        if(numRows==1)return s;
        string res;
        int temp=2*numRows-2;
        for(int i=0;i<numRows;i++)
        {
            for(int j=i;j<len;j+=temp)
            {
                res+=s[j];
                int x=j+temp-2*(j%temp);   //对称点的下标
                if(x!=j&&x<len&&x%temp)res+=s[x];  //x!=j排除底部转折点,x%temp排除顶部转折点
            }
        }
        return res;
    }
};

优化后的运行结果如下:
Z字形变换算法分析及优化(O(k*n)到O(n))
可以发现,优化后的算法时间效果好了许多,其主要原因是优化后的算法只需对每个字符遍历一次,即算法复杂度为O(N)。

相关文章: