LeetCode 2573. 找出对应 LCP 矩阵的字符串
题目描述
给你一个 n x n 的矩阵 lcp,其中 lcp[i][j] 表示字符串 s 从下标 i 开始和从下标 j 开始的最长公共前缀(LCP)的长度。你需要还原出由小写字母组成的字符串 s,如果不存在这样的字符串则返回空串。
示例:
输入:lcp = [[3,0,0],[0,2,1],[0,1,2]]
输出:"abb"
解释:字符串 "abb" 对应的 LCP 矩阵如上。
解题思路
关键性质
- 若
lcp[i][j] > 0,则必有s[i] == s[j],且lcp[i][j] = 1 + lcp[i+1][j+1](当i和j不是最后一个字符时)。 - 若
s[i] == s[j],则lcp[i][j] = 1 + lcp[i+1][j+1](如果i+1和j+1均未越界)。 - 若
s[i] != s[j],则lcp[i][j] = 0。
根据这些性质,我们可以先构造一个可能的字符串,再验证其与给定矩阵是否一致。
算法步骤
1. 贪心构造字符串
- 使用一个指针
i指向第一个未确定字符的位置(初始为 0)。 - 按照字母
'a'到'z'的顺序尝试填充:- 对于当前字母
c,遍历j从i到n-1:- 如果
lcp[i][j] > 0,说明s[j]必须与s[i]相等,因此将s[j]设为c。
- 如果
- 然后移动
i到第一个仍未被赋值的下标(s[i] == 0)。 - 如果
i == n,说明所有位置都已填满,提前结束。
- 对于当前字母
- 若 26 个字母用完后仍有未赋值的位置,说明无法构造,返回空串。
2. 验证矩阵一致性
- 从后向前遍历所有下标对
(i, j),计算在构造出的字符串s中应有的 LCP 值:- 若
s[i] != s[j],则实际值应为 0。 - 若
s[i] == s[j]:- 若
i == n-1或j == n-1,则实际值为 1。 - 否则实际值为
1 + lcp[i+1][j+1]。
- 若
- 若
- 如果任何一对的
lcp[i][j]与计算值不等,则返回空串。 - 验证通过后返回构造的字符串。
代码实现
cpp
class Solution {
public:
string findTheString(vector<vector<int>>& lcp) {
int n = lcp.size();
string s(n, 0); // 初始化全为0,表示未赋值
int i = 0;
// 贪心构造阶段
for (char c = 'a'; c <= 'z'; c++) {
// 将所有与位置 i 相等的字符设为当前字母 c
for (int j = i; j < n; j++) {
if (lcp[i][j] > 0)
s[j] = c;
}
// 移动 i 到下一个未赋值的位置
while (i < n && s[i] != 0) i++;
if (i == n) break; // 所有位置已填满
}
// 如果还有未赋值的位置,无解
if (i < n) return "";
// 验证阶段:从后向前检查 LCP 矩阵是否一致
for (int i = n - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
int actual = (s[i] != s[j]) ? 0 :
(i == n - 1 || j == n - 1) ? 1 :
lcp[i + 1][j + 1] + 1;
if (lcp[i][j] != actual)
return "";
}
}
return s;
}
};
复杂度分析
- 时间复杂度:构造阶段 O(26·n) ≈ O(n),验证阶段 O(n²),因此总时间复杂度为 O(n²)。
- 空间复杂度:O(n),用于存储结果字符串。
关键细节说明
-
构造阶段为什么可以贪心?
- 因为 LCP 矩阵的性质决定了,如果
lcp[i][j] > 0,那么s[i]和s[j]必然相等,且所有与s[i]相等的字符构成一个等价类。我们只需按顺序为每个等价类分配一个不同的字母(最多 26 个)即可。
- 因为 LCP 矩阵的性质决定了,如果
-
验证时的计算方式
- 从后向前计算是为了利用已经验证过的
lcp[i+1][j+1]值,避免重复计算。 - 当
s[i] == s[j]时,LCP 至少为 1,加上从下一个位置开始的 LCP 即可得到正确值。
- 从后向前计算是为了利用已经验证过的
-
为什么需要验证?
- 贪心构造只是保证了字符相等关系与矩阵中正数的对应,但矩阵中还有为 0 的值需要检查是否与字符不等关系一致。此外,矩阵的数值本身也必须满足递归关系,验证阶段可以捕获所有矛盾。