6. N 字形变换

题目

image.png

特殊情况

当numRows为一时,直接返回字符串,因为所有字符都在同一行。
image.png

相反,如果numRows大于字符串长度时,每一行只有一个字符,按顺序拼接起来和原字符串一样,直接返回字符串。

思路一 矩阵模仿N字形遍历

给一个长度为numRows的二维数组记录,遍历字符串,把字符放到对应的数组中,然后平铺数组并组合成字符串即可。

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
/**
* @param {string} s
* @param {number} numRows
* @return {string}
*/
function convert(s, numRows) {
// 特殊情况
if (numRows === 1 || numRows >= s.length) return s;
// 构造矩阵
let matrix = Array.from({ length: numRows }, () => []);
// N字走向
let direction = true;
// 对应位置
let idx = 0;

for (let val of s) {
// 字符放到对应位置
matrix[idx].push(val);

// 根据走向判断对应位置的变化
if (direction) {
idx += 1;
} else {
idx -= 1;
}
// 根据对应位置判断走向的变化
if (idx === 0) direction = true;
if (idx === numRows - 1) direction = false;
}

// 这里es6提供了平铺数组的方法,原本应该是遍历二维数组。
return matrix.flat(Infinity).join('');
};

复杂度

时间复杂度:假设字符串长度为n,二维数组的列数numRows为r,其中有遍历字符串O(n),加上后面遍历矩阵O(rn),然后去除常数、系数、低阶得出时间复杂度为O(rn)。

空间复杂度:矩阵二维数组O(rn)。

思路二 构造

image.png

很明显N字形成了一个周期性的结构,有以下特征,以示例2为例:

  1. 从第一行第一个(P)到第二行第二个(L),又或者从第一行第二个(I)到第二行第四个(I)。周期长度为6(2*numRows - 2)。
  2. 每个周期中,除了第一行和最后一行有一个元素,中间的其余行都有两个元素。比如从第一行第一个(P)到第二行第二个(L),第一行只有一个P,最后一行只有一个P,第二行有A/L,第三行有Y/A。

思路:利用周期性用for循环遍历特定的列,如示例2中的第一、第四、第七列,而中间第二第三第五第六列都只有一个元素,对应的是上述特征2的中间行多出来的一个元素。

这里有第三个特性,比如遍历到第二行第三个(S),第二行下标为1,而按字符串的顺序来说,由A的位置,加一个周期到达A的位置,再减2*1,到达L的位置正是同一周期中同一行的另一个字符。

根据以上三个特性可以覆盖所有的字符并进行编码了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var convert = function (s, numRows) {
const n = s.length, r = numRows;
// 特殊情况
if (r === 1 || r >= n) return s;
// 周期长度
const t = r * 2 - 2;
let ans = '';
// 枚举矩阵的行
for (let i = 0; i < r; i++) {
// 枚举矩阵的特定列
for (let j = i; j < n; j += t) {
ans += s[j];
// 是否非第一行和非最后一行,是否存在同一个周期中的第二个元素,并且不超出字符串长度
if (0 < i && i < r - 1 && j + t - 2 * i < n) {
// 当前周期的第二个字符
ans += s[j + t - 2 * i];
}
}
}
return ans;
};

复杂度

时间复杂度:两层循环几乎把s的所有字符都覆盖了一遍,并且在寻找同一周期的第二个元素的时候存在if判断,所以最坏的情况则是O(n),n为字符串s的长度。

空间复杂度:只用了常数个额外空间,O(1)。