尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

华为OD机试整数编码详解:位运算与Varint原理实战

华为OD机试整数编码详解:位运算与Varint原理实战 1. 问题引入从一道高频机试题说起最近在技术社区和求职论坛上“华为OD机试”的热度一直居高不下尤其是其中的“整数编码”题目几乎成了必刷的经典。很多朋友在准备时看到“编码”二字可能会联想到复杂的压缩算法或者通信协议心里先打起了退堂鼓。其实这道题的核心逻辑非常清晰它考察的是对整数二进制表示、位运算以及数据流拼接的基本功是检验一个程序员基础是否扎实的绝佳试金石。我自己在带团队和面试时也常常用类似的题目来快速判断候选人的逻辑思维和代码实现能力。简单来说这道题的任务是将一个非负整数比如 300转换为一串特殊的字节序列。这个序列的规则并非我们日常接触的UTF-8或Base64而是一种自定义的、用于高效传输或存储的紧凑格式。理解并实现这个规则关键在于抓住两个核心如何将一个整数拆分成7位一组以及如何用最高位第8位来标识一个字节是否是当前整数的最后一个字节。这听起来有点抽象别急我们接下来会用一个具体的例子手把手拆解整个过程并深入到二进制位操作的每一个细节。你会发现抛开对“编码”二字的畏惧后它的本质就是一次严谨的位操作练习。2. 规则拆解7位一组与最高位标识要编码一个整数我们首先要把它转换成二进制。但这里的转换不是直接输出二进制字符串而是要按照特定的规则重新“包装”。这个规则可以概括为以下几步获取二进制表示将输入的整数转换为二进制形式去掉开头的‘0b’。例如整数300的二进制是100101100。7位一组低位补零从二进制串的**最低位最右边**开始向左每7位分成一组。如果最左边的一组不足7位则在它的高位左边补零凑足7位。对于100101100从右向左分组第一组最低7位0101100注意原始低7位是101100补一个0成为7位第二组剩余位0000010剩余位是10补5个0成为7位设置最高位第8位这是编码规则的核心。对于分好的每一个7位组我们需要在前面左边加上一个“标识位”构成一个8位的字节byte。规则如果当前7位组是该整数的最后一个组即最左边的组则其标识位为0否则即后面还有组标识位为1。对于300第二组0000010是最后一个组前加0得到00000010十六进制0x02。第一组0101100不是最后一个组前加1得到10101100十六进制0xAC。逆序输出将处理好的字节按照从最后一个组到第一个组的顺序即与我们分组顺序相反的顺序输出得到最终的编码字节序列。对于300最终序列是0xAC0x02。这个过程可以用一个更直观的图示来理解我们像处理一个数字一样从个位开始每“7”进制进一位但用字节来承载。每个字节的低7位是“数值”最高位是“是否还有后续”的标记。注意这里提到的“从左到右”、“高位低位”是基于我们书写二进制串的习惯左边是高位右边是低位。而在分组和补零操作时一定要牢记是从数值的最低有效位开始操作这是位运算的正确视角。3. 关键逻辑实现位运算的实战理解了规则接下来就是用代码实现。这里最核心的技巧就是位运算它比字符串操作更高效、更直接。我们将以Python为例展示如何一步步实现编码器。其他语言如Java、C的逻辑是完全相通的。3.1 核心循环逐7位提取与判断编码的主逻辑是一个循环循环的条件是待编码的数值num大于0。在循环中我们不断从num中提取出低7位并判断是否还有后续数据。def encode_number(num): 编码一个非负整数 if num 0: return [0x00] # 特殊情况0的编码是 [0] result [] while num 0: # 1. 取出低7位 seven_bits num 0x7F # 0x7F 的二进制是 01111111按位与操作可屏蔽掉第8位及以上的所有位 # 2. 判断当前取出的7位是否是“最后”的7位 num 7 # 将原数值右移7位相当于去掉已经处理完的低7位 if num 0: # 如果不是最后7位则最高位置1 byte_val seven_bits | 0x80 # 0x80 的二进制是 10000000按位或操作可将最高位置1 else: # 如果是最后7位则最高位保持0 byte_val seven_bits # 3. 将构造好的字节加入结果列表 result.append(byte_val) # 4. 结果列表是“从后往前”添加的需要反转 result.reverse() return result让我们用num300来跟踪一下这个过程初始num 300(0b100101100)第一次循环:seven_bits 300 0x7F 44(0b0101100)num 7-num 2num (2) 0为真所以byte_val 44 | 0x80 172(0b10101100,0xAC)result [172]第二次循环:seven_bits 2 0x7F 2(0b0000010)num 7-num 0num (0) 0为假所以byte_val 2(0b00000010,0x02)result [172, 2]循环结束result.reverse()-[2, 172]等等这里有个关键点3.2 顺序的陷阱为什么不需要反转上面代码的最后一步进行了result.reverse()这是基于我们最初“从后往前分组”的理解。但在实际的循环算法中我们每次处理的是当前num的低7位并且当num右移后下次循环处理的就是“更高”的7位。也就是说在循环中我们首先得到的是原始数字的低位组对应最终输出的靠后字节然后得到的是高位组对应最终输出的第一个字节。因此如果我们把每次循环得到的字节直接按顺序添加到一个列表那么这个列表自然就是逆序的低位字节在前高位字节在后。但是在输出时我们通常要求按照从高位字节到低位字节的顺序输出。所以有两种处理方式在循环中将字节插入结果列表的头部result.insert(0, byte_val)这样循环结束后顺序就是正确的。但列表头部插入操作insert(0, ...)的时间复杂度是O(n)对于大数据量不友好。在循环中使用append添加到尾部O(1)操作循环结束后再反转列表。虽然反转也是O(n)但整体性能通常优于多次头部插入。然而在华为OD的这道题中输出要求往往是直接打印每个字节的十六进制字符串用空格分隔。此时我们完全可以利用循环的特性用一个栈Stack或者直接用一个列表再反转的思想但最终输出时从后往前遍历即可无需物理上反转列表。以下是更贴近机试要求的写法def encode_and_print(num): if num 0: print(00) return bytes_list [] while num 0: seven_bits num 0x7F num 7 # 关键判断如果右移后num还有值说明当前7位不是最后一组 byte_val seven_bits | 0x80 if num 0 else seven_bits bytes_list.append(byte_val) # 逆序输出因为bytes_list中低位组在前高位组在后 hex_strs [f{b:02X} for b in reversed(bytes_list)] print( .join(hex_strs)) # 测试 300 encode_and_print(300) # 输出AC 02这里f{b:02X}是格式化字符串:02X表示将整数b格式化为至少2位宽的十六进制大写字符串不足2位前面补零。这是机试中常见的输出格式要求。4. 边界处理与常见“坑点”在机试或实际编码中边界情况往往是丢分的关键。对于整数编码以下几个边界和细节必须特别注意4.1 输入为0的情况数字0的二进制表示是0。按照规则7位一组只有一组0000000。它是最后一组也是唯一一组所以最高位为0得到字节00000000(0x00)。输出00。如果代码中没有处理num0的情况while num 0循环根本不会进入结果就是一个空列表导致错误。因此必须在函数开始处显式处理。def encode_number(num): if num 0: return [0x00] # 或者直接返回 [0] # ... 其余逻辑4.2 负整数的处理题目通常明确要求是“非负整数”。如果输入可能是负数需要首先确认需求。对于有符号整数常见的处理方式有两种拒绝处理直接抛出异常或返回错误。转换处理如果需要支持通常采用ZigZag编码等方式先将有符号整数映射到无符号整数域再进行编码。但这超出了本题的原意除非题目特别说明。在华为OD的上下文中务必仔细阅读题目描述确认输入范围。99%的情况下输入都是非负整数。4.3 大整数的支持Python的整数本身支持任意精度大整数所以直接处理很大的数比如2**1000也没有问题。但在Java或C中需要使用BigInteger或类似的大数类。在机试中如果使用这些语言要留意题目给出的数据范围如果可能超过long(C:long long) 的范围就要考虑大数类。不过这道题目的测试用例一般都在标准整型范围内。4.4 输出格式的严格匹配机试系统的判题是严格的字符串比对。常见的输出要求有每个字节以两位十六进制大写表示AC 02。字节间用一个空格分隔。末尾不能有多余空格或换行但通常print自带的换行是允许的。一个健壮的输出部分代码如下encoded_bytes encode_number(num) # 假设这个函数返回字节值列表 # 将字节列表转换为十六进制字符串列表确保两位大写 hex_list [f{b:02X} for b in encoded_bytes] # 用空格连接并打印 print( .join(hex_list))4.5 思维误区字符串操作的陷阱有些初学者可能会尝试用字符串截取的方式来做7位分组例如bin_str bin(num)[2:] # 获取二进制字符串 # 然后从右向左截取7位...这种方法虽然直观但极其不推荐原因有三效率低字符串操作特别是反转、补零比位运算慢得多。容易出错处理补零、分组顺序时下标计算非常容易搞混。不优雅没有体现出对计算机底层位操作的理解。位运算,|,才是解决此类问题的“正统”和高效方法也是面试官希望看到的。5. 从编码到解码逆向思维的验证一个完整的编码系统通常包含编码和解码两部分。虽然题目可能只要求编码但自己实现解码是验证编码逻辑是否正确、加深理解的最佳方式。解码就是编码的逆过程读取编码后的字节序列。对于每个字节取出低7位byte 0x7F作为数值部分。检查字节的最高位byte 0x80如果为1说明还有后续字节将当前数值部分暂存并等待下一个字节。如果为0说明这是当前整数的最后一个字节。组合所有数值部分第一个读到的高位组是整数的最高位部分。我们需要将从第一个字节到最后一个字节的所有7位组按顺序拼接起来。具体做法是初始化结果result 0每读到一个7位组先将result左移7位然后加上这个7位组的值。def decode_bytes(byte_list): 解码字节列表返回整数 num 0 for byte in byte_list: # 取出低7位 seven_bits byte 0x7F # 将之前的结果左移7位并加上新的7位 num (num 7) | seven_bits # 检查是否结束最高位为0 if (byte 0x80) 0: # 在实际流式解码中这里可以返回num并重置以处理多个整数。 # 本题假设字节列表只编码了一个整数所以循环结束即解码完成。 # 但为了逻辑完整我们可以在这里break不过由于是最后一个字节才为0不break也会结束。 pass return num # 测试解码 encoded encode_number(300) # [0xAC, 0x02] decoded decode_bytes(encoded) print(decoded) # 输出300自己动手写一遍解码你会对“最高位是延续标记”这一设计有更深刻的体会。它使得解码器无需预先知道整数的长度可以一个字节一个字节地读取并累积直到遇到最高位为0的字节就知道一个整数编码结束了。这是一种非常简洁有效的流式编码方案。6. 实战扩展与相关题目思路掌握了整数编码的核心后我们可以看看它的变体和相关题目做到举一反三。6.1 变体多整数连续编码这是更常见的场景如何编码一个整数列表使其变成一个紧凑的字节序列并能正确解码还原方案只需连续调用单个整数的编码函数并将结果字节依次写入输出流即可。解码时持续读取字节并解码直到输入流结束。因为每个整数的编码都以最高位为0的字节结尾解码器可以明确区分每个整数的边界。def encode_list(num_list): result [] for num in num_list: result.extend(encode_number(num)) # encode_number 是之前定义的函数 return result def decode_stream(byte_list): result_nums [] current_num 0 for byte in byte_list: seven_bits byte 0x7F current_num (current_num 7) | seven_bits if (byte 0x80) 0: # 遇到结束字节保存当前整数并重置 result_nums.append(current_num) current_num 0 return result_nums6.2 相关算法Varint (Protocol Buffers)如果你觉得这个编码方式很眼熟那就对了。这正是Google Protocol Buffers中用于编码整数的Varint算法的核心思想。Varint使用每个字节的最高位作为延续位continuation bit用低7位存储数据。它对于小的正整数编码效率非常高比如小于128的数只需1个字节而对于大的数则会使用更多字节。这与我们的题目完全一致。理解本题就等于理解了Varint的基础。6.3 机试中的快速实现技巧在紧张的机试环境中如何又快又准地实现模板化将核心的编码循环和解码循环作为“肌肉记忆”代码块。一看到“整数编码”、“7位分组”、“最高位标记”立刻套用位运算循环模板。先写注释在代码框架里先把步骤1、2、3、4的注释写好然后再填充代码避免逻辑混乱。立即测试用几个典型用例快速测试包括0、1、127刚好1个字节、128需要2个字节、一个很大的数。确保输出格式完全符合要求。注意输入读取机试题目通常是连续输入多个测试用例。要使用while True: try: line input() except EOFError: break这样的结构来读取所有输入并对每一行每个整数进行处理。整数编码这道题表面考的是编码规则实则考察的是候选人对二进制、位运算、循环控制以及边界条件处理的基本功。它不涉及复杂的数据结构和算法但正因如此任何细节上的疏忽都会导致失败。希望这篇详细的拆解能帮你彻底吃透这个考点在机试中遇到时能从容应对。
返回列表