将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
第一种算法
自己没看答案前,自己写的,主要思路是:根据给定的字符串的长度和行数,算出一共多少列,然后将字符串再按照要求按列依次填入二维数组中,最后再按照行进行读取字符串。
代码如下,附有详细的注释:
def convert1(s, numRows):
# 如果为1行,就直接返回原字符串
if numRows == 1:
return s
# 记录字符串长度
n = len(s)
# 行数
row = numRows
# 计算列数
# 假设每row+(row-2)个字符为一组,这一个组的字符个数表示为:
g_nums = row + (row - 2)
# 那么这一组所占的列数为
g_column = 1 + (row - 2)
# 计算字符串中共有多少个组
gs = n // g_nums
# 余下的字符个数
rest_num = n % g_nums
# 计算剩下的不够一组的字符占多少列,默认为0
o_column = 0
# 余下的字符个数小于等于行数,刚好占一列
if 1 <= rest_num <= row:
o_column = 1
# 余下的字符个数大于行数,列数等于余下的个数减去行数 +1
elif rest_num > row:
o_column = 1 + (rest_num - row)
# 计算总的列数
column = gs * g_column + o_column
# 创建二维数组,没有字符的,用"*"占位
# 不能[["*"] * column] * row 这么创建,因为里面每个元素没有自己的存储空间
arr = [["*"] * column for _ in range(row)]
# 先是一列一列的按列遍历,依次"Z"字形填入字符串
# 记录遍历到第几组数据,默认为0
for j in range(column):
for i in range(row):
# 当在完整的一个组时
if j < gs * g_column:
# 第一个条件:当整列都有数据的列时
# 第二个条件:当一列只有一个数据时,找规律有如下关系
if j % g_column == 0 or i == (g_column - j % g_column):
arr[i][j] = s[0]
# 每次赋值后,将赋值的那个字符舍弃,这样每次都是拿第一个字符
s = s[1:]
# 当余下的不够一个组时
else:
# 赋值时要保证字符串还有字符
# 余下的一组中的第一列或者其他列时
if len(s) > 0 and ((j - gs * g_column) == 0 or i == (g_column - j % g_column)):
arr[i][j] = s[0]
# 如果字符串为空了,就退出遍历
s = s[1:]
if len(s) == 0:
break
# 按行读取字符串
# 记录读取后的字符串
rs = ""
for i in range(row):
for j in range(column):
if arr[i][j] != "*":
rs += arr[i][j]
return rs
第二种方法
参考力扣官方答案和精选答案,我们可以从左到右迭代 s,将每个字符添加到合适的行。可以使用当前行和当前方向这两个变量对合适的行进行跟踪。只有当我们向上移动到最上面的行或向下移动到最下面的行时,当前方向才会发生改变。
代码如下,附有详细注释:
def convert2(s, numRows):
# 如果行数等于1,就直接返回原字符串
if numRows == 1:
return s
# 可能存在字符串的长度比给的行数小,那么只需要字符串长度的行就可以了
row = min(numRows, len(s))
# 声明一个字符串数组,用于记录每行的字符串
row_strs = ["" for _ in range(row)]
# 默认当前行为0,即第一行
current_row = 0
# 记录是不是方向向下,默认为false
going_down = False
# 遍历字符串,将每个字符加到合适的行
for c in s:
row_strs[current_row] += c
# 当在第一行或者最后一行时,改变方向
if current_row == 0 or current_row == row - 1:
going_down = not going_down
# 如果going_down为true,当前行+1,否则-1
current_row += 1 if going_down else -1
# 将数组拼接成字符串,即是要求的字符串
return "".join(row_strs)
复杂度分析
- 时间复杂度 O(N) :遍历一遍字符串 s;
- 空间复杂度 O(N) :各行字符串共占用 O(N) 额外空间。
第三种方法
参考力扣精选答案,采用了数学规律法,即是找出数学规律,然后按行访问直接拼接成最终需要的字符串。
规律如下:
我们先假定有 numRows=4 行来推导下,其中 2numRows-2 = 6 , 我们可以假定为 step=2numRows-2 ,我们先来推导下规则:
第0行: 0 - 6 - 12 - 18
==> 下标间距 6 - 6 - 6 ==> 下标间距 step —— step —— step
第1行: 1 - 5 - 7 - 11 - 13
==> 下标间距 4 - 2 - 4 - 2 ==> 下标间距step-2 * 1(行) ——2 * 1(行)——step-2 * 1(行)——2 * 1(行)
第2行: 2 - 4 - 8 - 10 - 14
==> 下标间距 2 - 4 - 2 - 4 ==> 下标间距step-2 * 2(行)——2 * 2(行)——step-2 * 2(行)——2 * 2(行)
第3行:3 - 9 - 15 - 21
==> 下标间距间距 6 - 6 - 6 ==>下标间距step - step - step
可以得出以下结论:
- 起始下标都是行号
- 第0层和第numRows-1层的下标间距总是step 。
- 中间层的下标间距总是step-2行数,2行数交替。
- 下标不能超过len(s)-1
代码如下:
def convert3(s, numRows):
if numRows == 1: return s
res = "" # 声明一个返回的字符串
step = numRows * 2 - 2 # 下标间距
index = 0 # 记录s的下标,从0开始
n = len(s) # 字符串长度
add = 0 # 真实的下标间距
for i in range(numRows): # i 表示行号
index = i
add = i * 2
while index < n: # 当下标超出字符串长度就计算下一层
res += s[index] # 当前行的第一个字符
add = step - add # 第一次的间距是step - 2*i,第二次是2*i
# 第0行和最后一行使用step间距,其他行都使用add间距
if i == 0 or i == numRows - 1:
index += step
else:
index += add
return res # 返回结果字符串