将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入:s = “PAYPALISHIRING”, numRows = 3
输出:“PAHNAPLSIIGYIR”
示例 2:
输入:s = “PAYPALISHIRING”, numRows = 4
输出:“PINALSIGYAHRPI”
解释:
P I N
A L S I G
Y A H R
P I
示例 3:
输入:s = “A”, numRows = 1
输出:“A”
提示:
1 <= s.length <= 1000
s 由英文字母(小写和大写)、‘,’ 和 ‘.’ 组成
1 <= numRows <= 1000
我们可以创建一个矩阵,在矩阵上按z字形填写字符串。但若我们设置m ∗ * ∗ n阶矩阵(m代表numRows行,n代表字符串长度),则无疑会浪费很多空间,因为有的位置没有填写字符。可以看出我们的输出是按行输出字符,且空字符不输出,所以我们在矩阵每一行添加字符时,可以直接添加到该行的现有字符串后面,从而形成一个压缩矩阵。最后输出答案时,只需按行读取字符。这样就节省了存储空间和遍历时间。
下面可以结合代码看一下具体实现。设r为当前行号(从数组下标0开始),transformFlag为遍历方向标识,当transformFlag为false,r向下遍历;当transformFlag为true,r向上遍历。当r遍历到第0行时,transformFlag变为false,r由向上遍历改为向下遍历;当r遍历到第numRows - 1行时,transformFlag变为true,r由向下遍历改为向上遍历。同时每遍历到某一行,就把当前字符加到该行所在的字符串末尾。
public String convert(String s, int numRows) {
if (numRows == 1) {
return s;
}
StringBuilder[] mat = new StringBuilder[Math.min(s.length(), numRows)];
for (int i = 0; i < mat.length; i++) {
mat[i] = new StringBuilder();
}
int r = 0;
//代表r的遍历方向,false代表r向下遍历,true代表r向上遍历
boolean transformFlag = false;
for (int i = 0; i < s.length(); i++) {
if (r == 0) {
transformFlag = false;
} else if (r == mat.length - 1) {
transformFlag = true;
}
if (!transformFlag) {
mat[r++].append(s.charAt(i));
} else {
mat[r--].append(s.charAt(i));
}
}
StringBuilder res = new StringBuilder();
for (int i = 0; i < mat.length; i++) {
res.append(mat[i]);
}
return res.toString();
}
时间复杂度: O ( N ) O(N) O(N)
空间复杂度: O ( N ) O(N) O(N)
如上图所示,以红圈圈出的部分为一个z字变换的周期,可以看出z字变换的周期长度为length = (numRows - 1) ∗ * ∗ 2。
以下部分结合代码进行理解。设i为当前遍历的行数(从0开始),j为当前行数遍历的字符位置,当i = 0或i = numRows - 1时,j的下一个字符位置即为j += length;否则,每个周期j要遍历的字符分两部分,第一次先将当前j位置的字符加到结果字符串末尾,然后j = j + (numRows - (i + 1)) ∗ * ∗ 2,第二次也先将当前j位置的字符加到结果字符串末尾,然后j = j + i ∗ * ∗ 2。
public String convert(String s, int numRows) {
if (numRows == 1) {
return s;
}
char[] c = new char[s.length()];
//一次z字遍历的长度
int length = (numRows - 1) * 2;
int index = 0;
for (int i = 0; i < numRows; i++) {
if (i == 0 || i == numRows - 1) {
for (int j = i; j < s.length(); j += length) {
c[index++] = s.charAt(j);
}
} else {
int j = i;
//false代表加前半截的字符,true代表加后半截的字符
boolean flag = false;
while (j < s.length()) {
c[index++] = s.charAt(j);
if (!flag) {
flag = true;
j = j + (numRows - (i + 1)) * 2;
} else {
flag = false;
j = j + i * 2;
}
}
}
}
return new String(c);
}
时间复杂度: O ( N ) O(N) O(N)
空间复杂度: O ( 1 ) O(1) O(1)