• LeetCode 第6题:Z字形变换(Python3解法)


    1:问题描述

    来源:LeetCode

    难度:中等


    问题详情:
    将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。

    比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:

    P   A   H   N
    A P L S I I G
    Y   I   R
    
    • 1
    • 2
    • 3

    之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"


    2:问题分析

    2.1 时间复杂度和空间复杂度

    在真正开始介绍各种算法前,先以表格形式展示各自的时间复杂度和空间复杂度, n n n 表示字符串 s s s 的长度。

    算法时间复杂度空间复杂度
    二维矩阵 O ( n u m R o w s × n ) O(numRows×n) O(numRows×n) O ( n u m R o w s × n ) O(numRows×n) O(numRows×n)
    二维矩阵改进O( n n n) O ( n ) O(n) O(n)
    构造法 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)

    2.2 二维矩阵

    使用二维矩阵依次按照 第1节 中那样的顺序存进二维数组,没有字符的用空字符串代替。在竖线中的元素是行不断增加,列保持不变;在斜线中的元素依次是行不断减少,列不断增大;

    2.2.1 构建矩阵

    首先分析该字符串写成Z字形后,一共有几列。
    在这里插入图片描述

    我们以这个倒N字形的竖线和斜线上的元素为一个单元。 竖线上的元素个数= n u m R o w s numRows numRows;斜线上的元素个数为 n u m R o w s − 2 numRows-2 numRows2;因此一个单元中的元素个数总共 r = n u m R o w s × 2 − 2 r=numRows×2-2 r=numRows×22;每一个单元的列数: 1 + n u m R o w s − 2 = n u m R o w s − 1 1+numRows-2=numRows-1 1+numRows2=numRows1

    则共有 ⌊ n r ⌋ \lfloor \dfrac{n}{r}\rfloor rn个单元,而那些组不成一个单元的元素个数,也要看是否 > n u m R o w s >numRows numRows。如果小于,则余下的元素构成一列(因为一个竖线有 n u m R o w s numRows numRows个元素);如果大于,超过numRows的部分(即斜线部分)每一个元素独占一列。

    则求列数的公式如下:
    c o l = ⌊ n r ⌋ × ( n u m R o w s − 1 ) r e = n % r c o l 1 = ⌊ r e n u m R o w s ⌋ + r e % n u m R o w s c o l = c o l + c o l 1

    col=nr×(numRows1)re=n%rcol1=renumRows+re%numRowscol=col+col1" role="presentation" style="position: relative;">col=nr×(numRows1)re=n%rcol1=renumRows+re%numRowscol=col+col1
    col=rn×(numRows1)re=n%rcol1=numRowsre+re%numRowscol=col+col1

    而这个矩阵的行数为 n u m R o w s numRows numRows
    因此该矩阵的尺寸 ( n u m R o w s , c o l ) (numRows,col) (numRows,col)。‘

    而官方解法则是选择了一个偷懒的方法,让那些构不成一个单元的,通过填补空白使其构成一个单元。这样的解法需要 ⌈ n r ⌉ \lceil \dfrac{n}{r}\rceil rn 列,是向上取整的。虽然这样做代码比较简洁,但是会比我的解法占用更多的空间。

    2.2.2 判断位置

    为了判断当前元素是在竖线还是在斜线上,我们需要构建一个判断标准。

    官方的解法在前面已经略有提及,通过当前字符在字符串中的索引 i n d e x index index 对一个单元的元素个数 r r r 求余,其余数小于 n u m R o w s numRows numRows,则表明在竖线上;大于等于numRows则表明在斜线上;

    我的解法则是:

    • 设置一个 f l a g flag flag ,默认为 F a l s e False False
    • 如果行数 i = 0 i=0 i=0时, f l a g = F a l s e flag=False flag=False,表明将进入竖线
    • i = n u m R o w s − 1 i=numRows-1 i=numRows1时, f l a g = T r u e flag=True flag=True,表明下一个元素将进入斜线
    • 如果 f l a g = F a l s e flag=False flag=False,行数 i + = 1 i+=1 i+=1,列数 j j j保持不变
    • 如果 f l a g = T r u e flag=True flag=True,行数 i − = 1 i-=1 i=1,列数 j + = 1 j+=1 j+=1

    2.2.3 边界

    该问题的边界问题就是,转换后只有一行或者一列的状态。当只有一行时,那就是 n u m R o w s = 1 numRows=1 numRows=1;如果只有一列,那就是 n u m R o w s > = n numRows >= n numRows>=n;

    2.2.4 代码

    def convert(s: str, numRows: int) -> str:
        length = len(s)
        # 计算一共占了多少列
        # 当排列后为一行或者一列的情况,则直接返回原字符串
        if length <= numRows or numRows == 1:
            return s
        else:
            # 以Z字形中的一个横线和对角线为一个单元,计算s中能有多少个这样的单元
            t = length // (numRows + numRows - 2)
            temp = length % (numRows + numRows - 2)
            col1 = temp // numRows
            col1 += temp % numRows
            # 一个单元占了 (numRows-2 + 1)列,而剩下的不成单元的占col1列
            col = (numRows - 2 + 1) * t + col1
        # 先创建一个空字符列表,一共numRows行col列
        result = [[""] * col for i in range(numRows)]
        index = 0
        i = 0  # 第几行
        j = 0  # 第几列
        diag = False  # 表示是否在对角线上
        # 通过分析,可以得知当行标走到numRows-1时,下一步就要走到对角线处了,此时行不断往上,列不断往右
        # 当行标再次回到0的时候,就是走出了对角线,此时行不断往下,列保持不变
        while index <= length - 1:
            if i == 0:
                diag = False
            if i == numRows - 1:
                diag = True
            result[i][j] = s[index]
            index += 1
            if not diag:
                i += 1
            else:
                i -= 1
                j += 1
    
        result_2 = ''
        for row_item in result:
            for col_item in row_item:
                if col_item != '':
                    result_2 += col_item
    
        return result_2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    对于时间复杂度,创建和遍历 r e s u l t result result的消耗较高,为 O ( 行数 × 列数 ) O(行数×列数) O(行数×列数),行数 = n u m R o w s =numRows =numRows,列数可以粗略地看作 O ( n ) O(n) O(n),时间复杂度为 O ( n u m R o w s × n ) O(numRows×n) O(numRows×n) w h i l e while while内部时间复杂度为 O ( n ) O(n) O(n),因此总的时间复杂度为 O ( n u m R o w s × n ) O(numRows×n) O(numRows×n)

    空间复杂度,则同样是 O ( n u m R o w s × n ) O(numRows×n) O(numRows×n)

    2.3 改进的二维矩阵

    上述的二维矩阵存在大量空白,浪费了许多空间。

    因此我们可以将每一行作为一个列表,每次移动到该行时,就将该元素添加至这一行对应的列表中。其余则与第一种解法相同。

    2.3.1 代码

    def convert2(s: str, numRows: int) -> str:
        """使用多个空白列表存储每一行的字符"""
        length = len(s)
        if length <= numRows or numRows == 1:
            return s
        else:
            # 生成一个numRows行的列表存储每一行的字符
            result = [[] for i in range(numRows)]
            i = 0  # 表示行
            diag = False
            for item in s:
                result[i].append(item)
                if i == 0:
                    diag = False
                elif i == numRows - 1:
                    diag = True
                if diag:
                    i -= 1
                else:
                    i += 1
    
        from itertools import chain
        return ''.join(chain(*result))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    对于时间复杂度, w h i l e while while和列表的创建和遍历时间复杂度都是 O ( n ) O(n) O(n),因此算法的时间复杂度为 O ( n ) O(n) O(n)

    对于空间复杂度,列表中存储了所有的字符串,空间复杂度 O ( n ) O(n) O(n)

    2.4 构造法

    我们可以直接去探究某一下标的字符应该放在第几行。
    | |0列 | 1列| ...| t-1列|t列|
