返回
探秘回文串分割的奥秘:破解 LeetCode 131
前端
2023-09-11 08:15:34
前言
在算法竞赛和编程实践中,字符串处理是一个不可或缺的领域。LeetCode 131 “分割回文串”便是其中一道经典题目,考察了我们对回文串、动态规划和递归算法的理解。本文将深入解析这道题目,揭示算法背后的精髓,助力读者在算法竞赛和编程实践中更上一层楼。
回文串的奥秘
回文串是指一个从左到右读和从右到左读都相同的字符串。例如,“level”和“racecar”都是回文串。在回文串分割问题中,我们需将给定的字符串分割成若干个子串,且每个子串都是回文串。
暴力回溯:穷举所有可能
解决回文串分割问题的一个朴素方法是暴力回溯。暴力回溯是一种深度优先的搜索算法,它通过穷举所有可能的分割方案,找到最优解。
public List<List<String>> partition(String s) {
List<List<String>> result = new ArrayList<>();
backtrack(s, 0, new ArrayList<>(), result);
return result;
}
private void backtrack(String s, int start, List<String> path, List<List<String>> result) {
if (start == s.length()) {
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i < s.length(); i++) {
String substring = s.substring(start, i + 1);
if (isPalindrome(substring)) {
path.add(substring);
backtrack(s, i + 1, path, result);
path.remove(path.size() - 1);
}
}
}
private boolean isPalindrome(String s) {
int i = 0, j = s.length() - 1;
while (i < j) {
if (s.charAt(i) != s.charAt(j)) {
return false;
}
i++;
j--;
}
return true;
}
动态规划:优化效率
暴力回溯虽然能找到最优解,但效率较低,特别是对于较长的字符串。我们可以通过动态规划优化算法的效率。动态规划是一种自底向上的算法,它通过存储子问题的最优解,避免重复计算。
public List<List<String>> partition(String s) {
int n = s.length();
boolean[][] dp = new boolean[n][n]; // 记录子串是否为回文串
List<List<String>>[] memo = new List[n]; // 记录子串的分割方案
for (int i = 0; i < n; i++) {
dp[i][i] = true;
memo[i] = new ArrayList<>();
}
for (int l = 2; l <= n; l++) {
for (int i = 0; i <= n - l; i++) {
int j = i + l - 1;
if (l == 2) {
dp[i][j] = (s.charAt(i) == s.charAt(j));
} else {
dp[i][j] = (s.charAt(i) == s.charAt(j) && dp[i + 1][j - 1]);
}
}
}
for (int i = 0; i < n; i++) {
if (dp[i][n - 1]) {
memo[i].add(s.substring(i, n));
}
for (int j = i + 1; j < n; j++) {
if (dp[i][j - 1] && !memo[j].isEmpty()) {
for (String right : memo[j]) {
memo[i].add(s.substring(i, j) + " " + right);
}
}
}
}
return memo[0];
}
总结
回文串分割问题看似复杂,但通过暴力回溯和动态规划等算法,我们可以有效地解决它。在算法竞赛和编程实践中,理解算法背后的精髓至关重要,它不仅能帮助我们解决难题,更能让我们在算法的世界中游刃有余。