
简介CSP-J2024题目与答案解析以单份PDF文档呈现面向准备CSP-J认证的初学者、竞赛选手及辅导教师用于熟悉真题结构、核对答案并梳理考点。压缩包内共1个PDF文件大小约797KB按单项选择题与阅读程序两大板块编排便于在电脑或移动端直接查阅。已有7555人学习下载具备一定参考热度。内容覆盖int存储范围、二进制格雷码、存储单位换算、C基本数据类型与循环语句、ASCII码、二分查找、无向图度数和、二叉树遍历、栈操作及排列组合等基础知识并对阅读程序中的质数判断统计、函数封装调用、数组字符串输入输出和位运算逐题给出解析。读者可借助答案与题解定位错因理解命题思路形成从概念辨析到代码阅读的复习路径适合赛前自测与查漏补缺。1. 先看清分值结构30 分选择题70 分代码题CSP-J 第一轮的分值分布是最容易被误判的地方单项选择题 15 题每题 2 分合计 30 分阅读程序 40 分完善程序 30 分。后两块加在一起 70 分全部是读代码、判断题和填空题。刷一百道选择题带来的提分幅度通常不如把三篇阅读程序逐行推一遍。这场考试考的不是“会不会从零写算法”而是在 90 分钟里能不能看懂别人写的代码、能不能在四个选项里挑出唯一自洽的那个。适合第一次参赛、目标是过晋级线或冲一等的同学也适合带学生的教练当作讲评素材。拿分的核心就两条把固定考点做成条件反射把代码题按“先看结构、再代小数据、最后验边界”的顺序做完。2. 单项选择题15 道题里的送分题与分水岭2.1 先把 15 道题分个层这一部分每题 2 分看起来零碎但考点高度固定。把近几年的题面摊开看结构几乎一样5 到 6 道计算机基础常识、4 到 5 道数据结构与算法常识、3 道左右计数与组合、2 到 3 道 C 语言细节。真正拉开差距的只有两三个位置其余都是“背到就会、没背就丢”的题。题号考点失分风险1int 类型的存储范围低记住 32 位与补码即可2进制换算后求十进制值中容易在进位处算错3三个部门抽人每部门至少一人中高要分类讨论4格雷码序列中靠定义推算5MB 换算成 bit低但漏乘 8 是常见坑6C 基本数据类型低7C 循环语句低8ASCII 码加法低9二分查找最多比较次数中10操作系统辨识低11无向图顶点度数和低12前序 中序还原后序中高13栈的出栈序列可能性高14女生必须相邻的排列数中15编译器的作用低按这张表安排时间前 10 题控制在 12 分钟内做完把省下的时间全部投给第 3、12、13 题。这三道题合计 6 分但如果思路没建立起来很容易连错两题以上。2.2 计算机基础与单位换算三类必背题第一类是整数范围。32 位有符号 int 用补码表示取值范围是 -2^31 到 2^31-1也就是 -2147483648 到 2147483647。记法只有一句负数比正数多一个因为 0 占用了正数那一侧的一个位置。题目常把这个范围写成二进制形式让你挑注意区分“位数”和“范围端点”。第二类是存储单位。1 字节等于 8 个二进制位1KB 等于 1024 字节1MB 等于 1024KB。所以 1MB 换算成 bit 是 1024 × 1024 × 8即 2^23。这类题的陷阱全在“字节”和“位”之间那一步乘法题面写的是 1Kb 位 1024 字节这种表述时先统一成字节再乘 8别看到 1024 就直接选。第三类是常识辨识。操作系统有 Linux、Windows、macOS 这一类记事本只是某个系统里的一个附属程序不是操作系统。编译器的作用是把源代码翻译成机器代码它不负责执行、不负责调试、也不负责运行时内存管理——这三件事分别是解释器/运行环境、调试器、内存管理机制干的活。C 的基本数据类型只有整型、浮点型、字符型、布尔型这一类struct 是复合类型是“自己拼出来的”不算基本类型。循环语句只有 for、while、do-while 三种repeat-until 属于别的语言。2.3 数据结构常识四个公式 一个模拟动作二分查找的比较次数题面一般给 n 个有序元素问最多比较几次。结论是 ⌊log2 n⌋ 1等价于向上取整的 log2(n1)。当 n 是 2 的整数次幂时最直观n 1024 时最多比较 11 次因为每次折半折到只剩一个元素为止。无向图的顶点度数之和等于边数的两倍。理由很直白每一条边有两个端点对两个端点的度数各贡献 1。这条结论在有向图里要拆成入度和出度分别看别混。二叉树遍历是必考项。已知前序和中序求后序标准流程是三步前序的第一个元素是根在中序里找到这个根把中序切成左段和右段左段的长度就是前序里紧跟根之后那一段的长度。切完递归下去。规范做法是在草稿纸上画括号别在脑子里硬转。栈的出栈序列题最稳的办法是模拟不是凭感觉。逐步执行“能出就出、不能出就进”把每个选项走一遍走不通的那个就是答案。有个经验判断出栈序列中如果出现某个较小元素排在两个比它大的元素后面且这三个元素的原始入栈顺序是递增的那它很可能不合法——但这只是排查线索最终仍要模拟确认。格雷码的定义是相邻两个编码只有一位不同。做题时按定义逐个推第一个是 n 个 0之后每次翻转从右往左第一个与右边相邻位相同的位……更省事的办法是记生成式第 i 个格雷码等于 i 异或 (i 1)。// 生成 n 位格雷码序列的前几项i 从 0 开始 int gray(int i) { return i ^ (i 1); // 异或右移一位相邻项必只差一位 } // gray(0)0, gray(1)1, gray(2)3, gray(3)2, gray(4)6 ...这段代码的关键在i 1它把当前编号整体右移一位再与自身异或得到的结果序列天然满足“相邻只变一位”。答题时不必写代码但记住这个式子可以三秒内排出前 8 项的格雷码比在草稿纸上反复推演靠谱。2.4 计数问题捆绑法与分类讨论排列组合在选择题里出现频率极高题型只有两类。一类是“必须相邻”用捆绑法把要求相邻的元素打包成一个整体先算这个整体内部的排列数再和其余元素一起全排列。比如三个女生必须相邻就把三个女生捆成一束内部有 3! 种排法这一束再和其余男生一起排列若男生有 m 个则总数为 3! × (m1)!。注意“束”和男生一起排时元素个数是 m1 而不是 m这是最常见的漏项。另一类是“每个部门至少一人”用分类讨论先按人数分配型把 6 个名额拆成若干种形态比如 1-1-4、1-2-3、2-2-2每种形态内部分别用组合数相乘最后把各类结果相加。这类题的关键是先定“分配型”再套组合数 C(n, k)顺序反了就会重复计数。答案里通常有一个选项正好是漏掉某一类分配型的结果那就是专门给你准备的。2.5 真题演练五道题的完整推导第一题1MB 等于多少二进制位。先把 1MB 拆成 1024KB再拆成 1024 × 1024 字节最后乘 8 得 8388608 位即 2^23。四个选项里通常有一个是 2^20只算到字节、一个是 2^30把 KB 当成 1000 算答案取 2^23。第二题前序 [A,B,D,E,C,F,G]、中序 [D,B,E,A,F,C,G] 求后序。前序首元素 A 是根中序里 A 左边是 D,B,E右边是 F,C,G。于是左子树的前序是 B,D,E、中序是 D,B,E右子树同理。左子树中 B 是根D 在左、E 在右右子树中 C 是根F 在左、G 在右。整棵树是深度 3 的满二叉树后序为 D,E,B,F,G,C,A。第三题给定入栈顺序判断哪个出栈序列不可能。逐项模拟如果某个选项要求先弹出栈底元素而此时栈顶还有别的元素压着那就不可能。规范动作是把入栈序列写成一列对每个选项从左到右走遇到不匹配就继续入栈入到没有可入的还匹配不上判死。第四题三个女生必须相邻。按捆绑法3! 乘上男生数 1的阶乘代入题面给的男生人数直接算。第五题编译器的作用。四个选项里“直接执行源代码”是解释器的活“进行代码调试”是调试器的活“管理程序运行时的内存”是运行环境或内存管理机制的活只剩“把源代码转换为机器代码”。做这一节时有个习惯值得养成每道题在草稿纸上写一行“这题在考什么”写完再选。选择题失分八成不是不会而是看错题——问的是“不是”还是“是”是“最多”还是“最少”。3. 阅读程序读懂别人的代码比会写更值钱3.1 质数三函数判分点全在边界上第一篇阅读程序给三个函数判断质数、统计质数个数、求质数和。三块代码结构一样都是“外层循环从 2 到 n内层调用判断函数”。判断题和选择题的判分点集中在三处。bool isPrime(int n) { if (n 1) return false; // 出口 10 和 1 都不是质数漏了这行必错 for (int i 2; i * i n; i) // 出口 2i*in 等价于 isqrt(n)写成乘法避开浮点误差 if (n % i 0) return false; // 找到一个因子就能判定不是质数 return true; } int countPrimes(int n) { int count 0; for (int i 2; i n; i) // 从 2 开始1 不计入 if (isPrime(i)) count; return count; }第一个判分点是n 1。没有这一行isPrime(1) 会因为循环条件 1*11 成立而进入循环体、找不到因子、返回 true把 1 当成质数。第二个判分点是i * i n而不是i n。改写成i n或i n/2结果不变只是循环次数变多、效率变低。这是判断题最爱设的陷阱题目说“把条件改成 in/2输出会变”答案是错误因为判断逻辑没被破坏。第三个判分点是数值。10 以内的质数是 2、3、5、7个数 4、和 17。20 以内有 8 个质数。50 以内质数之和是 328。这几个数建议直接记住考场上省一分钟。题目里还有一类“把某行改掉后输出会怎样”的选择题处理方法统一改的如果是循环上界或循环变量的初值判断逻辑通常不变只有效率变化改的如果是累加、比较、返回语句才可能改变结果。先分类再动手算。3.2 动态规划阅读相邻两个必须选一个第二篇程序是一段一维 DP。题面给的场景是给定一个花费数组任意相邻两个位置必须选一个求最小总和。int compute(vectorint cost) { int n cost.size(); vectorint dp(n 1, 0); dp[1] cost[0]; // 只走 1 级时的起步花费 for (int i 2; i n; i) dp[i] min(dp[i - 1], dp[i - 2]) cost[i - 1]; // 上一步跨 1 级或 2 级取小的 return min(dp[n], dp[n - 1]); // 最后一步允许踩到 n-1 就收工 }状态含义是“走到第 i 级、并且把第 i 级的花费计入时的最小总和”。转移方程体现的是到达第 i 级只可能从第 i-1 级跨一步来或从第 i-2 级跨两步来取较小值再加上当前这一级的花费。最后返回min(dp[n], dp[n-1])有点反直觉为什么不是直接返回 dp[n]因为题目允许最后一步跨过第 n 级停在 n-1 也算到达终点所以两个候选都要看。这一行是判断题和选择题的固定考点。手推的方法是从小数组开始。cost 为 {10, 15, 20} 时dp[1] 等于 10dp[2] 等于 min(10, 0) 15即 15dp[3] 等于 min(15, 10) 20即 30返回 min(30, 15) 等于 15。这组数适合先在草稿纸上走一遍确认自己理解的状态含义是对的再去推长数组。长数组如 {1, 100, 1, 1, 1, 100, 1, 1, 100, 1} 手推会烦但规律明显遇到 100 就绕开靠前一级或前两级的 1 跨过去最后答案是 6。考场上时间紧张时可以先看选项中量级最小的那个再用“连续几个 100 不可能同时被选中”去验证。还有一类变形题把min(dp[i-1], dp[i-2]) cost[i-1]改成dp[i-1] cost[i-2]问输出是多少。这时候不要试图推状态含义直接带小数组模拟三步就能出数。3.3 递归与边界那个“返回值”和“输出”不是一回事第三篇程序是一个两参数的递归函数主体是return a customFunction(a, b - 1)出口是b 0时返回 a。主函数拿到返回值后再平方输出。int customFunction(int a, int b) { if (b 0) return a; // 出口里还要再加一个 a所以总共是 b1 个 a return a customFunction(a, b - 1); // 每下探一层就加一个 a } int main() { int x, y; cin x y; int result customFunction(x, y); cout pow(result, 2) endl; // 输出的是平方不是函数返回值 return 0; }先算清楚这个函数到底返回什么b 从初始值一路减到 0每次累加一个 a出口那层又返回一个 a所以一共是 b1 个 a 相加即 a × (b 1)。输入 2 和 3 时函数返回 2 × 4 等于 8主函数输出 8 的平方 64。判断题说“customFunction(2,3) 的返回值为 64”答案是错误——64 是主函数的输出不是函数的返回值题目故意在“返回值”和“输出”之间做了区分。输入 5 和 4 时函数返回 5 × 5 等于 25主函数输出 625。输入 3 和 3 时返回 3 × 4 等于 12输出 144。变体题把函数体改成return a customFunction(a - 1, b - 1)问输入 3 和 3 时输出什么。逐层展开f(3,3) 3 f(2,2) 3 2 f(1,1) 3 2 1 f(0,0) 3 2 1 0 6输出 36。改一个参数就从“乘法”变成“递减求和”这是这类题最典型的考法。还有一个固定判断题b 为负数时会怎样。因为出口条件是 b 0负数永远不会等于 0递归无终止条件最终爆栈。答案是正确的。这类“边界不收敛”的判断只要出口条件不是“小于等于”或“大于等于”就要警惕。3.4 判断 选择混排时的作答顺序这一大题的题面结构是先给一段完整程序再跟 4 到 5 个判断题每题 1.5 分和 3 到 4 个选择题每题 3 分。分值上选择题是判断题的两倍但判断题往往只需要代一组特殊值。合理的顺序是先用 2 分钟通读代码把函数名、循环边界、返回值这三样在草稿纸上写下来然后一口气把判断题做完判断题基本靠特殊值代入最后回头做选择题选择题需要完整手推占分也重。有一个判断值得反复提醒所有“改了这一行输出会不会变”的题先问自己“这一行影响的是结果还是效率”。影响效率的结果不变影响结果的再具体算。这条判断能砍掉一大半纠结时间。4. 完善程序两道填空的固定套路4.1 平方数判断枚举的上下界与返回值第一道完善程序要求补全“判断一个正整数是否完全平方数”的函数。思路是枚举可能的平方根从 1 枚举到 √num逐个检查 i × i 是否等于 num。bool isSquare(int num) { int i 1; // ① 平方根从 1 开始枚举0 不是正整数 int bound (int)floor(sqrt(num)); // ② 上界取 num 的算术平方根并向下取整 for (; i bound; i) { if (num i * i) { // ③ 判断的是是否相等不是赋值 return true; // ④ 找到就返回 true } } return false; // ⑤ 枚举完都没找到返回 false }五个空的判断依据各不相同。第①空看的是变量含义i 表示“可能的平方根”平方根最小是 1所以填 1而不是 0——填 0 时 num 为 1 会判错。第②空看的是循环的终止条件枚举到 √num 就够了超过这个值 i × i 必然大于 num所以填(int)floor(sqrt(num))。选项里那个sqrt(num) - 1是专门给算错边界的人准备的。第③空考的是运算符num i * i是赋值num i * i才是判断。这类空每年都出见到单等号先警觉。第④空和第⑤空是一对循环内部找到时返回 true循环整体结束还没找到返回 false两个空一真一假不能同时填成同一个值。4.2 汉诺塔递归四个参数的意义在递归中会互换第二道完善程序是汉诺塔。函数签名是dfs(i, src, tmp, tgt)含义是“把 i 个圆盘从 src 借助 tmp 移到 tgt”。void dfs(int i, char src, char tmp, char tgt) { if (i 1) { // ① 只剩一个盘子直接搬 move(src, tgt); // ② 从哪里搬到哪里 return; } dfs(i - 1, src, tgt, tmp); // ③ 先把上面 i-1 个挪到临时柱注意后两个参数互换 move(src, tgt); // 把最大的那个搬到目标柱 dfs(i - 1, tmp, src, tgt); // ④⑤ 再把临时柱上的 i-1 个挪到目标柱 }这道题的难点不在递归思想而在于参数的顺序在两次递归调用里是反的。第一次递归是为了“把上面 i-1 个挪开”此时目的是临时柱 tmp所以调用变成(i-1, src, tgt, tmp)——原来当临时柱的那个位置现在要放目标柱。第二次递归是从临时柱搬到目标柱起点换成 tmp辅助柱换成 src调用是(i-1, tmp, src, tgt)。判断哪个空填什么有个笨但有效的办法不需要理解汉诺塔的最优解只需要检查“这次调用想把盘子从哪搬到哪”。第一次递归结束后要执行 move(src, tgt)说明在它之前最大盘还压在 src 上、上面那摞必须堆在 tmp 上所以这一摞的终点是 tmp参数自然写成 src, tgt, tmp。这个推导比背诵递归模板可靠。第①空是递归出口剩 1 个盘子时直接搬填 1。第②空填src, tgt因为只剩一个时就从起点搬到终点没有中间柱。第⑤空是规模参数每次减一填i - 1。三个空里有两个可以靠“参数含义”推只有一个需要真正理解递归。4.3 填空类题目的三步检查法第一步看变量含义。每个空所在的变量是干什么的先把它的语义写在旁边再决定填什么。第①空、第⑤空基本都是语义题。第二步看循环或递归的边界。平方数题的 bound、汉诺塔的 i 1都是边界边界填错程序要么多跑一轮要么少跑一轮输出直接错。第三步看返回值和类型的自洽。返回 bool 的函数不能返回整数 1虽然能隐式转换但选项里若同时给 true 和 1选 true。这类细节选项专门用来筛人。做完三个空之后把填好的代码在草稿纸上用最小规模跑一遍。平方数题代 num 1 和 num 4汉诺塔代 i 2能跑通基本就对了。这三十秒的验证比在四个空之间来回纠结有用得多。5. 易错点五个每年都有人失分的地方5.1 把主函数的输出当成函数的返回值错在哪题目问 customFunction(2, 3) 的返回值看到程序最终打印 64就填了 64。为什么错函数返回的是 8主函数拿 8 去平方才输出 64。题面用词是“返回值为”考的就是这两者的区分而“程序的输出为”问的才是最终打印结果。出题人把两个问法放在同一篇程序里轮换就是让人看漏。怎么答对读题时把问句圈出来出现“返回值”就在草稿上写“函数体算出来的那个数”出现“输出”就写“cout 打印的那个数”。养成先圈问句再动笔的习惯这一分基本不丢。5.2 以为改了循环条件结果一定会变错在哪题面把i * i n改成i n/2或i n凭直觉判断“循环次数变了结果肯定变了”选了错误选项。为什么错这类循环的作用是“找因子”只要上界不小于 √n找因子的能力就没有被削弱。循环跑得更多只是在做无用功判断结论完全一致。出题人利用的是“改动等于结果变化”的错觉。怎么答对把改动分成两类——动结果的累加、比较、返回、初值和动效率的循环上界放大、循环变量步长变化但覆盖面不变。只有前一类需要重算后一类直接判“输出不变只影响运行时间”。这条分类标准建议写进错题本第一页。5.3 二叉树还原时把左右子树的区间切反错在哪前序是根在前的顺序中序是根在中间的顺序切分时把中序左段的长度套到前序的右边去还原出来的树左右颠倒后序自然全错。为什么错前序的结构是“根 | 左子树 | 右子树”中序的结构是“左子树 | 根 | 右子树”。左子树在两个序列里的长度必须相等这是唯一的锚点。凭感觉切很容易把中序里根的左段对应到前序里根的右段。怎么答对固定动作——先在前序取首元素为根再在中序里定位这个根数出它左边有几个元素记作 L然后到前序里从根之后的第一个元素开始数 L 个这一段就是左子树的前序。整个过程在纸上画括号画完再写后序。不要跳步。5.4 栈的出栈序列靠“感觉”判断错在哪看到选项里有几个数顺序颠倒就判“不可能”结果错在看似正常的那一项上。为什么错栈的合法出栈序列有严格约束但约束不直观。凭感觉判断时注意力会被“升降序”带偏忽略了栈内元素的相互压制关系——只要某个元素还在栈里比它晚入栈的元素就不可能先出来。怎么答对老老实实模拟。把入栈序列竖着写一列对每个选项从左往右扫用一个指针指向当前待入栈元素扫到不匹配就继续入栈入完还匹配不上就判死。五步之内能出结论比反复琢磨快。5.5 汉诺塔填空照抄模板不看参数位置错在哪两次递归调用的后三个参数直接照抄上一行或者把(i-1, src, tgt, tmp)和(i-1, tmp, src, tgt)写反。为什么错递归里参数的“身份”会随目标变化而互换。第一次递归的目的是把上面一摞挪到临时柱那一刻临时柱就成了这次调用的目标柱第二次递归的起点变成了临时柱。位置一错整段代码虽然能编译能跑但移动步骤全乱。怎么答对不背模板改问一句话——这次调用要把盘子从哪搬到哪谁是辅助。三个答案写出来参数顺序自然就定了。做完再代 i 2 手动走一遍先把上面 1 个挪到临时柱把最大的搬到目标柱再把那 1 个挪过去三步对得上就没错。上面五条里第一条和第二条属于“读题层面”的错靠习惯就能堵住第三条到第五条属于“方法层面”的错需要靠固定动作矫正。备考时把这几条单独抄一页每次模考前扫一眼比多刷十套卷更划算。6. 时间分配与押题90 分钟怎么切出这 10 分时间要给够但不能平均给。比较稳的切法是单项选择题 20 分钟阅读程序 40 分钟完善程序 25 分钟留 5 分钟回头补空和检查答题卡填涂。板块分值建议用时策略单项选择题3020 分钟前 10 题 12 分钟内清完第 3、12、13 题留足时间阅读程序4040 分钟每篇 13 分钟先判断题后选择题完善程序3025 分钟每道 12 分钟填完立刻代最小规模验证检查—5 分钟只看空着的和改过的押题方向不用猜太细看分值结构就能反推。单项选择题里进制与单位换算、树与图的常识、栈与队列的操作、排列组合四块必占大头阅读程序里质数/约数类枚举、一维 DP、递归求和这三类模板轮流上场判断题目基本围绕“边界改一改结果变不变”完善程序偏爱“枚举 边界”和“递归 参数”两种填空平方数、汉诺塔、二分查找、进制转换是常客。真正值得练的不是题量是节奏。我自己的习惯是每做完一篇阅读程序就在草稿纸右上角写一句“这篇在考什么”写完再进下一篇。这个动作看着多余但它在最后 5 分钟检查时救过我很多次——翻回去看那行字就能立刻判断某道题当时是不是想岔了。从那以后我每次带人练这套题都强制走一遍“圈问句、分类改动、代最小规模”这三步哪怕只做一道题也要走完。慢是慢一点但错题率降下来之后那 10 分是实打实多出来的。希望帮到你。本文还有配套的精品资源点击获取