You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

357 lines
9.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package class_2023_04_2_week;
// 来自小红书、谷歌、Bloomberg、微软、亚马逊、字节跳动、摩根大通、Uber
// 你会得到一个字符串 text
// 你应该把它分成 k 个子字符串 (subtext1, subtext2 subtextk)
// 要求满足:
// subtexti 是 非空 字符串
// 所有子字符串的连接等于 text
// ( 即subtext1 + subtext2 + ... + subtextk == text )
// subtexti == subtextk - i + 1 表示所有 i 的有效值( 即 1 <= i <= k )
// 返回k可能最大值。
// 测试链接 : https://leetcode.cn/problems/longest-chunked-palindrome-decomposition/
public class Code05_LongestChunkedPalindromeDecomposition {
// 时间复杂度O(N^2)
// 题解上的做法
// 但这不是最优解
// 而且没有帖子写出最优解
public static int longestDecomposition1(String str) {
if (str.length() == 1) {
return 1;
}
char[] s = str.toCharArray();
int n = s.length;
int l = 0;
int r = n - 1;
int ans = 0;
while (l <= r) {
int size = 1;
for (; 2 * size <= r - l + 1; size++) {
if (same1(s, l, r, size)) {
break;
}
}
if (2 * size <= r - l + 1) {
ans += 2;
}
l += size;
r -= size;
}
return r == l - 1 ? ans : (ans + 1);
}
public static boolean same1(char[] s, int l, int r, int size) {
for (int i = l, j = r - size + 1; j <= r; i++, j++) {
if (s[i] != s[j]) {
return false;
}
}
return true;
}
// 时间复杂度O(N * logN)
// 最优解
// 利用DC3算法生成后缀数组、进而生成高度数组来做的
// 需要体系学习班DC3生成后缀数组详解
// 不过Leetcode的数据量太小根本显不出最优解的优异
// 所以我自己写了测试来展示
public static int longestDecomposition2(String str) {
if (str.length() == 1) {
return 1;
}
char[] s = str.toCharArray();
int n = s.length;
DC3 dc3 = generateDC3(s, n);
int[] rank = dc3.rank;
RMQ rmq = new RMQ(dc3.height);
int l = 0;
int r = n - 1;
int ans = 0;
while (l <= r) {
int size = 1;
for (; 2 * size <= r - l + 1; size++) {
if (same2(rank, rmq, l, r, size)) {
break;
}
}
if (2 * size <= r - l + 1) {
ans += 2;
}
l += size;
r -= size;
}
return r == l - 1 ? ans : (ans + 1);
}
public static boolean same2(int[] rank, RMQ rmq, int l, int r, int size) {
int start1 = l;
int start2 = r - size + 1;
int minStart = Math.min(rank[start1], rank[start2]);
int maxStart = Math.max(rank[start1], rank[start2]);
return rmq.min(minStart + 1, maxStart) >= size;
}
public static DC3 generateDC3(char[] s, int n) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (char cha : s) {
min = Math.min(min, cha);
max = Math.max(max, cha);
}
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = s[i] - min + 1;
}
return new DC3(arr, max - min + 1);
}
public static class DC3 {
public int[] sa;
public int[] rank;
public int[] height;
public DC3(int[] nums, int max) {
sa = sa(nums, max);
rank = rank();
height = height(nums);
}
private int[] sa(int[] nums, int max) {
int n = nums.length;
int[] arr = new int[n + 3];
for (int i = 0; i < n; i++) {
arr[i] = nums[i];
}
return skew(arr, n, max);
}
private int[] skew(int[] nums, int n, int K) {
int n0 = (n + 2) / 3, n1 = (n + 1) / 3, n2 = n / 3, n02 = n0 + n2;
int[] s12 = new int[n02 + 3], sa12 = new int[n02 + 3];
for (int i = 0, j = 0; i < n + (n0 - n1); ++i) {
if (0 != i % 3) {
s12[j++] = i;
}
}
radixPass(nums, s12, sa12, 2, n02, K);
radixPass(nums, sa12, s12, 1, n02, K);
radixPass(nums, s12, sa12, 0, n02, K);
int name = 0, c0 = -1, c1 = -1, c2 = -1;
for (int i = 0; i < n02; ++i) {
if (c0 != nums[sa12[i]] || c1 != nums[sa12[i] + 1] || c2 != nums[sa12[i] + 2]) {
name++;
c0 = nums[sa12[i]];
c1 = nums[sa12[i] + 1];
c2 = nums[sa12[i] + 2];
}
if (1 == sa12[i] % 3) {
s12[sa12[i] / 3] = name;
} else {
s12[sa12[i] / 3 + n0] = name;
}
}
if (name < n02) {
sa12 = skew(s12, n02, name);
for (int i = 0; i < n02; i++) {
s12[sa12[i]] = i + 1;
}
} else {
for (int i = 0; i < n02; i++) {
sa12[s12[i] - 1] = i;
}
}
int[] s0 = new int[n0], sa0 = new int[n0];
for (int i = 0, j = 0; i < n02; i++) {
if (sa12[i] < n0) {
s0[j++] = 3 * sa12[i];
}
}
radixPass(nums, s0, sa0, 0, n0, K);
int[] sa = new int[n];
for (int p = 0, t = n0 - n1, k = 0; k < n; k++) {
int i = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2;
int j = sa0[p];
if (sa12[t] < n0 ? leq(nums[i], s12[sa12[t] + n0], nums[j], s12[j / 3])
: leq(nums[i], nums[i + 1], s12[sa12[t] - n0 + 1], nums[j], nums[j + 1], s12[j / 3 + n0])) {
sa[k] = i;
t++;
if (t == n02) {
for (k++; p < n0; p++, k++) {
sa[k] = sa0[p];
}
}
} else {
sa[k] = j;
p++;
if (p == n0) {
for (k++; t < n02; t++, k++) {
sa[k] = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2;
}
}
}
}
return sa;
}
private void radixPass(int[] nums, int[] input, int[] output, int offset, int n, int k) {
int[] cnt = new int[k + 1];
for (int i = 0; i < n; ++i) {
cnt[nums[input[i] + offset]]++;
}
for (int i = 0, sum = 0; i < cnt.length; ++i) {
int t = cnt[i];
cnt[i] = sum;
sum += t;
}
for (int i = 0; i < n; ++i) {
output[cnt[nums[input[i] + offset]]++] = input[i];
}
}
private boolean leq(int a1, int a2, int b1, int b2) {
return a1 < b1 || (a1 == b1 && a2 <= b2);
}
private boolean leq(int a1, int a2, int a3, int b1, int b2, int b3) {
return a1 < b1 || (a1 == b1 && leq(a2, a3, b2, b3));
}
private int[] rank() {
int n = sa.length;
int[] ans = new int[n];
for (int i = 0; i < n; i++) {
ans[sa[i]] = i;
}
return ans;
}
private int[] height(int[] s) {
int n = s.length;
int[] ans = new int[n];
for (int i = 0, k = 0; i < n; ++i) {
if (rank[i] != 0) {
if (k > 0) {
--k;
}
int j = sa[rank[i] - 1];
while (i + k < n && j + k < n && s[i + k] == s[j + k]) {
++k;
}
ans[rank[i]] = k;
}
}
return ans;
}
}
public static class RMQ {
public int[][] min;
public RMQ(int[] arr) {
int n = arr.length;
int k = power2(n);
min = new int[n + 1][k + 1];
for (int i = 1; i <= n; i++) {
min[i][0] = arr[i - 1];
}
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
min[i][j] = Math.min(min[i][j - 1], min[i + (1 << (j - 1))][j - 1]);
}
}
}
public int min(int l, int r) {
l++;
r++;
int k = power2(r - l + 1);
return Math.min(min[l][k], min[r - (1 << k) + 1][k]);
}
private int power2(int m) {
int ans = 0;
while ((1 << ans) <= (m >> 1)) {
ans++;
}
return ans;
}
}
// 为了测试
public static String generateS(int a, int b) {
char[] ans = new char[a + b];
for (int i = 0; i < a; i++) {
ans[i] = 'a';
}
for (int i = a, j = 0; j < b; i++, j++) {
ans[i] = 'b';
}
return String.valueOf(ans);
}
// 为了测试
public static String generateT(String part, int n) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < n; i++) {
builder.append(part);
}
return builder.toString();
}
// 为了测试
public static void main(String[] args) {
System.out.println("先展示一下DC3的用法");
String test = "aaabaaa";
DC3 dc3 = generateDC3(test.toCharArray(), test.length());
System.out.println("sa[i]表示字典序排名第i的是什么位置开头的后缀串");
int[] sa = dc3.sa;
for (int i = 0; i < test.length(); i++) {
System.out.println(i + " : " + sa[i]);
}
System.out.println("rank[i]表示i位置开头的后缀串的字典序排多少名");
int[] rank = dc3.rank;
for (int i = 0; i < test.length(); i++) {
System.out.println(i + " : " + rank[i]);
}
System.out.println("height[i]表示字典序排名i的后缀串和前一个排名的后缀串最长公共前缀是多长");
int[] height = dc3.height;
for (int i = 0; i < test.length(); i++) {
System.out.println(i + " : " + height[i]);
}
System.out.println("性能测试开始");
long start, end;
// 构造一个字符串s
// s = a....一共30万个a....ab
String s = generateS(300000, 1);
// 构造总的字符串t
// t = s + s
// t的总长度是60万长度 -> 6 * 10^5
String t = generateT(s, 2);
// longestDecomposition1跑完是很慢的
// 耐心等吧运行时间在15秒左右
start = System.currentTimeMillis();
System.out.println("方法1的结果 : " + longestDecomposition1(t));
end = System.currentTimeMillis();
System.out.println("方法1的运行时间 : " + (end - start) + " 毫秒");
// longestDecomposition2跑完是很快的
// 而且你构造不出让longestDecomposition2方法慢的例子
// 字符串长度在10^6以内可以随意构造字符串
// longestDecomposition2方法都会很快
start = System.currentTimeMillis();
System.out.println("方法2的结果 : " + longestDecomposition2(t));
end = System.currentTimeMillis();
System.out.println("方法2的运行时间 : " + (end - start) + " 毫秒");
System.out.println("性能测试结束");
}
}