Hard
题目描述
我们定义由 n 个小写英文字母组成的字符串 word 的 lcp 矩阵为 n x n 的网格,满足:
lcp[i][j] 等于子字符串 word[i,n-1] 和 word[j,n-1] 之间最长公共前缀的长度。
给定 n x n 矩阵 lcp,返回与 lcp 对应的字典序最小的字符串 word。如果不存在这样的字符串,则返回空字符串。
字符串 a 在字典序上小于字符串 b(长度相同)是指:在 a 和 b 不同的第一个位置上,a 中的字母在字母表中出现在 b 中对应字母之前。例如,“aabd” 在字典序上小于 “aaca”,因为它们不同的第一个位置是第三个字母,‘b’ 在 ‘c’ 之前。
示例 1:
输入:lcp = [[4,0,2,0],[0,3,0,1],[2,0,2,0],[0,1,0,1]]
输出:"abab"
解释:lcp 对应任何具有两个交替字母的 4 字母字符串。其中字典序最小的是 "abab"。
示例 2:
输入:lcp = [[4,3,2,1],[3,3,2,1],[2,2,2,1],[1,1,1,1]]
输出:"aaaa"
解释:lcp 对应任何具有单个不同字母的 4 字母字符串。其中字典序最小的是 "aaaa"。
示例 3:
输入:lcp = [[4,3,2,1],[3,3,2,1],[2,2,2,1],[1,1,1,3]]
输出:""
解释:lcp[3][3] 不能等于 3,因为 word[3,...,3] 只包含一个字母;因此,不存在答案。
约束条件:
- 1 <= n == lcp.length == lcp[i].length <= 1000
- 0 <= lcp[i][j] <= n
提示:
- 使用 LCP 数组确定哪些元素组必须相等
- 将最小的字母匹配到包含最小未分配索引的组
- 构建结果字符串的 LCP 矩阵,然后检查它是否等于目标 LCP
解题思路
这道题需要根据最长公共前缀(LCP)矩阵来重建字符串。解题思路分为三个步骤:
步骤1:理解约束条件
- 如果
lcp[i][j] > 0,那么word[i] == word[j](同一位置字符相等) - 如果
lcp[i][j] == 0,那么word[i] != word[j](同一位置字符不等) - 对角线元素
lcp[i][i]应该等于n-i(从位置i到末尾的长度)
步骤2:构造字符串 使用并查集思想,将必须相等的位置分组:
- 遍历LCP矩阵,如果
lcp[i][j] > 0,则位置i和j必须使用相同字符 - 为了得到字典序最小的结果,按索引顺序为每个组分配字母(‘a’, ‘b’, ‘c’…)
步骤3:验证结果 构造完字符串后,重新计算LCP矩阵并与给定矩阵比较:
- 计算每对后缀的最长公共前缀长度
- 如果计算结果与给定LCP矩阵不匹配,返回空字符串
这种方法确保了构造出的字符串既满足LCP约束,又是字典序最小的。
代码实现
class Solution {
public:
string findTheString(vector<vector<int>>& lcp) {
int n = lcp.size();
vector<int> parent(n);
iota(parent.begin(), parent.end(), 0);
function<int(int)> find = [&](int x) {
return parent[x] == x ? x : parent[x] = find(parent[x]);
};
// Union positions that must have same character
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (lcp[i][j] > 0) {
int pi = find(i), pj = find(j);
if (pi != pj) {
parent[max(pi, pj)] = min(pi, pj);
}
}
}
}
// Assign characters
string result(n, 'z');
char ch = 'a';
for (int i = 0; i < n; i++) {
if (find(i) == i) {
if (ch > 'z') return "";
result[i] = ch++;
}
}
for (int i = 0; i < n; i++) {
result[i] = result[find(i)];
}
// Verify
vector<vector<int>> computed(n, vector<int>(n, 0));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
int k = 0;
while (i + k < n && j + k < n && result[i + k] == result[j + k]) {
k++;
}
computed[i][j] = k;
}
}
return computed == lcp ? result : "";
}
};
class Solution:
def findTheString(self, lcp: List[List[int]]) -> str:
n = len(lcp)
parent = list(range(n))
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
# Union positions that must have same character
for i in range(n):
for j in range(n):
if lcp[i][j] > 0:
pi, pj = find(i), find(j)
if pi != pj:
parent[max(pi, pj)] = min(pi, pj)
# Assign characters
result = ['z'] * n
ch = ord('a')
for i in range(n):
if find(i) == i:
if ch > ord('z'):
return ""
result[i] = chr(ch)
ch += 1
for i in range(n):
result[i] = result[find(i)]
# Verify
computed = [[0] * n for _ in range(n)]
for i in range(n):
for j in range(n):
k = 0
while i + k < n and j + k < n and result[i + k] == result[j + k]:
k += 1
computed[i][j] = k
return ''.join(result) if computed == lcp else ""
public class Solution {
public string FindTheString(int[][] lcp) {
int n = lcp.Length;
int[] parent = new int[n];
for (int i = 0; i < n; i++) parent[i] = i;
int Find(int x) {
return parent[x] == x ? x : parent[x] = Find(parent[x]);
}
// Union positions that must have same character
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (lcp[i][j] > 0) {
int pi = Find(i), pj = Find(j);
if (pi != pj) {
parent[Math.Max(pi, pj)] = Math.Min(pi, pj);
}
}
}
}
// Assign characters
char[] result = new char[n];
char ch = 'a';
for (int i = 0; i < n; i++) {
if (Find(i) == i) {
if (ch > 'z') return "";
result[i] = ch++;
}
}
for (int i = 0; i < n; i++) {
result[i] = result[Find(i)];
}
// Verify
int[][] computed = new int[n][];
for (int i = 0; i < n; i++) {
computed[i] = new int[n];
for (int j = 0; j < n; j++) {
int k = 0;
while (i + k < n && j + k < n && result[i + k] == result[j + k]) {
k++;
}
computed[i][j] = k;
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (computed[i][j] != lcp[i][j]) return "";
}
}
return new string(result);
}
}
var findTheString = function(lcp) {
const n = lcp.length;
const parent = Array.from({length: n}, (_, i) => i);
function find(x) {
return parent[x]
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n³) | 构建并查集O(n²α(n)),验证LCP矩阵O(n³) |
| 空间复杂度 | O(n²) | 存储并查集数组和验证矩阵 |