题设:
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:
L C I R
E T O E S I I G
E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"。
命原字符串为s,行数为numRows
很明显:这是图形是循环的,每个循环节可以被视为一组(如题设例子中的第一组LEET),既然输出是按行输出的,那么我们在读取的时候也考虑按行顺序逐个读取(如题设例子中第一行LCIR),每组的个数可以确定(numRows*2-2),然后每行在s中跳跃读取,只需要注意除开首尾两行,每行中每组都有两个字符(如题设例子中第一组的ET)。
考虑到代码的简洁性,应该将这两类情况处理在同一个循环内部,然后用if进行讨论,这里明显要进行两层循环:按行,按组;组循环在外部的话需要讨论除开完整组之外的剩余字符(如题设例子再多加一个字符,除开完整的4组,还会多一个字符出来),但是行循环为外部循环的话,就不需要考虑这个问题,同时内部的组循环只需要判断是否超出s边界就可以了,因此内部组循环从根据组数计算索引从s中逐个取出相连直到完整组全部取完,变为根据索引是否超出边界就可以了,前者还需要对不完整组进行单独讨论。
繁杂代码(但是由于讨论的情况较多,而且循环采用range的for循环而非while循环,效率更高(才怪):
def convert(self, s: str, numRows: int) -> str:
L = len(s)
conGroup = 2*numRows-2
if conGroup == 0:
return s
numGroup = L//conGroup
out = ""
if L == 0:
return ""
if L < conGroup:
out += s[0]
for i in range(1,numRows-1):
if i < L:
out += s[i]
if conGroup-i < L:
out += s[conGroup-i]
if L >= numRows:
out += s[numRows - 1]
return out
for i in range(0, numGroup):
out += s[i*conGroup]
if L%conGroup>0:
out += s[(i+1)*conGroup]
for i in range(1,numRows-1):
for j in range(0, numGroup):
out += s[j*conGroup+i]+s[(j+1)*conGroup-i]
if (j+1)*conGroup+i < L:
out += s[(j+1)*conGroup+i]
if (j+2)*conGroup-i < L:
out += s[(j+2)*conGroup-i]
for i in range(0, numGroup):
out += s[i*conGroup+numRows-1]
if L%conGroup>=numRows:
out += s[(i+1)*conGroup+numRows-1]
return out
优化后的代码:
def convert(self, s: str, numRows: int) -> str:
L = len(s)
conGroup = 2*numRows-2
if conGroup == 0:
return s
out = ""
for i in range(0, numRows):
j = 0
while i+j < L:
out += s[i + j]
if 0<i<numRows-1 and j+conGroup-i<L:
out += s[j+conGroup-i]
j += conGroup
return out