6. Z 字形变换
✨核心逻辑
本题采用 模拟(按行存取)+ 方向控制 的策略:
- 特殊边界处理:如果行数
numRows为 1,或者字符串长度小于等于行数(即根本撑不满 Z 字形),直接返回原字符串即可。 - 构建行容器:创建一个长度为
numRows的StringBuilder数组,数组的每一个元素代表 Z 字形中的一行。 - 遍历字符并模拟写入:遍历字符串中的每一个字符,根据当前所在的行数
h,将其追加到对应的行容器中。 - 方向转向控制:
- 初始方向设为向下(
direction = 1)。 - 当到达第 0 行(顶部)时,将方向修改为向下(
direction = 1),以便下一轮能往下走。 - 当到达最后一行
numRows - 1(底部)时,将方向修改为向上(direction = -1),以便下一轮能往上走。 - 每次追加完字符后,行数
h根据direction进行自增或自减。
- 初始方向设为向下(
- 重组结果:遍历结束后,将所有行的
StringBuilder按顺序(从上到下)拼接在一起,即为结果。
🔥代码实现(含详细变量注释)
class Solution {
public String convert(String s, int numRows) {
// 判断极端情况:如果只需要排一行,或者字符串很短无法形成多行Z字,直接返回原字符串
if (numRows == 1 || s.length() <= numRows) {
return s;
}
// sb:创建一个 StringBuilder 数组,长度为 numRows,每个位置代表 Z 字形的一行
StringBuilder[] sb = new StringBuilder[numRows];
// 初始化数组中的每个 StringBuilder 对象
for (int i = 0; i < numRows; i++) {
sb[i] = new StringBuilder();
}
// direction:控制字符填充的方向,1 表示向下移动,-1 表示向上移动
int direction = 1;
// h:当前字符应该填入的行索引,初始从第 0 行开始填充
int h = 0;
// 遍历输入字符串 s 的每一个字符
for (int i = 0; i < s.length(); i++) {
// c:当前遍历到的字符
char c = s.charAt(i);
// 将当前字符追加到它对应的行 sb[h] 中
sb[h].append(c);
// 到达 Z 字形的顶部(第 0 行)时,下一次必须向下走
if (h == 0) {
direction = 1;
}
// 到达 Z 字形的底部(最后一行 numRows - 1)时,下一次必须向上走
else if (h == numRows - 1) {
direction = -1;
}
// 根据方向更新行索引 h(向下 +1,向上 -1),准备处理下一个字符
h += direction;
}
// s1:用于拼接最终结果,按从上到下的顺序,将每一行的 StringBuilder 内容合并起来
StringBuilder s1 = new StringBuilder();
for (StringBuilder st : sb) {
s1.append(st);
}
// 返回最终生成的 Z 字形字符串
return s1.toString();
}
}
- ⏱️复杂度分析
时间复杂度:O(N),其中 N 是字符串 s 的长度。我们需要遍历字符串一次,将每个字符追加到对应的 StringBuilder 中;最后拼接时遍历一次 sb 数组,总体是线性时间。
空间复杂度: O(N),主要用于维护 numRows 个 StringBuilder 来存放字符(最终存放了所有 N 个字符),以及构建结果字符串 s1 的开销
🔍总结一下:
觉得困扰的难点是输的字符串方向以及输出的字符串方向不同,且无法控制 Z 这个方向,无从下手
代码的巧妙之处在于创建出输入的行数个的StringBuilder个数组,通过控制方向来添加元素,最后拼接即可