简易变长编码规则

83 阅读8分钟

简易变长编码规则

背景介绍

在计算机系统中,为了高效地存储和传输文本,我们经常使用变长编码方案。这种方案可以用较少的字节表示常用字符,用较多的字节表示不常用的字符。UTF-8 就是最著名的一种。

本题要求你实现一个名为 XUTF 的简易变长编码规则,将一个给定的字符编号值(unicodeVal)转换为其对应的字节序列。

XUTF 编码规则

1. 编码格式总览

XUTF 使用 1 到 6 个字节来表示一个字符,具体使用几个字节取决于字符编号 unicodeVal 的大小。下表定义了编码的格式:

字节数编号值范围 (unicodeVal)二进制格式 (x 表示有效数据位)有效位数
1 字节[0, 2⁷)1xxxxxxx7位
2 字节[2⁷, 2¹¹)001xxxxx 01xxxxxx11位 (5+6)
3 字节[2¹¹, 2¹⁶)0001xxxx 01xxxxxx 01xxxxxx16位 (4+6+6)
4 字节[2¹⁶, 2²¹)00001xxx 01xxxxxx 01xxxxxx 01xxxxxx21位 (3+6+6+6)
5 字节[2²¹, 2²⁶)000001xx 01xxxxxx 01xxxxxx 01xxxxxx 01xxxxxx26位 (2+6+6+6+6)
6 字节[2²⁶, 2³¹)0000001x 01xxxxxx 01xxxxxx 01xxxxxx 01xxxxxx 01xxxxxx31位 (1+6*5)
2. 规则文字说明
  • 单字节字符 (n = 1):

    • 字节的第 1 位固定为 1
    • 剩余的 7 位 (xxxxxxx) 用于存放数据。
  • 多字节字符 (n > 1):

    • 首字节:n 位固定为 0,第 n+1 位固定为 1
    • 后续字节: 每个后续字节的前 2 位都固定为 01
    • 其余所有标记为 x 的位都是有效位,用于存放 unicodeVal 的二进制数据。

编码过程详解

  1. 确定字节数: 根据输入的 unicodeVal,对照上表,确定编码需要 1, 2, 3, 4, 5, 还是 6 个字节。
  2. 填充有效位:unicodeVal 的二进制表示,从右到左(从低位到高位)依次填入二进制格式中的 x (有效位)。
  3. 处理高位: 如果 unicodeVal 的位数不足以填满所有有效位,则在左侧(高位)的有效位补 0

解答要求

  • 时间限制: 1000ms
  • 内存限制: 256MB

输入格式

  • 一个十进制整数 unicodeVal
  • 0 <= unicodeVal < 2^31

输出格式

  • 一个大写的十六进制字符串,表示该字符的 XUTF 编码。
  • 注意: 如果最终编码后的字节数为奇数个,导致十六进制字符串长度为奇数,则需要在最前面补充一个前导字符 '0',以确保输出长度为偶数。

样例

输入样例 1

21326

输出样例 1

"154D4E"

样例 1 解释:

  1. 确定字节数: 21326 落在范围 [2048, 65536) (即 [2¹¹, 2¹⁶)) 内,所以需要 3个字节

  2. 获取格式和有效位数: 3字节的格式为 0001xxxx 01xxxxxx 01xxxxxx,共有 4 + 6 + 6 = 16 个有效位。

  3. 转换 unicodeVal: 21326 的二进制表示是 101001101001110,共 15 位。

  4. 填充: 我们需要 16 位有效数据,但只有 15 位,所以在左侧补一个 0,得到 0101001101001110

    • 将其从高位到低位依次填入 x 的位置:

      • 第一个字节的 xxxx -> 0101
      • 第二个字节的 xxxxxx -> 001101
      • 第三个字节的 xxxxxx -> 001110
  5. 组合成字节:

    • 字节1: 00010101 -> 二进制 00010101 -> 十六进制 15
    • 字节2: 01001101 -> 二进制 01001101 -> 十六进制 4D
    • 字节3: 01001110 -> 二进制 01001110 -> 十六进制 4E
  6. 最终结果: 拼接起来得到 "154D4E"


