LeetCode 214:最短回文串
LeetCode 214:最短回文串
问题定义与核心挑战
给定字符串 s
,需通过在前面添加字符将其转换为回文串,求最短的 such 回文串。例如:
- 输入
s = "abcd"
→ 输出dcbabcd
(添加dcb
到前面)。 - 核心挑战:如何高效找到 最长回文前缀(从开头开始的最长回文子串),从而最小化添加的字符数。
核心思路:KMP 的 LPS 数组
利用 KMP 算法的最长公共前后缀(LPS)数组,将问题转换为寻找字符串的对称结构:
- 构造辅助串:
t = s + "#" + reverse(s)
(#
避免前缀和后缀过度重叠)。 - 计算 LPS 数组:LPS 数组的最后一个元素值,即为
s
的最长回文前缀长度。 - 构造结果:将
s
中最长回文前缀之后的部分反转,添加到原字符串前。
算法步骤详解
步骤 1:反转字符串,构造辅助串 t
- 反转
s
得到rev_s
,构造t = s + "#" + rev_s
。 - 作用:通过
t
的 LPS 数组,找到s
的前缀与rev_s
的后缀的最长匹配(该匹配对应s
的最长回文前缀)。
String rev_s = new StringBuilder(s).reverse().toString();
String t = s + "#" + rev_s;
步骤 2:计算 LPS 数组(KMP 核心)
LPS 数组定义:lps[i]
表示 t[0..i]
的最长公共前后缀长度(前缀和后缀相同的最长子串长度)。
计算逻辑:
- 初始化
len = 0
(当前最长公共前后缀长度),lps[0] = 0
。 - 遍历
t
(从i=1
开始):- 若
t[i] == t[len]
,则len++
,lps[i] = len
,i++
。 - 若不匹配,若
len > 0
,则len = lps[len-1]
(回退到之前的有效长度);否则lps[i] = 0
,i++
。
- 若
int[] lps = new int[t.length()];
int len = 0; // 当前最长公共前后缀长度
for (int i = 1; i < t.length(); ) {if (t.charAt(i) == t.charAt(len)) {len++;lps[i] = len;i++;} else {if (len > 0) {len = lps[len - 1]; // 回退} else {lps[i] = 0;i++;}}
}
步骤 3:构造最短回文串
- 最长回文前缀长度为
lps[t.length()-1]
(LPS 数组最后一个元素)。 - 截取
s
中最长回文前缀之后的部分(s.substring(max_len)
),反转后添加到原字符串前。
int max_len = lps[t.length() - 1];
String add = new StringBuilder(s.substring(max_len)).reverse().toString();
return add + s;
关键逻辑解析
1. 辅助串 t
的作用
t = s + "#" + rev_s
确保s
的前缀与rev_s
的后缀匹配时,不会跨s
和rev_s
的边界(如s=aaaa
,rev_s=aaaa
,不加#
会导致匹配长度为 8,而实际最长回文前缀是 4)。
2. LPS 数组的意义
lps[t.length()-1]
表示s
的前缀与rev_s
的后缀的最长匹配长度,该长度对应s
的最长回文前缀长度(因为rev_s
是s
的反转,匹配的前缀必然是回文)。
3. 时间复杂度
- 构造
t
:O(n)
(n
是s
的长度)。 - 计算 LPS 数组:
O(n)
(每个字符最多入队、出队一次)。 - 反转和拼接:
O(n)
。 - 整体复杂度:
O(n)
,高效处理大输入(如n=5×10⁴
)。
完整代码(Java)
public class Solution {public String shortestPalindrome(String s) {// 处理空字符串if (s == null || s.isEmpty()) {return "";}// 步骤1:反转字符串,构造辅助串 t = s + "#" + rev_sString rev_s = new StringBuilder(s).reverse().toString();String t = s + "#" + rev_s;// 步骤2:计算 LPS 数组int[] lps = new int[t.length()];int len = 0; // 当前最长公共前后缀的长度for (int i = 1; i < t.length(); ) {if (t.charAt(i) == t.charAt(len)) {len++;lps[i] = len;i++;} else {if (len > 0) {// 回退到前一个可能的长度len = lps[len - 1];} else {lps[i] = 0;i++;}}}// 步骤3:构造最短回文串int max_len = lps[t.length() - 1];String add = new StringBuilder(s.substring(max_len)).reverse().toString();return add + s;}
}
示例验证(以示例 2 为例)
输入:s = "abcd"
- 反转与构造
t
:rev_s = "dcba"
,t = "abcd#dcba"
。 - 计算 LPS 数组:最后一个元素
lps[8] = 1
(最长匹配长度为 1,即s
的最长回文前缀是"a"
)。 - 构造结果:
s.substring(1) = "bcd"
,反转后为"dcb"
,最终结果dcb + abcd = "dcbabcd"
,与示例一致。
该方法通过 KMP 思想 高效定位最长回文前缀,将时间复杂度优化到 O(n)
,是处理“最短回文串”问题的经典方案。