234. Palindrome Linked List

234. Palindrome Linked List

Question

Given the head of a singly linked list, return true if it is a palindrome.

Solution 1

快慢指针,快指针遍历到底时慢指针正好到一半。
根据快指针的位置判断奇数个还是偶数个。
如果是奇数个则翻转链表时需要向前移动一位。

接下来翻转后半段链表。
翻转完成后从第一段的头部和翻转过的链表头部开始遍历。
如果两个节点的值不同则返回false。

遍历完毕返回true。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {

public boolean isPalindrome(ListNode head) {
int len = 0;
ListNode slow = head, fast = head;

while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}

ListNode curr = fast == null ? slow : slow.next, prev = null; //make sure curr is the head of the 2nd part

while(curr != null){ //reverse the 2nd part of the nodes
ListNode temp = curr.next; //preserve nodes that after current node
curr.next = prev; //add previous node to the current node's next
prev = curr; //save previous node
curr = temp; //update current node
}

while(head != null && prev != null){
if(head.val != prev.val) return false;
prev = prev.next;
head = head.next;
}
return true;
}
}

Solution 2

快慢指针的原理,不是记录长度而是当快指针遍历到链表尾时停止。
然后对照栈内的值是否和剩余的值相等。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {

public boolean isPalindrome(ListNode head) {
ListNode slow = head, fast = head;
Stack<ListNode> stack = new Stack<>();

while(fast != null && fast.next != null){
stack.add(slow);

slow = slow.next;
fast = fast.next.next;
}

if(fast != null) slow = slow.next;

while(!stack.isEmpty()){
if(stack.pop().val != slow.val) return false;
slow = slow.next;
}

return true;
}
}

Solution 3

回文,先计算链表长度。
记录链表长度是否为奇数。

如果链表长度为偶数,则将一半的节点放入栈中。
如果为奇数则抛弃一个节点。
剩下一半的节点和栈顶节点比较,如果值不相同则返回false。

遍历完成返回true。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {

public boolean isPalindrome(ListNode head) {
int len = 0;
ListNode root = head;
Stack<ListNode> stack = new Stack<>();

while(root != null){
root = root.next;
len++;
}

root = head;
for(int i = 0; i < len/2; i++){
stack.add(root);
root = root.next;
}

if(len % 2 != 0) root = root.next;

for(int i = 0; i < len/2; i++){
if(stack.peek().val == root.val) stack.pop();
else return false;
root = root.next;
}
return true;
}
}
1332. Remove Palindromic Subsequences

1332. Remove Palindromic Subsequences

Question

You are given a string s consisting only of letters 'a' and 'b'. In a single step you can remove one palindromic subsequence from s.

Return the minimum number of steps to make the given string empty.

A string is a subsequence of a given string if it is generated by deleting some characters of a given string without changing its order. Note that a subsequence does not necessarily need to be contiguous.

A string is called palindrome if is one that reads the same backward as well as forward.

Solution

当字符为空时,返回0。
注意本题中的子序列不需要连续。
由于只有’a’与’b’两个字符,因此最多两步即可将字符串清空。

辅助方法判断字符串是否为回文字符串。
如果为真则返回1,只需要删除一次。

如果为假则返回2,需要先删除所有的’a’,再删除所有的’b’。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int removePalindromeSub(String s) {
if(s.length() == 0) return 0;
return isPalindrome(s) ? 1 : 2;
}

private boolean isPalindrome(String s){
int i = 0, j = s.length() - 1;
while(j > i){
if(s.charAt(i) != s.charAt(j)) return false;
i++;
j--;
}
return true;
}
}
647. Palindromic Substrings

647. Palindromic Substrings

Question

Given a string s, return the number of palindromic substrings in it.

A string is a palindrome when it reads the same backward as forward.

A substring is a contiguous sequence of characters within the string.

Solution

Manacher算法,与5. Longest Palindromic Substring的实现方法相同。

