2982. 找出出现至少三次的最长特殊子字符串 II
给你一个仅由小写英文字母组成的字符串 s 。
如果一个字符串仅由单一字符组成,那么它被称为 特殊 字符串。例如,字符串 “abc” 不是特殊字符串,而字符串 “ddd”、“zz” 和 “f” 是特殊字符串。
返回在 s 中出现 至少三次 的 最长特殊子字符串 的长度,如果不存在出现至少三次的特殊子字符串,则返回 -1 。
子字符串 是字符串中的一个连续 非空 字符序列。
示例 1:
输入:s = “aaaa”
输出:2
解释:出现三次的最长特殊子字符串是 “aa” :子字符串 “aaaa”、“aaaa” 和 “aaaa”。
可以证明最大长度是 2 。
示例 2:
输入:s = “abcdef”
输出:-1
解释:不存在出现至少三次的特殊子字符串。因此返回 -1 。
示例 3:
输入:s = “abcaba”
输出:1
解释:出现三次的最长特殊子字符串是 “a” :子字符串 “abcaba”、“abcaba” 和 “abcaba”。
可以证明最大长度是 1 。
提示:
3 <= s.length <= 5 * 105
s 仅由小写英文字母组成。
- 关键规律在于,当出现连续重复的字符长度 len 大于等于 3 时,那么一定存在出现三次的特殊子串长度为 len - 2。比如 aaaaa,那么一定有长度为 3 的 aaa 出现了三次。我用了 hashmap 记录特殊子串的出现次数,如果出现连续重复字符串,就更新最终结果 res,记录长度大于 res 的特殊串的次数即可(因为我们只求最长子串的长度,出现次数大于等于 3 即可);否则就正常记录每个特殊子串的出现次数即可。以下代码可以优化,我就偷个懒了。
-
public int maximumLength(String s) { Map<String, Integer> map = new HashMap<>(); int n = s.length(); int res = -1; for(int i = 0; i < n; i++){ int j = i + 1; while(j < n && s.charAt(j) == s.charAt(i)){ j++; } // 连续重复子串长度大于等于 3,例如 aaaaa if(j - i >= 3){ // 包含三个 aaa res = Math.max(res, j - i - 2); // 包含一个 aaaaa map.merge(s.substring(i, j), 1, Integer::sum); // 包含两个 aaaa map.merge(s.substring(i, j - 1), 2, (a,b) -> a + 2); i = j - 1; continue; } // 不大于 3 for(int k = i + 1; k <= j; k++){ map.merge(s.substring(i, k), 1, Integer::sum); } } // 出现次数大于等于 3 的都比较一下长度 for(Map.Entry<String, Integer> entry : map.entrySet()){ int count = entry.getValue(); if(count >= 3){ String str = entry.getKey(); res = Math.max(res, str.length()); } } return res; }
- 他人解法:可以先统计每个字符对应的连续字符的长度,例如 aabbaaa,可以得到字母 a 对应的长度有 [2,3],b 对应 [2]。在此基础上我们可以设法不遗漏地得到重复三次的特殊子串。由于我们需要长度最长的,所以我们先倒序排序。比如 a 对应的长度排序后为 [3,2]。
- 从最长的特殊子串 a[0] 中可以得到长度为 a[0] - 2 的三个特殊子串
- 或是从最长的 a[0] 和次长的 a[1] 中获取
- a[0] = a[1]:比如 aaa 和 aaa, 那么我们可以得到四个 aa,即 a[0] - 1 或 a[1] - 1
- a[0] > a[1]:比如 aaaa 和 aaa,那么我们可以从 aaaa 中得到两个 aaa,aaa 中得到一个 aaa,即 a[1] 就是结果
- 综上,我们可以得到 min(a[0]-1, a[1]),这个式子才能满足上述两种情况
- 上面的这个写成
a[0] > a[1] ? a[1] : a[0] - 1
也行,把 a[0] - 1 换成 a[1] - 1 也行,上面的就不能换了,否则 a[0] 和 a[1] 没法比较了
- 或是从最长、次长、第三长的 a[0],a[1],a[2] 中各取一个 a[2],例如 aa,aa,aa 中我们最长只能取到三个 aa
- 以上三种情况取最大值即可
- 由于我们最多需要三个连续子串的长度,所以如果某个字符没有特殊子串我们就跳过,否则预先添加两个长度 0 凑足至少三个长度
-
public int maximumLength(String S) { char[] s = S.toCharArray(); List<Integer>[] groups = new ArrayList[26]; Arrays.setAll(groups, i -> new ArrayList<>()); int count = 0; // 记录连续子串长度 for(int i = 0; i < s.length; i++){ count++; if(i + 1 == s.length || s[i] != s[i + 1]){ groups[s[i] - 'a'].add(count); count = 0; } } int res = 0; for(List<Integer> a : groups){ if (a.isEmpty()) continue; a.sort(Collections.reverseOrder()); a.add(0); a.add(0); res = Math.max( res, Math.max( a.get(0) - 2, Math.max(Math.min(a.get(0) - 1, a.get(1)), a.get(2)) ) ); } return res > 0 ? res : -1; }