2025A-TLV编码
题目描述与示例
题目
TLV 编码是按 TagLengthValue 格式进行编码的。
一段码流中的信元用 tag
标识,tag
在码流中唯一不重复,length
表示信元 value
的长度,value
表示信元的值,码流以某信元的 tag
开头,tag
固定占一个字节,length
固定占两个字节,字节序为小端序。
现给定 TLV 格式编码的码流以及需要解码的信元 tag
,请输出该信元的value
。
输入码流的 16
进制字符中,不包括小写字母;且要求输出的 16
进制字符串中也不要包含小写字母;码流字符串的最大长度不超过 50000
个字节。
输入描述
第一行为第一个字符串 ,表示待解码信元的 tag
;输入第二行为一个字符串, 表示待解码的 16
进制码流;字节之间用 空格
分割。
输出描述
输出一个字符串,表示待解码信元以 16 进制表示的 value
。
示例
输入
31
32 01 00 AE 90 02 00 01 02 30 03 00 AB 32 31 31 02 00 32 33 33 01 00 CC
输出
32 33
说明
需要解析的信元的 tag 是 31
;从码流的起始处开始匹配:
第一个信元的tag 为 32
,其长度为 1
(01 00
,小端序表示为十六进制的0001
,十进制为1
),匹配接下来的1
个字节AE
;
第二个信元的 tag 为 90
其长度为 2
(02 00
,小端序表示为十六进制的0002
,十进制为2
,匹配接下来的2
个字节01 02
;
第三个信元的 tag 为 30
其长度为 3
(03 00
,小端序表示为十六进制的0003
,十进制为3
,匹配接下来的3
个字节AB 32 31
;
第四个信元的 tag 为 31
其长度为 2
(02 00
,小端序表示为十六进制的0002
,十进制为2
,匹配接下来的2
个字节32 33
;
所以返回长度后面的两个字节即可 为 32 33
。
解题思路
又是一道比较难读懂的题目,需要一些前置知识的理解。
大端序和小端序
这道题涉及一个前置知识,大端序和小端序。
这两种排序都是字节序,表示的是数据字节在内存中存储方式。
大端序(Big-Endian)指的是数据的低位字节存放在内存的高位地址,高位字节存放在内存的低位地址。这种排列方式与数据用字节表示时的书写顺序一致,符合人类的阅读习惯。
小端序(Little-Endian)指的数据的低位字节存放在内存的低位地址,高位字节存放在高位地址。小端序与人类的阅读习惯相反,但更符合计算机读取内存的方式,因为CPU读取内存中的数据时,是从低地址向高地址方向进行读取的。

看起来有点拗口,但只需要记住大端序从左到右排列符合人类阅读习惯,小端序从右到左排列不符合人类阅读习惯即可。
TLV码流的解码过程
下图显示了示例的码流的解码过程
- 蓝绿红构成的一整段内容表示一个信元,其中
- 蓝色表示信元的唯一标识
tag
- 绿色表示信元的
value
的长度 - 红色表示信元的
value
的的内容
- 蓝色表示信元的唯一标识
32 01 00 AE 90 02 00 01 02 30 03 00 AB 32 31 31 02 00 32 33 33 01 00 CC
我们要做的事情,就是找到tag
为所第一行输入的target_tag
的那个信元,所对应的value
的内容。
模拟过程
本题剩余内容,只需要按照题目要求进行模拟即可。
我们可以构建一个help
辅助函数,用于一个特定信元的解码。
其中,传入的参数stream
为输入码流数组,idx
为当前信元标识在数组中的索引。容易得到代码
# 构建一个用于解码的辅助函数
def help(stream, idx):
cur_tag = stream[idx]
# 信元长度length为小端序排布,得到十六进制的字符串结果
cur_length_hex = stream[idx+2] + stream[idx+1]
# 将十六进制转换为十进制
cur_length = int(cur_length_hex, base = 16)
# 返回当前信元标识,信元内容的长度
return cur_tag, cur_length
在这个函数外部,由于idx
增加的幅度是根据每次解码后得到的信元内容的长度length
决定的,并不是一个固定值,我们需要在while
循环中进行遍历。
循环条件为,idx
在stream
中对应的字符串不是目标信元target_tag
。
在循环中,我们调用help
函数,得到当前信元的标识cur_tag
和长度cur_length
。
信元表示和长度一共占3
位,信元中的信息占cur_length
位,因此下一个信元的标识tag
的位置位于stream
数组中的idx + 3 + cur_length
处。综上可以得到代码
# idx是stream流中的索引,初始化为0
idx = 0
# 进行循环,循环条件为idx对应的字符串不是目标信元target_tag
while stream[idx] != target_tag:
# 根据当前的idx,获得当前信元的tag和长度
cur_tag, cur_length = help(stream, idx)
# 信元和长度一共占3位,信元中的信息占cur_length位
# 下一个信元tag的位置位于 idx + 3 + cur_length 处
# 更新idx
idx += 3 + cur_length
退出while
循环后,idx
所在的位置即为目标信元target_tag
的标识。
再一次调用help
函数,进行信元解码,得到目标信元的长度cur_length
。
此时stream
中,从索引idx+3
到idx+3+cur_length
位置的切片,就是目标信元的内容,将其输出即为最终的答案。
# 退出循环后再进行一次信元解码
# 此时得到的cur_tag一定是target_tag
cur_tag, cur_length = help(stream, idx)
# 此时stream中,从 idx+3 到 idx+3+cur_length 位置的切片
# 即为信元target_tag解码得到的字节
print(" ".join(stream[idx+3:idx+3+cur_length]))
代码
Python
# 欢迎来到「欧弟算法 - 华为OD全攻略」,收录华为OD题库、面试指南、八股文与学员案例!
# 地址:https://www.odalgo.com
# 华为OD机试刷题网站:https://www.algomooc.com
# 添加微信 278166530 获取华为 OD 笔试真题题库和视频
# 构建一个用于解码的辅助函数
def help(stream, idx):
cur_tag = stream[idx]
# 信元长度length为小端序排布,得到十六进制的字符串结果
cur_length_hex = stream[idx+2] + stream[idx+1]
# 将十六进制转换为十进制
cur_length = int(cur_length_hex, base = 16)
return cur_tag, cur_length
# 待解码信元的tag
target_tag = input()
# 待解码的编码流
stream = input().split()
# idx是stream流中的索引,初始化为0
idx = 0
# 进行循环,循环条件为idx对应的字符串不是目标信元target_tag
while stream[idx] != target_tag:
# 根据当前的idx,获得当前信元的tag和长度
cur_tag, cur_length = help(stream, idx)
# 信元和长度一共占3位,信元中的信息占cur_length位
# 下一个信元tag的位置位于 idx + 3 + cur_length 处
# 更新idx
idx += 3 + cur_length
# 退出循环后再进行一次信元解码
# 此时得到的cur_tag一定是target_tag
cur_tag, cur_length = help(stream, idx)
# 此时stream中,从 idx+3 到 idx+3+cur_length 位置的切片
# 即为信元target_tag解码得到的字节
print(" ".join(stream[idx+3:idx+3+cur_length]))
Java
import java.util.*;
public class Main {
// 构建一个用于解码的辅助函数
public static String[] decodeHelper(String[] stream, int idx) {
String curTag = stream[idx];
// 信元长度length为小端序排布,得到十六进制的字符串结果
String curLengthHex = stream[idx + 2] + stream[idx + 1];
// 将十六进制转换为十进制
int curLength = Integer.parseInt(curLengthHex, 16);
return new String[] { curTag, String.valueOf(curLength) };
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 待解码信元的tag
String targetTag = scanner.nextLine();
// 待解码的编码流
String[] stream = scanner.nextLine().split(" ");
// idx是stream流中的索引
int idx = 0;
// 进行循环,循环条件为idx对应的字符串不是目标信元targetTag
while (!stream[idx].equals(targetTag)) {
// 根据当前的idx,获得当前信元的tag和长度
String[] result = decodeHelper(stream, idx);
String curTag = result[0];
int curLength = Integer.parseInt(result[1]);
// 信元和长度一共占3位,信元中的信息占curLength位
// 下一个信元tag的位置位于 idx + 3 + curLength 处
idx += 3 + curLength;
}
// 退出循环后再进行一次信元解码
// 此时得到的curTag一定是targetTag
String[] result = decodeHelper(stream, idx);
String curTag = result[0];
int curLength = Integer.parseInt(result[1]);
// 输出信元的字节
System.out.println(String.join(" ", Arrays.copyOfRange(stream, idx + 3, idx + 3 + curLength)));
scanner.close();
}
}
C++
#include <iostream>
#include <vector>
#include <sstream>
using namespace std;
// 构建一个用于解码的辅助函数
pair<string, int> decodeHelper(const vector<string>& stream, int idx) {
string cur_tag = stream[idx];
// 信元长度length为小端序排布,得到十六进制的字符串结果
string cur_length_hex = stream[idx + 2] + stream[idx + 1];
// 将十六进制转换为十进制
int cur_length = stoi(cur_length_hex, nullptr, 16);
return {cur_tag, cur_length};
}
int main() {
string target_tag;
getline(cin, target_tag);
string line;
getline(cin, line);
// 将输入的编码流分割并存入vector中
vector<string> stream;
stringstream ss(line);
string token;
while (ss >> token) {
stream.push_back(token);
}
// idx是stream流中的索引
int idx = 0;
// 进行循环,循环条件为idx对应的字符串不是目标信元target_tag
while (stream[idx] != target_tag) {
// 根据当前的idx,获得当前信元的tag和长度
auto [cur_tag, cur_length] = decodeHelper(stream, idx);
// 信元和长度一共占3位,信元中的信息占cur_length位
// 下一个信元tag的位置位于 idx + 3 + cur_length 处
idx += 3 + cur_length;
}
// 退出循环后再进行一次信元解码
// 此时得到的cur_tag一定是target_tag
auto [cur_tag, cur_length] = decodeHelper(stream, idx);
// 输出信元的字节
for (int i = idx + 3; i < idx + 3 + cur_length; ++i) {
cout << stream[i] << " ";
}
cout << endl;
return 0;
}
C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_STREAM_SIZE 50000 // 设定最大输入流大小
#define MAX_TAG_SIZE 10 // 设定最大Tag长度
// 构建一个用于解码的辅助函数
void decodeHelper(char **stream, int idx, char *curTag, int *curLength) {
strcpy(curTag, stream[idx]);
// 信元长度length为小端序排布,得到十六进制的字符串结果
char curLengthHex[5];
sprintf(curLengthHex, "%s%s", stream[idx + 2], stream[idx + 1]);
// 将十六进制转换为十进制
*curLength = (int)strtol(curLengthHex, NULL, 16);
}
int main() {
char targetTag[MAX_TAG_SIZE]; // 待解码信元的tag
char *stream[MAX_STREAM_SIZE]; // 待解码的编码流
char buffer[MAX_STREAM_SIZE * 5]; // 用于存储输入的字符串
int idx = 0;
// 读取目标信元的tag
scanf("%s", targetTag);
getchar(); // 读取换行符,防止干扰
// 读取编码流
fgets(buffer, sizeof(buffer), stdin);
// 解析输入流
char *token = strtok(buffer, " ");
int streamSize = 0;
while (token != NULL) {
stream[streamSize++] = token;
token = strtok(NULL, " ");
}
// 进行循环,循环条件为idx对应的字符串不是目标信元targetTag
while (strcmp(stream[idx], targetTag) != 0) {
char curTag[MAX_TAG_SIZE];
int curLength;
// 获取当前信元的tag和长度
decodeHelper(stream, idx, curTag, &curLength);
// 信元和长度一共占3位,信元中的信息占curLength位
// 下一个信元tag的位置位于 idx + 3 + curLength 处
idx += 3 + curLength;
}
// 退出循环后再进行一次信元解码
char curTag[MAX_TAG_SIZE];
int curLength;
decodeHelper(stream, idx, curTag, &curLength);
// 输出信元的字节
for (int i = idx + 3; i < idx + 3 + curLength; i++) {
printf("%s ", stream[i]);
}
printf("\n");
return 0;
}
Node JavaScript
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// 解码辅助函数
function decodeHelper(stream, idx) {
let curTag = stream[idx];
// 信元长度为小端序,需要交换字节顺序
let curLengthHex = stream[idx + 2] + stream[idx + 1];
// 将十六进制字符串转换为十进制数
let curLength = parseInt(curLengthHex, 16);
return [curTag, curLength];
}
let inputLines = [];
rl.on('line', (line) => {
inputLines.push(line);
if (inputLines.length === 2) {
rl.close();
}
}).on('close', () => {
let targetTag = inputLines[0]; // 待解码信元的 tag
let stream = inputLines[1].split(" "); // 编码流
let idx = 0;
// 进行循环,直到找到目标信元
while (stream[idx] !== targetTag) {
let [curTag, curLength] = decodeHelper(stream, idx);
// 信元和长度信息占3字节,数据部分占 curLength 字节
idx += 3 + curLength;
}
// 退出循环后,解码目标信元
let [curTag, curLength] = decodeHelper(stream, idx);
// 输出信元的字节信息
console.log(stream.slice(idx + 3, idx + 3 + curLength).join(" "));
});
Go
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// 构建一个用于解码的辅助函数
func decodeHelper(stream []string, idx int) (string, int) {
curTag := stream[idx]
// 信元长度length为小端序排布,得到十六进制的字符串结果
curLengthHex := stream[idx+2] + stream[idx+1]
// 将十六进制转换为十进制
curLength, _ := strconv.ParseInt(curLengthHex, 16, 64)
return curTag, int(curLength)
}
func main() {
reader := bufio.NewReader(os.Stdin)
// 读取目标信元的tag
targetTag, _ := reader.ReadString('\n')
targetTag = strings.TrimSpace(targetTag)
// 读取编码流
line, _ := reader.ReadString('\n')
stream := strings.Fields(line) // 按空格拆分字符串
idx := 0
// 进行循环,循环条件为idx对应的字符串不是目标信元targetTag
for stream[idx] != targetTag {
// 获取当前信元的tag和长度
_, curLength := decodeHelper(stream, idx)
// 信元和长度一共占3位,信元中的信息占curLength位
// 下一个信元tag的位置位于 idx + 3 + curLength 处
idx += 3 + curLength
}
// 退出循环后再进行一次信元解码
_, curLength := decodeHelper(stream, idx)
// 输出信元的字节
fmt.Println(strings.Join(stream[idx+3:idx+3+curLength], " "))
}
时空复杂度
时间复杂度:O(N)
。需要一次遍历码流数组stream
。
空间复杂度:O(1)
。进入若干长度变量