LeetCode刷题笔记(Python3)——6. Z 字形变换(难度:中等)(2021-05-12)

没有按照顺序做,因为第4题是困难题,虽然代码通过了测试,但解法存在两层进阶,需要仔细整理,待后面专门抽时间整理;又因为把第6题想简单了,所以也临时跳过了第5题。

LeetCode刷题笔记(Python3)——6. Z 字形变换
(点击查看题目)
(点击查看官方题解)

注意:此题的官方题解没有Python代码,但提供了两种解题思路:按行排序和按列排序。

一、解题思想和算法

此题有两种解题思路——按列排序和按行访问。(参考官方题解

1.1 按列排序

思路
通过从左向右迭代字符串,我们可以轻松地确定字符位于 Z 字形图案中的哪一行。

算法
我们可以使用 m i n ( n u m R o w s , l e n ( s ) ) min(numRows,len(s)) min(numRows,len(s)) 个列表来表示 Z 字形图案中的非空行。

从左到右迭代 s s s,将每个字符添加到合适的行。可以使用当前行和当前方向这两个变量对合适的行进行跟踪。

只有当我们向上移动到最上面的行或向下移动到最下面的行时,当前方向才会发生改变。

1.2 按行访问

思路
按照与逐行读取 Z 字形图案相同的顺序访问字符串。

算法
首先访问 行 0 中的所有字符,接着访问 行 1,然后 行 2,依此类推。

对于所有整数k,

  1. 行 0 中的字符位于索引 k ( 2 ⋅ n u m R o w s − 2 ) k(2⋅numRows−2) k(2numRows2) 处;
  2. 中间行 i i i中的字符位于索引 k ( 2 ⋅ n u m R o w s − 2 ) + i k(2⋅numRows−2)+i k(2numRows2)+i 以及 ( k + 1 ) ( 2 ⋅ n u m R o w s − 2 ) − i (k+1)(2⋅numRows−2)−i (k+1)(2numRows2)i 处;
  3. n u m R o w s − 1 numRows−1 numRows1 中的字符位于索引 k ( 2 ⋅ n u m R o w s − 2 ) + n u m R o w s − 1 k(2⋅numRows−2)+numRows−1 k(2numRows2)+numRows1 处;

二、几种解法及代码

初始解法

# 时间复杂度:O(N) 空间复杂度:O(N)
class Solution:
    def convert(self, s: str, numRows: int) -> str:
        if numRows == 1:
            return s
        else:
            step = 2 * numRows - 2
            r = s[::step]
            for i in range(1, numRows - 1):
                a = s[i::step]
                b = s[step-i::step]
                # 将a和b交错合并(a和b的长度不一定相同)
                min_len = min(len(a), len(b))
                res = [''] * min_len * 2
                res[::2] = a[:min_len]
                res[1::2] = b[:min_len]
                r += ''.join(res) + a[min_len:] + b[min_len:]
            return r + s[numRows-1::step]

初始解法采用了按行访问的思路(直觉认为按列排序会很慢,所以直接选择了找规律的按行访问),但是由于中间行事实上存在两个有规律的子串a和b,将二者交错合并在python里面的实现不使用for循环会较为困难(具体实现的办法还是网上现搜的),所以实际运行速度很慢。

优化解法

查看了官方的按行访问思路后,选择用for循环读取中间行字符

class Solution:
    def convert(self, s: str, numRows: int) -> str:
        if numRows == 1:
            return s
        else:
            len_s = len(s)
            step = 2 * numRows - 2
            r = s[::step]
            for i in range(1, numRows - 1):
                for j in range(len_s):
                    pos_1 = step * j + i
                    if pos_1 < len_s:
                        r += s[pos_1]
                    else:
                        break

                    pos_2 = step * (j+1) - i
                    if pos_2 < len_s:
                        r += s[pos_2]
                    else:
                        break
            return r + s[numRows-1::step]

该算法仍使用按行访问的思路,但运行速度明显比初始算法更快。

1. 切片用法的注意事项

a = "abcdefgh"
b = [::2]  # "aceg"

切片的两个冒号分开的三者为 start: stop: step,start为起始位置,如果是0,可以省略;stop为终止位置+1(注意不是结束位置),如果直接到结尾,可以省略;step为间隔大小。

range函数的三个参数也是上述顺序,即 range( start, stop[, step] )

最快解法

参考了运行时间最短的代码,其使用的思路就是按列排序后连接。

class Solution:
    def convert(self, s: str, numRows: int) -> str:
        if numRows < 2:
            return s
        n = len(s)
        lst = ['' for _ in range(numRows)]
        down = -1  # down = 1代表行数增加;反之,行数减少
        row = 0
        for ch in s:
            lst[row] += ch
            if row == numRows-1 or row == 0:
                down *= -1           
            row += down
        return ''.join(lst)

2. 多行字符串的生成方法

个人之所以写不出来(包括在写初始算法还需要查询子算法),很重要的一个原因是不知道如何生成需要的多行字符串。事实上,生成方法很简单,只需要借助列表即可:

lst = ['' for _ in range(numRows)]  # numRows为行数

另外,还可以从上述代码中了解到,如果只需要占位时,可以直接使用“_”,而无需定义变量。

3. join()方法的使用

(参考菜鸟教程
join方法用于将序列中的元素以指定的字符连接生成一个新的字符串。其语法为

str.join(sequence)

其中,sequence为要连接的元素序列(列表或字典均可,使用字典时连接的是键而非值),str为用于连接sequence中各字符的中间字符。例如,

'*'.join(['a','b','c'])  # 'a*b*c'
'-'.join(['a','b','c'])  # 'a-b-c'
''.join(['a','b','c'])  # 'abc'

4. 加快程序速度的小技巧:“等于”和“大于/小于”

如果要判断一个数是否是某个值,而可能的其余所有值都大于(或小于)该值,那么此时使用“<”(或“>”)的速度要快于使用“==”的速度。例如,在最快算法中正式代码的第一行中,如果将

if numRows < 2:

替换为

if numRows == 1:

将会明显降低运算速度。(在本题的连续两次测试中,前者的运行时间为52ms,而后者为60ms。)这其中的原理其实与“==”和“>”、“<”的计算原理有关,后面两者明显更快(请读者自己直观想想)。

猜你喜欢

转载自blog.csdn.net/AbaloneVH/article/details/116723740
今日推荐