找出出现至少三次的最长特殊子字符串 I

标签: 哈希表 字符串 二分查找 计数 滑动窗口

难度: Medium

给你一个仅由小写英文字母组成的字符串 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 <= 50
  • s 仅由小写英文字母组成。

Submission

运行时间: 34 ms

内存: 16.2 MB

class Solution:
    def maximumLength(self, s: str) -> int:
        d = dict()
        for i in range(26):
            d[i] = list()
        
        n = len(s)
        cnt = 1
        for i in range(1, n):
            if s[i] == s[i-1]:
                cnt += 1
            else:
                d[ord(s[i-1])-ord('a')].append(cnt)
                cnt = 1
        d[ord(s[n-1])-ord('a')].append(cnt)
        ans = 0
        for i in range(26):
            if len(d[i]) == 0 or sum(d[i]) < 3:
                continue
            ans = max(ans, 1)
            d[i].sort()
            ans = max(ans, d[i][-1]-2)
            if len(d[i]) > 1 and d[i][-2] >= d[i][-1]-1:
                ans = max(ans, d[i][-1]-1)
            if len(d[i]) > 2 and d[i][-3] == d[i][-1]:
                ans = max(ans, d[i][-1])
                
        if ans == 0:
            return -1
        return ans
                

Explain

本题解采用哈希表记录每个字符的连续长度,从而确定是否存在符合条件的特殊子字符串。首先,初始化一个大小为26的哈希表(字典),对应于每个英文字母,用来存储该字符连续出现的长度。遍历字符串s,使用一个计数器来记录当前字符连续出现的次数。当遇到不同的字符时,将当前计数值存入相应字符的列表中,并重置计数器。遍历完成后,检查每个字符的列表,分析是否存在长度至少为3的子字符串。通过计算特定规则(如单个字符长度减2,最长两个字符长度比较等),更新可能的最长特殊子字符串长度。最后,如果没有找到符合条件的特殊子字符串,返回-1,否则返回记录的最大长度。

时间复杂度: O(n)

空间复杂度: O(n)

class Solution:
    def maximumLength(self, s: str) -> int:
        d = dict()
        for i in range(26):  # 初始化存储每个字母的长度的字典
            d[i] = list()

        n = len(s)
        cnt = 1  # 初始化计数器
        for i in range(1, n):
            if s[i] == s[i-1]:
                cnt += 1  # 相同字母计数增加
            else:
                d[ord(s[i-1])-ord('a')].append(cnt)  # 不同字母时,保存当前字母的长度
                cnt = 1  # 重置计数器
        d[ord(s[n-1])-ord('a')].append(cnt)  # 处理字符串末尾的连续字母

        ans = 0  # 初始化答案
        for i in range(26):
            if len(d[i]) == 0 or sum(d[i]) < 3:  # 如果该字母出现的总次数小于3,忽略
                continue
            ans = max(ans, 1)  # 至少长度为1
            d[i].sort()  # 排序以便于处理
            ans = max(ans, d[i][-1]-2)  # 尝试当前长度减2的情况
            if len(d[i]) > 1 and d[i][-2] >= d[i][-1]-1:
                ans = max(ans, d[i][-1]-1)  # 检查前两个最长的连续长度
            if len(d[i]) > 2 and d[i][-3] == d[i][-1]:
                ans = max(ans, d[i][-1])  # 检查前三个长度是否相等

        if ans == 0:  # 如果没有找到符合条件的长度
            return -1  # 返回-1
        return ans  # 返回找到的最长特殊子字符串的长度

Explore

使用哈希表和为每个字母分别创建列表的方法能够有效地跟踪和存储每个字符在字符串中出现的所有连续长度。这种数据结构允许我们在完成字符串的单次遍历后,能够快速访问任何字符的所有连续出现记录,从而便于后续分析这些长度是否满足题目的特殊条件。哈希表的键值对应于字母,值为该字母出现的所有连续长度的列表,使得数据的插入和查询操作都非常高效。

算法在循环外处理最后一个字符是为了确保连续字符的计数被正确保存。在字符串遍历的最后,无论最后一个字符是否与前一个字符相同,其连续计数都需要被记录。如果不在循环外额外处理,那么字符串的最后一个连续字符序列将无法被记录,从而导致数据丢失。因此,这种处理方式是必要且正确的,能够确保所有字符连续性的完整记录。

选择长度减2或减1的策略是为了确保找到的子字符串真正满足特殊性条件(即至少出现三次)。长度减2的情况考虑的是去掉一个字符后,剩余的子字符串是否仍有可能满足出现至少三次的条件。当一个字符连续出现的次数最少为3次时,去掉最前面和最后面的一个字符,中间的子字符串仍然可能满足条件。长度减1的策略则用于处理当第二长的连续长度足够接近最长长度时的情况。这些策略确保了算法在考虑减少字符的情况下仍能找到满足条件的最大长度,提高了方法的通用性和准确性。

如果连续字符的长度列表中的最长三个长度相等,表明这个长度的子字符串至少出现了三次,且这三次出现是完全独立的。在这种情况下,即使考虑到可能的边界影响,三个相同的连续长度已经充分证明了这个长度的子字符串的重复性和稳定性,因此不需要进一步减少长度来满足至少出现三次的条件。直接使用这个长度能够确保不遗漏可能的最长特殊子字符串。