题目描述
UTF-8 中的一个字符可能的长度为 1 到 4 字节,遵循以下的规则:
- 对于
1
字节的字符,字节的第一位设为0
,后面7
位为这个符号的 unicode 码。 - 对于
n
字节的字符(n > 1
),第一个字节的前n
位都设为1
,第n+1
位设为0
,后面字节的前两位一律设为10
。剩下的没有提及的二进制位,全部为这个符号的 unicode 码。
这是 UTF-8 编码的工作方式:
Char. number range | UTF-8 octet sequence
(hexadecimal) | (binary)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
给定一个表示数据的整数数组,返回它是否为有效的 utf-8 编码。
注意
输入是整数数组。只有每个整数的最低 8
个有效位用来存储数据。这意味着每个整数只表示 1
字节的数据。
样例
data = [197, 130, 1], 表示 8 位的序列: 11000101 10000010 00000001
返回 true 。
这是有效的 utf-8 编码,为一个2字节字符,跟着一个1字节字符。
data = [235, 140, 4], 表示 8 位的序列: 11101011 10001100 00000100
返回 false 。
前 3 位都是 1 ,第 4 位为 0 表示它是一个3字节字符。
下一个字节是开头为 10 的延续字节,这是正确的。
但第二个延续字节不以 10 开头,所以是不符合规则的。
算法
(分类讨论) $O(n)$
- 遍历数组,定义五类合法的区间
00000000-01111111
,表示 1 字节的字符。10000000-10111111
,表示这是跟在后面的字符。称之为 补全字符。11000000-11011111
,表示 2 字节字符的开头。11100000-11101111
,表示 3 字节字符的开头。11110000-11110111
,表示 4 字节字符的开头。
- 对于每个数字,划分到对应区间中。如果不符合这些区间,直接返回
false
。 - 如果划分到第 1,3,4,5 类区间中,判断当前是否还有需要补全字符。如果有,则说明不合法,返回
false
;如果没有,则在第 3 类区间值,设置补全字符的个数为 1,在第 4 类区间中设置补全字符的个数为 2,在第 5 类区间中设置补全字符的个数为 3。 - 如果划分到第 2 类区间中,判断当前是否还有需要补全字符,如果没有,则说明不合法;如果有,则补全字符个数减 1。
- 遍历结束后,还需要判断当前是否还存在需要补全的字符。
时间复杂度
- 遍历数组一次,故总时间复杂度为 $O(n)$。
空间复杂度
- 仅需要常数的额外空间。
C++ 代码
class Solution {
public:
bool validUtf8(vector<int>& data) {
int c = 0;
for (int v : data) {
if (v < 0x80) {
if (c > 0) return false;
} else if (0x80 <= v && v <= 0xBF) {
if (c == 0) return false;
c--;
} else if (0xC0 <= v && v <= 0xDF) {
if (c > 0) return false;
c = 1;
} else if (0xE0 <= v && v <= 0xEF) {
if (c > 0) return false;
c = 2;
} else if (0xF0 <= v && v <= 0xF7) {
if (c > 0) return false;
c = 3;
} else {
return false;
}
}
if (c > 0) return false;
return true;
}
};