6. Z 字形变换

将一个给定字符串 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  # 返回结果字符串

力扣答案传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容