输入样例 2

34

输出样例 2

"A2"

解释:

  • 字节数: 34 落在 [0, 128) 范围外,但 34 < 2048,咦,样例解释有误。应该是落在 [0, 2⁷) 范围内。让我们重新检查范围。2⁷=1280 <= 34 < 128。所以需要 1个字节
  • 格式: 1xxxxxxx
  • 转换: 34 的二进制是 100010,共 6 位。
  • 填充: 有效位有 7 位,需在左侧补一个 0,得到 0100010
  • 组合: 10100010 -> 二进制 10100010 -> 十六进制 A2

输入样例 3

1225859

输出样例 3

"0C6B5243"

解释:

  • 字节数: 1225859 落在 [2¹⁶, 2²¹) 范围内,需要 4个字节
  • 编码后: 计算得到的字节序列为 0C 6B 52 43
  • 原始十六进制字符串: "C6B5243",长度为 7(奇数)。
  • 格式化输出: 根据要求,前面补充一个前导 0,最终输出 "0C6B5243"
/**
 * 实现一个自定义的XUTF变长字符编码器.
 */
public class XutfEncoder {
    /**
     * 对给定的unicode值进行XUTF编码.
     * @param unicodeVal 十进制表示的字符编号值
     * @return 编码后的大写十六进制字符串
     */
    public String encode(int unicodeVal) {
        // 最终编码生成的字节数组
        byte[] resultBytes;

        // --- 步骤 1: 根据 unicodeVal 的范围确定编码所需的字节数 ---
        
        // 1字节编码: [0, 2^7)
        if (unicodeVal < (1 << 7)) { // 128
            resultBytes = new byte[1];
            // 编码格式: 1xxxxxxx
            // 固定位是 10000000 (0x80)
            // 将 unicodeVal 的7个有效位与固定位合并
            resultBytes[0] = (byte) (0x80 | unicodeVal);
        }
        // 2字节编码: [2^7, 2^11)
        else if (unicodeVal < (1 << 11)) { // 2048
            resultBytes = new byte[2];
            // 格式: 001xxxxx 01xxxxxx
            // byte 2: 固定位 01000000 (0x40), 有6个有效位
            resultBytes[1] = (byte) (0x40 | (unicodeVal & 0x3F)); // 0x3F 是 00111111, 用于取最低6位
            // byte 1: 固定位 00100000 (0x20), 有5个有效位
            resultBytes[0] = (byte) (0x20 | (unicodeVal >> 6)); // 右移6位后,剩下的就是高位的有效部分
        }
        // 3字节编码: [2^11, 2^16)
        else if (unicodeVal < (1 << 16)) { // 65536
            resultBytes = new byte[3];
            // 格式: 0001xxxx 01xxxxxx 01xxxxxx
            // byte 3: 01xxxxxx
            resultBytes[2] = (byte) (0x40 | (unicodeVal & 0x3F));
            // byte 2: 01xxxxxx
            resultBytes[1] = (byte) (0x40 | ((unicodeVal >> 6) & 0x3F));
            // byte 1: 0001xxxx, 固定位 00010000 (0x10), 有4个有效位
            resultBytes[0] = (byte) (0x10 | (unicodeVal >> 12));
        }
        // 4字节编码: [2^16, 2^21)
        else if (unicodeVal < (1 << 21)) { // 2097152
            resultBytes = new byte[4];
            // 格式: 00001xxx 01xxxxxx 01xxxxxx 01xxxxxx
            resultBytes[3] = (byte) (0x40 | (unicodeVal & 0x3F));
            resultBytes[2] = (byte) (0x40 | ((unicodeVal >> 6) & 0x3F));
            resultBytes[1] = (byte) (0x40 | ((unicodeVal >> 12) & 0x3F));
            // byte 1: 00001xxx, 固定位 00001000 (0x08), 有3个有效位
            resultBytes[0] = (byte) (0x08 | (unicodeVal >> 18));
        }
        // 5字节编码: [2^21, 2^26)
        else if (unicodeVal < (1 << 26)) { // 67108864
            resultBytes = new byte[5];
            // 格式: 000001xx ...
            resultBytes[4] = (byte) (0x40 | (unicodeVal & 0x3F));
            resultBytes[3] = (byte) (0x40 | ((unicodeVal >> 6) & 0x3F));
            resultBytes[2] = (byte) (0x40 | ((unicodeVal >> 12) & 0x3F));
            resultBytes[1] = (byte) (0x40 | ((unicodeVal >> 18) & 0x3F));
            // byte 1: 000001xx, 固定位 00000100 (0x04), 有2个有效位
            resultBytes[0] = (byte) (0x04 | (unicodeVal >> 24));
        }
        // 6字节编码: [2^26, 2^31)
        else { // unicodeVal < (1L << 31)
            resultBytes = new byte[6];
            // 格式: 0000001x ...
            resultBytes[5] = (byte) (0x40 | (unicodeVal & 0x3F));
            resultBytes[4] = (byte) (0x40 | ((unicodeVal >> 6) & 0x3F));
            resultBytes[3] = (byte) (0x40 | ((unicodeVal >> 12) & 0x3F));
            resultBytes[2] = (byte) (0x40 | ((unicodeVal >> 18) & 0x3F));
            resultBytes[1] = (byte) (0x40 | ((unicodeVal >> 24) & 0x3F));
            // byte 1: 0000001x, 固定位 00000010 (0x02), 有1个有效位
            resultBytes[0] = (byte) (0x02 | (unicodeVal >> 30));
        }

        // --- 步骤 2: 将生成的字节数组转换为十六进制字符串 ---
        return byteArrayToHexString(resultBytes);
    }

