Medium

题目描述

给定一个表示数据的整数数组 data,返回它是否为有效的 UTF-8 编码(即它是否可以翻译为一序列有效的 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         |   0xxxxxxx
         2         |   110xxxxx 10xxxxxx
         3         |   1110xxxx 10xxxxxx 10xxxxxx
         4         |   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

x 表示二进制形式的一位,可以是 0 或 1。

**注意:**输入是整数数组。只有每个整数的最低 8 个有效位用来存储数据。这意味着每个整数只表示 1 字节的数据。

示例 1:

输入:data = [197,130,1]
输出:true
解释:数据表示字节序列:11000101 10000010 00000001。
这是有效的 utf-8 编码,为一个 2 字节字符,跟着一个 1 字节字符。

示例 2:

输入:data = [235,140,4]
输出:false
解释:数据表示字节序列:11101011 10001100 00000100。
前 3 个位都是 1 ,第 4 个位是 0 表示它是一个 3 字节字符。
下一个字节是开头为 10 的延续字节,这是正确的。
但第二个延续字节不是以 10 开头,所以是不符合规则的。

提示:

  • 1 <= data.length <= 2 * 10^4
  • 0 <= data[i] <= 255

解题思路

解题思路

这道题需要我们验证一个整数数组是否符合 UTF-8 编码规则。关键在于理解 UTF-8 的编码模式:

  1. 1字节字符:以 0 开头 (0xxxxxxx)
  2. 2字节字符:第一字节以 110 开头,第二字节以 10 开头
  3. 3字节字符:第一字节以 1110 开头,后续两字节都以 10 开头
  4. 4字节字符:第一字节以 11110 开头,后续三字节都以 10 开头

核心算法

  1. 遍历数组,对每个字节判断它是字符的开始还是延续字节
  2. 如果是开始字节,根据前缀确定字符长度(1-4字节)
  3. 检查后续的延续字节是否都以 10 开头
  4. 使用位运算来检测字节模式:
    • byte & 0x80 == 0:检测是否以0开头(1字节字符)
    • byte & 0xE0 == 0xC0:检测是否以110开头(2字节字符首字节)
    • byte & 0xF0 == 0xE0:检测是否以1110开头(3字节字符首字节)
    • byte & 0xF8 == 0xF0:检测是否以11110开头(4字节字符首字节)
    • byte & 0xC0 == 0x80:检测是否以10开头(延续字节)

时间复杂度为 O(n),空间复杂度为 O(1)。

代码实现

class Solution {
public:
    bool validUtf8(vector<int>& data) {
        int i = 0;
        while (i < data.size()) {
            int byte = data[i] & 0xFF;
            int numBytes = 0;
            
            // 确定字符的字节数
            if ((byte & 0x80) == 0) {
                numBytes = 1;
            } else if ((byte & 0xE0) == 0xC0) {
                numBytes = 2;
            } else if ((byte & 0xF0) == 0xE0) {
                numBytes = 3;
            } else if ((byte & 0xF8) == 0xF0) {
                numBytes = 4;
            } else {
                return false;
            }
            
            // 检查是否有足够的字节
            if (i + numBytes > data.size()) {
                return false;
            }
            
            // 检查后续的延续字节
            for (int j = 1; j < numBytes; j++) {
                if ((data[i + j] & 0xC0) != 0x80) {
                    return false;
                }
            }
            
            i += numBytes;
        }
        
        return true;
    }
};
class Solution:
    def validUtf8(self, data: List[int]) -> bool:
        i = 0
        while i < len(data):
            byte = data[i] & 0xFF
            num_bytes = 0
            
            # 确定字符的字节数
            if (byte & 0x80) == 0:
                num_bytes = 1
            elif (byte & 0xE0) == 0xC0:
                num_bytes = 2
            elif (byte & 0xF0) == 0xE0:
                num_bytes = 3
            elif (byte & 0xF8) == 0xF0:
                num_bytes = 4
            else:
                return False
            
            # 检查是否有足够的字节
            if i + num_bytes > len(data):
                return False
            
            # 检查后续的延续字节
            for j in range(1, num_bytes):
                if (data[i + j] & 0xC0) != 0x80:
                    return False
            
            i += num_bytes
        
        return True
public class Solution {
    public bool ValidUtf8(int[] data) {
        int i = 0;
        while (i < data.Length) {
            int byteVal = data[i] & 0xFF;
            int numBytes = 0;
            
            // 确定字符的字节数
            if ((byteVal & 0x80) == 0) {
                numBytes = 1;
            } else if ((byteVal & 0xE0) == 0xC0) {
                numBytes = 2;
            } else if ((byteVal & 0xF0) == 0xE0) {
                numBytes = 3;
            } else if ((byteVal & 0xF8) == 0xF0) {
                numBytes = 4;
            } else {
                return false;
            }
            
            // 检查是否有足够的字节
            if (i + numBytes > data.Length) {
                return false;
            }
            
            // 检查后续的延续字节
            for (int j = 1; j < numBytes; j++) {
                if ((data[i + j] & 0xC0) != 0x80) {
                    return false;
                }
            }
            
            i += numBytes;
        }
        
        return true;
    }
}
/**
 * @param {number[]} data
 * @return {boolean}
 */
var validUtf8 = function(data) {
    let i = 0;
    
    while (i < data.length) {
        let byte = data[i];
        let numBytes;
        
        if ((byte >> 7) === 0) {
            numBytes = 1;
        } else if ((byte >> 5) === 0b110) {
            numBytes = 2;
        } else if ((byte >> 4) === 0b1110) {
            numBytes = 3;
        } else if ((byte >> 3) === 0b11110) {
            numBytes = 4;
        } else {
            return false;
        }
        
        if (i + numBytes > data.length) {
            return false;
        }
        
        for (let j = 1; j < numBytes; j++) {
            if ((data[i + j] >> 6) !== 0b10) {
                return false;
            }
        }
        
        i += numBytes;
    }
    
    return true;
};

复杂度分析

算法时间复杂度空间复杂度
位运算模式匹配O(n)O(1)

其中 n 是数组长度。我们需要遍历整个数组一次,对每个字节进行常数时间的位运算检查。