回文感觉好恶心,不知道为啥感觉做回文的题脑子就变成浆糊了,所以干脆一口气全部做完它,爽!
所谓回文字符串,就是一个字符串,从左到右读和从右到左读是完全一样的。比如”level” 、 “aaabbaaa”
在leetcode上https://leetcode.com/problemset/algorithms/ 的搜索框搜“pali”,它会模糊匹配出来所有的回文的题目:
- Palindrome Linked List 判断一个链表是否是回文
- Palindrome Number 判断一个数字是否是回文
- Valid Palindrome 判断一个字符串是否是有效的回文
- Longest Palindromic Substring 最长的回文字串
- Palindrome Partitioning 回文分割
- Palindrome Partitioning II 回文分割II
- 给你一个字符串s,如果可以在s的任意位置添加字符,最少需要几个字符可以让s整体都是回文字符串
- 给你一个字符串s,如果可以在s的任意位置删除字符,最少需要删除几个字符可以让s整体都是回文字符串
Palindrome Linked List 判断一个链表是否是回文
给你一个链表,判断这个链表里面的元素是否时回文。
需要在 O(n) 的时间和 O(1) 的空间内完成。
这道题作为开始做回文是一道很好的题目,因为可以同时复习一下链表的相关操作(快慢指针,反转链表)。
这道题的思路是:
- 用快慢指针遍历链表
- 遍历链表的同时把链表前半段给反转
- 最后再遍历比较前后半段链表,比较是否相等,全部相等则是回文
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
public class Solution {
public boolean isPalindrome(ListNode head) {
if(head == null) return true;
ListNode slow = head;
ListNode fast = head;
ListNode nextNode = head.next;
ListNode pre = head;
while(fast.next != null && fast.next.next != null){
fast = fast.next.next;
pre = slow;
slow = nextNode;
nextNode = nextNode.next;
slow.next = pre;
}
if(fast.next == null){
slow = pre;
}
while(nextNode != null){
if(slow.val != nextNode.val) return false;
slow = slow.next;
nextNode = nextNode.next;
}
return true;
}
}
Palindrome Number 判断一个数字是否是回文
给你一个整形的数字,判断这个数字是否是回文。
这道题可以用数学来解答,也是被分类到Math的条目下的。
思路:把这个整数反转过来,再和原来的比较,相等则是回文。
public class Solution {
public boolean isPalindrome(int x) {
if(x < 0) return false;
int y = x;
int rev = 0;
while(x != 0){
rev = rev*10 + x%10;
x = x/10;
}
return rev == y;
}
}
Valid Palindrome 判断一个字符串是否是有效的回文
判断一个字符串是否是有效的回文。
并且只考虑字符和数字,忽略大小写。
例如:
“A man, a plan, a canal: Panama” is a palindrome.
“race a car” is not a palindrome.
思路:用”三明治夹”的方法,前后两个指针不断缩小比较,遇到非法字符则跳过。
public class Solution {
public boolean isPalindrome(String s) {
if(s == null) return false;
s = s.toLowerCase();
int l = 0;
int r = s.length() - 1;
while(l < r){
if(!((s.charAt(l) >= 'a' && s.charAt(l) <= 'z') || (s.charAt(l) >= '0' && s.charAt(l) <= '9'))){
l++;
continue;
}
if(!((s.charAt(r) >= 'a' && s.charAt(r) <= 'z') || (s.charAt(r) >= '0' && s.charAt(r) <= '9'))){
r--;
continue;
}
if(s.charAt(l++) != s.charAt(r--)) return false;
}
return true;
}
}
Longest Palindromic Substring 最长的回文字串
给你一个字符串,找出它的最长回文字串。
思路:遍历字符串,
- 最长回文字串是奇数的情况的话,从每一个字符开始向左向右扩展,看以这个字符为中心的时候最大的回文字符串是多少
- 最长回文字串是偶数的情况的话,从每两个相同的字符开始向左向右扩展,看以这两个个字符为中心的时候最大的回文字符串是多少
public class Solution {
private int pos = 0, len = 0;
public String longestPalindrome(String s) {
if(s == null || s.length() < 2) return s;
for(int i = 0; i < s.length()-1; i++){
extend(s, i, i); //处理奇数
extend(s, i, i+1); //处理偶数
}
return s.substring(pos, pos + len);
}
private void extend(String s, int l, int r){
while(l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)){
l--;
r++;
}
if(len < r-l-1){
pos = l + 1;
len = r-l-1;
}
}
}
Palindrome Partitioning 回文分割
给你一个字符串s,分割s使得每个字串都是回文,返回所有的分割可能的情况。
例如:
s = “aab”
返回
[
[“aa”,”b”],
[“a”,”a”,”b”]
]
思路:用回溯的万能公式。
关于回溯,我写的另外一篇文章是专门做leetcode上回溯的题目的: [编程题] LeetCode上的backTracking类型的题目-难度Medium
public class Solution {
public List<List<String>> partition(String s) {
List<List<String>> res = new ArrayList<>();
backTracking(res, new ArrayList<>(), s, 0);
return res;
}
private void backTracking(List<List<String>> res, List<String> list, String s, int start){
if(start == s.length()) res.add(new ArrayList<>(list));
else {
for(int i = start; i < s.length(); i++){
if(isPalindrome(s, start, i)){
list.add(s.substring(start, i+1));
backTracking(res, list, s, i+1);
list.remove(list.size() - 1);
}
}
}
}
private boolean isPalindrome(String s, int low, int high){
while(low < high){
if(s.charAt(low++) != s.charAt(high--)) return false;
}
return true;
}
}
Palindrome Partitioning II 回文分割II
给你一个字符串s,返回最少的分割次数使得分割后的字串都为回文。
例如:
s = “aab”
返回 1,因为 s分割成 [“aa”,”b”] 字需要一次,也是最少的一次。
刚开始的时候想不出怎么用动态规划来做,于是就用了上面的回溯的方法,虽然是能解出来,但是超时了。先附上回溯的代码:
public class Solution {
public int minCut(String s) {
List<List<String>> res = new ArrayList<>();
backTracking(res, new ArrayList<>(), s, 0);
return maxPalindrome(res);
}
private void backTracking(List<List<String>> res, List<String> list, String s, int start){
if(start == s.length()) res.add(new ArrayList<>(list));
else {
for(int i = start; i < s.length(); i++){
if(isPalindrome(s, start, i)){
list.add(s.substring(start, i+1));
backTracking(res, list, s, i+1);
list.remove(list.size()-1);
}
}
}
}
private boolean isPalindrome(String s, int l, int r){
while(l < r){
if(s.charAt(l++) != s.charAt(r--)) return false;
}
return true;
}
private int maxPalindrome(List<List<String>> res){
int sum = res.get(0).size();
for(List<String> list: res){
sum = Math.min(sum, list.size());
}
return sum - 1;
}
}
后来我看了讨论,学习了他们用动态规划的思想来做,下面附上答案:
思想:
- 矩阵dp[i][j] 存的是给定的字符串 s 的 s[i~j] 字串是否是回文
- 数组res[i] 存的是 s[i~n-1] 的最小分割次数
然后如果 dp[i][j] == true 的话
- 如果 j == n-1 说明 s[i~n-1] 是回文,则不用分割,即分割次数为0,res[i] = 0;
- 如果 j!= n-1 说明 对s[i~n-1],在 j 这里切一刀,s[i~j] 是回文,看 s[j+1…n-1] 的最小切割数(res[j+1])是多少,res[j+1] + 1 和 原来的 res[i] 比较,取最小值。
- 即 res[i] = Math.min(res[i], res[j+1]+1)
res[0] 就是答案。
public class Solution {
public int minCut(String s) {
if(s == null || s.length() < 2) return 0;
int length = s.length();
boolean[][] dp = new boolean[length][length];
int[] res = new int[length];
for(int i = 0; i < length; i++){
Arrays.fill(dp[i], false);
}
for(int i = length-1; i >= 0; i--){
res[i] = length - i - 1; //初始化为最坏的情况
for(int j = i; j < length; j++){
/*如果字符串的两边相等的情况下
1、这个字符串只有两个字符
2、这个字符串不止两个字符,但是除了这两个字符的中间字符串是回文
那么这个字符串就是回文
*/
if(s.charAt(i) == s.charAt(j) && (j-i < 2 || dp[i+1][j-1])){
dp[i][j] = true;
if(j == length-1){
res[i] = 0;
} else {
res[i] = Math.min(res[i], res[j+1]+1);
}
}
}
}
return res[0];
}
}
除了LeetCode,在别的地方上也看到回文的题,但是LeetCode上没有的。
给你一个字符串s,如果可以在s的任意位置添加字符,最少需要几个字符可以让s整体都是回文字符串
思路:动态规划
- 矩阵dp[i][j] 存的是给定的字符串 s 的 s[i~j] 最少添加几个字符可以使得 s[i~j] 整体都是回文
- 如果 i == j, dp[i][j] = 0, 因为 s[i~j] 只有一个字符,就是回文
- 如果 i == j-1, s[i~j] 只有两个字符, 如果两个字符相等的话,dp[i][j] = 0 ,不等的话 dp[i][j] = 1
- 如果 i > j-1, s[i~j] 多于两个字符。如果 s[i] == s[j], dp[i][j] = dp[i+1][j-1]。 如果 s[i] != s[j],dp[i][j] = Math.min(dp[i][j-1], dp[i+1][j]) + 1;
求出dp矩阵之后,dp[0][s.length()-1] 就是我们的答案。
public int[][] getDP(String s){
char[] str = s.toCharArray();
int[][] dp = new [str.length][str.length];
for(int j = 1; j < str.length; j++) { //从dp矩阵的第一列开始
dp[j-1][j] = str[j-1] == str[j]? 0: 1;
for(int i = j-2; i > -1; i--){ //i表示行,从倒数的行计算到第0行
if(str[i] == str[j]) dp[i][j] = dp[i+1][j-1];
else dp[i][j] = Math.min(dp[i][j-1], dp[i+1][j]) + 1;
}
}
return dp;
}
那么题目换一换:
给你一个字符串s,如果可以在s的任意位置删除字符,最少需要删除几个字符可以让s整体都是回文字符串
题目由添加变成了删除,答案是一毛一样的!
解决完回文,简直爽!