z字形变换核心是模拟行索引抖动而非建二维数组,用vector按行存字符,通过step控制方向(+1向下、-1向上),在row=0或row=numrows-1时翻转step;需特判numrows=1或≥s.length()的情况。

z字形变换的核心逻辑是模拟行索引抖动
不是真要建二维数组填字母,而是用一个 vector<string></string> 按行存字符,关键在控制当前字符该塞进哪一行。行号 row 从 0 开始,往下走到 numRows - 1 后反弹,往上走到 0 后再向下——这叫“锯齿运动”。用一个 step 变量表示方向:+1 往下,-1 往上。
常见错误是手动写两层循环(先下再上),结果边界判断错位,比如在 row == 0 时没及时切方向,或在 row == numRows - 1 时多走了一步。
-
step初始设为 1,row初始为 0;每次填完一个字符后更新row += step - 当
row == 0或row == numRows - 1时,立刻翻转step = -step - 注意:
numRows == 1是特例,直接返回原串,否则step会卡死在 0
string 转 vector 再拼接的内存开销可控
有人担心每行都用 string 存、最后再拼,会不会慢?实际不会。C++ 的 string 移动语义 + reserve() 预分配能压平大部分开销。比起反复 += 一个大字符串,分行追加更缓存友好。
- 初始化
rows时用vector<string>(numRows)</string>,不带初始内容 - 对每个
rows[i]调用reserve(s.length() / numRows + 1)(粗略预估),避免多次扩容 - 最终用
ostringstream或循环+=拼接,别用accumulate——它对string是线性拷贝,O(n²)
LeetCode 测试用例里藏着两个易漏边界
一是 numRows = 1,这时 Z 字形退化成直线,直接返回原串;二是 numRows > s.length(),比如 s = "A", numRows = 5,此时所有字符都在第 0 行,其他行为空,拼出来还是 "A"。这两种情况如果靠主循环硬跑,step 会失效或 row 越界。
立即学习“C++免费学习笔记(深入)”;
- 开头加判断:
if (numRows == 1 || numRows >= s.length()) return s; - 不要依赖
numRows一定 ≤s.length(),LeetCode 确实会喂"A", 1000这种 - 循环中每次更新
row后,必须立刻检查是否越界并修正step,不能等下次迭代
用 char* 遍历比用 s[i] 稍快但没必要过度优化
对于纯遍历场景,const char* p = s.c_str() + 指针递增,确实比 s[i] 少一次 bounds check(Release 模式下编译器通常能优化掉,但 Debug 下明显)。不过这题瓶颈不在访问速度,而在逻辑分支和字符串拼接。
- 可读性优先:用
for (char c : s)最清晰,现代编译器优化后性能差距可忽略 - 若真想提速,把
rows改成vector<vector>></vector>,最后统一构造string,避免string多次重分配 - 别提前 micro-optimize:先写出正确逻辑,再看 profiler 数据——这题 100% 时间花在输出拼接,不在输入遍历
事情说清了就结束。Z 字形真正难的不是代码,是画两行图验证自己对 step 翻转时机的理解有没有偏差。









