LeetCode - 按标签分类刷题(字符串题解)——回文串系列
回文数
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1:
输入: 121 输出: true 复制代码
示例 2:
输入: -121 输出: false 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 复制代码
示例 3:
输入: 10 输出: false 解释: 从右向左读, 为 01 。因此它不是一个回文数。
-
普通解法
先将 整数转为字符串 ,然后将字符串分割为数组,只需要循环数组的一半长度进行判断对应元素是否相等即可。
///简单粗暴,看看就行 class Solution { public boolean isPalindrome(int x) { String reversedStr = (new StringBuilder(x + "")).reverse().toString(); return (x + "").equals(reversedStr); } }
-
数学解法
通过取整和取余操作获取整数中对应的数字进行比较。
举个例子:1221 这个数字。
- 通过计算 1221 / 1000, 得首位1
- 通过计算 1221 % 10, 可得末位 1
- 进行比较
- 再将 22 取出来继续比较
class Solution { public boolean isPalindrome(int x) { //边界判断 if (x < 0) return false; int div = 1; // while (x / div >= 10) div *= 10; while (x > 0) { int left = x / div; int right = x % 10; if (left != right) return false; x = (x % div) / 10; div /= 100; } return true; } }
-
巧妙解法
直观上来看待回文数的话,就感觉像是将数字进行对折后看能否一一对应。
所以这个解法的操作就是 通过除法
/
和取余%
的方式,将这个数字取出后半段进行翻转,然后比对两个数字的是否相等。这里需要注意的一个点就是由于回文数的位数可奇可偶,所以当它的长度是偶数时,它对折过来应该是相等的;当它的长度是奇数时,那么它对折过来后,有一个的长度需要去掉一位数(除以 10 并取整)。
具体做法如下:
- 每次进行取余操作 ( %10),取出最低的数字:
y = x % 10
- 将最低的数字加到取出数的末尾:
revertNum = revertNum * 10 + y
- 每取一个最低位数字,x 都要自除以 10
- 判断
x
是不是小于revertNum
,当它小于的时候,说明数字已经对半或者过半了 - 最后,判断奇偶数情况:如果是偶数的话,revertNum 和 x 相等;如果是奇数的话,最中间的数字就在revertNum 的最低位上,将它除以 10 以后应该和 x 相等。
class Solution { public boolean isPalindrome(int x) { //思考:这里大家可以思考一下,为什么末尾为 0 就可以直接返回 false if (x < 0 || (x % 10 == 0 && x != 0)) return false; int revertedNumber = 0; while (x > revertedNumber) { revertedNumber = revertedNumber * 10 + x % 10; x /= 10; } return x == revertedNumber || x == revertedNumber / 10; } }
- 每次进行取余操作 ( %10),取出最低的数字:
验证回文链表
-
当然我们可以将它转换为我们熟悉的回文数或者回文串进行计算,但是这同样没有用到链表的特性。
-
在验证回文链表的场景下,我们可以通过快慢指针的方式找到链表的中间节点,然后再将原链表的一半反转,之后开始比对。
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null) {
return true;
}
ListNode slow = head, fast = head;
ListNode pre = head, prepre = null;
while(fast != null && fast.next != null) {
pre = slow;
slow = slow.next;
fast = fast.next.next;
pre.next = prepre;
prepre = pre;
}
// 如果 fast 不为 null,说明是奇数,需要再进一位
if(fast != null) {
slow = slow.next;
}
// 此时 pre 为反转原链表前半部分的子链表
// slow 为原链表的中间节点
while(pre != null && slow != null) {
if(pre.val != slow.val) {
return false;
}
pre = pre.next;
slow = slow.next;
}
return true;
}
验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
示例 1:
输入: "A man, a plan, a canal: Panama" 输出: true 复制代码
示例 2:
输入: "race a car" 输出: false
/*
将字符串反转后比对
*/
public static boolean isPalindrome_1(String s) {
return new StringBuilder(s).reverse().equals(s);
}
/*
使用两个指针,从字符串的前后两个方向,向内夹
*/
public boolean isPalindrome_2(String s) {
int i = 0, j = s.length() - 1;
while(i < j){
//通过 isLetterOrDigit() 可以直接判断当前字符是不是只属于字母和数字。
while(i < j && !Character.isLetterOrDigit(s.charAt(i)))
i++;
while(i < j && !Character.isLetterOrDigit(s.charAt(j)))
j--;
if(Character.toLowerCase(s.charAt(i)) !=
Character.toLowerCase(s.charAt(j)))
return false;
i++; j--;
}
return true;
}
最长回文串
给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。
在构造过程中,请注意区分大小写。比如
"Aa"
不能当做一个回文字符串。注意:
假设字符串的长度不会超过 1010。示例 1:
输入: "abccccdd" 输出: 7 解释: 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
- 数组的方式,因为字符串中都是大小写字母,则利用数组,将出现奇数次的字符在数组中置为true,进行单个字符次数的统计
class Solution {
public int longestPalindrome(String s) {
if(s == null){
return 0;
}
char[] chars = s.toCharArray();
boolean[] arr = new boolean[58]; //默认都为false
for(int i = 0;i < chars.length;i++){
arr[chars[i] - 'A'] = !arr[chars[i] - 'A']; //出现奇数次,则置为true
}
int singleCharsCount = 0;
for(int i = 0;i < arr.length;i++){
if(arr[i]){//如果是奇数,则为true,则进行singleCharsCount加一
singleCharsCount++;
}
}
return singleCharsCount == 0 ? chars.length : chars.length + 1 - singleCharsCount;
}
}
-
哈希表统计出现次数
public int longestPalindrome(String s) { int n = s.length(); HashMap<Character,Integer> map = new HashMap<>(); //将字符串中字符出现的次数加入到hashMap中 for (int i = 0; i < n; i++) { char c = s.charAt(i); map.put(c,map.getOrDefault(c,0) + 1); } int res = 0; for (Character key : map.keySet()) { int count = map.get(key); if (count % 2 == 0) res += map.get(key); //如果是偶数的话,全部加进去 else res += map.get(key) - 1; // 如果是奇数的话,减一加进去 } return res < s.length() ? res + 1 : res;//如果没有加完,说明有奇数的存在,可以将奇数放在中间位置 }
回文子串
- 暴力法
/**
* 暴力法
* @param s
* @return
*/
//打印出所有子串
//判断子串是否是回文子串
public int countSubstring_1(String s){
int count = 0;
for (int i = 0; i < s.length(); i++) {
for (int j = i + 1; j < s.length(); j++) {
String sub = s.substring(i,j);
if (isPalin(sub)){
count++;
}
}
}
return count;
}
//判断子串是否是回文串
private boolean isPalin(String s) {
int l = s.length();
for (int i = 0;i < l/2;i++){
if (s.charAt(i) != s.charAt(l - i - 1)){ //把第一个与最后一个比较,如果不等直接返回false,如果相等则比较下一个
return false;
}
}
return true;
}
- 中心扩散法
/**
* 中心扩散法
*/
public int countSubstring(String s) {
int ans = 0;
for (int center = 0;center < 2 * s.length() - 1;center++){
//left 和 right指针和中心点的关系是什么
//首先是left,有一个很明显的2倍关系的存在,其次是right,可能和left指向同一个(偶数时),也可能往后移动一个(奇数)
int left = center / 2;
int right = left + center % 2;
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){
ans ++;
left--;
right++;
}
}
return ans;
}
最长回文子串
给你一个字符串
s
,找到s
中最长的回文子串。示例 1:
输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd" 输出:"bb"
示例 3:
输入:s = "a" 输出:"a"
示例 4:
输入:s = "ac" 输出:"a"
-
暴力解法
/** * 暴力解法 * 直接判断每一个子串是否是回文子串,然后取其中最长的值返回 * @param A * @param n * @return */ public static int getLongestPalindrome_1(String A,int n) { int maxLen = 0; //暴力解法 for(int i = 0; i < n;i++){ for (int j = i+ 1;j <= n;j++){ String now = A.substring(i,j); if (isPalindrome(now) && now.length() > maxLen){ //判断子串是否是回文串,并且新的子串要大于最大的回文子串值时才更新最大回文子串的值 maxLen = now.length(); } } } return maxLen; } //判断子串是不是回文子串 private static boolean isPalindrome(String s) { int l = s.length(); for (int i = 0;i < l/2;i++){ if (s.charAt(i) != s.charAt(l - i - 1)){ //把第一个与最后一个比较,如果不等直接返回false,如果相等则比较下一个 return false; } } return true; }
-
动态规划
//动态规划 public static int getLongestPalindrome(String A,int n){ char[] aa = A.toCharArray(); int max = 1; boolean[][] dp = new boolean[n][n]; for (int i = 0;i <n;i++){ dp[i][i] = true; } for (int i= 1;i < n;i++){ //i指向的是字符的最后一位 for (int j = i - 1;j >= 0;j--){ //j指向的是字符的前部 if (i - j == 1){ //当两个指针靠近时,直接判断 dp[j][i] = (aa[i] == aa[j]); if (max < i - j + 1) max = i - j + 1; } else{ if (dp[j+ 1][i-1] && aa[i] == aa[j]){ dp[j][i] = true; if (max < i - j + 1) max = i - j + 1; }else { dp[j][i] = false; } } } } return max; }
-
中心扩散法
/** * 中心扩散法 * @param A 输入的字符串 * @param n 字符串的长度 * @return */ public static int getLongestPalindrome_2(String A,int n) { if (n == 0) return 0; int maxLen = 1; //中心枚举到n - 2位置 for (int i = 0;i < n - 1; i++){ //比较以i为中心扩散的回文子串 && 以i和i+1为中心扩散的回文子串 哪个大取哪个 int len = Math.max(centerSpread(A, i, i), centerSpread(A, i, i + 1)); maxLen = Math.max(maxLen,len); } return maxLen; } private static int centerSpread(String s, int left, int right) { int len = s.length(); int l = left; int r = right; while (l >= 0 &&r <= len - 1){ //若相等则继续扩散 if (s.charAt(l) == s.charAt(r)){ l--; r++; } else { break; } } //为什么还要减2,因为上面while循环终止了,此时s.charAt(l) != s.charAt(r) //所以次数的回文子串的左右边界确实是l- 1,r - 1 return r - l + 1 - 2; }
分割回文串
题目描述
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例:
输入: "aab" 输出: [ ["aa","b"], ["a","a","b"] ]
首先,对于一个字符串的分割,肯定需要将所有分割情况都遍历完毕才能判断是不是回文数。不能因为 abba 是回文串,就认为它的所有子串都是回文的。
既然需要将所有的分割方法都找出来,那么肯定需要用到DFS(深度优先搜索)或者BFS(广度优先搜索)。
在分割的过程中对于每一个字符串而言都可以分为两部分:左边一个回文串加右边一个子串,比如 “abc” 可分为 “a” + “bc” 。 然后对"bc"分割仍然是同样的方法,分为"b"+“c”。
在处理的时候去优先寻找更短的回文串,然后回溯找稍微长一些的回文串分割方法,不断回溯,分割,直到找到所有的分割方法。
举个🌰:分割"aac"。
- 分割为 a + ac
- 分割为 a + a + c,分割后,得到一组结果,再回溯到 a + ac
- a + ac 中 ac 不是回文串,继续回溯,回溯到 aac
- 分割为稍长的回文串,分割为 aa + c 分割完成得到一组结果,再回溯到 aac
- aac 不是回文串,搜索结束
package 字符串问题.回文串;/**
* Copyright (C), 2019-2021
* author candy_chen
* date 2021/4/20 9:44
*
* @Classname 分割回文串
* Description: 测试
*/
import java.util.ArrayList;
import java.util.List;
/**
* 给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
* 返回 s 所有可能的分割方案。
*/
public class 分割回文串 {
List<List<String>> res = new ArrayList<>();
public List<List<String>> partition(String s){
if (s == null ||s.length() == 0){
return res;
}
dfs(s,new ArrayList<String>(),0);
return res;
}
private void dfs(String s, ArrayList<String> remain, int left) {
if (left == s.length()){//判断终止条件
res.add(new ArrayList<>(remain));
return;
}
for (int right = left;right < s.length();right++){//从left开始,依次判断left->right是不是回文串
if (isPalindroom(s,left,right)){//判断是否是回文串
remain.add(s.substring(left,right+1));//添加到当前回文串到list中
dfs(s,remain,right+1);//从right+1开始继续递归,寻找回文串
remain.remove(remain.size() - 1);//回溯,从而寻找更长的回文串
}
}
}
/**
* 判断是否是回文串
*/
private boolean isPalindroom(String s, int left, int right) {
while (left < right && s.charAt(left) == s.charAt(right)){
left++;
right--;
}
return left >= right;
}
}
单词拆分
题目描述
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
- 拆分时可以重复使用字典中的单词。
- 你可以假设字典中没有重复的单词。
示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。
示例 2:输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。
只需要去定义一个数组 boolean[] memo,其中第 i 位 memo[i] 表示待拆分字符串从第 0 位到第 i-1 位是否可以被成功地拆分。
然后分别计算每一位是否可以被成功地拆分。
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
int n = s.length();
int max_length=0;
for(String temp:wordDict){
max_length = temp.length() > max_length ? temp.length() : max_length;
}
// memo[i] 表示 s 中以 i - 1 结尾的字符串是否可被 wordDict 拆分
boolean[] memo = new boolean[n + 1];
memo[0] = true;
for (int i = 1; i <= n; i++) {
for (int j = i-1; j >= 0 && max_length >= i - j; j--) {
if (memo[j] && wordDict.contains(s.substring(j, i))) {
memo[i] = true;
break;
}
}
}
return memo[n];
}
}
反转字符串
题目描述
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组
char[]
的形式给出。不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
示例 1:
输入:["h","e","l","l","o"] 输出:["o","l","l","e","h"] 复制代码
示例 2:
输入:["H","a","n","n","a","h"] 输出:["h","a","n","n","a","H"]
public class 反转字符串 {
public void reverseString(char[] s){
int i=0;
int j=s.length - 1;
while (i < j){
char temp = s[i];
s[i] = s[j];
s[j] = temp;
i++;
j--;
}
}
}
将字符串转换成整数
题目描述
将一个字符串转换成一个整数,字符串不是一个合法的数值则返回 0,要求不能使用字符串转换整数的库函数。
public class 把字符串换成整数 {
public int StrToInt(String str){
if (str == null || str.length() == 0){
return 0;
}
boolean isNegative = str.charAt(0) == '-';
int ret = 0;
for (int i = 0;i <str.length();i++){
char c = str.charAt(i);
if (i == 0 && (c == '+' || c == '-')){
continue;
}
if (c < '0' || c > '9')
return 0;
ret = ret * 10 + (c - '0');
}
return isNegative ? -ret : ret;
}
}