最稳妥实现是用std::vector构建二维DP表,正确初始化dpi=i、dp0=j,遍历时用s1[i-1]==s2[j-1]比较字符,状态转移取删除、插入、替换三者最小值;虽空间O(mn),但安全可靠,适合多数场景。

用 std::vector 实现二维 DP 表最稳妥
Levenshtein 距离本质是动态规划问题,状态转移依赖左、上、左上三个格子。直接用原生二维数组容易越界或内存未初始化,std::vector<:vector>></:vector> 自动零初始化且边界安全。别手写 new int*[m] —— 容易漏 delete[],也难对齐。
- 初始化
dp[i][0] = i(删光 s1 前 i 个字符),dp[0][j] = j(增满 s2 前 j 个) - 遍历时注意字符串索引比 dp 表小 1:
s1[i-1] == s2[j-1]才能取dp[i-1][j-1] - 三者取最小:替换(
dp[i-1][j-1] + 1)、删除(dp[i-1][j] + 1)、插入(dp[i][j-1] + 1)
空间优化到 O(min(m,n)) 是可行的,但别硬套
滚动数组只保留两行确实能压空间,但前提是你要清楚自己在牺牲什么:无法回溯编辑路径,且代码可读性陡降。实际项目里除非处理超长字符串(>10k 字符)且内存受限,否则不值得。
- 必须先判断
s1.size() > s2.size(),把短串当“列”,否则空间仍是 O(max(m,n)) - 旧行要完整复制到新行前,不能只赋值部分——
prev = curr比循环拷贝更安全 - 常见错误:混淆
i和j对应的字符串下标,导致比较错位,结果偏大
std::string_view 可避免传参拷贝,但别在 DP 循环里用它取地址
传入函数时用 std::string_view 能省掉构造临时 std::string 的开销,尤其对字面量或已有 string 引用。但它不保证底层数据生命周期——DP 表计算过程中若你试图保存 sv.data() 地址做缓存,就踩坑了。
- 函数签名建议写成:
int levenshtein(std::string_view s1, std::string_view s2) - 内部立刻转成
s1.data()和s2.data()配合s1.size()使用,别反复调用.data() - 如果输入可能来自临时
std::string{...},而你又用了string_view参数,编译器不会报错,但运行时行为未定义
边界情况:空串、中文 UTF-8 字符、重复字符影响吗?
标准 Levenshtein 定义按“字符”计数,C++ 的 std::string 是字节序列,所以 UTF-8 中文会出错——一个汉字占 3 字节,被当成 3 个“字符”算距离。这不是算法问题,是编码层面没对齐。
立即学习“C++免费学习笔记(深入)”;
- 空串完全合法:
levenshtein("", "abc") == 3,DP 初始化已覆盖 - 重复字符(如
"aaa"vs"aa")不影响逻辑,DP 仍正确给出 1 - 真要支持 Unicode,得先用 ICU 或
std::codecvt_utf8(已弃用)转成std::u32string,再喂给 DP——这步开销远超 DP 本身


















