返回

长串珍藏:探索最长重复子串算法之美

后端

最长重复子串算法:Go语言实现

func findLongestSubstringWithKRepeat(s string, k int) int {
    // 滑动窗口的左右边界
    left, right := 0, 0
    // 哈希表存储字符及其出现次数
    charFreq := make(map[byte]int)
    // 满足条件的子串长度
    maxLen := 0

    for right < len(s) {
        // 将当前字符加入哈希表
        charFreq[s[right]]++
        // 统计满足条件的字符数量
        cnt := 0
        for _, freq := range charFreq {
            if freq == k {
                cnt++
            }
        }
        // 满足条件则更新最长子串长度
        if cnt == len(charFreq) {
            maxLen = max(maxLen, right-left+1)
        }
        // 滑动窗口右边界向右移动
        right++

        // 如果不满足条件,则将左边界向右移动并更新哈希表
        for cnt != len(charFreq) {
            charFreq[s[left]]--
            if charFreq[s[left]] == 0 {
                delete(charFreq, s[left])
            }
            left++
            cnt--
        }
    }

    return maxLen
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

最长重复子串算法:Java语言实现

import java.util.HashMap;
import java.util.Map;

class Solution {
    /**
     * 寻找至少包含 K 个重复字符的最长子串
     *
     * @param s 字符串
     * @param k 重复字符的数量
     * @return 最长子串的长度
     */
    public int findLongestSubstringWithKRepeat(String s, int k) {
        // 滑动窗口的左右边界
        int left = 0, right = 0;
        // 哈希表存储字符及其出现次数
        Map<Character, Integer> charFreq = new HashMap<>();
        // 满足条件的子串长度
        int maxLen = 0;

        for (right = 0; right < s.length(); right++) {
            // 将当前字符加入哈希表
            charFreq.put(s.charAt(right), charFreq.getOrDefault(s.charAt(right), 0) + 1);
            // 统计满足条件的字符数量
            int cnt = 0;
            for (int freq : charFreq.values()) {
                if (freq == k) {
                    cnt++;
                }
            }
            // 满足条件则更新最长子串长度
            if (cnt == charFreq.size()) {
                maxLen = Math.max(maxLen, right - left + 1);
            }
            // 滑动窗口右边界向右移动
            right++;

            // 如果不满足条件,则将左边界向右移动并更新哈希表
            while (cnt != charFreq.size()) {
                charFreq.put(s.charAt(left), charFreq.get(s.charAt(left)) - 1);
                if (charFreq.get(s.charAt(left)) == 0) {
                    charFreq.remove(s.charAt(left));
                }
                left++;
                cnt--;
            }
        }

        return maxLen;
    }
}

算法背后的思想

这两种算法都使用了滑动窗口的思想。滑动窗口是一种用于处理连续数据流的算法技术。它通过将数据流划分为固定大小的窗口,然后在窗口上执行某些操作来实现。

在寻找最长重复子串的问题中,滑动窗口的左右边界分别指向子串的起始和结束位置。算法首先将滑动窗口的右边界向右移动,并将遇到的每个字符添加到哈希表中。哈希表用于存储每个字符出现的次数。

当滑动窗口中包含了至少 K 个重复字符时,算法会更新最长子串的长度。然后,算法将滑动窗口的左边界向右移动,并将左边界处的字符从哈希表中删除。

算法不断地移动滑动窗口,直到它到达字符串的末尾。在移动过程中,算法会不断地更新最长子串的长度。最终,算法会返回最长子串的长度。

算法的复杂度

这两种算法的时间复杂度都是 O(n),其中 n 是字符串的长度。算法的空间复杂度都是 O(k),其中 k 是重复字符的数量。

结语

在这篇文章中,我们探索了两种寻找最长重复子串的算法:Go语言和Java语言的算法实现。这些算法都是基于滑动窗口的思想,它们的时间复杂度都是 O(n),空间复杂度都是 O(k)。希望这些算法能够帮助您解决实际问题,也希望您能从中学习到算法之美!