    /**
     * 辅助方法:将字节数组转换为大写的十六进制字符串.
     * @param bytes 待转换的字节数组
     * @return 转换后的十六进制字符串
     */
    private String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) {
            // String.format("%02X", b) 的作用:
            // %X: 以大写十六进制格式输出一个整数
            // 02: 指定输出宽度为2,如果不足2位,则在前面用 '0' 补齐。
            // 例如,byte值 5 会被格式化为 "05"。
            // (b & 0xFF) 是为了确保将byte作为无符号数处理,防止负数byte转换时出现问题。
            sb.append(String.format("%02X", b & 0xFF));
        }
        
        String hex = sb.toString();
        // 根据题目要求,如果最终的十六进制字符串长度为奇数,前面补充一个'0'
        // 虽然对于字节数组来说,这种情况理论上不会发生(一个byte总是两个hex字符),
        // 但作为防御性编程,可以保留这个逻辑。
        // 按标准字节转换逻辑,长度必为偶数。
        // 此处的实现遵循标准逻辑。
        if (hex.length() % 2 != 0) {
            return "0" + hex;
        }
        return hex;
    }
}


// --- ACM模式主类示例 ---
// 在实际平台提交时,通常只需要提交上面的 XutfEncoder 类。
// 下面的 Main 类用于本地测试和验证。
/*
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        XutfEncoder encoder = new XutfEncoder();
        Scanner scanner = new Scanner(System.in);
        
        System.out.println("请输入一个十进制整数:");
        while(scanner.hasNextInt()) {
            int unicodeVal = scanner.nextInt();
            String result = encoder.encode(unicodeVal);
            System.out.println("输入: " + unicodeVal);
            System.out.println("输出: "" + result + """);
            System.out.println("--------------------");
            System.out.println("请输入一个十进制整数:");
        }
        
        scanner.close();
    }
}
*/