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)
可以有两种方式实现:
- 暴力遍历
- 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;
}
}