LeetCode 6中等字符串
Z 字形变换 图解题解
这道题到底在问什么
把字符串 s 按从上到下、再斜着往上的 Z 字方式写成 numRows 行,然后逐行拼接读出新串。
- s
- "PAYPALISHIRING"
- numRows
- 3
- 输出
- "PAHNAPLSIIGYIR"
最优解:一步一步想明白
- 3关键就两个状态:当前行 cur 和 方向 step(+1 往下 / −1 往上)。碰到顶行或底行就掉头。盯住下面格子里字母怎么走位。
- 4cur=0 · 准备填 P先摆好 3 行的空格子(列数够用就行)。指针停在 第 0 行,方向往下,准备填第一个字母 P。
- 5cur=0 → 往下把 P 放进 (行0, 列0)。现在在顶行,方向是往下 +1,下一格去第 1 行。
- 6往下 · cur 0→1竖着往下,A 放进 (行1, 列0)。还没到底,继续往下。
- 7撞底 → 准备掉头Y 放进 (行2, 列0)——到最后一行了!方向要掉头变成往上 −1,而且斜着走时列要 +1。
- 8往上 · 列+1掉头往上!P 走到 (行1, 列1)——行号减 1、列号加 1,这就是 Z 字的斜笔。
- 9撞顶 → 再掉头A 走到 (行0, 列2)——又撞到顶行了!方向再掉头变回往下 +1,接下来又是竖着走。
- 10往下 · cur 0→1竖着往下,L 放进 (行1, 列2)。注意列号不变——竖着走时同一列往下叠。
- 11撞底 → 掉头往上I 放进 (行2, 列2),又到底了。掉头往上,下一步斜着走、列再 +1。
- 12往上 · 列+1斜笔!S 走到 (行1, 列3)。行减 1、列加 1。
- 13撞顶 → 掉头往下H 走到 (行0, 列4),撞顶,再次掉头往下。Z 字就是这样一上一下来回织。
- 14往下 · cur 0→1竖着往下,I 放进 (行1, 列4)。
- 15撞底 → 掉头R 放进 (行2, 列4),到底,掉头往上。
- 16往上 · 列+1斜笔,I 走到 (行1, 列5)。还剩两个字母 N、G。
- 17撞顶 → 掉头往下N 走到 (行0, 列6),撞顶,掉头往下。
- 18往下 · 填完!最后一个 G 往下放进 (行1, 列6)。14 个字母全部按 Z 字填完了!下面开始一行一行读出来。
- 19全部填完 · 看整张 Z 字退一步看整张图:字母的轨迹正是一笔一笔的 Z 字(竖下→斜上→竖下…)。原串顺序已经被打散到各行,接下来从上到下逐行读出来就是答案。
- 20row 0 → PAHN从第 0 行从左到右收字母:P、A、H、N → 拿到 "PAHN"。空格子直接跳过。
- 21row 1 → APLSIIG再读第 1 行:A、P、L、S、I、I、G → 接上变成 "PAHN" + "APLSIIG"。中间行字母最多,因为斜笔都落在这。
- 22row 2 → YIR · 完成最后读第 2 行:Y、I、R → 全拼起来 PAHN + APLSIIG + YIR = PAHNAPLSIIGYIR,正是答案!
- 23省内存的写法:不存格子,只存 numRows 个行串。指针在行间来回走,字母追加到对应行串,走到顶/底就翻转方向。
- 27只有 1 行 → 原样返回若 numRows=1,所有字母都挤在同一行,顶行就是底行、方向永远翻不过来。所以必须开头特判,直接原样返回 s(ABCDEF→ABCDEF)。
⚠️ 容易写错的地方
✗ 错:忘了 numRows == 1 的情形
✓ 对:开头特判直接返回 s
只有一行时 cur 永远是 0,顶/底是同一行,step 不会翻转,会死在原地或越界
✗ 错:只在底行翻向、顶行不翻
✓ 对:顶行(cur==0)翻成 +1、底行(cur==numRows−1)翻成 −1
两端都要掉头才能织成 Z 字,少一个方向就会冲出格子
完整代码(Python / C++ / Java)
Python
class Solution:
def convert(self, s, numRows):
if numRows == 1:
return s # 一行不用变换
rows = [''] * numRows # numRows 个行串
cur, step = 0, 1 # 当前行 / 方向
for ch in s:
rows[cur] += ch # 追加到当前行
if cur == 0:
step = 1 # 顶行 → 往下
elif cur == numRows - 1:
step = -1 # 底行 → 往上
cur += step
return ''.join(rows) # 逐行拼接C++
class Solution {
public:
string convert(string s, int numRows) {
if (numRows == 1) return s;
vector<string> rows(numRows);
int cur = 0, step = 1;
for (char ch : s) {
rows[cur] += ch;
if (cur == 0) step = 1;
else if (cur == numRows - 1) step = -1;
cur += step;
}
string res;
for (string& r : rows) res += r;
return res;
}
};Java
class Solution {
public String convert(String s, int numRows) {
if (numRows == 1) return s;
StringBuilder[] rows = new StringBuilder[numRows];
for (int i = 0; i < numRows; i++) rows[i] = new StringBuilder();
int cur = 0, step = 1;
for (int i = 0; i < s.length(); i++) {
rows[cur].append(s.charAt(i));
if (cur == 0) step = 1;
else if (cur == numRows - 1) step = -1;
cur += step;
}
StringBuilder res = new StringBuilder();
for (StringBuilder r : rows) res.append(r);
return res.toString();
}
}复杂度
时间复杂度
O(n)
n 为字符串长度;每个字符只被放入一次、读出一次,与 numRows 无关 → O(n)
空间复杂度
O(n)
numRows 个行串加起来恰好存下全部 n 个字符(不含返回值则 O(numRows))
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 Z 字形变换 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么不用真建二维数组?+
每个字符只关心落在第几行,用 numRows 个行串(桶)按行追加即可,省去二维空间和大量空格子。
时间复杂度是多少?+
O(n)。每个字符入桶一次、拼接时出桶一次,与 numRows 无关。
怎么确定一个字符落在第几行?+
用指针 cur 配合方向 step:顶行翻 +1、底行翻 −1,逐字符 cur += step。也可用数学公式按 2*(numRows−1) 周期直接算行号。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 Z 字形变换 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。