Z字形变换
- 我的想法:找规律,看Z字形每个字符的索引在每一行上的规律,发现是有规律的。规律是有的,但是涉及到的i,j,k迭代的变量挺多的,自己没绕出来。我这里的方法对应下面官方讲解的第二个方法,可直接跳到这里。
碎碎念:看了官方对规律的描述,发现它的规律与我的规律的描述差距挺大的。我的规律是相对每一行来说的,而官方的规律总是相对
numRows
来描述的,所以更容易表达出来,因为numRows
是个定值!而我找规律的基准是变化的,难怪没有绕出来,没法写出来…不过对于官方的规律,也不太容易想到吧。
方法一:按行排序
思路
从左向右迭代原始字符串,将每个字符都直接保存到其位于 Z 字形图案中的那一行。
算法
算法的关键在于如何找准每个字符应该存储到的行,我们观察示例可知,存储了一个字符后,要么就是在其下一行继续存储下一个字符,要么就是在其上一行继续存储下一个字符(注意,无须在意如何完全将那个Z字形打出来!关键是按照Z字形进行字符的重新保存!不要想偏了),所以我们可以使用变量curRow
表示当前行,然后按照规则分别对curRow
进行加1或减1操作。那么,这里的“规则”是怎样的呢?答:从第一行开始,存储完一个字符后每次都要进行加1操作,知道存储到了最后一行,然后开始进行减1操作。所以我们可以对第一行和最后一行设置一个goingDown
标志;goingDown
为True
时,表示改变当前行的操作是加1,goingDown
为False
时,表示改变当前行的操作是减1。然后我们可以借助一个列表rows
保存这numRows
行的每行的字符串,当然,需要初始化这些字符串为空。此时,curRow
就是rows
的下标,所以Z字形的某行字符串就是rows[curRow]
。
def convert(s, numRows):
if numRows == 1:
return s
rows = [''] * min(numRows, len(s)) # 相当于创建了一个“数组”
curRow = 0
'''
goingDown为True: curRow为前(numRows - 1)的情况
goingDown为False: curRow为最后一行numRows的情况
'''
goingDown = False
for ch in s:
rows[curRow] += ch
if curRow == 0 or curRow == numRows - 1:
goingDown = (not goingDown)
'''
goingDown为True时,下一当前行直接为(curRow + 1);
goingDown为False时,下一当前行需要跳转到前一行,所以(curRow - 1)
'''
curRow += (1 if goingDown == True else -1)
s_return = ''.join(rows)
return s_return
print(convert("LEETCODEISHIRING", 3))
复杂度分析:
方法二:按行访问
思路
按照与逐行读取 Z 字形图案相同的顺序访问字符串。
算法
对于Z字形中每一行的第k个字符(k从0开始),有:
- 第一行中的字符的索引:
k * ( 2 * numRows - 2 )
- 最后一行中的字符的索引:
k * ( 2 * numRows - 2 ) + numRows - 1
- 中间行 i 中的字符的索引:
k * ( 2 * numRows - 2 ) + i
以及( k + 1 ) * ( 2 * numRows - 2 ) - i
def convert(s, numRows):
if numRows == 1:
return s
s_return = ''
length = len(s)
cycleLen = 2 * numRows - 2
for i in range(numRows):
k = 0
while k * cycleLen + i < length:
if i == 0:
s_return += s[k * cycleLen]
elif i == numRows - 1:
s_return += s[k * cycleLen + numRows - 1]
else:
s_return += s[k * cycleLen + i]
x = ( k + 1 ) * cycleLen - i
if x < length:
s_return += s[x]
k += 1
return s_return
print(convert("PAYPALISHIRING", 3))
优化
观察上面的规律,可以发现:第一行 (i = 0)
以及最后一行 (i = numRows - 1)
中的字符的索引又可以归一成:k * ( 2 * numRows - 2 ) + i
。
def convert(s, numRows):
if numRows == 1:
return s
s_return = ''
length = len(s)
cycleLen = 2 * numRows - 2
for i in range(numRows):
for k in range(0, length, cycleLen):
if k + i < length:
s_return += s[k+i]
if (i != 0 and i != numRows-1 and k + cycleLen - i < length):
s_return += s[k + cycleLen - i]
return s_return
print(convert("PAYPALISHIRING", 3))