最长回文子序列,最长回文子串,回文串数量,最长回文串长度,回文数,回文链表
最长回文子序列
使用一个二维dp记录当前序列s[i … j]的最长回文子序列长度。
- 当当前序列头尾字符一样时,去掉头尾字符,里面应该还是回文子序列,所以当前序列的回文子序列等于里面最长回文子序列的长度+头尾字符的个数。如对序列babab来说,dp[0][4] = dp[1][3] + 2 = 5
- 当当前序列头尾字符不一样时,当前最长回文子序列一定在包含头或者包含尾字符的序列中。如对序列bba来说,dp[0][2] = max(dp[1][2], dp[0][1]) = 2
所以可以归纳出
- if(s[i] == s[j]) dp[i][j] = dp[i+1][j-1] + 2
- else dp[i][j] = Math.max( dp[i+1][j], dp[i][j-1] )
可以设置两重循环解决这个问题,第一重循环定每次判断的序列的长度,第二重循环定序列的起点指针
public int longestPalindromeSubseq(String s){
int[][] dp = new int[s.length()][s.length()];
for(int len = 1; len <= s.length(); len++){
for(int i = 0; i <= s.length() - len; i++){ //起点i最大只能取到s.length - len
int j = i + len - 1; //当前序列的终点指针j = i + len - 1
if(i == j){ //当len == 1时,所有序列的最长回文子序列长度都是1
dp[i][j] = 1;
continue;
}
if(s.charAt(i) == s.charAt(j))
dp[i][j] = dp[i+1][j-1] + 2;
else
dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
}
}
return dp[0][s.length() - 1];
}
最长的回文子串
取每一个字符向两侧扩散寻找,当该字符两边的字符相等时,将两边的字符拼入形成新的回文串并继续向两侧扩散寻找,直到两边字符不等,或扩散时超出边界就停止。这种思想称为中心扩散。
注意的是,当序列是奇数个时如abcba,中点是一个数,可以直接扩散;当序列是偶数个时如abccba,中点是一对数,所以要将中间两个数捆绑成一组进行扩散。
String res = "";
public String LongestPalindrome(String s){
for(int i = 0; i < s.length(); i++){ //对每个数都扩散一遍
expand(s, i, i); //序列是奇数个时
expand(s, i, i + 1); //序列是偶数个时
}
return res;
}
public void expand(String s, int left, int right){
//保证扩散不超出边界且两侧字符相等时,才扩散
while(left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){
left--;
right++;
}
//当跳出循环时left多减了一次,right多加了一次,所以取子串时left要加回来一次,right要减去一次
//注意substring函数是左区间包含右区间不包含的,所以右边写right时,子串只取到了right-1
String cur = s.substring(left + 1, right);
res = cur.length() > res.length() : cur : res; //更新最大回文子串
}
任给一个字符串,计算里面有多少回文串
思路同上
int res = 0;
public String LongestPalindrome(String s){
for(int i = 0; i < s.length(); i++){ //对每个数都扩散一遍
expand(s, i, i); //序列是奇数个时
expand(s, i, i + 1); //序列是偶数个时
}
return res;
}
public void expand(String s, int left, int right){
//保证扩散不超出边界切两侧字符相等时,才扩散
while(left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){
res++;
left--;
right++;
}
}
任给一个字符串,计算里面可形成的最长回文串的长度
如给一个字符串 abccccdd,可形成最大回文串dccaccd或者dccbccd,长度都是7
思路:遍历字符串,记录每一个字符出现的次数count(有点桶的思想),将它除以2,代表这个字符所能形成的回文串里占半边儿的个数,再乘2,代表这个字符所能形成的回文串里它占的总个数,若count是奇数,那么这个字符可能会是所形成的回文串的中心,若此时回文串的长度是偶数,那说明这个字符就是当前回文串的中心
public int LongestPalindrome(String s){
int[] counts = new int[128]; // 最大的字符z的ASCII码是120,其实设成长度是121就够了
for(char c : s.toCharArray())
counts[c]++; //以字符的ASCII码作为索引,若该字符出现了一次,则对应索引位置的数+1,表示的是该字符在s里出现的次数
int res = 0;
for(int count : counts){
//将除法乘法判断奇偶数的方法全部换成位运算提高效率
res += count >> 1 << 1;
if((count & 1) == 1 && (res & 1) == 0)
res++;
}
return res;
}
判断一个数是不是回文数
思路:将一个数分成一半,将后一半倒转,如果前一半等于后一半,则该数是回文数
public boolean isPalindrome(int x){
//如果是负数肯定不是回文数 -123 != 321-, 如果数以0结尾可是这个数又不是0肯定不是回文数 120 != 021
if(x < 0 || (x % 10 == 0 && x != 0))
return false;
int half = 0;
while(x > half){
half = half * 10 + x % 10;
x /= 10;
}
//当x是奇数位时,可以将half的最后一位直接去除而不影响结果,因为最后一位是中心位
//如x = 12321, 经过while , x = 12, half = 123
return x == half || x == half / 10;
}
判断一个链表是不是回文链表
思路:找到链表的中间点(用快慢指针的方法),将后半段翻转,然后按顺序在前后半段中依次判断对应结点是否相等
public boolean isPalindrome(ListNode head){
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
//此时slow是中间结点,将slow后面的链表翻转
slow = reverse(slow);
//将fast放回链表头
fast = head;
//两个指针同时开始向后遍历,如果出现对应结点数值不同,则不是回文链表,否则是回文链表
while(slow != null){
if(slow.val != fast.val) return false;
fast = fast.next;
slow = slow.next;
}
return true;
}
//迭代的方法
public ListNode reverse(ListNode head){
ListNode next = null;
ListNode pre = null;
while(head != null){
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
//递归的方法
public ListNode reverse(ListNode head){
if(head == null || head.next == null) return head;
ListNode next = head.next;
head.next = null;
ListNode newHead = reverse(next);
next.next = head;
return newHead;
}