面试经典150题[022]:Z 字形变换(LeetCode 6)

Z 字形变换(LeetCode 6)

题目链接:Z 字形变换(LeetCode 6)

难度:中等

1. 题目描述

给定一个字符串 s 和行数 numRows,按照 Z 字形从上到下、从左到右排列字符串,然后逐行读取生成新字符串。

  • 字符串长度 1 <= s.length <= 1000,由英文字母(大小写)、',' 和 '.' 组成。
  • 行数 1 <= numRows <= 1000

示例:

复制代码
输入: s = "PAYPALISHIRING", numRows = 3
输出: "PAHNAPLSIIGYIR"
解释:
P   A   H   N
A P L S I I G
Y   I   R

输入: s = "PAYPALISHIRING", numRows = 4
输出: "PINALSIGYAHRPI"
解释:
P     I    N
A   L S  I G
Y A   H R
P     I

输入: s = "A", numRows = 1
输出: "A"

2. 问题分析

2.1 规律

  • Z 字形排列是一个周期性模式:
    • 每个周期包含 numRows 个字符向下排列,再斜向上排列到第一行。
    • 周期长度为 2 * numRows - 2(向下 numRows 步,向上 numRows - 2 步)。
    • 特殊情况:
      • numRows = 1 时,直接返回原字符串。
      • numRows = 2 时,周期为 2,交替填充两行。
  • 最终输出需要按行读取,行内字符按顺序拼接。
  • 核心问题:如何确定每个字符在 Z 字形中的位置,并按行输出?

2.2 解法思路

方法 1:按行分配(直观模拟)
  • 模拟 Z 字形排列的过程:
    • 遍历字符串 s,按照 Z 字形的路径分配字符到对应行。
    • 使用一个数组(或字符串列表)存储每行的字符。
    • 跟踪当前行和移动方向(向下或向上)。
    • 最后按行拼接结果。
  • 优点:直观,易于理解;空间复杂度较高。
方法 2:按行访问(数学规律)
  • 直接计算每个字符在 Z 字形中的行号,优化空间复杂度。
  • 每个周期的字符位置有规律:
    • i 行的字符索引可以通过周期公式计算。
    • 第一行和最后行的字符索引间隔为 2 * numRows - 2
    • 中间行的字符有两个来源:周期内的向下和斜向上。
    • 向下的字符坐标:当前周期起始点+当前行号。
    • 斜向上的字符坐标:下一个周期的起始点-当前行号。
  • 优点:空间复杂度 O(1),仅需输出字符串的空间。
  • 本题采用 方法 2,因为它更高效且空间复杂度低。

2.3 示例

s = "PAYPALISHIRING", numRows = 4 为例:

复制代码
P     I    N
A   L S  I G
Y A   H R
P     I
  • 周期长度 = 2 * numRows - 2 = 6
  • 第 0 行:P (0), I (6), N (12) → 索引间隔 6。
  • 第 1 行:A (1), L (4), S (7), I (10) → 索引规律为周期内和周期间。
  • 第 2 行:Y (2), A (3), H (8), R (9)。
  • 第 3 行:P (5), I (11)。
  • 按行拼接:PINALSIGYAHRPI

3. 代码实现

Python

python 复制代码
class Solution:
    def convert(self, s: str, numRows: int) -> str:
        if numRows == 1 or numRows >= len(s):
            return s
        
        result = []
        n = len(s)
        cycle = 2 * numRows - 2
        
        for i in range(numRows):
            for j in range(0, n, cycle):
                # 第一行和最后行
                if i == 0 or i == numRows - 1:
                    if j + i < n:
                        result.append(s[j + i])
                # 中间行
                else:
                    # 周期内的字符
                    if j + i < n:
                        result.append(s[j + i])
                    # 周期间的字符(斜向上)
                    if j + cycle - i < n:
                        result.append(s[j + cycle - i])
        
        return ''.join(result)

C++

cpp 复制代码
class Solution {
public:
    string convert(string s, int numRows) {
        if (numRows == 1 || numRows >= s.length()) {
            return s;
        }
        
        string result;
        int n = s.length();
        int cycle = 2 * numRows - 2;
        
        for (int i = 0; i < numRows; ++i) {
            for (int j = 0; j < n; j += cycle) {
                // 第一行和最后行
                if (i == 0 || i == numRows - 1) {
                    if (j + i < n) {
                        result += s[j + i];
                    }
                }
                // 中间行
                else {
                    // 周期内的字符
                    if (j + i < n) {
                        result += s[j + i];
                    }
                    // 周期间的字符(斜向上)
                    if (j + cycle - i < n) {
                        result += s[j + cycle - i];
                    }
                }
            }
        }
        
        return result;
    }
};

4. 复杂度分析

  • 时间复杂度 :O(n),其中 n 是字符串 s 的长度。每个字符最多被访问一次。
  • 空间复杂度
    • Python:O(n),用于存储结果字符串(result 列表)。
    • C++:O(1),不计输出字符串的空间(result 直接追加)。

5. 总结

  • Z 字形变换 :核心是理解周期性规律,周期长度为 2 * numRows - 2
  • 按行访问方法利用数学规律,高效且空间优化。
  • 特殊情况(numRows = 1numRows >= n)需提前处理。
  • 可扩展到变体问题,如动态行数或不同排列方式。
相关推荐
好好研究3 小时前
力扣字符串刷题-六道题记录-1
数据结构·算法·leetcode
围巾哥萧尘3 小时前
创始人终极指南:2025年如何创办精益的AI原生创业公司🧣
面试
知其然亦知其所以然3 小时前
别再只会背八股了!一文带你彻底搞懂UNION与UNION ALL的区别
后端·mysql·面试
就是帅我不改3 小时前
99%的Java程序员都踩过的高并发大坑
后端·面试
GHOME3 小时前
复习-网络协议
前端·网络协议·面试
张诗焕3 小时前
Java面试小白-Redis缓存篇
面试
沐怡旸3 小时前
【算法--链表】25.K个一组翻转链表--通俗讲解
算法·面试
快去睡觉~3 小时前
力扣190:颠倒二进制位
数据结构·算法·leetcode
南北是北北3 小时前
Flow 里的上游/下游
前端·面试