doocs/leetcode 题解精讲:LeetCode 0006 Z 字形变换的模拟解法与多语言实现
示例工程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载本文基于 doocs/leetcode 仓库中 0006.Zigzag Conversion 题解文档完整讲解 LeetCode 第 6 题「Zigzag ConversionZ 字形变换」的题面、约束与官方推荐的模拟解法通过一个方向变量k在首尾行之间翻转行走方向用numRows个容器按行收集字符最终逐行拼接得到答案。读完后你可以掌握该题O(n)时间复杂度的模拟实现细节、numRows 1这类边界条件的成因并能在仓库中直接找到 Python3、Java、C、Go、TypeScript、Rust、JavaScript、C#、C、PHP 共 10 种语言的对照实现。题目描述与约束将一个给定字符串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);三个示例输入输出说明s PAYPALISHIRING,numRows 3PAHNAPLSIIGYIR如上文的 3 行 Z 字形s PAYPALISHIRING,numRows 4PINALSIGYAHRPIP I N/A L S I G/Y A H R/P Is A,numRows 1A只有一行时输出即原串约束条件1 s.length 1000s由英文字母小写和大写、,和.组成1 numRows 1000解题思路为什么模拟不需要二维网格题解文档给出的「思考」部分值得逐句推敲它解释了为什么最终实现里没有真正的二维数组二维网格有大量空位按 Z 字形把字符填入网格再按行读出时间与空间都是线性n 10^3时完全可过但网格中多数位置为空列这一维并不出现在最终输出中白白浪费。答案只由两件事决定每个字符所在的行号以及同一行内的相对顺序。行号沿0, 1, ..., numRows - 1往复变化到达顶端或底端后改变方向。方向变量在边界处取反只需维护numRows个列表用方向变量k在边界处取反。k的初值设为-1表示向上走这样遇到第一行时立即翻转为向下。numRows 1必须特判此时顶行与底行重合如果不直接返回原串方向会在同一行上反复翻转行号始终为 0i k后越界因此函数开头统一先判numRows 1并原样返回s。模拟算法逐步拆解算法核心只有三步与文档描述完全一致初始化i 0当前行号和k -1方向变量-1 表示向上、1 表示向下从左到右遍历字符串s每个字符c追加到第i行的容器若i 0或i numRows - 1到达拐点执行k -k翻转方向然后i k移动到下一行遍历结束后把numRows个容器按行顺序拼接返回。下面用示例 1s PAYPALISHIRING,numRows 3走一遍关键节点验证方向翻转逻辑字符写入行该行容器写入后是否拐点翻转后 k下一行P0P是顶行11A1A否12Y2Y是底行-11P1AP否-10A0PA是顶行11..................N0PAHN是顶行11G1APLSIIG否12遍历完成后三个容器分别为[PAHN, APLSIIG, YIR]拼接得到PAHNAPLSIIGYIR与题目示例输出一致。从源码结构看字符所在行号序列是0, 1, ..., numRows-1, numRows-2, ..., 1的循环可以推断出其周期为2 * numRows - 2numRows 1时这正是该题另一种按行下标直接计算数学解法的基础而本文实现的模拟法不依赖周期公式代码更短、语言间一致性更强。Python3 参考实现仓库中的 Solution.py 与题解文档中的 Python3 代码块一致class Solution: def convert(self, s: str, numRows: int) - str: if numRows 1: return s g [[] for _ in range(numRows)] i, k 0, -1 for c in s: g[i].append(c) if i 0 or i numRows - 1: k -k i k return .join(chain(*g))几个细节值得注意numRows 1时直接返回s这是全部分支实现共同的第一道防线g [[] for _ in range(numRows)]按行建numRows个字符列表等价于文档中只维护行号维度的结论拼接结果使用chain(*g)将各行字符按顺序串起再join避免逐行字符串相加的额外开销。其余 9 种语言实现题解文档同时提供了 Java、C、Go、TypeScript、Rust、JavaScript、C#、C、PHP 十种语言的实现仓库 0006.Zigzag Conversion 目录 下的独立源文件与文档代码块一一对应便于交叉验证。JavaSolution.javaclass Solution { public String convert(String s, int numRows) { if (numRows 1) { return s; } StringBuilder[] g new StringBuilder[numRows]; Arrays.setAll(g, k - new StringBuilder()); int i 0, k -1; for (char c : s.toCharArray()) { g[i].append(c); if (i 0 || i numRows - 1) { k -k; } i k; } return String.join(, g); } }用StringBuilder[]充当按行缓冲区Arrays.setAll一次性初始化最终String.join(, g)完成逐行拼接。CSolution.cppclass Solution { public: string convert(string s, int numRows) { if (numRows 1) { return s; } vectorstring g(numRows); int i 0, k -1; for (char c : s) { g[i] c; if (i 0 || i numRows - 1) { k -k; } i k; } string ans; for (auto t : g) { ans t; } return ans; } };GoSolution.gofunc convert(s string, numRows int) string { if numRows 1 { return s } g : make([][]byte, numRows) i, k : 0, -1 for _, c : range s { g[i] append(g[i], byte(c)) if i 0 || i numRows-1 { k -k } i k } return string(bytes.Join(g, nil)) }Go 版本把每行建成[]byte切片append追加字符最后bytes.Join(g, nil)零分隔拼接比逐段拼接string更省内存拷贝。TypeScriptSolution.tsfunction convert(s: string, numRows: number): string { if (numRows 1) { return s; } const g: string[][] new Array(numRows).fill(0).map(() []); let i 0; let k -1; for (const c of s) { g[i].push(c); if (i numRows - 1 || i 0) { k -k; } i k; } return g.flat().join(); }RustSolution.rsimpl Solution { pub fn convert(s: String, num_rows: i32) - String { if num_rows 1 { return s; } let num_rows num_rows as usize; let mut g vec![String::new(); num_rows]; let mut i 0; let mut k -1; for c in s.chars() { g[i].push(c); if i 0 || i num_rows - 1 { k -k; } i (i as isize k) as usize; } g.concat() } }Rust 中由于num_rows以i32传入需要as usize转成切片索引行号更新处i (i as isize k) as usize用isize中转保证k -1时减法不会产生下溢。JavaScriptSolution.js/** * param {string} s * param {number} numRows * return {string} */ var convert function (s, numRows) { if (numRows 1) { return s; } const g new Array(numRows).fill(_).map(() []); let i 0; let k -1; for (const c of s) { g[i].push(c); if (i 0 || i numRows - 1) { k -k; } i k; } return g.flat().join(); };JavaScript 与 TypeScript 版本结构完全相同new Array(numRows).fill(_).map(() [])是构造numRows个互不共享引用的空数组的惯用写法。C#Solution.cspublic class Solution { public string Convert(string s, int numRows) { if (numRows 1) { return s; } int n s.Length; StringBuilder[] g new StringBuilder[numRows]; for (int j 0; j numRows; j) { g[j] new StringBuilder(); } int i 0, k -1; foreach (char c in s.ToCharArray()) { g[i].Append(c); if (i 0 || i numRows - 1) { k -k; } i k; } StringBuilder ans new StringBuilder(); foreach (StringBuilder t in g) { ans.Append(t); } return ans.ToString(); } }CC 语言无法使用动态字符串容器Solution.c 为每行手动分配字符缓冲逻辑与其他语言一致char* convert(char* s, int numRows) { if (numRows 1) { return strdup(s); } int len strlen(s); char** g (char**) malloc(numRows * sizeof(char*)); int* idx (int*) malloc(numRows * sizeof(int)); for (int i 0; i numRows; i) { g[i] (char*) malloc((len 1) * sizeof(char)); idx[i] 0; } int i 0, k -1; for (int p 0; p len; p) { g[i][idx[i]] s[p]; if (i 0 || i numRows - 1) { k -k; } i k; } char* ans (char*) malloc((len 1) * sizeof(char)); int pos 0; for (int r 0; r numRows; r) { for (int j 0; j idx[r]; j) { ans[pos] g[r][j]; } free(g[r]); } ans[pos] \0; free(g); free(idx); return ans; }idx[r]记录每行实际写入长度拼接阶段只拷贝idx[r]个有效字符numRows 1分支返回strdup(s)而非直接返回s符合 LeetCode C 接口对返回内存的约定输出缓冲需由调用方释放。PHPSolution.phpclass Solution { /** * param String $s * param Integer $numRows * return String */ function convert($s, $numRows) { if ($numRows 1) { return $s; } $g array_fill(0, $numRows, ); $i 0; $k -1; $length strlen($s); for ($j 0; $j $length; $j) { $c $s[$j]; $g[$i] . $c; if ($i 0 || $i $numRows - 1) { $k -$k; } $i $k; } return implode(, $g); } }边界情况与复杂度分析结合约束1 numRows 1000与代码逐行逻辑可以从源码结构确认两个边界行为numRows 1所有实现第一步就特判返回s。若去掉该分支方向变量k会在i 0处每次都被取反i k使行号在-1与0之间跳变并越界这是文档中顶与底重合应直接返回原串警告的由来。numRows s.length字符依次落在第0到第len(s) - 1行永远走不到底行拐点k保持-1不再触发翻转最终逐行拼接的结果恰好是原串本身算法对该情况天然正确无需额外处理。复杂度每个字符只做一次O(1)的追加与一次方向判断逐行拼接同样是线性扫描因此时间复杂度为O(n)空间复杂度为O(n)n为字符串s的长度与文档给出的结论一致。小结本仓库对该题的解法设计有一个清晰的取舍放弃直观的二维网格把问题压缩为每个字符属于哪一行这一个维度用方向变量k在首行、末行之间翻转来驱动行号。这一设计使 10 种语言的实现共享同一套骨架特判 → 按行收集 → 边界翻转 → 逐行拼接差异只体现在字符串容器的选择上list、StringBuilder、[]byte、String等是阅读 doocs/leetcode 多语言题解做横向对比时值得留意的范例。更多题目可参考 solution 目录总览 与 0000-0099 区间文档。赞分享示例工程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载相关推荐doocs/leetcode 题解精讲面试题 01.08 零矩阵Zero Matrix双解法与多语言实现doocs/leetcode 题解精讲面试题 01.08 零矩阵Zero Matrix双解法与多语言实现 本文以 doocs/leetcode 仓库中 l示例工程LeetCode 6. Z 字形变换矩阵模拟与数学规律双解法实战解析LogicStack-LeetCode 刷穿系列LeetCode 6. Z 字形变换矩阵模拟与数学规律双解法实战解析LogicStack LeetCode 刷穿系列 本文是 LogicStack Lee教程文档OCLP实战4步给停更的老Mac装上新版macOSOCLP实战4步给停更的老Mac装上新版macOS OpenCore Legacy Patcher下文简称 OCLP是一个给离开苹果官方支持的 Intel桌面应用操作系统固件上一篇Backstage Tracing Servicealpha为后端插件统一输出 OpenTelemetry 应用级 Traces下一篇PPT Master 品牌预设实战逐段解读 Anthropic / Claude 品牌家族 design_spec 及其在模板系统中的落地机制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考