Leetcode回文序列问题——Java

本文探讨了LeetCode中关于回文序列的各种问题,包括最长回文子序列、最长回文子串、回文串数量、最长回文串长度、回文数以及回文链表的判断。详细介绍了动态规划和中心扩散等算法来解决这些问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最长回文子序列,最长回文子串,回文串数量,最长回文串长度,回文数,回文链表

最长回文子序列

使用一个二维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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值