[leetcode 周赛 148] 1147 段式回文

1147 Longest Chunked Palindrome Decomposition 段式回文

描述

段式回文 其实与 一般回文 类似,只不过是最小的单位是 一段字符 而不是 单个字母
举个例子,对于一般回文 "abcba" 是回文,而 "volvo" 不是,但如果我们把 "volvo" 分为 "vo"、"l"、"vo" 三段,则可以认为 “(vo)(l)(vo)” 是段式回文(分为 3 段)。
给你一个字符串 text,在确保它满足段式回文的前提下,请你返回 段 的 最大数量 k。
如果段的最大数量为 k,那么存在满足以下条件的 a_1, a_2, ..., a_k

每个 a_i 都是一个非空字符串;

将这些字符串首位相连的结果 a_1 + a_2 + ... + a_k 和原始字符串 text 相同;
对于所有1 <= i <= k,都有 a_i = a_{k+1 - i}

  • 示例1

    输入:text = "ghiabcdefhelloadamhelloabcdefghi"
    输出:7
    解释:我们可以把字符串拆分成 "(ghi)(abcdef)(hello)(adam)(hello)(abcdef)(ghi)"。

  • 示例2

    输入:text = "merchant"
    输出:1
    解释:我们可以把字符串拆分成 "(merchant)"。

  • 示例3

    输入:text = "antaprezatepzapreanta"
    输出:11
    解释:我们可以把字符串拆分成 "(a)(nt)(a)(pre)(za)(tpe)(za)(pre)(a)(nt)(a)"。

  • 示例4

    输入:text = "aaa"
    输出:3
    解释:我们可以把字符串拆分成 "(a)(a)(a)"。

  • 提示

    text 仅由小写英文字符组成。
    1 <= text.length <= 1000

思路

  • 两边字段定位 [i, j) --> [n-j-1, n-i-1)

可以有两种方式实现:

  1. 暴力遍历
  2. String.substring(beginIndex, endIndex) 注意字段范围[beginIndex, endIndex)
  • 进入下一轮判断
    有两种方式:递归/迭代

递归: (0, n-1) --> (0, s)==(e,n-1) (s,e) -->

开始时, 起始为0, 终止为n-1
遍历0~n-1, 找到(0, s)==(e,n-1)
此时设起始为s, 终止为e 开始下一轮

迭代: i -> n/2 --> (j, i) -->

开始遍历 1~n/2 枚举为终止位置i
选取j=0 作为开始位置
找到(j, i)==(n-i-1, n-j-1)
设j=i 作为开始位置 开始下一循环

代码实现

贪心算法

每次两端有相同的字段就将其分词, 并投入到下一轮递归中, 直至字符串为空或者无法进行分词为止

class Solution {
    public int longestDecomposition(String text) {
        if (null == text) return -1;
        
        int n = text.length();
        // 将字符串分成 [0,i) ~ [n-i, n)
        // 如果上面相等 表示有2份回文字段
        // 并将剩余字段投入下一轮迭代 [i, n-i)
        // i 其实表示的是比较字段的长度
        for (int i = 1; i <= n/2; i++) {
            if (text.substring(0, i).equals(text.substring(n-i, n))) {
                return 2 + longestDecomposition(text.substring(i, n-i));
            }
        }
        
        // 当最终剩余字段大于零 则其单独成一段 +1
        return n == 0 ? 0 : 1;
    }
}

动态规划(使用二维数组)

class Solution {
    int[][] dp;
    String text;
    
    public int longestDecomposition(String text) {
        int n = text.length();
        dp = new int[n][n];
        
        // 初始化为-1 用以表示该位置是否被处理
        for (int i = 0; i < n; i++) Arrays.fill(dp[i], -1);
        this.text = text;
        
        // 求整个字符串的回文字段k
        return helper(0, n-1);
    }
    
    /**
     * 求回文字段数 text的子串
     * @param s 起始位置
     * @param e 终止位置
     * @return 该字段的回文字段数
     **/
    int helper(int s, int e) {
        // 非法字段 起始位置大于终止位置
        if (s > e) return 0;
        // 特殊情况 刚好只剩下一个字符
        if (s == e) return dp[s][e] = 1;
        // 不为-1 则表示以及处理过 直接拿来用就可以
        if (dp[s][e] != -1) return dp[s][e];
        
        // 字符串本身算一回文字段
        int res = 1;
        // 枚举长度 1 ~ (e-s+1)/2 从s开始的子串
        for (int l = 1; l <= (e-s+1)/2; l++) {
            String st = text.substring(s, s+l);
            String ed = text.substring(e-l+1, e+1);
            // 如果两子串相等 表示找到回文字段 +2
            if (st.equals(ed)) {
                int tmp = helper(s+l, e-l);
                // 更新结果值
                res = tmp+2;
            }
        }
        
        return dp[s][e] = res;
    }
}

动态规划(使用一维数组)

class Solution {
    // 存储表示0~i 出现的回文字段
    int dp[] = new int[1050];
    
    public int longestDecomposition(String text) {
        int n = text.length(), ans = 1;
        char[] chs = text.toCharArray();
        
        // dp数组初始化
        Arrays.fill(dp, -1);
        // 肯定是有从0开始的回文字段
        dp[0] = 0;
        
        // 表示最新的子串开始位置
        int left = 0;
        // i 表示终止位置 (j, i)
        for (int i = 1; i <= n/2; i++) {
            for (int j = left; j < i; j++) {
                // 可不可以作为起始点
                if (dp[j] == -1) continue;
                // [j, i-1]是否可以作为回文字段
                if (!check(chs, j, i, n)) continue;
                // 如果可以作为回文字段
                // [0,i) 有多少回文字段
                dp[i] = dp[j]+1;
                // 更新最新子串开始位置
                left = i;
            }
        }

        // 如果最终处理字段的位置 并没有遍历完字符串(n/2) 表示还有剩余1字段可以作为回文字段
        return Math.max(dp[left]*2 + (left*2<n?1:0), ans);
    }
    
    /**
     * 检查[j,i-1]段前后是否相等
     * 暴力遍历
     **/
    boolean check(char[] chs, int j, int i, int n) {
        // j ~ i-1 --> n-i ~ n-j-1
        for (int ii = j; ii < i; ii++) {
            if (chs[ii] != chs[n-i-j+ii]) return false;
        }
        return true;
    }
}

猜你喜欢

转载自www.cnblogs.com/slowbirdoflsh/p/11312365.html