--|--|---|---|---|--|
**0行**| |  |  || 
**1行**| |  | | | 
**2行**| |  | | |
    如上图所示,假设 n u m R o w s = 4 numRows=4 numRows=4.

    这里的 t = r t=r t=r(2.2.1节提到,每个单元的元素个数)。

    1. 我们可以发现在每个单元注意,这里说的是每个单元,而不是这个表格中的一行)中第0行的元素下标都是 t t t的整数倍,也就是 x t xt xtd的形式
    2. 每个单元中在最后一行的元素的下标应该是这样的形式 x t + n u m R o w s − 1 xt+numRows-1 xt+numRows1
    3. 中间这些行每个单元有两个元素,第一个元素与最后一行的元素规律相同的,只需要满足下标符合这样的形式 x t + i xt+i xt+i,就是在第 i i i行;而第二个元素则满足 ( x t + t − i ) (xt + t - i) (xt+ti)的形式;
    4. 我们统一一下上述的情形,所有行的第一个元素下标都是符合 x t + i xt+i xt+i的形式;
      0 < i < n u m R o w s − 1 00<i<numRows1, 其第二个元素的下标满足 ( x t + t − i ) (xt + t - i) (xt+ti)的形式

    2.4.1 代码

    其中的 j j j是值为 0 t , 1 t , 2 t , 3 t . . . 0t, 1t,2t,3t... 0t,1t,2t,3t...的序列;而 i i i表示第 i i i行, 再次提示这里的 r = t r=t r=t.

    def convert4(s: str, numRows: int) -> str:
        """在方法1的基础上,分析字符串下标对应二维数组索引"""
        length = len(s)
        if length <= numRows or numRows == 1:
            return s
        else:
    
            ans = []
            r = 2 * numRows - 2  # 一个单元拥有的字符数
            for i in range(numRows):
                # 因为每一行最少有一个元素,因此-i,可以避免i与j相加后下标越界
                # j = 0r, 1r, 2r, 3r,  s.t. j < length-i
                for j in range(0, length - i, r):
                    # 添加每个单元中竖线中的元素
                    # j + i:
                    # 第0行元素下标,0r+0, 1r+0,2r+0
                    # 第1行元素下标,0r+1, 1r+1, 2r+1
                    # 第i行元素下标,j+i
                    ans.append(s[j + i])  # 第一个元素
                    # 因为斜线中的元素下标为
                    # 第0行没有斜线
                    # 第1行元素下标,1r-1, 2r-1
                    # 第i行元素下标,j+r-i
                    if 0 < i < numRows - 1 and j + r - i < length:
                        ans.append(s[j + r - i])  # 第二个元素
    
        return ''.join(ans)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    对于时间复杂度,是将所有字符串中的元素都添加到列表中,所以时间复杂度为 O ( n ) O(n) O(n);

    对于空间复杂度,ans是返回值,不计入在空间复杂度的统计当中,因此空间复杂度为O(1)。

  • 相关阅读:
    1.5 计算机网络的类别
    如何在Django中使用分布式定时任务并结合消息队列
    紫光展锐荣评“5G技术创新力企业”,5G赋能千行百业
    C#图解教程总结(第二章)
    VS2022 性能提升:更快的 C++ 代码索引
    链表的简单描述及代码的简单实现
    linux查看python的py文件的命令
    【FPGA的小娱乐】在tft显示屏上画X型
    14.Tensor Product:Covector-Covector Pairs
    科研方法-X_LAB-方法总结和实践记录
  • 原文地址:https://blog.csdn.net/weixin_43490422/article/details/126513310