力扣2022/3/13每日一题(UTF-8编码验证)

什么是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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值