最近做了好多回文类题目,一连串发现了好多,做了如下汇总:
1.判断单链表是否是回文 - Palindrome Linked List
Time Complexity: O(n), Space Complexity: O(n)
使用快慢指针,每次快指针走两步,慢指针走一步。同时还要用栈,每次慢指针走一步,都把值存入栈中。等快指针走完时,链表的前半段都存入栈中了。最后慢指针继续往前走,每次与栈顶元素进行比较。
public static boolean isPalindrome(ListNode head) {
if (head == null) {
return true;
}
int lenth = 0;// 记录长度
ListNode slow = head;
ListNode fast = head.next;
Stack<ListNode> s = new Stack<>();
while (fast != null && fast.next != null) {// 找到中间节点
s.push(slow);
slow = slow.next;
fast = fast.next.next;
lenth++;
}
fast = slow;
while (fast != null) {// 计算长度
fast = fast.next;
lenth++;
}
slow = slow.next;
if (lenth % 2 == 0) {// 如果为奇数,丢弃中间值
s.push(slow);
}
while (slow != null) {
if (slow.val != s.pop().val) {
return false;
}
slow = slow.next;
}
return true;
}
2.判断字符串是否是回文 - Valid Palindrome
时间复杂度O(n),空间复杂度O(1)
左右夹逼法
public static boolean isPalindrome(String s) {
s = s.toLowerCase();
int left = 0;
int right = s.length() - 1;
while (left < right) {
if (!Character.isLetterOrDigit(s.charAt(left)))
++left;
else if (!Character.isLetterOrDigit(s.charAt(right)))
--right;
else if (s.charAt(left) != s.charAt(right))
return false;
else {
++left;
--right;
}
}
return true;
}
3. 判断数组是否是回文 - Palindrome Number
// 时间复杂度O(1),空间复杂度O(1)
// 不断地取第一位和最后一位(10进制下)进行比较,相等则取第二位和倒数第二位,直到完成比较或者中途找到了不一致的位。
public static boolean isPalindrome(int x) {
if (x < 0)
return false;
int d = 1; // divisor
while (x / d >= 10)
d *= 10;
while (x > 0) {
int q = x / d; // quotient
int r = x % 10; // remainder
if (q != r)
return false;
x = x % d / 10;
d /= 100;
}
return true;
}
4.求最长子回文 - Longest Palindromic Substring
方法一:
动规,时间复杂度O(n^2),空间复杂度O(n^2)
设状态为 f(i,j) ,表示区间[i,j]是否为回文串
public static String longestPalindrome(String s) {
final int n = s.length();
final boolean[][] f = new boolean[n][n];
int maxLen = 1, start = 0; // 最长回文子串的长度,起点
for (int i = n - 1; i >= 0; i--) {
f[i][i] = true;
for (int j = i; j < n; j++) { // [j, i]
f[i][j] = (s.charAt(i) == s.charAt(j) && (j - i < 2 || f[i + 1][j - 1]));
if (f[i][j] && maxLen < (j - i + 1)) {
maxLen = j - i + 1;
start = i;
}
}
}
return s.substring(start, start + maxLen);
}
方法二:
Manacher,时间复杂度O(n),空间复杂度O(n)
public static String Manacher(String s) {
int len = s.length();
// 转换原始串
char[] tmp = new char[2 * len + 1];
for (int i = 0; i < 2 * len; i += 2) {
tmp[i] = '#';
tmp[i + 1] = s.charAt(i / 2);
}
tmp[2 * len] = '#';
// Manacher算法计算过程
int[] Len = new int[2 * len + 1];
int index = -1;
int mx = 0, ans = 0, po = 0;// mx即为当前计算回文串最右边字符的最大值
for (int i = 0; i < 2 * len + 1; i++) {
if (mx > i)
Len[i] = Math.min(mx - i, Len[2 * po - i]);// 在Len[j]和mx-i中取个小
else
Len[i] = 1;// 如果i>=mx,要从头开始匹配
while (i - Len[i] >= 0 && i + Len[i] < 2 * len + 1 && tmp[i - Len[i]] == tmp[i + Len[i]])
Len[i]++;
if (Len[i] + i > mx)// 若新计算的回文串右端点位置大于mx,要更新po和mx的值
{
mx = Len[i] + i;
po = i;
}
if (ans < Len[i]) {
ans = Len[i];
index = i / 2;
}
}
int n = ans - 1;
String ret = "";
if (n % 2 == 0) {
ret = s.substring(index - n / 2, index) + s.substring(index, index + n / 2);
} else {
ret = s.substring(index - n / 2, index) + s.substring(index, index + n / 2 + 1);
}
return ret;// 返回Len[i]中的最大值-1即为原串的最长回文子串额长度
}
5.切分字符串,求所有切分的回文串 - Palindrome Partitioning
// 深搜,时间复杂度O(2^n),空间复杂度O(n)
// 在每一步都可以判断中间结果是否为合法结果,用回溯法。
public static List<List<String>> partition(String s) {
List<List<String>> result = new ArrayList<>();
List<String> path = new ArrayList<>(); // 一个partition方案
dfs(s, path, result, 0);
return result;
}
// 搜索必须以s[start]开头的partition方案
private static void dfs(String s, List<String> path, List<List<String>> result, int start) {
if (start == s.length()) {
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i < s.length(); i++) {
if (isPalindrome(s, start, i)) { // 从i位置砍一刀
path.add(s.substring(start, i + 1));
dfs(s, path, result, i + 1); // 继续往下砍
path.remove(path.size() - 1); // 撤销上上行
}
}
}
private static boolean isPalindrome(String s, int start, int end) {
while (start < end && s.charAt(start) == s.charAt(end)) {
++start;
--end;
}
return start >= end;
}
6. 切分字符串,求最小切分数 - Palindrome Partitioning
// 动规,时间复杂度O(n^2),空间复杂度O(n^2)
// f(i)=区间[i, n-1]之间最小的cut数 ,n为字符串长度,
public static int minCut(String s) {
final int n = s.length();
int[] f = new int[n + 1];
// f[n] = Integer.MAX_VALUE;
boolean[][] p = new boolean[n][n];
// the worst case is cutting by each char
for (int i = n - 1; i >= 0; i--) {
p[i][i] = true;
f[i] = f[i + 1] + 1;
for (int j = i; j < n; j++) {
if (s.charAt(i) == s.charAt(j) && (j - i < 2 || p[i + 1][j - 1])) {
p[i][j] = true;
f[i] = Math.min(f[i], f[j + 1] + 1);
}
}
// System.out.println(f[i]);
}
return f[0] - 1;
}
7.数组中插入数变成回文,使插入数的和最小 - PalindromeArray
//动规, dp[i][j],i到j的回文数组的和
public static void palindromeArray(int[] nums) {
int len = nums.length;
Integer[][] dp = new Integer[len][len];
int res = palindromeArrayHelper(nums, dp, 0, len - 1);
System.out.println(res);
}
public static int palindromeArrayHelper(int[] nums, Integer[][] dp, int i, int j) {
if (i > j) {
return 0;
}
if (i == j) {
return nums[i];
}
if (dp[i][j] != null) {
return dp[i][j];
}
if (nums[i] == nums[j]) {
dp[i][j] = 2 * nums[i] + palindromeArrayHelper(nums, dp, i + 1, j - 1);
} else {
dp[i][j] = Math.min(2 * nums[i] + palindromeArrayHelper(nums, dp, i + 1, j),
2 * nums[j] + palindromeArrayHelper(nums, dp, i, j - 1));
}
return dp[i][j];
}
8.只能在字符串末尾插入字符,使成为回文,输出末尾插入的字符串
// 类似于Longest Palindromic Substring,只能在末尾填加
// 动规,时间复杂度O(n^2),空间复杂度O(n^2)
// 设状态为 f(i,j) ,表示区间[i,j]是否为回文串
public static String Lastpalindrome(String s) {
final int n = s.length();
final boolean[][] f = new boolean[n][n];
int maxLen = 1, start = 0; // 最长回文子串的长度,起点
for (int i = n - 1; i >= 0; i--) {
f[i][i] = true;
for (int j = i; j < n; j++) {
f[i][j] = (s.charAt(i) == s.charAt(j) && (j - i < 2 || f[i + 1][j - 1]));
if (j == n - 1 && f[i][j] && maxLen < j - 1 + 1) {
maxLen = j - i + 1;
start = i;
}
}
}
return new StringBuffer(s.substring(0, n - maxLen)).reverse().toString();
}