资讯动态

蓝桥杯十六进制转八进制:用二进制桥接实现大数进制转换

发布时间:2026/10/17 1:27:08 来源:尧图企业网站定制
十六进制转八进制这道题在蓝桥杯练习系统里的编号是BASIC-12属于基础练习里很经典的一道。但你别看它挂着“基础”两个字就真拿它当入门题看——它卡住的初学者数量绝对能排进这个级别的前三名。这道题表面上是进制转换实际考的却是字符串处理能力或者说是你能不能看穿“数据范围”这个隐藏条件。当年我在这道题上交过学费所以今天把它拆开揉碎说说背后的门道。1. 题目回顾与核心难点拆解1.1 这道题到底在考什么题目要求很简单读入一个十六进制数输出它对应的八进制数。但有个细节特别关键——输入的十六进制数长度最多可以达到100000位。注意是100000位不是100000这个数值是字符个数。很多人看到“进制转换”四个字第一反应就是这还不简单int()一下或者Integer.parseInt()一下再换成八进制输出不就完了这么做在普通场景下没错但一旦数据规模上来就全完了。int最多能表示大概21亿左右的十进制数也就是最多10位十进制数的样子。就算你用long也就19位十进制数封顶。而一个100000位的十六进制数换算成十进制位数大概在12万位左右远超任何内置整数类型的表示范围。所以这道题的本质考察点是你对“大数处理”的敏感度。你看到题目的第一反应不应该是“用什么内置函数”而应该是“这种数量级肯定不能用数值型变量直接扛”。1.2 为什么不能直接“换进制”了事有同学可能会说那我用大数库行不行比如BigInteger十六进制字符串转BigInteger再转八进制字符串输出。用BigInteger理论上可行但性能上会出问题。一个100000位的十六进制数转成BigInteger本身要做大量乘法运算时间复杂度并不好看。蓝桥杯的评测系统对时间是有要求的常规模块上BigInteger可能勉强能过但已经是在危险的边缘试探。更重要的是这条路没有抓住题目想要你锻炼的能力点。这道题放在基础练习区目的就是让你体会“进制之间的转换可以通过二进制作为桥梁来实现”。为什么因为十六进制和八进制都与二进制有天然的对应关系1位十六进制数对应4位二进制数。1位八进制数对应3位二进制数。这个对应关系是固定的所以完全可以先十六进制转二进制再把二进制转八进制全程用字符串操作不涉及任何大数运算时间复杂度优秀思路清晰代码也短。2. 解法核心巧用二进制这座桥2.1 为什么是二进制而不是十进制进制转换的传统做法是先把原进制转成十进制再转目标进制。这种做法在数值范围内有效但在字符串级别的数据规模下完全不可行。二进制则不同。十六进制、八进制、二进制三者源于“2的幂次关系”16是2的4次方8是2的3次方。这种幂次关系使得位与位之间可以直接映射不需要经过十进制的乘法乘法再除法。打个比方。你从北京去上海可能得先坐高铁到南京再倒车但如果你本来就在上海到南京之间的一条直通高速上那直接开过去就行。十进制在这里就是个“中转站”而二进制则是那条直通高速。十六进制转二进制、二进制转八进制位与位之间的转换是机械的、局部的根本不需要看整体数值的大小。这就是为什么这个解法能把100000位的大数问题简化为纯粹的字符串处理问题——处理的规模是线性的每个字符处理是常数时间的整体复杂度是O(n)其中n是输入字符串长度。2.2 进制转换的位运算规律先看十六进制转二进制。十六进制的每一位取值从0到F也就是十进制的0到15恰好能对应4位二进制十六进制: 0 1 2 3 4 5 6 7 8 9 A B C D E F 二进制: 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111我建议把这个对应表背下来不是死记硬背而是通过重复使用形成条件反射。熟练之后看到A脑子里自动就出1010看到7自动就出0111根本不用查表。比如十六进制数1A3F拆开就是1 - 0001 A - 1010 3 - 0011 F - 1111拼起来就是0001101000111111。再看二进制转八进制。八进制每一位取值从0到7对应3位二进制二进制: 000 001 010 011 100 101 110 111 八进制: 0 1 2 3 4 5 6 7把二进制串从右往左每3位一组不够的左边补0然后每组直接换成对应的八进制数字拼起来就是答案。还是拿0001101000111111来说从右往左3位一组000 110 100 011 111 1位数不够左边补0变成000 001 101 000 111 111对应八进制就是015077。注意最左边的0是补出来的实际输出时要去掉前导零所以结果是15077。这里有个容易错的地方很多人习惯从左往右分组。分组必须从右往左因为前面补0和后面补0效果完全不同。从左往右分组时最后一组可能不足3位你得单独判断从右往左分组时只有最左边那一组可能不足3位补0之后逻辑统一代码更好写。2.3 补零问题的本质补零的规则本质上是“位数对齐”的问题。十六进制转二进制时理论上每一位都固定转成4位二进制。但有一点要注意如果左边第一位转出来是0001那拼到字符串里这4位是要保留的因为它对应着原数的最高位。比如十六进制1转二进制就是0001如果你把这个0001转八进制时从右往左3位一组得到001和000结果是10没毛病。但如果十六进制转二进制时你自作主张把前导零删了变成1再3位一组就是1查表没有单独的1这一项你得手动补零反而多出错机会。所以我的建议是十六进制转二进制时每一位都严格转成4位不做任何删减二进制转八进制时再统一从右往左每组3位、左边补零。两个阶段各做各的补位逻辑简单不容易乱。3. 完整代码实现与逐段拆解3.1 整体思路与代码框架先看我用C语言写的版本这也是蓝桥杯系统里最稳妥的选择#include stdio.h #include string.h // 十六进制字符 - 4位二进制字符串 void hexToBin(char h, char *out) { switch (h) { case 0: strcpy(out, 0000); break; case 1: strcpy(out, 0001); break; case 2: strcpy(out, 0010); break; case 3: strcpy(out, 0011); break; case 4: strcpy(out, 0100); break; case 5: strcpy(out, 0101); break; case 6: strcpy(out, 0110); break; case 7: strcpy(out, 0111); break; case 8: strcpy(out, 1000); break; case 9: strcpy(out, 1001); break; case A: case a: strcpy(out, 1010); break; case B: case b: strcpy(out, 1011); break; case C: case c: strcpy(out, 1100); break; case D: case d: strcpy(out, 1101); break; case E: case e: strcpy(out, 1110); break; case F: case f: strcpy(out, 1111); break; default: strcpy(out, 0000); break; } } int main() { int n; scanf(%d, n); while (n--) { char hex[100005]; char bin[400005]; // 16进制1位 - 二进制4位 char oct[200005]; // 二进制3位 - 八进制1位 scanf(%s, hex); // 阶段一十六进制转二进制 int idx 0; int len strlen(hex); for (int i 0; i len; i) { char temp[5]; hexToBin(hex[i], temp); bin[idx] temp[0]; bin[idx] temp[1]; bin[idx] temp[2]; bin[idx] temp[3]; } bin[idx] \0; // 阶段二二进制转八进制 int binLen strlen(bin); int remainder binLen % 3; int octIdx 0; int start 0; // 处理最左边不足3位的情况 if (remainder ! 0) { int val 0; for (int i 0; i remainder; i) { val val * 2 (bin[i] - 0); } oct[octIdx] 0 val; start remainder; } // 处理正常3位一组 for (int i start; i binLen; i 3) { int val (bin[i] - 0) * 4 (bin[i1] - 0) * 2 (bin[i2] - 0) * 1; oct[octIdx] 0 val; } oct[octIdx] \0; printf(%s\n, oct); } return 0; }3.2 十六进制转二进制的两种写法上面代码用的是switch逐一映射胜在直白、容易调试。但如果你觉得写16个case太啰嗦有更简洁的写法。可以用一个二维字符数组做映射表char map[16][5] { 0000, 0001, 0010, 0011, 0100, 0101, 0110, 0111, 1000, 1001, 1010, 1011, 1100, 1101, 1110, 1111 };然后对字符做判断如果是数字字符直接用c - 0作为下标如果是大写字母用c - A 10如果是小写字母用c - a 10。这样转换逻辑就集中在一个函数里了代码更短也不容易漏掉某个字符。这两种写法本质没有区别我建议新手先用switch版本把每一个分支都写出来这样可以加深对进制对应关系的记忆。等熟练了再切换成查表法减少代码量。3.3 二进制转八进制时如何处理“最左边不足3位”这是最容易出bug的环节。二进制串长度不一定正好是3的倍数所以从右往左每3位一组时最左边可能剩下1位或2位。代码里我用了remainder binLen % 3来记录这个情况。如果remainder不等于0先把最左边这1位或2位单独转成一个八进制数字剩1位时这个位的权重是1值就是0或1。剩2位时左边位的权重是2右边位的权重是1所以val bin[i] * 2 bin[i1] * 1。算出val之后0 val就把它转成了对应的字符。本质上这里做的是“不够三位左边补0”这个操作只是我没有真的去修改字符串而是直接用逻辑计算。这样少一次字符串操作效率更高代码也更干净。3.4 输出前导零的处理十六进制数0转二进制是0000转八进制分组后是000查表转出来是0输出也是0没问题。但要注意如果输入是1十六进制转二进制是0001分组是0001左边剩1位转出来1后面一组000转出来0结果是10不对我重新算一下。0001从右往左3位一组最左边的组是0余下是001查表是1结果是1。这下对了。所以0开头的组会被转换成0这就是前导零。我代码里remainder ! 0的分支把最左边不足3位的那组单独转成数字填进oct的开头。比如bin是0001101000111111binLen是1616 % 3 1所以最左边单独那一位是0算出来val 0oct[0] 0。后面的组依次转换最终oct字符串以0开头输出时就带着这个前导零。这样输出的结果可能有前导零但蓝桥杯评测时数字字符串的前导零是允许的不会影响正确性。如果你比较讲究可以在最后输出前去一下前导零int start 0; while (oct[start] 0 oct[start 1] ! \0) { start; } printf(%s\n, oct start);不过按我的经验这种题不处理前导零也能过纯属锦上添花。4. 常见问题与排错实录4.1 用%d或%x直接读入导致溢出这是我见过的最常见错误。int x; scanf(%x, x); printf(%o\n, x);这段代码能过测试用例里的短数据比如39、ABC这种。但一旦评测数据是几百位长的十六进制数scanf(%x)直接读取失败或者溢出结果完全错乱。问题在于很多初学者只在练习系统里跑过了那几个短例子就误以为“自己已经AC了”结果一到正式比赛或者隐藏数据就翻车。这时候才明白题目限制里的“长度不超过100000位”才是这道题真正的门槛。所以拿到任何进制相关的题目第一件事就是看数据范围。如果数据范围超过64位整数的表示能力马上切换到字符串处理思路别犹豫。4.2 二进制字符串数组开得不够大十六进制每一位转成4位二进制所以长度为n的十六进制字符串对应的二进制串长度是4n。如果你开数组的时候只盯着十六进制字符串的长度忘了乘4程序运行到一半就会发生数组越界轻则输出错误重则直接段错误崩溃。同理二进制转八进制后八进制字符串长度大约是4n/3所以也要留够空间。我习惯的做法是稍微开大一点毕竟蓝桥杯的内存限制给得很宽裕。char hex[100005]; // 题目给的输入上限 char bin[400005]; // 4倍必然够用 char oct[200005]; // 不超过4/3倍宽松点取200005这里多开一点空间节约的是调试时间。别在这种无关紧要的地方抠门。4.3 十六进制字符大小写问题十六进制数可能包含大写字母也可能包含小写字母比如A和a都是十进制的10。很多人在转换时只处理了A-F遇到a-f就直接走了分支输出的二进制串全是0000结果自然不对。解决方式很简单映射表里大小写都写上或者在进入转换函数之前统一把字符转成大写if (c a c f) { c c - a A; }这个是细节题但每次比赛都能看到有人在这里栽跟头。4.4 多组数据之间的变量残留题目通常会输入多组测试数据如果你定义的是全局变量或者在外面初始化的数组处理完一组数据后没有清空下一组数据拼接出来的字符串就会带着上一轮的残留内容。我习惯把idx、octIdx这些游标变量声明在while循环内部每次循环重新赋值从0开始。字符串数组本身只要在末尾正确加上\0内容残留也无所谓因为所有操作都是从0号下标开始的。4.5 关于strlen的重复调用如果一组数据的长度是nstrlen的时间复杂度是O(n)只在循环外调用一次问题不大。但如果你把strlen(bin)写在for循环的条件里每次循环都重新扫描一遍字符串O(n)就变成了O(n²)数据一大性能直接崩掉。尤其是这道题数据长度到100000位O(n²)的代价是不可接受的。// 错误示范for 循环条件里每次都算 strlen for (int i 0; i strlen(bin); i 3) { ... } // 正确示范先存长度再进循环 int binLen strlen(bin); for (int i start; i binLen; i 3) { ... }这是个微小的性能优化但这种习惯在算法题里很重要。数据规模小的时候看不出来差距数据一大一个是秒过一个可能超时。5. 备赛视角这道题教会了我们什么5.1 善用“位运算对应关系”解决大数问题二进制、八进制、十六进制三者之间的转换是计算机系统里特别基础但特别重要的关系。因为它们在“2的幂”上同源所以可以用位映射的方式直接转换不涉及大数运算。我之前碰到过一道模拟题要求实现一个十六进制转十进制的程序数据也很大。当时很多人都绕不开大数乘法但我发现如果先把十六进制转成二进制再通过逐位累加的方式构造十进制字符串效率和稳定性反而更好。这不是说每个进制转换题都必须走二进制而是说你要培养这个意识进制之间存在幂次关系时可以先考虑是否能用位映射简化问题。5.2 字符串与字符之间的灵活切换这道题里你时时刻刻在做字符和数字的转换0 val是把数字变成字符bin[i] - 0是把字符变成数字。很多初学者容易在这种转换上卡壳。记住一个原则字符0到9在ASCII表里是连续的所以0 5就等于57 - 0就等于7。这不仅仅是这道题的技巧也是将来做字符串处理题目的基本功。5.3 做题从暴力解法开始然后逐步优化初学者拿到这道题完全可以先用BigInteger写一版能跑通的程序哪怕数据极限时可能超时。先把整个流程跑通确认自己理解进制转换的逻辑然后再用二进制桥接法优化。我自己比较推荐这种“先实现再优化最后抽象总结”的学习路径。如果你一上来就要求自己写出最优解很容易卡在某个细节上反而寸步难行。6. 一个更精简的实现版本如果你C语言的功底已经不错了下面这个版本更适合在竞赛中使用代码量更少出错概率也低#include stdio.h #include string.h int main() { char h[100005], b[400005], o[200005]; char map[16][5] {0000,0001,0010,0011,0100,0101,0110,0111, 1000,1001,1010,1011,1100,1101,1110,1111}; int n; scanf(%d, n); while (n--) { scanf(%s, h); int len strlen(h), bl 0, ol 0; for (int i 0; i len; i) { int v; if (h[i] 0 h[i] 9) v h[i] - 0; else if (h[i] A h[i] F) v h[i] - A 10; else v h[i] - a 10; for (int j 0; j 4; j) b[bl] map[v][j]; } b[bl] \0; // 从右往左3位一组 int rem bl % 3; if (rem) { int val 0; for (int i 0; i rem; i) val val * 2 (b[i] - 0); o[ol] val 0; } for (int i rem; i bl; i 3) { int val (b[i] - 0) * 4 (b[i1] - 0) * 2 (b[i2] - 0); o[ol] val 0; } o[ol] \0; printf(%s\n, o); } return 0; }这个版本去掉了hexToBin函数改成了查表法和字符拼接整体流程更紧凑。我个人的体会是考试的时候代码写得越短调试越容易。因为你心理压力大的时候100行的代码和50行的代码找bug的难度完全不同。当然前提是你真的理解每一行在干什么而不是为了短而短。另外最后一个细节题目如果是多组数据记得用while(n--)处理别自己手动数循环次数容易数错。这道题做透了进制转换里最麻烦的“大数转换”这个坎就算过了。以后遇到类似的题目不管是十六进制转十进制还是八进制转二进制你的思路都会清晰很多。要是你能不看任何参考自己写下这个二进制桥接的解法那这道题的价值就算真正吃透了。

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

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

免费获取报价 →
↑