资讯动态

LeetCode 157 用 Read4 读取 N 个字符:基于交互式模拟的多语言题解

发布时间:2026/10/17 6:33:39 来源:尧图企业网站定制
示例工程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载本篇题解围绕 doocs/leetcode 仓库中 0157.Read N Characters Given Read4 一题展开剖析如何只借助read4这个「每次固定读 4 个字符」的底层 API 来实现read方法、按需读取 n 个字符。读完本文你将掌握交互式Interactive题型的核心套路——临时缓冲 循环模拟的边界处理技巧并能直接复用仓库中 Python3、Java、C、Go 四套可直接运行的实现。题目背景与核心概念本题是 LeetCode 上一道加锁题属于「数组 / 交互 / 模拟」标签下的经典题目。它模拟了一个非常贴近真实工程的场景一个文件只能通过给定的read4方法读取你被要求在此基础上实现read方法且不能直接操作文件。read4 API以 4 字符为单位的读取原语read4的定义如下参数类型: char[] buf4 返回类型: int 注意: buf4[] 是目标缓存区不是源缓存区read4 的返回结果将会复制到 buf4[] 当中。它每次从文件中读取4 个连续的字符写入调用方提供的缓存数组buf4返回值是实际读取到的字符个数。需要特别强调的是read4()自身拥有独立的文件指针fp很像 C 语言中的FILE *fp——也就是说多次调用read4时文件指针会自动前进不需要也不允许你手动维护位置。下面这张示意图直观展示了read4的工作过程图片来源英文版题解![LeetCode 157 read4 从文件中读取连续字符到 buf4 缓冲区的工作原理示例](https://raw.gitcode.com/doocs/leetcode/raw/0b2b77121bbd592d32a4020611e34e92412448d8/solution/0100-0199/0157.Read N Characters Given Read4/images/157_example.png?utm_sourcegitcode_repo_files)结合示例代码可以更清晰地理解它的行为File file(abcde); // 文件名为 abcde初始文件指针 (fp) 指向 a char[] buf4 new char[4]; // 创建一个缓存区使其能容纳足够的字符 read4(buf4); // read4 返回 4。现在 buf4 abcdfp 指向 e read4(buf4); // read4 返回 1。现在 buf4 efp 指向文件末尾 read4(buf4); // read4 返回 0。现在 buf4 fp 指向文件末尾可见read4的返回值有三种情况4读满一组、13文件剩余不足 4 个最后一次读取、0已经到达文件末尾 EOF。这个「返回值即实际字符数」的设计是后续模拟算法的关键依据。read 方法基于 read4 封装目标读取你要实现的方法定义如下参数类型: char[] buf, int n 返回类型: int 注意: buf[] 是目标缓存区不是源缓存区你需要将结果写入 buf[] 中。与read4类似buf同样只是目标缓存区你需要把从文件中读到的字符写入buf返回值是实际读取到的字符数。关键约束本题有三个重要约束直接决定了算法形态不能直接操作文件文件只能通过read4访问不能用read之外的手段读取read函数只在每个测试用例调用一次这意味着不需要考虑跨调用保留缓冲区内残留字符的问题实现可以大幅简化目标缓存数组buf保证有足够空间可以放心写入 n 个字符而不必担心越界。此外题目数据范围见 英文版文档为1 file.length 500文件由英文字母和数字组成1 n 1000。示例分析题目给出了四个典型示例覆盖了各种边界情况输入 filen输出说明abc43文件只有 3 个字符读不满 n返回实际读取数 3abcde55文件共 5 个字符恰好读完返回 5abcdABCD12341212文件共 12 个字符是 4 的整数倍完整读回leetcode55文件字符多于 n只读取前 5 个字符leetc返回 5前两个示例分别覆盖了「文件比 n 短」和「文件长度不是 4 的倍数」两种情况后两个则分别覆盖了「恰好是 4 的倍数」与「文件比 n 长、需要提前截断」的情况。任何实现都必须同时正确处理这四种场景。解题思路循环模拟读取过程题解文档给出的方法一是模拟Simulation思路非常直接准备一个长度固定为 4 的临时缓冲buf4循环调用read4(buf4)把读到的v个字符逐个拷贝到目标缓冲buf中每拷贝一个字符计数i加一一旦i达到n就提前返回 n当read4返回值小于 4即文件已读完时结束循环返回实际拷贝的字符数i。题解文档中的思考要点原话为只能通过read4读文件且本题read只调用一次。需要的字符数可能不是 4 的倍数最后一次还可能不足 4 个。循环调用read4写入临时缓冲再拷到目标凑满n或读到文件末尾即停。这句话点明了算法的三个终止条件凑满 n、读到文件末尾、以及二者的任意组合。时间复杂度为O(n)其中 n 为要读取的字符数目。多语言实现仓库目录 0157.Read N Characters Given Read4 下提供了 Python3、Java、C、Go 四种语言的完整解法源码与题解文档中的代码一一对应。下面逐一展开。Python3Python 版本中read4已由平台预定义你只需实现Solution.read The read4 API is already defined for you. param buf4, a list of characters return an integer def read4(buf4): # Below is an example of how the read4 API can be called. file File(abcdefghijk) # File is abcdefghijk, initially file pointer (fp) points to a buf4 [ ] * 4 # Create buffer with enough space to store characters read4(buf4) # read4 returns 4. Now buf [a,b,c,d], fp points to e read4(buf4) # read4 returns 4. Now buf [e,f,g,h], fp points to i read4(buf4) # read4 returns 3. Now buf [i,j,k,...], fp points to end of file class Solution: def read(self, buf, n): :type buf: Destination buffer (List[str]) :type n: Number of characters to read (int) :rtype: The number of actual characters read (int) i 0 buf4 [0] * 4 v 5 while v 4: v read4(buf4) for j in range(v): buf[i] buf4[j] i 1 if i n: return n return i对应源码见 Solution.py。这里用[0] * 4预分配 4 格缓冲v 5作为初始哨兵值保证循环至少执行一次原因见下文「实现细节」一节。JavaJava 版本中read4定义在父类Reader4中Solution通过继承获得该 API/** * The read4 API is defined in the parent class Reader4. * int read4(char[] buf4); */ public class Solution extends Reader4 { /** * param buf Destination buffer * param n Number of characters to read * return The number of actual characters read */ public int read(char[] buf, int n) { char[] buf4 new char[4]; int i 0, v 5; while (v 4) { v read4(buf4); for (int j 0; j v; j) { buf[i] buf4[j]; if (i n) { return n; } } } return i; } }对应源码见 Solution.java。Java 中char数组默认初始化为\0但每次read4后只读取前v个有效字符因此残留值不会影响结果。CC 版本与 Java 结构一致read4同样是父类Reader4提供的接口/** * The read4 API is defined in the parent class Reader4. * int read4(char *buf4); */ class Solution { public: /** * param buf Destination buffer * param n Number of characters to read * return The number of actual characters read */ int read(char* buf, int n) { char buf4[4]; int i 0, v 5; while (v 4) { v read4(buf4); for (int j 0; j v; j) { buf[i] buf4[j]; if (i n) { return n; } } } return i; } };对应源码见 Solution.cpp。GoGo 版本使用了闭包式的函数签名read4作为参数传入solution返回一个func([]byte, int) int类型的读取函数/** * The read4 API is already defined for you. * * read4 : func(buf4 []byte) int * * // Below is an example of how the read4 API can be called. * file : File(abcdefghijk) // File is abcdefghijk, initially file pointer (fp) points to a * buf4 : make([]byte, 4) // Create buffer with enough space to store characters * read4(buf4) // read4 returns 4. Now buf [a,b,c,d], fp points to e * read4(buf4) // read4 returns 4. Now buf [e,f,g,h], fp points to i * read4(buf4) // read4 returns 3. Now buf [i,j,k,...], fp points to end of file */ var solution func(read4 func([]byte) int) func([]byte, int) int { // implement read below. return func(buf []byte, n int) int { buf4 : make([]byte, 4) i, v : 0, 5 for v 4 { v read4(buf4) for j : 0; j v; j { buf[i] buf4[j] i if i n { return n } } } return i } }对应源码见 Solution.go。可以看出四种语言的算法骨架完全一致只是语法与函数签名不同。实现细节与边界处理v 4 循环条件与哨兵初始化四份实现都不约而同地使用了v 5初始化 while (v 4)循环结构的组合。这并非随意为之而是精心设计的技巧read4的合法返回值只有 0、1、2、3、4 五种把v初始化为 5可以保证第一次进入循环时v 4恒成立从而让循环必然执行至少一次完成首次read4调用循环体末尾对v重新赋值后v 4意味着「上一次确实读满了 4 个字符文件还没读完继续读」一旦v 4包括读到 3 个及以下的最后一次或读到 0 到达 EOF循环立即终止。这样就不需要额外的while Truebreak结构代码更紧凑且不易出错。提前返回 n 与 EOF 兜底返回 i在将buf4拷贝到buf的内层循环中每拷贝一个字符都会检查if (i n) return n;这是为了处理「文件字符数多于 n」的情况如示例 4 中file leetcode只取前 5 个字符一旦目标数量已凑满立即返回n避免多读或越界写入。而循环结束后return i则是 EOF 兜底当read4返回不足 4 个字符时说明文件已耗尽此时i就是实际读到的字符总数如示例 1 中返回 3。由于i不可能超过n超了会在内层提前返回两种返回路径的结果都是「实际读取的字符数」语义一致。边界场景推演把四种示例套入上述逻辑逐一验证file abc, n 4首次read4返回 3v 3 4循环结束i 3返回 3file abcde, n 5第一次read4返回 4拷贝 4 个i 4未达 5继续循环第二次read4返回 1拷贝 1 个后i 5 n返回 5file abcdABCD1234, n 12三次read4各返回 4最后一次拷贝中i 12恰好触发return nfile leetcode, n 5第一次read4返回 4拷贝 4 个第二次read4返回 4但只拷贝第 1 个字符后i 5即提前返回 5剩余字符被丢弃——这正是本题「每次测试只调用一次 read」约束所允许的行为。从源码结构看正是因为题目约束了read只调用一次才可以用这种「读到就返回」的简单策略若换成多次调用的场景需要把buf4中未消费的字符缓存到下一次调用就必须额外维护一个残留缓冲逻辑会复杂得多。这也是本题与其系列后续题目支持多次调用版本的核心区别点。复杂度分析时间复杂度O(n)。循环每轮调用一次read4至多处理 4 个字符总处理字符数不超过 n或文件总长度且每个字符至多被拷贝一次空间复杂度O(1)。仅使用固定大小的临时缓冲buf4长度为 4不随输入规模增长。总结LeetCode 157 是一道非常典型的「交互式 模拟」题目给定一个只能按 4 字符粒度读取的底层 API要求封装出按需读取 n 个字符的上层方法。其核心要点可以浓缩为三点临时缓冲 拷贝用固定 4 格的buf4承接read4结果再逐字符写入目标buf双重终止条件i n提前返回、read4返回值小于 4 时自然退出循环哨兵技巧v 5初始化配合v 4循环条件保证循环必然执行且逻辑自洽。仓库中 题解文档 与 英文版 提供了完整的题目描述与解法说明Solution.py、Solution.java、Solution.cpp、Solution.go 四份源码可直接对照阅读或运行。掌握这道题你也就掌握了处理「受限 API 封装」类交互题目的通用方法论。赞分享示例工程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载相关推荐刷穿 LeetCode 1374生成每种字符都是奇数个的字符串——基于奇偶性的模拟构造题解LogicStack-LeetCode 仓库实战解读刷穿 LeetCode 1374生成每种字符都是奇数个的字符串——基于奇偶性的模拟构造题解LogicStack LeetCode 仓库实战解读 本篇技术指教程文档交替合并字符串LeetCode 1768双指针模拟实现 O(nm) 交替拼接——LogicStack-LeetCode 刷穿系列题解交替合并字符串LeetCode 1768双指针模拟实现 O nm 交替拼接——LogicStack LeetCode 刷穿系列题解 本篇技术指南围绕 L教程文档LeetCode 6. Z 字形变换Zigzag Conversion——doocs/leetcode 模拟法多语言题解全解析LeetCode 6. Z 字形变换Zigzag Conversion——doocs/leetcode 模拟法多语言题解全解析 本文围绕 LeetCode示例工程上一篇明日方舟美术素材怎么找一套开源资源库集齐立绘、头像与技能图标的完整方法下一篇Cocos Creator 网页调试工具快速上手ccc-devtools 一键安装与使用完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