题目描述
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:
P | A | H | N | |||
A | P | L | S | I | I | G |
Y | I | R |
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。
示例 1:
输入:s = “PAYPALISHIRING”, numRows = 3
输出:“PAHNAPLSIIGYIR”
示例 2:
输入:s = “PAYPALISHIRING”, numRows = 4
输出:“PINALSIGYAHRPI”
P | I | N | ||||
A | L | S | I | G | ||
Y | A | H | R | |||
P | I |
示例 3:
输入:s = “A”, numRows = 1
输出:“A”
提示:
1 <= s.length <= 1000
s 由英文字母(小写和大写)、‘,’ 和 ‘.’ 组成
1 <= numRows <= 1000
解题思路
思路一:过程模拟
定义numRows个数组,用于存放每一行的所有字符,然后直接模拟排列过程,最后先按行连接再按列连接即可,空间复杂度较高。
易错点在于,当numRows == 1或numRows >= s.length时,需要特殊处理,直接返回s。
class Solution:
def convert(self, s: str, numRows: int) -> str:
if numRows == 1 or numRows >= len(s):
return s
all = [[] for i in range(numRows)]
row, dire = 0, 1
for i in range(len(s)):
all[row].append(s[i])
if row == numRows-1:
dire = -1
elif row == 0:
dire = 1
row += dire
res = ""
for i in range(numRows):
res += "".join(all[i])
return res
思路二:寻找排列规律
同一行的数字存在一定的规律,如果找到这个规律,那么只需要一次遍历就能够完成题解。
先来看看示例1,数字代表字符串的下标。
0 | 4 | 8 | 12 | |||
1 | 3 | 5 | 7 | 9 | 11 | 13 |
2 | 6 | 10 |
第一行:0,4,8,12(依次增4)
第二行:1,3,5,7,9,11,13(先增2再增2,2+2=4)
第三行:2,6,10(依次增4)
再来看看示例2,同上。
0 | 6 | 12 | ||||
1 | 5 | 7 | 11 | 13 | ||
2 | 4 | 8 | 10 | |||
3 | 9 |
第一行:0,6,12(依次增6)
第二行:1,5,7,11,13(先增4再增2,4+2=6)
第三行:2,4,8,10(先增2,再增4,2+4=6)
第四行:3,9(依次增6)
可以推得,排列的周期为T = 2*numRows-2。
对于第零行,每个周期内只产生一个字符,且字符的坐标x满足x%T=0。
对于第 i 行(0<i<numRows-1),每个周期产生两个字符,且第一个字符的坐标x满足x%T=i,第二个字符的坐标y满足x%T=T-i。
对于最后一行,每个周期产生一个字符,且字符的坐标x满足x%T=numRows-1。
if numRows == 1 or numRows >= len(s):
return s
T = 2*numRows-2
ans = ["" for i in range(numRows)]
for x in range(len(s)):
mod = x%T
if 0 <= mod <= numRows-1:
ans[mod] += s[x]
else:
ans[T-mod] += s[x]
return "".join(ans)
官方题解
思路一
class Solution:
def convert(self, s: str, numRows: int) -> str:
n, r = len(s), numRows
if r == 1 or r >= n:
return s
t = r * 2 - 2
c = (n + t - 1) // t * (r - 1)
mat = [[''] * c for _ in range(r)]
x, y = 0, 0
for i, ch in enumerate(s):
mat[x][y] = ch
if i % t < r - 1:
x += 1 # 向下移动
else:
x -= 1
y += 1 # 向右上移动
return ''.join(ch for row in mat for ch in row if ch)
# 时间复杂度:O(r⋅n),其中r=numRows,n 为字符串 s 的长度。时间主要消耗在矩阵的创建和遍历上,矩阵的行数为r,列数可以视为O(n)。
# 空间复杂度:O(r⋅n)。矩阵需要O(r⋅n) 的空间。
# 矩阵压缩
class Solution:
def convert(self, s: str, numRows: int) -> str:
n, r = len(s), numRows
if r == 1 or r >= n:
return s
t = r * 2 - 2
c = (n + t - 1) // t * (r - 1)
mat = [[''] * c for _ in range(r)]
x, y = 0, 0
for i, ch in enumerate(s):
mat[x][y] = ch
if i % t < r - 1:
x += 1 # 向下移动
else:
x -= 1
y += 1 # 向右上移动
return ''.join(ch for row in mat for ch in row if ch)
# 时间复杂度:O(n)。
# 空间复杂度:O(n)。压缩后的矩阵需要 O(n) 的空间。
思路二
class Solution:
def convert(self, s: str, numRows: int) -> str:
n, r = len(s), numRows
if r == 1 or r >= n:
return s
t = r * 2 - 2
ans = []
for i in range(r): # 枚举矩阵的行
for j in range(0, n - i, t): # 枚举每个周期的起始下标
ans.append(s[j + i]) # 当前周期的第一个字符
if 0 < i < r - 1 and j + t - i < n:
ans.append(s[j + t - i]) # 当前周期的第二个字符
return ''.join(ans)
# 时间复杂度:O(n),其中 n 为字符串 s 的长度。s 中的每个字符仅会被访问一次,因此时间复杂度为O(n)。
# 空间复杂度:O(1)。返回值不计入空间复杂度。