首先处理字符串。
然后采用中心拓展计算各个位置的拓展长度,并维护c与r。
每次遍历时计算结果,将当前可扩展长度加一然后除以二后加入结果。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
int n;
int[] p;
String newString;
public int countSubstrings(String s) {
n = s.length();
StringBuffer sb = new StringBuffer();
if(n == 0) { //对字符串进行处理
sb.append("^$");
}
else{
sb.append("^");
for(int i = 0; i < s.length(); i++){
sb.append('#');
sb.append(s.charAt(i));
}
sb.append("#$");
}
p = new int[sb.length()];
newString = sb.toString();

return manacher();
}

private int manacher(){
int res = 0, c = 0, r = 0;
for(int i = 1; i < newString.length()-1; i++){
int m = 2 * c - i;
if(i < r) p[i] = Math.min(r-i, p[m]);

while(newString.charAt(i+p[i]+1) == newString.charAt(i-p[i]-1)){ //中心拓展
p[i]++;
}

if(i+p[i] > r){ //更新c与r
c = i;
r = i + p[i];
}
res += (p[i]+1)/2; //向上取整
}
return res;
}
}

Solution 2

中心拓展,辅助方法count()用来计算每个字符可以组成的最大回文数。

注意每次需要计算奇数长度回文和偶数长度回文两种情况。
遍历所有字符并加和,返回总数。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
String word;
public int countSubstrings(String s) {
word = s;
int ret = 0;
for(int i = 0; i < s.length(); i++){
ret+=count(i);
}
return ret;
}

private int count(int i){
int count = 1, left = i-1, right = i+1;

while(left >= 0 && right < word.length() && word.charAt(left) == word.charAt(right)){
count++;
left--;
right++;
}

left = i;
right = i+1;

while(left >= 0 && right < word.length() && word.charAt(left) == word.charAt(right)){
count++;
left--;
right++;
}
return count;
}
}
5. Longest Palindromic Substring

5. Longest Palindromic Substring

Question

Given a string s, return the longest palindromic substring in s.

Solution

马拉车算法,专门用于计算最大回文子字符串长度。

预处理

首先需要对字符串进行预处理,将每两个字符之间(包括两端)加上一个符号’#’。
然后在字符串的前后加入任意两个不同的符号。(防止中心扩展时继续搜索)

这个操作使得偶数项的回文字符串变为奇数项的回文字符串,便于接下来进行统一的中心扩展操作。

Manacher算法

参考资料:一文让你彻底明白马拉车算法

用一个数组p[]记录处理后字符串位置的中心展开长度。

初始化一个回文中心的下标位置i,和当前回文中心的右侧范围r。
在进行算法时维护这两个参数。

遍历字符串中除了首尾两个字符以外的位置。

  • 初始化i位置关于c的镜像位置m为2*c-i。
    由于回文字符串的对称特性,此时p[i]的中心拓展长度应等于p[m]。
  • 有几种特例情况,p[i]不等于p[m]:
  1. 如果p[m] + m的和大于r,则以m为中心扩展的范围超过当前c的中心扩展范围的右界。
    此时p[i]无法保证等于p[m]。但是可以确保p[i]至少可以扩展到r,因此将p[i]更新为r-i。
  2. 如果i的位置等于r,则可扩展距离为0。
  3. 如果m的位置为原字符串的左边界,则此时将p[i]赋值为1是不正确的。
    因为p[m]的计算是遇到了边界停止的,而p[i]则没有遇到边界,接下来对i位置从i+p[i]与i-p[i]开始继续进行中心扩展即可。

中心扩展

奇数项的回文字符串由中心扩展,只需要保证i+p[i]+1与i-p[i]-1(两个位置分别表示当前中心扩展的边缘再前进1)的字符相同,则可以继续向下扩展,将p[i]++。

c与r的更新

由于要保证i在r的范围之内,当从i出发的中心扩展范围大于c的中心扩展范围r,需要更新c与r。

时间复杂度

