LeetCode 6. Z字形变换【中等】

106 阅读3分钟

题干

将一个给定字符串 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);

示例 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"

题解

本题较为简单的想法是用一个二维矩阵去模拟出所谓Z变换,然后顺序遍历每一行得到结果,空间复杂度为O(n^2),因此考虑能否优化空间复杂度。根据Z变换的性质,我们知道数组的第0、1、2个元素分别是Z变换各行第一列的元素,因此得到启发,我们以每一行的第一个元素为起点,能否在遍历过程中推算下一个元素在原数组中的下标,知道了原数组的下标,就知道了这个元素是啥,也就知道了按行遍历的顺序。首先来看第一行,假设当前处在下标i,则第一行下一个元素的下标一定满足i + 2 * (numRows-1),这个不难理解,由于现在处在第一行,则当前元素的下面一定还有numRows-1个元素,想象从当前元素出发一路向下走,再向上走回到和当前位置齐平的高度,走过的距离就是2 * (numRows - 1),最后一行也是同理。问题在于中间的几行应该如何推算。假设当前行为curRow(从0开始记),那么处在curRow下方的元素就有numRow - curRow个,处在curRow上方的元素就有curRow - 1个,根据推算第一行的思路,我们需要从当前元素出发向下或者是向上走,走到和当前元素齐平的高度,走过的距离加上当前元素在原数组的下标,就是下一个元素在原数组的下标,因此,我们可以知道,当从当前元素向下走时,下一个元素的下标一定满足i + (numRow - curRow) * 2;当从当前元素向上走时,下一个元素的下标一定满足i + (curRow - 1) * 2;我们在推导下一个元素下标的过程中,总是重复着向上向下交替的过程,直到遍历完这一行,以numRows = 3为例:

Z变换.png

func convert(s string, numRows int) string {
	// 判断边界条件
	if numRows == 1 {
		return s
	}
	ans := ""
	n := len(s)
	// 计算第一行,步长间隔为2 * (numRows - 1)
	for idx := 0; idx < n; idx += 2 * (numRows - 1) {
		ans += string(s[idx])
	}
	// 计算中间行,步长动态计算
	curRow := 1
	for curRow < numRows-1 {
		pos := curRow
		// 定义step为步长,初始值为当前行距离最大行数的距离
		step := numRows - curRow - 1
		for pos < n {
			ans += string(s[pos])
			pos += 2 * step
			// 由于N字型是一上一下的,所以下一次循环中的步长为最大行数减去当前步长,即取补
			step = numRows - step - 1
		}
		curRow++
	}
	// 计算最后一行是,步长间隔为2 * (numRows - 1)
	for idx := numRows - 1; idx < n; idx += 2 * (numRows - 1) {
		ans += string(s[idx])
	}
	return ans
}