每日一道leetcode(2026.03.28):找出对应 LCP 矩阵的字符串
- [1. 题目](#1. 题目)
- [2. 分析](#2. 分析)
- [3. 代码实现](#3. 代码实现)
- [4. 总结](#4. 总结)
1. 题目
对任一由
n个小写英文字母组成的字符串word,我们可以定义一个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
中的对应字母,则认为字符串 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
2. 分析
这道题贼恶心:(
题目看了数遍!!
我一开始的理解是,矩阵对角线要等于n-i,然后左右两部分对称,然后再根据行对应数字映射到字母的字符序列上去,遗漏了好几个需求点。
为了便于理解,先来说道说道这个lcp,何为lcp, Longest Common Prefix,最长公共前缀。举个实际例子可能更好的理解:
比如字符串abac,对应的这个字符串有一个唯一的lcp矩阵
{4,0,1,0},
{0,3,0,0},
{1,0,2,0},
{0,0,0,1}
一言以蔽之:字符序列abac的第i和第j开始之后的子序列,有多少个相同的字符。
- i=0,j=0时,子序列s[i]=abac,s[j]=abac,所以相同前缀长度为4,很明显,当i=j时,lcp[i][j]=子序列的长度;
- i=0,j=1时,子序列s[i]=abac,s[j]=bac,公共前缀长度为0;
- i=0,j=2时,子序列s[i]=abac,s[j]=ac,公共前缀长度为1;
- i=0,j=3时,子序列s[i]=abac,s[j]=c,公共前缀长度为0;
- 以此类推
其实站在这个角度,很好理解,但反向的从矩阵来判断符合lcp矩阵,就更有难度了。下面列举一下lcp矩阵的规则:
- 对角线为n-i
java
lcp[i][i] = n - i
- 矩阵对称
java
lcp[i][j] = lcp[j][i]
- 长度上限
java
lcp[i][j] ≤ min(n-i, n-j)
这个其实好理解,比如ab和abc两个子序列的最大公共序列最大也就是全匹配的情况,为2
- 递推规则
如果 s[i] == s[j],
那么:lcp[i][j] = lcp[i+1][j+1] + 1
这个规则其实没有那么明显,但提到了还是比较好理解,还是上面的例子,ab和abc两个子序列,他们的最大公共序列长度为2,那么b和bc两个子序列的最大公共长度肯定为1。
3. 代码实现
java
class Solution {
public String findTheString(int[][] lcp) {
int n = lcp.length;
for (int i = 0; i < n; i++) {
// 对角线等于n-i
if (lcp[i][i] != n - i) {
System.out.println(i + "对角线不匹配");
return "";
}
for (int j = 0; j < i; j++) {
// 矩阵对角线元素相等
if (lcp[i][j] != lcp[j][i]) {
System.out.println(i + "," + j + ",矩阵对角线元素不相等");
return "";
}
// 矩阵元素小于等于min(n-i,n-j)
int min = Math.min((n - i), (n - j));
if (!(lcp[i][j] <= min)) {
System.out.println(i + "," + j + ",矩阵元素大于min");
return "";
}
// 递推规则
if (lcp[i][j] > 0 && i < n - 1 && j < n - 1) {
if (lcp[i][j] != lcp[i + 1][j + 1] + 1) {
System.out.println(i + "," + j + ",矩阵元素不满足递推");
return "";
}
}
}
}
System.out.println("矩阵满足要求");
// 还原字典序最小的字符串
char[] chars = new char[n];
char letter = 'a';
int count = 0;
for (int i = 0; i < n; i++) {
boolean matched = false;
for (int j = i; j < n; j++) {
if (i == j && chars[i] > 0) {
break;
}
if (lcp[i][j] > 0) {
chars[j] = letter;
matched = true;
count++;
}
}
if (count == n) {
break;
}
if (matched) {
letter++;
if (letter > 'z') {
// 全部由小写英文字母组成
System.out.println("超出小写字母序列范围");
return "";
}
}
}
StringBuilder sb = new StringBuilder();
for (char aChar : chars) {
sb.append(aChar);
}
String str = sb.toString();
char[] strArray = str.toCharArray();
// 最终检查
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (lcp[i][j] > 0) {
if (strArray[i] != strArray[j]) {
System.out.println("字典序不满足");
return "";
}
}
}
}
return str;
}
}

4. 总结
这道题花费了很多时间,主要是题目理解和隐藏彩蛋上。
超出规则范围的大概有两点:
一是后面字符串还原的时候,要保证所有的字符在a到z范围内,超出了,说明不符合;
二是输入的矩阵符合所有的规则,但是最终还原出来的字符串不符合预期答案。这里最恶心的就是从字符串肯定能映射到一个lcp的n*n矩阵,但是矩阵就算满足了上面提到的那些规则,还可能违背一些逻辑。比如a和b有公共前缀,a和c也有公共前缀时,那么b和c肯定有公共前缀。这一点没有列举到上面的规则中,在通过矩阵遍历来实现,维度放得很大,通过最终生成的字符串来逆向验证,反倒更加简单些。
跑出来的效率只超过了6%,可以看到代码还是有优化空间的,把上下两次全遍历合二为一,有兴趣的朋友可以尝试一下。