字符串回文问题

364 阅读2分钟

「这是我参与2022首次更文挑战的第22天,活动详情查看:2022首次更文挑战」。

问题一:一个字符串至少需要添加多少个字符能整体变成回文串

问题二:返回问题一的其中一种添加结果

一、分析

范围尝试模型,原始字符串是固定长度,所以从L...R这段要变成回文串,至少需要添加字符

f(str,L,R):str[L...R]上,添加的字符数

L<=R 有效,L> R 无效

对角线都是0,一个字符(L == R),L = R - 1时,两个字符(倒数第二条斜线),需要添加1个,比如ab,不等添加一个(aba 或 bab),aa相等不需要添加。

dp[i][j]

  • dp[i][j-1],最后j,在开始位置添加一个,dp[i][j-1] + 1
  • dp[i+1][j],开始i,在结尾位置添加一个,dp[i+1][j] + 1
  • dp[i+1][j-1],i == j

a b c b b

0 1 2 3 4

01234
001212
1×0101
2××011
3×××00
4××××0

L≠R时,当前格子 = min(左边格子,下边的格子) + 1

L=R时,当前格子 = 左下格子

剩下的格子,从下到上,从左到右,分三种情况依次填好

dp[0][4]位置上的值就是需要添加几个字符

二、实现

// 问题一
public static int minInsertions(String s) {
    if (s == null || s.length() < 2) {
        return 0;
    }
    char[] str = s.toCharArray();
    int N = str.length;
    int[][] dp = new int[N][N];
    for (int i = 0; i < N - 1; i++) {
        dp[i][i + 1] = str[i] == str[i + 1] ? 0 : 1;
    }
    for (int i = N - 3; i >= 0; i--) {
        for (int j = i + 2; j < N; j++) {
            // 可能性一、可能性二决策
            dp[i][j] = Math.min(dp[i][j - 1], dp[i + 1][j]) + 1;
            if (str[i] == str[j]) { // 可能性三
                dp[i][j] = Math.min(dp[i][j], dp[i + 1][j - 1]);
            }
        }
    }
    return dp[0][N - 1];
}

// 问题二,返回其中一种结果
public static String minInsertionsOneWay(String s) {
    if (s == null || s.length() < 2) {
        return s;
    }
    char[] str = s.toCharArray();
    int N = str.length;
    int[][] dp = new int[N][N];
    for (int i = 0; i < N - 1; i++) {
        dp[i][i + 1] = str[i] == str[i + 1] ? 0 : 1;
    }
    for (int i = N - 3; i >= 0; i--) {
        for (int j = i + 2; j < N; j++) {
            dp[i][j] = Math.min(dp[i][j - 1], dp[i + 1][j]) + 1;
            if (str[i] == str[j]) {
                dp[i][j] = Math.min(dp[i][j], dp[i + 1][j - 1]);
            }
        }
    }

    int L = 0;
    int R = N - 1;
    char[] ans = new char[N + dp[L][R]];
    int ansl = 0;
    int ansr = ans.length - 1;
    while (L < R) {
        if (dp[L][R - 1] == dp[L][R] - 1) {
            ans[ansl++] = str[R];
            ans[ansr--] = str[R--];
        } else if (dp[L + 1][R] == dp[L][R] - 1) {
            ans[ansl++] = str[L];
            ans[ansr--] = str[L++];
        } else {
            ans[ansl++] = str[L++];
            ans[ansr--] = str[R--];
        }
    }
    if (L == R) {
        ans[ansl] = str[L];
    }
    return String.valueOf(ans);
}

三、总结

动态规划、范围尝试模型