LC 3031. 将单词恢复初始状态所需的最短时间 II

https://leetcode.cn/problems/minimum-time-to-revert-word-to-initial-state-ii/description/

这题其实可以先从简单的版本开始做 https://leetcode.cn/problems/minimum-time-to-revert-word-to-initial-state-i/description/ 其实这题就是在找后缀的最长匹配,但是后缀的边界是在k个字符上。后面补上的字符其实完全可以根据之前匹配的情况进行补全。

class Solution:
    def minimumTimeToInitialState(self, word: str, k: int) -> int:
        i, ans = 0, 0
        n = len(word)
        while i < n:
            i += k
            ans += 1
            if i < n and word[:n - i] == word[i:]:
                break
        return ans

对于小数据量版本,完全可以直接使用字符串检查的方式。但是如果字符串很长的话,那么每次检查字符串相等就比较耗时。一个办法是使用rolling hash function来做,另外一个则是使用类似kmp的算法。

这里如果使用kmp算法则需要稍微做一些改进,因为我们每次选择的后缀边界是在k个字符上。一个简单做法就是,我们可以先对k个字符进行group(最后一个group可能不是k个字符),然后使用kmp来比较每个group. 假设我们kmp算法得到的是 `max_match[i]` 表示i group的匹配前缀的最长长度是多少,那么答案就是 `len(max_match) - max_match[-1]`.

比如假设 max_match 的值是 [0,1,2,1,2,3] 的话,最后一个max_match[-1]是3,表示和前缀可以匹配3个groups, 那么说明向前数3个group可以完全可以前缀进行匹配,答案就是3.

class KMP:
    @staticmethod
    def build_max_match(t):
        n = len(t)
        match = [0] * n
        c = 0

        def eq(a, b):
            sz = min(len(a), len(b))
            return a[:sz] == b[:sz]

        for i in range(1, n):
            v = t[i]
            while c and not eq(t[c], v):
                c = match[c - 1]
            if eq(t[c], v):
                c += 1
            match[i] = c
        return match


class Solution:
    def minimumTimeToInitialState(self, word: str, k: int) -> int:
        w = []
        i = 0
        while i < len(word):
            w.append(word[i:i + k])
            i += k
        match = KMP.build_max_match(w)
        ans = len(match) - match[-1]
        return ans