LeetCode 393 - UTF-8 编码验证

在这里插入图片描述
在这里插入图片描述

摘要

UTF-8 是目前互联网上使用最广泛的字符编码方式之一,它的灵活性和兼容性使得我们几乎在所有系统中都能正常显示各种语言的字符。
不过,正因为 UTF-8 的变长特性(1~4 个字节表示一个字符),验证一个字节序列是否是合法的 UTF-8 编码并不那么直观。

这道题(LeetCode 393)让我们从底层去“读懂” UTF-8 的规则,通过判断一个整数数组是否能组成合法的 UTF-8 字节序列,间接地理解字符编码的逻辑。

这其实不仅是算法题,更是一道关于底层数据表示和系统字符编码理解的题。

描述

我们需要判断一个整数数组 data 是否是合法的 UTF-8 编码。

每个整数(0 <= data[i] <= 255)代表一个字节,也就是 8 位二进制数。
UTF-8 的编码规则如下:

字节数格式(二进制)说明
1 字节0xxxxxxx普通 ASCII 字符
2 字节110xxxxx 10xxxxxx双字节字符
3 字节1110xxxx 10xxxxxx 10xxxxxx三字节字符
4 字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx四字节字符

规律总结:

  • 第一个字节告诉我们当前字符有多少个字节;
  • 后续的字节必须以 10 开头;
  • 所有不满足这两条规则的情况都是非法编码。

题解答案

整体思路可以分成两步:

  1. 统计当前字节开头的 1 的数量
    这个数量决定了当前字符占用多少字节(例如 110xxxxx 就说明需要 2 个字节)。

  2. 验证后续字节
    接下来的若干个字节(数量 = 上一步统计的结果 - 1)必须以 10 开头。
    只要有任何一个不满足条件,就立即返回 false。

整个过程一次遍历即可完成。

题解代码分析

下面是完整的 Swift 代码实现,可以直接运行:

import Foundation

class UTF8Validator {
    func validUtf8(_ data: [Int]) -> Bool {
        var remainingBytes = 0  // 记录当前还需要的字节数
        
        for byte in data {
            // 只取最低 8 位
            let b = byte & 0xFF
            
            if remainingBytes == 0 {
                // 判断当前字节的前缀有多少个 1
                if (b >> 5) == 0b110 {        // 110xxxxx -> 2 字节
                    remainingBytes = 1
                } else if (b >> 4) == 0b1110 { // 1110xxxx -> 3 字节
                    remainingBytes = 2
                } else if (b >> 3) == 0b11110 { // 11110xxx -> 4 字节
                    remainingBytes = 3
                } else if (b >> 7) == 1 {       // 10xxxxxx 开头不合法
                    return false
                }
            } else {
                // 检查后续字节必须以 10 开头
                if (b >> 6) != 0b10 {
                    return false
                }
                remainingBytes -= 1
            }
        }
        
        // 所有字节验证完后,remainingBytes 必须为 0
        return remainingBytes == 0
    }
}

代码讲解

  • b >> n 表示右移 n 位,检查二进制前缀;
  • (b >> 5) == 0b110 判断是否以 110 开头;
  • remainingBytes 表示当前还需要多少个后续字节;
  • 如果遇到非法的 10xxxxxx 开头字节,立刻返回 false
  • 最后检查 remainingBytes == 0,确保整个序列刚好结束,没有“半个字符”残留。

这段逻辑跟 UTF-8 的标准一一对应,非常直观。

示例测试及结果

我们来验证几个典型例子

let validator = UTF8Validator()

print(validator.validUtf8([197, 130, 1]))
// 输出: true
// 解释:11000101 10000010 00000001
// 110 开头说明第一个字符是 2 字节编码,后面的 10xxxxxx 是合法延续字节。
// 最后的 00000001 是单字节字符,全序列合法。

print(validator.validUtf8([235, 140, 4]))
// 输出: false
// 解释:11101011 10001100 00000100
// 第一个是 3 字节字符 (1110 开头),第二个没问题 (10 开头),但第三个不是 10 开头,非法。

