6【Z字型变换】超详细的leetcode题目,把这些代码给你说明白

99 阅读3分钟

Z字型变换\color{#0d9ded}{Z字型变换}

难度\color{orange}{难度}中等

题目简介:

将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右\color{red}{从上往下、从左到右}进行 Z 字形排列。

比如输入字符串为 "ABCDEFGHIJKLMNO" 行数为 5 时,排列如下:

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

结果为 AIBHJCGKODFLNEM

请你实现这个将字符串进行指定行数变换的函数:

 

示例 1:

输入: s = "PAYPALISHIRING", numRows = 3
输出: "PAHNAPLSIIGYIR"

示例 2:

输入: s = "PAYPALISHIRING", numRows = 4
输出: "PINALSIGYAHRPI"
解释:
P     I    N
A   L S  I G
Y A   H R
P     I

示例 3:

输入: s = "A", numRows = 1
输出: "A"

 

提示:

  • 1 <= s.length <= 1000
  • s 由英文字母(小写和大写)、',' 和 '.' 组成
  • 1 <= numRows <= 1000

思路

循环行数,之后获取每一行的字符,放入数组,最后转为字符串。\color{orange}{循环行数,之后获取每一行的字符,放入数组,最后转为字符串。}

代码结果

这是简单版,详细的在下面

function convert(s: string, numRows: number): string {
  const len = s.length;
  if (numRows === 1 || len <= numRows) {
    return s;
  }

  //周期, 如 numRows = 5,cycle就为8,表示每8个字符完成一个Z,
  const cycle = 2 * numRows - 2;

  const result: string[] = [];

  for (let i = 0; i < numRows; i++) {
    for (let j = 0; j < len - i; j += cycle) {
    //获取该周期内,当前行的第一个字符
      result.push(s[j + i]);   

      if (i > 0 && numRows - 1 > i && len > j + cycle - i) {
        //获取该周期内,当前行的第二个字符
        result.push(s[j + cycle - i]);
      }
    }
  }

  return result.join("");
}

 convert("PAYPALISHIRING", 3);



图文详解回答

function convert(s: string, numRows: number): string {
  const len = s.length;
  //   numRows === 1  只有一行,直接返回
  //  len <= numRows  只有一列,直接返回
  if (numRows === 1 || len <= numRows) {
    return s;
  }
 convert("ABCDEFGHIJKLMNO", 5)

只有一行\color{orange}{只有一行} : numRows === 1

只有一列\color{orange}{只有一列} : len <= numRows
字符串长度 小于 行长度的话,则一行最多只有一个字符,也就是只有一列


function convert(s: string, numRows: number): string {
  //周期, 如 numRows = 5,cycle就为8,表示每8个字符完成一个Z,
  const cycle = 2 * numRows - 2;
}

一个周期的字符数为,一列(绿色区域\color{#51ff92}{绿色区域})➕ 斜着的部分(蓝色区域\color{#4186f2}{蓝色区域}

绿色区域\color{#51ff92}{绿色区域} = 行数 = numRows
蓝色区域\color{#4186f2}{蓝色区域} = 行数 - 第一行 - 最后一行 = numRows - 1 - 1

一个周期的字符数\color{orange}{一个周期的字符数} = numRows + numRows - 1 - 1 = 2 * numRows -2


准备工作做完,这时候,要开始循环行数了

function convert(s: string, numRows: number): string {
    //用于返回的结果
  const result: string[] = [];
  //  循环行数,获取每一行
  for (let i = 0; i < numRows; i++) {
  //  循环剩余字符
    for (let j = 0; j < len - i; j += cycle) {
     // s[j + i] : 获取当前周期内的第一列的值
      result.push(s[j + i]);
    }
  }
}

第一个循环是获取每一行

第二个循环: 循环剩余字符

当第一层循环 i = 0时,第二层循环则要遍历所有字符,以获取 A、I

当第二层循环 i = 1时,第二层循环则要遍历除A以外\color{orange}{遍历除A以外}所有的字符(要排除A比较容易,而排除I,后续不好计算\color{orange}{要排除A比较容易,而排除I,后续不好计算}),以获取 B、H、J

当第三层循环 i = 2时,第二层循环则要遍历除AB以外\color{orange}{遍历除A、B以外}所有的字符(要排除AB比较容易,而排除IHJ,后续不好计算\color{orange}{要排除A、B比较容易,而排除I、H、J,后续不好计算}),以获取 C、G、K、O

按照上面的步骤:

j<leni\color{orange}{j < len - i} : 排除A、B等

j+=cycle\color{orange}{j += cycle } : 这个解释如下

i=0\color{#0c96ea}{当 i = 0时}
第二循环的第一轮次时,j=0,j 指向 A 的索引

第二循环的第二轮次时,j=8,j 指向 I 的索引

i=1\color{#0c96ea}{当 i = 1时}
第二循环的第一轮次时,j=0,j+i\color{orange}{j+i} 指向 B 的索引

第二循环的第二轮次时,j=8,j+i\color{orange}{j+i} 指向 J 的索引

注意,第二循环的第二轮次时,直接 获取的是 J,而不是 H,因为 s[j + i],只能获取该周期内的第一个数,而 H属于第一周期的第二个数,需要下面这种方式获取


function convert(s: string, numRows: number): string {
  const result: string[] = [];
  for (let i = 0; i < numRows; i++) {
    for (let j = 0; j < len - i; j += cycle) {
      result.push(s[j + i]);
      
      // 获取该周期的第二个数
      if (i > 0 && numRows - 1 > i && len > j + cycle - i) {
      
      //将该周期的第二列放入数组
        result.push(s[j + cycle - i]);
      }
    }
  }
}

可以看到,单个周期中的第一行(i=0\color{orange}{i=0时})和 最后一行 (i=4\color{orange}{i=4时},是没有第二列的。

i>0\color{orange}{i > 0} : 排除了第一行
numRows1>i\color{orange}{numRows - 1 > i} : 排除了第一行

由于在第二层循环中, j的增长方式是 j+=cycle\color{orange}{j+=cycle},而cycle为一个完整周期的数量,看下面的图片。当第二个周期时,j+cycle\color{orange}{j + cycle} 获取的是到 AP\color{orange}{ A 到 P } 的数量,而字符串中根本没有P\color{orange}{ 没有P },所以要排除掉

len>j+cyclei\color{orange}{len > j + cycle - i} : 排除单个周期不存在的第二列


之后,获取的数组变成字符串就可以了。