6. Z 字形变换

6. Z 字形变换

✨核心逻辑

本题采用 模拟(按行存取)+ 方向控制 的策略:

  1. 特殊边界处理:如果行数 numRows 为 1,或者字符串长度小于等于行数(即根本撑不满 Z 字形),直接返回原字符串即可。
  2. 构建行容器:创建一个长度为 numRowsStringBuilder 数组,数组的每一个元素代表 Z 字形中的一行。
  3. 遍历字符并模拟写入:遍历字符串中的每一个字符,根据当前所在的行数 h,将其追加到对应的行容器中。
  4. 方向转向控制
    • 初始方向设为向下direction = 1)。
    • 当到达第 0 行(顶部)时,将方向修改为向下direction = 1),以便下一轮能往下走。
    • 当到达最后一行 numRows - 1(底部)时,将方向修改为向上direction = -1),以便下一轮能往上走。
    • 每次追加完字符后,行数 h 根据 direction 进行自增或自减。
  5. 重组结果:遍历结束后,将所有行的 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个数组,通过控制方向来添加元素,最后拼接即可