package class41; import java.util.Arrays; import java.util.HashMap; // 来自小红书 // 有四种诗的韵律分别为: AABB、ABAB、ABBA、AAAA // 比如 : 1 1 3 3就属于AABB型的韵律、6 6 6 6就属于AAAA型的韵律等等 // 一个数组arr,当然可以生成很多的子序列,如果某个子序列一直以韵律的方式连接起来,我们称这样的子序列是有效的 // 比如, arr = { 1, 1, 15, 1, 34, 1, 2, 67, 3, 3, 2, 4, 15, 3, 17, 4, 3, 7, 52, 7, 81, 9, 9 } // arr的一个子序列为{1, 1, 1, 1, 2, 3, 3, 2, 4, 3, 4, 3, 7, 7, 9, 9} // 其中1, 1, 1, 1是AAAA、2, 3, 3, 2是ABBA、4, 3, 4, 3是ABAB、7, 7, 9, 9是AABB // 可以看到,整个子序列一直以韵律的方式连接起来,所以这个子序列是有效的 // 给定一个数组arr, 返回最长的有效子序列长度 // 题目限制 : arr长度 <= 4000, arr中的值<= 10^9 // 离散化之后,arr长度 <= 4000, arr中的值<= 4000 public class Code02_PoemProblem { // arr[i.....]符合规则连接的最长子序列长度 // public static int zuo(int[] arr, int i) { // if (i + 4 > arr.length) { // return 0; // } // // 最终的,符合规则连接的最长子序列长度,就是不要i位置的字符 // int p0 = zuo(arr, i + 1); // // p1使用for循环搞定的! // int p1 = 找到arr[i..s]是最短的,且能搞出AABB来的(4个) + zuo(arr, s + 1); // // p2使用for循环搞定的! // int p2 = 找到arr[i..t]是最短的,且能搞出ABAB来的(4个) + zuo(arr, t + 1); // // p3使用for循环搞定的! // int p3 = 找到arr[i..k]是最短的,且能搞出ABBA来的(4个) + zuo(arr, k + 1); // // p4没用 // int p4 = 找到arr[i..f]是最短的,且能搞出AAAA来的(4个) + zuo(arr, f + 1); // return p0~p4的最大值 // } // AABB // ABAB // ABBA // AAAA public static int maxLen1(int[] arr) { if (arr == null || arr.length < 4) { return 0; } int[] path = new int[arr.length]; return process1(arr, 0, path, 0); } public static int process1(int[] arr, int index, int[] path, int size) { if (index == arr.length) { if (size % 4 != 0) { return 0; } else { for (int i = 0; i < size; i += 4) { if (!valid(path, i)) { return 0; } } return size; } } else { int p1 = process1(arr, index + 1, path, size); path[size] = arr[index]; int p2 = process1(arr, index + 1, path, size + 1); return Math.max(p1, p2); } } public static boolean valid(int[] p, int i) { // AABB // ABAB // ABBA // AAAA return (p[i] == p[i + 1] && p[i + 2] == p[i + 3]) || (p[i] == p[i + 2] && p[i + 1] == p[i + 3] && p[i] != p[i + 1]) || (p[i] == p[i + 3] && p[i + 1] == p[i + 2] && p[i] != p[i + 1]); } // 0 : [3,6,9] // 1 : [2,7,13] // 2 : [23] // [ // [3,6,9] // ] public static int maxLen2(int[] arr) { if (arr == null || arr.length < 4) { return 0; } int n = arr.length; int[] sorted = Arrays.copyOf(arr, n); Arrays.sort(sorted); HashMap vmap = new HashMap<>(); int index = 0; vmap.put(sorted[0], index++); for (int i = 1; i < n; i++) { if (sorted[i] != sorted[i - 1]) { vmap.put(sorted[i], index++); } } int[] sizeArr = new int[index]; for (int i = 0; i < n; i++) { arr[i] = vmap.get(arr[i]); sizeArr[arr[i]]++; } int[][] imap = new int[index][]; for (int i = 0; i < index; i++) { imap[i] = new int[sizeArr[i]]; } for (int i = n - 1; i >= 0; i--) { imap[arr[i]][--sizeArr[arr[i]]] = i; } return process2(arr, imap, 0); } // AABB // ABAB // ABBA // AAAA public static int process2(int[] varr, int[][] imap, int i) { if (i + 4 > varr.length) { return 0; } int p0 = process2(varr, imap, i + 1); // AABB int p1 = 0; int rightClosedP1A2 = rightClosed(imap, varr[i], i); if (rightClosedP1A2 != -1) { for (int next = rightClosedP1A2 + 1; next < varr.length; next++) { if (varr[i] != varr[next]) { int rightClosedP1B2 = rightClosed(imap, varr[next], next); if (rightClosedP1B2 != -1) { p1 = Math.max(p1, 4 + process2(varr, imap, rightClosedP1B2 + 1)); } } } } // ABAB int p2 = 0; for (int p2B1 = i + 1; p2B1 < varr.length; p2B1++) { if (varr[i] != varr[p2B1]) { int rightClosedP2A2 = rightClosed(imap, varr[i], p2B1); if (rightClosedP2A2 != -1) { int rightClosedP2B2 = rightClosed(imap, varr[p2B1], rightClosedP2A2); if (rightClosedP2B2 != -1) { p2 = Math.max(p2, 4 + process2(varr, imap, rightClosedP2B2 + 1)); } } } } // ABBA int p3 = 0; for (int p3B1 = i + 1; p3B1 < varr.length; p3B1++) { if (varr[i] != varr[p3B1]) { int rightClosedP3B2 = rightClosed(imap, varr[p3B1], p3B1); if (rightClosedP3B2 != -1) { int rightClosedP3A2 = rightClosed(imap, varr[i], rightClosedP3B2); if (rightClosedP3A2 != -1) { p3 = Math.max(p3, 4 + process2(varr, imap, rightClosedP3A2 + 1)); } } } } // AAAA int p4 = 0; int rightClosedP4A2 = rightClosed(imap, varr[i], i); int rightClosedP4A3 = rightClosedP4A2 == -1 ? -1 : rightClosed(imap, varr[i], rightClosedP4A2); int rightClosedP4A4 = rightClosedP4A3 == -1 ? -1 : rightClosed(imap, varr[i], rightClosedP4A3); if (rightClosedP4A4 != -1) { p4 = Math.max(p4, 4 + process2(varr, imap, rightClosedP4A4 + 1)); } return Math.max(p0, Math.max(Math.max(p1, p2), Math.max(p3, p4))); } public static int rightClosed(int[][] imap, int v, int i) { int left = 0; int right = imap[v].length - 1; int ans = -1; while (left <= right) { int mid = (left + right) / 2; if (imap[v][mid] <= i) { left = mid + 1; } else { ans = mid; right = mid - 1; } } return ans == -1 ? -1 : imap[v][ans]; } public static int maxLen3(int[] arr) { if (arr == null || arr.length < 4) { return 0; } int n = arr.length; int[] sorted = Arrays.copyOf(arr, n); Arrays.sort(sorted); HashMap vmap = new HashMap<>(); int index = 0; vmap.put(sorted[0], index++); for (int i = 1; i < n; i++) { if (sorted[i] != sorted[i - 1]) { vmap.put(sorted[i], index++); } } int[] sizeArr = new int[index]; for (int i = 0; i < n; i++) { arr[i] = vmap.get(arr[i]); sizeArr[arr[i]]++; } int[][] imap = new int[index][]; for (int i = 0; i < index; i++) { imap[i] = new int[sizeArr[i]]; } for (int i = n - 1; i >= 0; i--) { imap[arr[i]][--sizeArr[arr[i]]] = i; } int[] dp = new int[n + 1]; for (int i = n - 4; i >= 0; i--) { int p0 = dp[i + 1]; // AABB int p1 = 0; int rightClosedP1A2 = rightClosed(imap, arr[i], i); if (rightClosedP1A2 != -1) { for (int next = rightClosedP1A2 + 1; next < arr.length; next++) { if (arr[i] != arr[next]) { int rightClosedP1B2 = rightClosed(imap, arr[next], next); if (rightClosedP1B2 != -1) { p1 = Math.max(p1, 4 + dp[rightClosedP1B2 + 1]); } } } } // ABAB int p2 = 0; for (int p2B1 = i + 1; p2B1 < arr.length; p2B1++) { if (arr[i] != arr[p2B1]) { int rightClosedP2A2 = rightClosed(imap, arr[i], p2B1); if (rightClosedP2A2 != -1) { int rightClosedP2B2 = rightClosed(imap, arr[p2B1], rightClosedP2A2); if (rightClosedP2B2 != -1) { p2 = Math.max(p2, 4 + dp[rightClosedP2B2 + 1]); } } } } // ABBA int p3 = 0; for (int p3B1 = i + 1; p3B1 < arr.length; p3B1++) { if (arr[i] != arr[p3B1]) { int rightClosedP3B2 = rightClosed(imap, arr[p3B1], p3B1); if (rightClosedP3B2 != -1) { int rightClosedP3A2 = rightClosed(imap, arr[i], rightClosedP3B2); if (rightClosedP3A2 != -1) { p3 = Math.max(p3, 4 + dp[rightClosedP3A2 + 1]); } } } } // AAAA int p4 = 0; int rightClosedP4A2 = rightClosed(imap, arr[i], i); int rightClosedP4A3 = rightClosedP4A2 == -1 ? -1 : rightClosed(imap, arr[i], rightClosedP4A2); int rightClosedP4A4 = rightClosedP4A3 == -1 ? -1 : rightClosed(imap, arr[i], rightClosedP4A3); if (rightClosedP4A4 != -1) { p4 = Math.max(p4, 4 + dp[rightClosedP4A4 + 1]); } dp[i] = Math.max(p0, Math.max(Math.max(p1, p2), Math.max(p3, p4))); } return dp[0]; } // 课堂有同学提出了贪心策略(这题还真是有贪心策略),是正确的 // AABB // ABAB // ABBA // AAAA // 先看前三个规则:AABB、ABAB、ABBA // 首先A、A、B、B的全排列为: // AABB -> AABB // ABAB -> ABAB // ABBA -> ABBA // BBAA -> 等同于AABB,因为A和B谁在前、谁在后都算是 : AABB的范式 // BABA -> 等同于ABAB,因为A和B谁在前、谁在后都算是 : ABAB的范式 // BAAB -> 等同于ABBA,因为A和B谁在前、谁在后都算是 : ABBA的范式 // 也就是说,AABB、ABAB、ABBA这三个规则,可以这么用: // 只要有两个不同的数,都出现2次,那么这一共4个数就一定符合韵律规则。 // 所以: // 1) 当来到arr中的一个数字num的时候, // 如果num已经出现了2次了, 只要之前还有一个和num不同的数, // 也出现了两次,则一定符合了某个规则, 长度直接+4,然后清空所有的统计 // 2) 当来到arr中的一个数字num的时候, // 如果num已经出现了4次了(规则四), 长度直接+4,然后清空所有的统计 // 但是如果我去掉某个规则,该贪心直接报废,比如韵律规则变成: // AABB、ABAB、AAAA // 因为少了ABBA, 所以上面的化简不成立了, 得重新分析新规则下的贪心策略 // 而尝试的方法就更通用(也就是maxLen3),只是减少一个分支而已 // 这个贪心费了很多心思,值得点赞! public static int maxLen4(int[] arr) { // 统计某个数(key),出现的次数(value) HashMap map = new HashMap<>(); // tow代表目前有多少数出现了2次 int two = 0; // ans代表目前符合韵律链接的子序列增长到了多长 int ans = 0; // 当前的num出现了几次 int numTimes = 0; for (int num : arr) { // 对当前的num,做次数统计 map.put(num, map.getOrDefault(num, 0) + 1); // 把num出现的次数拿出来 numTimes = map.get(num); // 如果num刚刚出现了2次, 那么目前出现了2次的数,的数量,需要增加1个 two += numTimes == 2 ? 1 : 0; // 下面的if代表 : // 如果目前有2个数出现2次了,可以连接了 // 如果目前有1个数出现4次了,可以连接了 if (two == 2 || numTimes == 4) { ans += 4; map.clear(); two = 0; } } return ans; } // 为了测试 public static int[] randomArray(int len, int value) { int[] arr = new int[len]; for (int i = 0; i < len; i++) { arr[i] = (int) (Math.random() * value); } return arr; } // 为了测试 public static void main(String[] args) { // 1111 2332 4343 7799 int[] test = { 1, 1, 15, 1, 34, 1, 2, 67, 3, 3, 2, 4, 15, 3, 17, 4, 3, 7, 52, 7, 81, 9, 9 }; System.out.println(maxLen1(test)); System.out.println(maxLen2(test)); System.out.println(maxLen3(test)); System.out.println(maxLen4(test)); System.out.println("==========="); int len = 16; int value = 10; int[] arr = randomArray(len, value); int[] arr1 = Arrays.copyOf(arr, arr.length); int[] arr2 = Arrays.copyOf(arr, arr.length); int[] arr3 = Arrays.copyOf(arr, arr.length); int[] arr4 = Arrays.copyOf(arr, arr.length); System.out.println(maxLen1(arr1)); System.out.println(maxLen2(arr2)); System.out.println(maxLen3(arr3)); System.out.println(maxLen4(arr4)); System.out.println("==========="); long start; long end; int[] longArr = randomArray(4000, 20); start = System.currentTimeMillis(); System.out.println(maxLen3(longArr)); end = System.currentTimeMillis(); System.out.println("运行时间(毫秒) : " + (end - start)); System.out.println("==========="); start = System.currentTimeMillis(); System.out.println(maxLen4(longArr)); end = System.currentTimeMillis(); System.out.println("运行时间(毫秒) : " + (end - start)); System.out.println("==========="); } }