# 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);
示例 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
# 想法:
# 1. 数学模拟解法
对于一个 Z 字形变换,可以很明显的观察出,第一行和最后一行每个元素之间的间隔是 2*numRows-2
,而每一行第一个元素我们又是已知的。那么很容易就会想到遍历行进行模拟。
0 6 12
1 57 1113
24 810
3 9
那么如何确定当前行结束呢,这里采用的是轮数来决定。对于上述例子,第一轮指的是
0
1 5
24
3
因此可以计算出最大轮数是 Math.ceil(s.length/(2*(numRows)-2));
接下来就是中间行的处理,对于每一轮来说,一行会有两个元素,且每个元素之间的索引差是 2*(numRows-row)-2
, 其中 row
表示的就是当前行。那么只需要计算出每一轮第一个元素的索引即可。
这个也很好计算,因为每行第一个元素就是 row
,而每轮之间的索引差也是固定的,所以可以算出,中间行每轮的第一个元素索引是 row+(2*(numRows)-2)*(count);
因此,整个代码就很简单了。
# 代码
var convert = function(s, numRows) { | |
if(numRows===1||numRows>=s.length){ | |
return s; | |
} | |
let res=[]; | |
const fill=(row,s)=>{ | |
let count=0; | |
let max=Math.ceil(s.length/(2*(numRows)-2)); | |
let i=row; | |
while(count++<max){ | |
if(s[i]) res.push(s[i]); | |
// 中间行需要特别注意 | |
if(row!=0&&row!=numRows-1){ | |
i=i+2*(numRows-row)-2; | |
if(s[i]) | |
res.push(s[i]); | |
} | |
i=row+(2*(numRows)-2)*(count); | |
} | |
} | |
for(let i=0;i<numRows;i++){ | |
fill(i,s); | |
} | |
return res.join(''); | |
}; |
# 2. 直接模拟法
上述的模拟还是比较麻烦的,更近一步的,我们会注意到,在 Z 字形变换的时候,填入时行序列是从 0
到 numRows
, 再从 numRows
到 0
,我们只要模拟这个行索引的变换过程就可以快速解答出来。
var convert = function(s, numRows) { | |
if(numRows===1||numRows>s.length){ | |
return s; | |
} | |
let res=new Array(numRows).fill(""); | |
let i=0; | |
let flag = -1; | |
for (let c of s){ | |
res[i] += c | |
if(i == 0 || i == numRows -1) flag = - flag; | |
i += flag; | |
} | |
return "".concat(...res) | |
}; |