什么是UTF-8?
UTF-8此篇文章中有纤细讲解,在UTF8中一个汉字占有3个字节,而GDK占有2个字节
https://zhuanlan.zhihu.com/p/137875615
了解完UTF-8后才可读懂此题
给定一个表示数据的整数数组 data ,返回它是否为有效的 UTF-8 编码。
UTF-8 中的一个字符可能的长度为 1 到 4 字节,遵循以下的规则:
对于 1 字节 的字符,字节的第一位设为 0 ,后面 7 位为这个符号的 unicode 码。
对于 n 字节 的字符 (n > 1),第一个字节的前 n 位都设为1,第 n+1 位设为 0 ,后面字节的前两位一律设为 10 。剩下的没有提及的二进制位,全部为这个符号的 unicode 码。
这是 UTF-8 编码的工作方式:
先说一下我遇到的错误:。 ①题目说了utf-8编码为1~4个字节,如果第一个字节是111110xx形式,则说明是一个5字节的utf-8编码,显然不符合题意。 ②同时还要注意一下数组越界的问题,因为可能第一个字节为11110xxx形式,也即是一个4字节的utf-8编码,但是剩余的字节的数量少于4
题目分析:
这道题目最难的恐怕还是看懂题目
UTF-8 中的一个字符可能的长度为 1 到 4 字节
1.如果是只有一个字节,一定形如 0xxxxxxx,也就是只能以 0 开头
2.如果有多个字节组成
3.后面还有1个字节:首字节一定形如 110xxxxx,也就是只能以 110 开头
4.后面还有2个字节:首字节一定形如 1110xxxx,也就是只能以 1110 开头
5.后面还有3个字节:首字节一定形如 11110xxx,也就是只能以 11110 开头
6.后续跟着的字节一定形如 10xxxxxx,也就是只能以 10 开头
其一解题思路(JAVE):
只要搞清楚了UTF-8编码的组成方式,就能直接通过模拟的方式枚举每一种可能的情况
然后将不符合条件的剔除
但是题目中并没有明说哪个数字是作为的首字节,所以这里我们就需要自己进行判断
代码如下:
class Solution {
public boolean validUtf8(int[] data) {
// 用于判断data首字节后一共有几个字节
int bitIndex = 0;
for(int num : data) {
if(bitIndex == 0) {
// 后移5位,以0b110开头,后面需要再接一个字节
if((num >> 5) == 0b110) bitIndex = 1;
// 后移4位,以0b1110开头,后面需要再接两个字节
else if((num >> 4) == 0b1110) bitIndex = 2;
else if((num >> 3) == 0b11110) bitIndex = 3;
// 不能单一个1开头
else if((num >> 7) == 1) return false;
} else {
// 后面都只能是0b10开头
if((num >> 6) != 0b10) return false;
--bitIndex;
}
}
return bitIndex == 0;
}
}
其二解题思路(JS):
根据题目模拟,从第一个数开始遍历,我们判断每个数的第1位,如果是0代表它只能表示1个字节字符,直接i++继续。如果第1位是1,代表后面的数字要跟当前数字一起组成字符。具体字节取决于当前数字前面有几个1
*注意:
题目说明字符的长度为1 - 4的字节,所以我们要保证当前数前面的1要大于1个,且小于5个。具体用例可以参考:
[250,145,145,145,145] 字节为5个,不符合
[128,1,1] 128有1个1,无法代表单字节、多字节
js运算符优先级。(4 & (1 << 7)) === 0 和 4 & (1 << 7) === 0 结果不一样,=== 优先级大于 &
代码如下:
/**
* @param {number[]} data
* @return {boolean}
*/
var validUtf8 = function(data) {
// 看第一个字节的首位,是1还是0,0就代表是是一个字节字符。再看下一个字节
// 如果是1,累计几个1,后面几个字节都要以10开头。
let i = 0;
while (i < data.length) {
const current = data[i];
// 首位是0, 代表一个字节
if ((current & (1 << 7)) === 0) {
i++;
} else {
let j = 6; // 注: 本身也算个字节,所以要少算个1
i++;
// 可能最后一个字节首位是1,那就不符合条件
if (i === data.length) {
return false;
}
// 开始从下个字节检查是否以10开头,这过程必须保证current的当前进制位是1
while ((current & (1 << j)) && i < data.length) {
const next = data[i];
if ((next & (1 << 7)) === 0 || (next & (1 << 6)) !== 0) {
return false;
}
j--;
i++;
}
// 注意审题:可能的长度为1到4字节,所以我们判断j是否小于3(6 - 3),是否等于6 返回false
if (j < 3 || j === 6) {
return false
}
// 如果是current当前位置还是1,且i超过data.length。说明多字节字符不符合。
if ((current & (1 << j)) && i >= data.length) {
return false
}
}
}
return true;
};
复杂度分析
时间复杂度:O(m),其中m 是数组 data的长度。需要遍历数组 data 一次,对于数组中的每个元素的计算时间都是O(1)
空间复杂度:O(1)