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;
        }
    }
    
    

验证回文链表

  1. 当然我们可以将它转换为我们熟悉的回文数或者回文串进行计算,但是这同样没有用到链表的特性。

  2. 在验证回文链表的场景下,我们可以通过快慢指针的方式找到链表的中间节点,然后再将原链表的一半反转,之后开始比对。

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"。

  1. 分割为 a + ac
  2. 分割为 a + a + c,分割后,得到一组结果,再回溯到 a + ac
  3. a + ac 中 ac 不是回文串,继续回溯,回溯到 aac
  4. 分割为稍长的回文串,分割为 aa + c 分割完成得到一组结果,再回溯到 aac
  5. 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;
    }
}

posted @ 2021-04-20 13:15  your_棒棒糖  阅读(42)  评论(0编辑  收藏  举报