[编程题] LeetCode上的Palindrome(回文)类型的题目

回文感觉好恶心,不知道为啥感觉做回文的题脑子就变成浆糊了,所以干脆一口气全部做完它,爽!

所谓回文字符串,就是一个字符串,从左到右读和从右到左读是完全一样的。比如”level” 、 “aaabbaaa”

在leetcode上https://leetcode.com/problemset/algorithms/ 的搜索框搜“pali”,它会模糊匹配出来所有的回文的题目:



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整体都是回文字符串

思路:动态规划

  1. 矩阵dp[i][j] 存的是给定的字符串 s 的 s[i~j] 最少添加几个字符可以使得 s[i~j] 整体都是回文
  2. 如果 i == j, dp[i][j] = 0, 因为 s[i~j] 只有一个字符,就是回文
  3. 如果 i == j-1, s[i~j] 只有两个字符, 如果两个字符相等的话,dp[i][j] = 0 ,不等的话 dp[i][j] = 1
  4. 如果 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整体都是回文字符串

题目由添加变成了删除,答案是一毛一样的!


解决完回文,简直爽!

猜你喜欢

转载自blog.csdn.net/notHeadache/article/details/52401231
今日推荐