LeetCode 405. 数字转换为十六进制数
题目描述

题意分析
将一个 32 位有符号整数写成小写十六进制,除零本身外不能有前导零。负数输出其 32 位补码对应的位模式,而不是负号加绝对值;题目不允许调用现成的十六进制转换函数。
解法:按 4 位一组转换补码
核心思路
[!blue]
十六进制的一位能表示 0 到 15,恰好对应 4 个二进制位。因此可以把 32 位补码从低到高拆成最多 8 组,每组直接映射为一个字符。
num & 15只保留最低 4 位,得到当前十六进制位的值。取出这一位后右移 4 位,让下一组成为最低位。这里必须让高位补零:Java 使用>>>,Go 先转为uint32再右移。这样读取的是原来的 32 位位模式,负数也会在 8 次以内归零。取出的顺序是从最低位到最高位,而字符串需要相反的顺序。准备长度为 8 的缓冲区,每次先将
index减一,再写入当前字符;已写部分始终是最终答案的后缀,循环结束后直接返回它,不必反转。当剩余位全为零时停止,因此不会写入多余的前导零。只有输入为零时循环一次也不执行,必须单独返回
"0";负数的最高位为一,所以最终恰好得到 8 个十六进制字符。
解题步骤
- 若
num == 0,直接返回"0"。- 用
"0123456789abcdef"建立数值到字符的映射,令index指向长度为 8 的缓冲区末尾。- 只要尚有未处理的非零位,就取最低 4 位对应的字符,写入
buffer[--index]。- 将位模式无符号右移 4 位,继续处理下一组。
- 返回从
index开始的已写后缀,忽略前面未使用的位置。
代码实现
class Solution {
public String toHex(int num) {
if (num == 0) {
return "0";
}
String digits = "0123456789abcdef";
char[] buffer = new char[8];
int index = buffer.length;
while (num != 0) {
buffer[--index] = digits.charAt(num & 15);
// 高位补零,负数的补码也会在八组以内全部取完。
num >>>= 4;
}
// 缓冲区由右向左写入,只返回已写后缀,不再反转。
return new String(buffer, index, buffer.length - index);
}
}
func toHex(num int) string {
if num == 0 {
return "0"
}
const digits = "0123456789abcdef"
// 按三十二位无符号位模式读取,避免负数右移持续补一。
value := uint32(num)
var buffer [8]byte
index := len(buffer)
for value != 0 {
index--
buffer[index] = digits[value&15]
value >>= 4
}
// 缓冲区由右向左写入,只返回已写后缀,不再反转。
return string(buffer[index:])
}
复杂度分析
- 时间复杂度:$O(1)$。32 位整数最多处理 8 组,生成字符串的长度也不超过 8。
- 空间复杂度:$O(1)$。字符映射和缓冲区的大小均固定。
关键点总结
[!green]
- 4 个二进制位对应一个十六进制位,掩码
15用来提取这一组。- 无符号右移统一处理正数和负数,无需求绝对值或单独转换补码。
- 从缓冲区末尾写入,把低位优先的提取顺序直接变成正确输出顺序。
易错点总结
[!yellow]
- Java 的
>>会为负数持续补一,无法归零;Go 也应先转为固定宽度的uint32,不能直接右移负的int。- 求负数的绝对值会丢失补码语义,最小整数的绝对值还无法用原类型表示。
- 忘记特判零会返回空串;返回整个缓冲区则会带上未写位置。
- 当前缓冲区已经按正确方向写入,不能再反转;字符映射必须使用小写字母。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 504. 七进制数 | 简单 | 同样按进制取数位,原题负数保留负号,本题负数必须按32位补码输出十六进制。 |
| 补充题 145. 有符号整数的进制转换 | 中等 | 变形题返回带符号的任意进制表示,本题固定十六进制且负数使用补码,符号处理不能混用。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!