leetcode回文相关题目

本文汇总分析leetcode上与回文相关的题目,包括以下题目:

leetcode5.最长回文子串

题目链接

给你一个字符串s,找到s中最长的回文子串。

一个简单的思路是遍历字符串s的所有子串,判断每个子串是否为回文串,并找出长度最长的子串,伪代码如下:

public String longestPalindrome(String s) {
    int maxLen = 0;
    int start = -1, end = -1;

    for (int i = 0; i < s.length(); i++) {
        for (int j = i; j < s.length(); j++) {
            if (isPalindrome(s, i, j)) {
                if (maxLen < j - i + 1) {
                    maxLen = j - i + 1;
                    start = i;
                    end = j;
                }
            }
        }
    }

    return s.substring(start, end + 1);
}

isPalindrome(s, i, j)函数用于判断子串s[i~j]是否为回文串,简单算法如下:

private boolean isPalindrome(String s, int i, int j) {
    while (i < j) {
        if (s.charAt(i) != s.charAt(j)) {
            return false;
        }
        i++;
        j--;
    }
    return true;
}

isPalindrome的时间复杂度为 O ( n ) O(n) O(n),再加上遍历字串的时间复杂度为 O ( n 2 ) O(n^2) O(n2),整个算法的时间复杂度为 O ( n 3 ) O(n^3) O(n3),对于 1 ≤ s . l e n g t h ≤ 1000 1 \leq s.length \leq 1000 1s.length1000的数据规模比较吃力。

实际上,isPalindrome(s, i, j)还可以通过递推计算得到:

i s P a l i n d r o m e ( s , i , j ) = { t r u e , i = = j s [ i ] = = s [ j ] , i + 1 = = j s [ i ] = = s [ j ] ∧ i s P a l i n d r o m e ( s , i + 1 , j − 1 ) , e l s e isPalindrome(s,i,j) = \begin{cases} true, & i==j \\ s[i]==s[j], & i+1==j \\ s[i]==s[j] \wedge isPalindrome(s,i+1,j-1), & else \end{cases} isPalindrome(s,i,j)=true,s[i]==s[j],s[i]==s[j]isPalindrome(s,i+1,j1),i==ji+1==jelse

isPalindrome的递推算法似乎与直接通过循环计算没区别,但是配合记忆化搜索后,以上递推算法可以保证在 O ( n 2 ) O(n^2) O(n2)内求出所有子串的回文性,完整实现如下:

private Boolean[][] cache;

private boolean isPalindrome(String s, int i, int j) {
    if (cache[i][j] != null) {
        return cache[i][j];
    }

    if (i == j) {
        return cache[i][j] = true;
    }

    if (i + 1 == j) {
        return cache[i][j] = s.charAt(i) == s.charAt(j);
    }

    return cache[i][j] = s.charAt(i) == s.charAt(j) && isPalindrome(s, i + 1, j - 1);
}

由于有了缓存,不管isPalindrome被调用多少次,最多只会占用 n 2 n^2 n2的计算次数,而不是每次调用都是 O ( n ) O(n) O(n),因此加上遍历子串的 O ( n 2 ) O(n^2) O(n2)后时间复杂度仍然为 O ( n 2 ) O(n^2) O(n2),完整代码如下:

class Solution {
    private Boolean[][] cache;

    public String longestPalindrome(String s) {
        int maxLen = 0;
        int start = -1, end = -1;
        cache = new Boolean[s.length()][s.length()];

        for (int i = 0; i < s.length(); i++) {
            for (int j = i; j < s.length(); j++) {
                if (isPalindrome(s, i, j)) {
                    if (maxLen < j - i + 1) {
                        maxLen = j - i + 1;
                        start = i;
                        end = j;
                    }
                }
            }
        }

        return s.substring(start, end + 1);
    }

    private boolean isPalindrome(String s, int i, int j) {
        if (cache[i][j] != null) {
            return cache[i][j];
        }

        if (i == j) {
            return cache[i][j] = true;
        }

        if (i + 1 == j) {
            return cache[i][j] = s.charAt(i) == s.charAt(j);
        }

        return cache[i][j] = s.charAt(i) == s.charAt(j) && isPalindrome(s, i + 1, j - 1);
    }
}

leetcode647.回文子串

题目链接

给你一个字符串s,请你统计并返回这个字符串中回文子串的数目。

本题与5.最长回文子串类似,只不过最后求的是所有回文子串的数量,也是通过遍历s的所有子串进行计数,可以复用isPalindrome函数,核心代码如下:

for (int i = 0; i < s.length(); ++i) {
    for (int j = i; j < s.length(); ++j) {
        if (isPalindrome(s, i, j)) {
            cnt++;
        }
    }
}
return cnt;

leetcode131.分割回文串

题目链接

给你一个字符串s,请你将s分割成一些子串,使每个子串都是回文串,返回s所有可能的分割方案。

为得到所有合法的分割方案,可使用DFS枚举所有分割,并在枚举过程中记录每次分割得到的子串。假设dfs(index)表示分割s[index~len-1],然后尝试使用i枚举下一次划分的位置,将s[index~len-1]划分为s[index~i]s[i+1~len-1],在s[index~i]为回文串的条件下,递归执行dfs(i+1)

核心代码如下:

class Solution {
    public List<List<String>> partition(String s) {
        List<List<String>> result = new ArrayList<>();
        dfs(s, 0, new LinkedList<>(), result);
        return result;
    }

    /**
     * 从s[index]开始分割
     * @param s      待划分字符串
     * @param index  开始划分的索引
     * @param path   保存当前划分方案
     * @param result 保存所有划分方案
     */
    private void dfs(String s, int index, LinkedList<String> path, List<List<String>> result) {
        if (index == s.length()) {
            result.add(new ArrayList<>(path));
            return;
        }

        // 划分成s[index~i]和s[i+1~len-1]
        for (int i = index; i < s.length(); i++) {
            if (isPalindrome(s, index, i)) {
                path.addLast(s.substring(index, i + 1));
                dfs(s, i + 1, path, result);
                path.removeLast();
            }
        }
    }
}

leetcode132.分割回文串II

题目链接

给你一个字符串s,请你将s分割成一些子串,使每个子串都是回文,返回符合要求的最少分割次数。

本题与131.分割回文串类似,需要求所有划分中最少的分割次数,可以使用动态规划思想解决本题。

dp(s,index)表示将s[0~index]分割成若干回文子串的最小分割次数,则可以使用i枚举0~index之间的分割点,将s[0~index]分割成s[0~i]s[i+1~index]两部分,并保证s[i+1~index]为回文串,然后递归求s[0~i]的最小分割次数,最后返回所有分割点的最小分割次数的最小值加1。在判断回文串时,仍然可以复用前面的isPalindrome函数。

dp函数的核心代码如下:

// 将s[0~index]分割成若干回文子串的最小分割次数
private int dp(String s, int index) {
    // 如果s[0~index]是回文串,则无需分割,直接返回0
    if (isPalindrome(s, 0, index)) {
        return 0;
    }

    // 枚举分割点:s[0~i]、s[i+1~index]
    int res = index;
    for (int i = index - 1; i >= 0; --i) {
        if (isPalindrome(s, i + 1, index)) {
            res = Math.min(res, dp(s, i) + 1);
        }
    }

    return res;
}

leetcode1745.回文串分割IV

题目链接

给你一个字符串s,如果可以将它分割成三个非空回文子字符串,那么返回true,否则返回false

本题与131.分割回文串类似,只是固定了分割次数为3,因此只需使用双重for循环枚举两个分割位置,再用isPalindrome函数判断每段是否为回文串即可,核心代码如下:

public boolean checkPartitioning(String s) {
    // 将s分割成[0, i)、[i, j)和[j, len)三段
    for (int i = 1; i < s.length(); i++) {
        if (!isPalindrome(s, 0, i - 1)) {
            continue;
        }
        for (int j = i + 1; j < s.length(); j++) {
            if (!isPalindrome(s, i, j - 1)) {
                continue;
            }
            if (isPalindrome(s, j, s.length() - 1)) {
                return true;
            }
        }
    }

    return false;
}

leetcode1312.让字符串成为回文串的最少插入次数

题目链接

给你一个字符串s,每一次操作你都可以在字符串的任意位置插入任意字符。

请你返回让s成为回文串的最少操作次数。

本题可用动态规划求解。假设dp(i, j)表示将s[i~j]变成回文串的最小插入次数:

  • i >= j时,显然dp(i, j) = 0
  • 如果s[i] == s[j],则子问题缩小为dp(i + 1, j - 1)
  • 如果s[i] != s[j],有两种方案可以选择:
    • s[i]左边插入s[j],字符串变成s[j], s[i], ..., s[j],子问题缩小为dp(i, j - 1)
    • s[j]右边插入s[i],字符串变成s[i], ..., s[j], s[i],子问题缩小为dp(i + 1, j)

综上所述,dp(i, j)有如下递推关系:

d p ( i , j ) = { 0 , i ≥ j d p ( i + 1 , j − 1 ) , s [ i ] = = s [ j ] 1 + m a x ( d p ( i , j + 1 ) , d p ( i + 1 , j ) ) , e l s e dp(i,j) = \begin{cases} 0, & i \geq j \\ dp(i+1,j-1), & s[i]==s[j] \\ 1+max(dp(i,j+1),dp(i+1,j)), & else \end{cases} dp(i,j)=0,dp(i+1,j1),1+max(dp(i,j+1),dp(i+1,j)),ijs[i]==s[j]else

通过使用记忆化搜索,可实现时间复杂度为 O ( n 2 ) O(n^2) O(n2)的算法,完整代码如下:

class Solution {
    private Integer[][] cache;

    public int minInsertions(String s) {
        cache = new Integer[s.length()][s.length()];
        return dp(s, 0, s.length() - 1);
    }

    // 将s[i~j]变成回文串的最小插入次数
    private int dp(String s, int i, int j) {
        if (i >= j) {
            return 0;
        }

        if (cache[i][j] != null) {
            return cache[i][j];
        }

        if (s.charAt(i) == s.charAt(j)) {
            return cache[i][j] = dp(s, i + 1, j - 1);
        }

        return cache[i][j] = 1 + Math.min(dp(s, i + 1, j), dp(s, i, j - 1));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byx2000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值