字符串题目:Z 字形变换

文章目录

题目

标题和出处

标题:Z 字形变换

出处:6. Z 字形变换

难度

4 级

题目描述

要求

将一个给定字符串 s \texttt{s} s 根据给定的行数 numRows \texttt{numRows} numRows,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 "PAYPALISHIRING" \texttt{"PAYPALISHIRING"} "PAYPALISHIRING",行数为 3 \texttt{3} 3 时,排列如下:

P   A   H   N
A P L S I I G
Y   I   R

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如: "PAHNAPLSIIGYIR" \texttt{"PAHNAPLSIIGYIR"} "PAHNAPLSIIGYIR"。

请你实现这个将字符串进行指定行数变换的函数:

string   convert(string   s,   int   numRows); \texttt{string convert(string s, int numRows);} string convert(string s, int numRows);

示例

示例 1:

输入: s   =   "PAYPALISHIRING",   numRows   =   3 \texttt{s = "PAYPALISHIRING", numRows = 3} s = "PAYPALISHIRING", numRows = 3
输出: "PAHNAPLSIIGYIR" \texttt{"PAHNAPLSIIGYIR"} "PAHNAPLSIIGYIR"

示例 2:

输入: s   =   "PAYPALISHIRING",   numRows   =   4 \texttt{s = "PAYPALISHIRING", numRows = 4} s = "PAYPALISHIRING", numRows = 4
输出: "PINALSIGYAHRPI" \texttt{"PINALSIGYAHRPI"} "PINALSIGYAHRPI"
解释:

P     I    N
A   L S  I G
Y A   H R
P     I

示例 3:

输入: s   =   "A",   numRows   =   1 \texttt{s = "A", numRows = 1} s = "A", numRows = 1
输出: "A" \texttt{"A"} "A"

数据范围

  • 1 ≤ s.length ≤ 1000 \texttt{1} \le \texttt{s.length} \le \texttt{1000} 1≤s.length≤1000
  • s \texttt{s} s 由英文字母(小写和大写)、 ‘,’ \texttt{`,'} ‘,’ 和 ‘.’ \texttt{`.'} ‘.’ 组成
  • 1 ≤ numRows ≤ 1000 \texttt{1} \le \texttt{numRows} \le \texttt{1000} 1≤numRows≤1000

解法

思路和算法

首先考虑最简单的情形:当 numRows = 1 \textit{numRows}=1 numRows=1 时,将字符串 s s s 按照 Z 字形排列只有 1 1 1 行,且字符顺序不变,因此在 Z 字形变换之后的字符串仍然是 s s s,直接返回 s s s。

当 numRows ≥ 2 \textit{numRows} \ge 2 numRows≥2 时,Z 字形排列会有多行。在生成 Z 字形排列时,需要从左到右遍历字符串 s s s,对于每个字符,计算得到该字符位于 Z 字形排列的哪一行,然后将该字符拼接到所在行。

每个字符在 Z 字形排列中的行下标为从 0 0 0 增加到 numRows − 1 \textit{numRows}-1 numRows−1,然后减少到 0 0 0,重复在该范围内变化。因此需要维护行下标 rowIndex \textit{rowIndex} rowIndex 和变化方向 direction \textit{direction} direction,初始时 rowIndex = 0 \textit{rowIndex}=0 rowIndex=0, direction = 1 \textit{direction}=1 direction=1。遍历 s s s 的每个字符时,将该字符拼接到下标为 rowIndex \textit{rowIndex} rowIndex 的行,然后将 rowIndex \textit{rowIndex} rowIndex 的值增加 direction \textit{direction} direction,则更新后的 rowIndex \textit{rowIndex} rowIndex 为下一个字符应该拼接到的行的下标。由于行下标的取值范围是 [ 0 , numRows − 1 ] [0, \textit{numRows}-1] [0,numRows−1],因此当更新后的 rowIndex \textit{rowIndex} rowIndex 为 0 0 0 或 numRows − 1 \textit{numRows}-1 numRows−1 时,需要将 direction \textit{direction} direction 取相反数,表示下次行下标的更新需要往相反方向移动。

对 s s s 遍历结束之后,即可得到 Z 字形排列的每一行,将每一行拼接之后,即可得到 Z 字形变换后的结果。

具体实现方面,由于涉及到字符串的拼接,因此使用 StringBuffer \texttt{StringBuffer} StringBuffer 实现。创建长度为 numRows \textit{numRows} numRows 的 StringBuffer \texttt{StringBuffer} StringBuffer 类型数组,该数组的每个元素对应 Z 字形排列的每一行。在拼接每一行得到最终答案时,同样使用 StringBuffer \texttt{StringBuffer} StringBuffer 类型进行拼接。

还有一个可以优化的点。由于 Z 字形排列的每一行都至少有一个字符,因此 Z 字形排列的行数不会超过 s s s 的长度。当 s s s 的长度为 n n n 时,Z 字形排列的行数为 min ⁡ ( numRows , n ) \min(\textit{numRows}, n) min(numRows,n),因此在生成 Z 字形排列之前,令 numRows = min ⁡ ( numRows , n ) \textit{numRows} = \min(\textit{numRows}, n) numRows=min(numRows,n),可以达到优化的效果。

代码

class Solution {
    public String convert(String s, int numRows) {
        if (numRows == 1) {
            return s;
        }
        int length = s.length();
        numRows = Math.min(numRows, length);
        StringBuffer[] rows = new StringBuffer[numRows];
        for (int i = 0; i < numRows; i++) {
            rows[i] = new StringBuffer();
        }
        int rowIndex = 0, direction = 1;
        for (int i = 0; i < length; i++) {
            rows[rowIndex].append(s.charAt(i));
            rowIndex += direction;
            if (rowIndex == 0 || rowIndex == numRows - 1) {
                direction = -direction;
            }
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < numRows; i++) {
            sb.append(rows[i]);
        }
        return sb.toString();
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。创建 StringBuffer \texttt{StringBuffer} StringBuffer 类型的数组并初始化元素需要 O ( numRows ) O(\textit{numRows}) O(numRows) 的时间,生成 Z 字形排列和拼接得到 Z 字形变换后的结果各需要 O ( n ) O(n) O(n) 的时间,由于会对 numRows \textit{numRows} numRows 预处理确保 numRows ≤ n \textit{numRows} \le n numRows≤n,因此总时间复杂度是 O ( n ) O(n) O(n)。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。Z 字形排列的结果需要 O ( n ) O(n) O(n) 的空间存储,Z 字形变换后的结果也需要 O ( n ) O(n) O(n) 的空间存储。由于 Java 中的 String \texttt{String} String 类型的对象不可变,因此存储 Z 字形变换后的结果的 O ( n ) O(n) O(n) 空间无法省略。

上一篇:猜数字游戏


下一篇:字符串题目:写字符串需要的行数