从零学算法2982

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;
      }
    
  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值