在进行扩展时,每次访问过的位置不会进入中心扩展算法。(比较从中心扩展的边缘开始,即i+p[i]+1与i-p[i]-1)
因此总的时间复杂度为O(n)。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Solution {
int n;
int[] p;
String newString;
public String longestPalindrome(String s) {
n = s.length();
StringBuffer sb = new StringBuffer();
if(n == 0) { //对字符串进行处理
sb.append("^$");
}
else{
sb.append("^");
for(int i = 0; i < s.length(); i++){
sb.append('#');
sb.append(s.charAt(i));
}
sb.append("#$");
}
p = new int[sb.length()];
newString = sb.toString();
manacher();

int c = 0;
for(int i = 0; i < sb.length(); i++){
if(p[i] > p[c]) c = i;
}
return s.substring( (c-p[c])/2, (c-p[c])/2+p[c] );
}

private void manacher(){
int c = 0, r = 0;
for(int i = 1; i < p.length - 1; i++){
int m = 2 * c - i;
if(i < r){ //当i在r的范围内时,p[i]的值与p[m]相等。
p[i] = Math.min(r-i, p[m]); //当p[m] + i超过了r时(当前中心拓展边界),p[i]至少可以取值到r-i
}
else{
p[i] = 0; //当i等于r时,该位置拓展距离为0
}

while (newString.charAt(i+p[i]+1) == newString.charAt(i-p[i]-1)) { //中心拓展算法,当两个位置相等时则可拓展距离+1
p[i]++;
}

if(i + p[i] > r){ //当当前位置的右侧边界大于现有边界时,更新位置r
c = i;
r = i + p[i]; //新的右侧边界为新的中心+拓展长度p[i]
}
}
}
}

Solution 2

getLength方法,计算每一个字符位置的回文长度。(将left填成i+1,right填成i则可以搜索偶数回文的长度。)
如果出现更长的回文,则根据返回的长度,和当前的i计算出字符串的范围。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public String longestPalindrome(String s) {
int best = 0;
int left = 0;
int right = 0;
for(int i = 0; i < s.length(); i++){
int length = Math.max(getLength(s,i,i), getLength(s,i+1,i));
if(best < length){
left = i-((length-1)/2-1);
right = i+(length/2);
best = length;
}
}
return s.substring(left,right);
}

private int getLength(String s, int left, int right){
while(left >= 0 && right < s.length() && left < s.length()){
if(s.charAt(left) == s.charAt(right)){
left--;
right++;
}
else break;
}

return right - left + 1;
}
}

409. Longest Palindrome

Given a string s which consists of lowercase or uppercase letters, return the length of the longest palindrome that can be built with those letters.

Letters are case sensitive, for example, “Aa” is not considered a palindrome here.

回文字符串除了中心一个元素,其他的字符必须成对出现。因此可以直接统计字符串里有几组成对的字符。

用数组记录字符出现的次数。
如果数组中某个字符出现了两次,则可以将其归零。然后可以组成的最长回文数字加2。
如果字符串还剩下其他字符未选择,则最长回文数可以加一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int longestPalindrome(String s) {
int[] alphabet = new int['z' - 'A' + 1];
int count = 0;
int total = s.length();

for(int i = 0; i < s.length(); i++){
int index = s.charAt(i) - 'A';
alphabet[index]+=1;
if( alphabet[index] == 2 ){
total -= 2;
count += 2;
alphabet[index] = 0;
}
}

if(total != 0){
count++;
}

return count;
}
}

680. Valid Palindrome II

问题
Given a string s, return true if the s can be palindrome after deleting at most one character from it.

双指针,字符串两边对比。
如果两边字符不相等,则更新两边指针,并分别传入辅助方法再次对比。
两个结果有一个是true则返回true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public boolean validPalindrome(String s) {
int left = 0;
int right = s.length() - 1;

while (left < right){
if (s.charAt(left) == s.charAt(right) ){
left++;
right--;
}
else{
return ( checkPalindrome(s, left+1, right) || checkPalindrome(s, left, right-1));
}
}
return true;
}
private boolean checkPalindrome(String s, int left, int right){
while (left < right){
if (s.charAt(left)==s.charAt(right)){
left++;
right--;
}
else{
return false;
}
}
return true;
}
}