1. LC 3292 形成目标字符串需要的最少字符串数Ⅱ
这题在3291的基础上开大数据量了。
3291我是比较标准的dp+字典树优化匹配。先把所有word扔到字典树里面,定义dp[i]表示到target[i]需要的最少次数。对于每个i进行target子串的最长前缀匹配,随后向后刷表。
其实,“target子串的最长前缀匹配”,其实就是主站45题跳跃游戏Ⅱ的贪心。如果你在抵达当前跳的途中,遇到右端点更远的索引,即可更新当前跳为该跳,次数即为更新次数:
不过字典树的O(nk)显然是超时的,n是words数组长度,k是words数组元素长度总和。
这里看了灵神题解后,学到了单模(这里单模因为数据量小,不会哈希碰撞)字符串哈希的操作。首先对于任意索引i,如果target[i:j]有匹配前缀,那么target[i:j-k](其中j-k≥i)一定有匹配;若target[i:j]没有匹配,那么target[i:j+k](其中k≥0)一定没有匹配。
因此可以把所有前缀的哈希置入一个set的列表sets,sets[j]表示前缀长度为j的字符串的哈希值的集合。二分检查最长的匹配对应的是哪个set即可找到最长匹配。然后套跳跃游戏的贪心即可:
from typing import List
from random import randint
class Solution:
def minValidStrings(self, words: List[str], target: str) -> int:
n = len(target)
MOD = 1_070_777_777
BASE = randint(8*10**8,9*10**8)
pow_base = [1] + [0 for _ in range(n)]
pre_hash = [0 for _ in range(n+1)]
for i,c in enumerate(target):
pow_base[i+1] = pow_base[i]*BASE%MOD
pre_hash[i+1] = (pre_hash[i]*BASE+ord(c))%MOD
max_len = max(map(len,words))
sets = [set() for _ in range(max_len+1)]
for w in words:
hash = 0
for i,c in enumerate(w):
hash = (hash*BASE+ord(c))%MOD
sets[i+1].add(hash)
def sub_hash(l:int,r:int)->int:
return (pre_hash[r]-pre_hash[l]*pow_base[r-l])%MOD
# 45
ans,cur_r,nxt_r = 0,0,0
for i in range(n):
def len_bs(start:int)->int:
l,r,ans = 0,min(n-start,max_len)+1,0
while l<r:
mid = (l+r)//2
check = sub_hash(i,i+mid) in sets[mid]
if check:
ans = mid
l = mid+1
else:
r = mid
return ans
nxt_r = max(nxt_r,i+len_bs(i))
if i==cur_r:
if i==nxt_r:
return -1
cur_r = nxt_r
ans += 1
return ans
复杂度:O(nlogn+k)。我没用过bisect,也懒得学怎么用,直接手写了个二分。