找到最长的半重复子字符串

标签: 字符串 滑动窗口

难度: Medium

给你一个下标从 0 开始的字符串 s ,这个字符串只包含 0 到 9 的数字字符。

如果一个字符串 t 中至多有一对相邻字符是相等的,那么称这个字符串 t半重复的 。例如,00100020200123200254944 是半重复字符串,而 001010221101234883 不是。

请你返回 s 中最长 半重复 子字符串的长度。

一个 子字符串 是一个字符串中一段连续 非空 的字符。

示例 1:

输入:s = "52233"
输出:4
解释:最长半重复子字符串是 "5223" ,子字符串从 i = 0 开始,在 j = 3 处结束。

示例 2:

输入:s = "5494"
输出:4
解释:s 就是一个半重复字符串,所以答案为 4 。

示例 3:

输入:s = "1111111"
输出:2
解释:最长半重复子字符串是 "11" ,子字符串从 i = 0 开始,在 j = 1 处结束。

提示:

  • 1 <= s.length <= 50
  • '0' <= s[i] <= '9'

Submission

运行时间: 44 ms

内存: 16.1 MB

class Solution:
    def longestSemiRepetitiveSubstring(self, s: str) -> int:
        left=same=ans=pre=0
        right=1
        n=len(s)
        while right<n:
            if s[right]==s[right-1]:
                same+=1
                if same==1:
                   pre=right
            if same==2:
              ans=max(ans,right-left)
              left=pre
              pre=right
              same-=1
            right+=1
        ans=max(right-left,ans)
        return ans

Explain

题解采用了双指针(滑动窗口)的方法来找到最长的半重复子字符串。这里使用了两个指针left和right分别表示当前考虑的子字符串的开始和结束位置。变量same用于记录当前窗口内相邻字符相等的对数,pre用于记录最后一对相等字符的位置。遍历字符串s,当发现相邻字符相等时,same增加。如果same等于2(即存在两对相等的相邻字符),则计算当前的子字符串长度并尝试更新最大长度ans。然后调整left到上一对相等字符之后的位置,重新计算窗口。遍历结束后,需要比较一次以确保最后的窗口也被考虑进最大长度。

时间复杂度: O(n)

空间复杂度: O(1)

class Solution:
    def longestSemiRepetitiveSubstring(self, s: str) -> int:
        left = same = ans = pre = 0  # 初始化指针和计数器
        right = 1  # 初始化右指针
        n = len(s)  # 字符串长度
        while right < n:  # 遍历字符串
            if s[right] == s[right-1]:  # 发现相邻字符相等
                same += 1  # 更新计数
                if same == 1:  # 第一次遇到相等字符
                   pre = right  # 记录位置
            if same == 2:  # 第二次遇到相等字符
              ans = max(ans, right - left)  # 更新最长长度
              left = pre  # 调整左指针
              pre = right  # 更新pre
              same -= 1  # 更新same
            right += 1  # 右指针右移
        ans = max(right - left, ans)  # 检查最后一个窗口
        return ans  # 返回最长长度

Explore

在这个算法中,当左指针left移动到前一对相等字符之后的位置时,我们减少了same计数,因为我们排除了一个已经计数过的相等对。这样做是为了保持same计数的准确性,反映当前窗口内相邻字符相等的对数。通过这种方式,我们确保在移动左指针后,窗口内的状态(same计数)仍然正确,反映当前考虑的子字符串的实际情况。

选择将左指针移动到上一对相等字符之后的位置是为了尽可能保持窗口的大小,同时确保窗口内不超过一对相等字符。这样做可以使窗口在满足半重复条件(即至多包含一对相邻相等字符)的前提下,尽可能大,从而有可能获得更长的子字符串长度。如果选择其他位置,可能会不必要地减小窗口大小,从而错过更优的解。

在算法结束时需要再次使用max函数比较right - left和ans,是因为最后一个考虑的窗口可能是最长的有效窗口,但未在循环中更新ans。特别是如果字符串的结尾部分符合条件但未因再次遇到相等对而更新,这一步就确保这最后的窗口长度也被考虑进最大长度的计算中。

如果字符串s中没有相邻的相等字符,same计数将始终为0。在这种情况下,算法将简单地遍历整个字符串,因为没有需要调整left指针的情况发生。最终,right将移动到字符串的末尾,ans通过最后一次比较可以得到整个字符串的长度,这反映了最长的半重复子字符串就是整个字符串本身。因此,没有必要特别处理这种情况,算法已经能够正确处理并给出正确答案。