📌  相关文章
📜  所有字符至少出现K次的最大子字符串(1)

📅  最后修改于: 2023-12-03 15:25:44.456000             🧑  作者: Mango

所有字符至少出现K次的最大子字符串

给定一个字符串s和一个整数k,找到s中的最长子串,使得该子串中的每个字符至少出现k次。

算法思路
暴力算法
  • 枚举所有子串,找出每个子串中每个字符的出现次数
  • 判断是否满足每个字符至少出现k次的条件
  • 记录满足条件的子串的最大长度

时间复杂度

假设字符串s的长度为n,暴力算法时间复杂度为O(n^3),由于需要统计每个字符的出现次数,又需要循环遍历所有子串,因此最坏情况下,时间复杂度为O(n^2 * c),其中c代表字符集的大小。

分治算法

分治算法的思路与暴力算法有点类似,但是通过分治算法的优化,可以优化时间复杂度,达到O(n*logn)。

  • 统计s中每个字符的出现次数,得到一个计数器count
  • 遍历字符串s,找到其中第一个出现次数小于k的字符c
  • 递归判断在子串中去除字符c后,最长符合条件的子串
  • 取所有子串中的最长值作为结果返回

时间复杂度

假设字符串s的长度为n,分治算法时间复杂度为O(n*logn),其中统计计数器count的时间复杂度为O(n),递归过程的时间复杂度为O(logn)。

滑动窗口算法

滑动窗口算法通过维护一个窗口,并在窗口内部移动,达到快速找到符合条件的最长子串的目的。

  • 统计s中每个字符的出现次数,得到一个计数器count
  • 初始化窗口的左右下标l和r均为0,统计窗口中每个字符的出现次数nowCount
  • 若nowCount中的每个字符出现次数都大于等于k,则判断当前窗口长度是否大于当前最长符合条件的子串长度maxLen,是则更新maxLen
  • 若nowCount中存在字符出现次数小于k,则移动左下标l,同时需要减少nowCount中对应字符的计数
  • 若l移动后nowCount中对应字符的计数为0,则需要减少计数器count中的对应字符的计数,并移动l

时间复杂度

假设字符串s的长度为n,滑动窗口算法时间复杂度为O(26*n),由于常数项比较小,因此滑动窗口算法是最优的算法之一。

代码实现
暴力算法
def is_legal(substr, k):
    count = {}
    for c in substr:
        count[c] = count.get(c, 0) + 1
    for c in count:
        if count[c] < k:
            return False
    return True

def max_substring(s, k):
    n = len(s)
    max_len = 0
    for i in range(n):
        for j in range(i+1, n+1):
            if is_legal(s[i:j], k):
                max_len = max(max_len, j-i)
    return max_len
分治算法
def max_substring(s, k):
    count = {}
    for c in s:
        count[c] = count.get(c, 0) + 1

    left, right = 0, len(s)
    for i in range(len(s)):
        if count[s[i]] < k:
            leftMax = max_substring(s[left:i], k) if (i - left) >= k else 0
            rightMax = max_substring(s[i+1:right], k) if (right - i - 1) >= k else 0
            return max(leftMax, rightMax)
    return right - left
滑动窗口算法
def max_substring(s, k):
    count = {}
    for c in s:
        count[c] = count.get(c, 0) + 1

    max_len = 0
    left, right = 0, 0
    n = len(s)
    while right < n:
        if count[s[right]] >= k:
            right += 1
        else:
            max_len = max(max_len, right-left)
            count[s[left]] -= 1
            if count[s[left]] == 0:
                del count[s[left]]
            left += 1
    
    return max(max_len, right-left)
总结

本文介绍了三种求解最长符合条件的子串的算法,分别为暴力算法、分治算法和滑动窗口算法。这三种算法的时间复杂度从高到低分别为O(n^3)、O(n*logn)、O(n),因此在算法的选择上,应该优先选择滑动窗口算法。