print(validator.validUtf8([0b11100010, 0b10000010, 0b10101100]))
// 输出: true
// 这是字符 “€”(欧元符号)的 UTF-8 编码,合法。

输出结果:

true
false
true

时间复杂度

整段代码只需要遍历数组一次,每个字节的检查是 O(1) 操作。

时间复杂度:O(n),其中 n 是字节数。

空间复杂度

整个过程只用了一个变量 remainingBytes 来保存状态,没有使用额外空间。

空间复杂度:O(1)

总结

这道题的关键在于理解 UTF-8 的编码规则,而不是写复杂的算法。
它的难点在于逻辑判断:

  • 如何判断字符头;
  • 如何验证后续字节;
  • 如何保证整体字节数刚好匹配。

从工程角度看,这种验证逻辑常用于:

  • 文件或网络传输时判断数据是否被截断;
  • 数据库或日志系统中检测是否有乱码;
  • 编写底层解析器或协议时(比如 JSON/HTTP)进行编码合法性校验。

这道题不仅能锻炼逻辑思维,还能让你对 “文本是如何变成二进制存储的” 有更直观的理解。

### LeetCode 中 Python 出现 `invalid syntax` 错误的原因分析与解决方案 在 VSCode 或其他环境中运行 Python 脚本时遇到 `SyntaxError: invalid syntax` 错误,通常是由以下几个原因引起的: #### 1. **命令行输入错误** 如果尝试通过交互式解释器(如 REPL)执行整个脚本路径,则会引发语法错误。这是因为 Python 解释器期望的是合法的 Python 表达式而非 shell 命令[^1]。 ```python >>> /usr/local/bin/python3 /Users/xxx/leetcode/9.回文数.py File "<stdin>", line 1 /usr/local/bin/python3 /Users/xxx/leetcode/9.回文数.py ^ SyntaxError: invalid syntax ``` 要解决此问题,请直接在终端中运行脚本而不是在交互模式下键入完整的路径。例如,在终端中使用以下命令来运行脚本: ```bash python3 /path/to/script.py ``` --- #### 2. **非法字符或编码问题** Python 对源码文件有严格的编码要求,默认情况下为 UTF-8 编码。如果文件中存在不可见的特殊字符或者 BOM 字节序标记,可能会导致解析失败并抛出语法错误[^2]。 建议检查代码文件是否存在隐藏字符,并确保保存为纯文本格式且无多余空白符。可以使用以下方法验证和修复: - 使用编辑器设置文件编码UTF-8- 删除多余的空格或制表符,尤其是混合使用的场景。 --- #### 3. **语法不符合规范** 某些特定情况下的语法可能违反了 Python 的设计原则,比如未遵循缩进规则、缺少必要的冒号或其他结构化需求。以下是几个常见例子及其修正方式: ##### (a) 缩进不一致 Python 非常依赖于正确的缩进来定义代码块。如果同一层次的代码行之间混用了不同数量的空间或 Tab 符号,就会触发此类错误。例如: ```python def test(): print("Hello") # 缺少缩进 ``` 应改为: ```python def test(): print("Hello") ``` ##### (b) 不支持的关键字或变量名 当试图创建带有保留词作为名称的对象时也会出现问题。例如: ```python class = "Mathematics" ``` 这显然是不允许的,因为 `class` 是 Python 的关键字之一。应该改用其他有效的标识符代替。 ##### (c) 外部作用域访问不当 对于嵌套函数中的变量操作,需注意区分局部变量与外部作用域的关系。如果不小心修改了一个只读状态的上层变量而忘记标注其属性,则同样会产生异常。利用 `nonlocal` 和 `global` 可以显式指定这些关系[^4]。 示例对比: ```python # 正确示范 x = 0 def outer(): y = 10 def inner(): nonlocal y y += 5 return y return inner() result = outer() print(result) # 输出结果:15 ``` --- #### 总结 综上所述,针对上述提到的各种可能性逐一排查即可定位到具体的根源所在。务必保持良好的编程实践习惯以及熟悉所选用的语言特性以便更高效地解决问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

网罗开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值