6. N 字形变换

1. 简介

https://leetcode-cn.com/problems/zigzag-conversion/

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

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

1
2
3
P   A   H   N
A P L S I I G
Y I R

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

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

1
string convert(string s, int numRows);

示例 1:

1
2
输入:s = "PAYPALISHIRING", numRows = 3
输出:"PAHNAPLSIIGYIR"

示例 2:

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

示例 3:

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

提示:

  • 1 <= s.length <= 1000
  • s 由英文字母(小写和大写)、',''.' 组成
  • 1 <= numRows <= 1000

2. 题解

与其说是 Z 字形变换,不如说是 N 字形变换,整体上看可分成 2 步:先排列,后输出。

(1)先进行 Z 字形排列;

(2)逐行输出得到结果。

2.1. 按行访问

这种方式比较好理解,就是排列好之后,逐行从左往右读取字符。

(1)下图中,行0 中的字符,在原字符串中的索引为 k * (2 * numRows - 2) ,其中 k=0,1,2,...。故在下图的例子中,行0 字符在原字符串中对应的索引为 6k,从图中易知 行0 字符在原字符串中的索引分别是 0、6、12,满足该式。

(2)行numRows-1 中的字符,在原字符串中的索引为 k * (2 * numRows - 2) + numRows -1 ,其中 k=0,1,2,...。故在下图的例子中,行numRows-1 字符在原字符串中对应的索引为 6k+3,从图中易知 行numRows-1 字符在原字符串中的索引分别是 3 和 9,满足该式;

(3)中间的 行i 上的字符,在原字符串中的索引为 k * (2 * numRows - 2) + i 以及 (k + 1) * (2 * numRows - 2) - i ,其中 k=0,1,2,...。在我们的例子中,行i 字符在原字符串中对应的索引为 6k+i 以及 6(k+1)-i,从图中易知满足该式。

其实,从我们的例子中可以看出, 行0 中的 6k 其实可以看做 6k+0行numRows-1 中的 6k+3 其实都是 6k+ii 是行号。

所以除了首尾两行,我们都可以用 6k+i 来取字符,中间行还需要用 6(k+1)-i 来取字符。

根据上述内容,实现的 python 代码如下

  • 时间复杂度:$O(N)$
  • 空间复杂度:$O(N)$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution:
def convert(self, s: str, numRows: int) -> str:
if numRows==1:
return s
N = len(s)
cycle = 2 * numRows - 2
res = ""
for i in range(numRows):
for k in range(N):
j = k * cycle + i # ck+i
if j>=N:
break
res += s[j]
j = (k+1) * cycle - i # c(k+1)-i
if i!=0 and i!=(numRows-1) and j<N:
res += s[j]
return res

2.2. 按行排序

numRows 行字符串分别为 $s_1 , s_2 ,…, s_n$,容易发现按顺序遍历字符串 s1,每个字符 c 在 $Z$ 字形中对应的 行索引 先从 $s_1$ 增大至 $s_n$,再从 $s_n$ 减小至 $s_1$,如此反复。

可以使用当前行当前方向这两个变量对合适的行进行跟踪。只有当我们向上移动到最上面的行或向下移动到最下面的行时,当前方向才会发生改变。

根据上述内容,实现的 python 代码如下

  • 时间复杂度:$O(N)$
  • 空间复杂度:$O(N)$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def convert(self, s: str, numRows: int) -> str:
if numRows==1:
return s
res = ["" for _ in range(numRows)]
i = 0 # 当前行
flag = -1 # 当前方向,1向下,-1向上
for c in s:
res[i] += c
if i==0 or i==(numRows-1):
flag = -flag
i += flag # 前进,flag为1则向下,否在向上
res = "".join(res)
return res

参考资料

1. https://leetcode-cn.com/problems/zigzag-conversion/solution/zzi-xing-bian-huan-by-jyd/