题干
将一个给定字符串 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为例:
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
}