int max_len = 0; for (auto& w : words) { max_len = max(max_len, (int) w.length()); } vector<unordered_set<int>> sets(max_len); for (auto& w : words) { longlong h = 0; for (int j = 0; j < w.size(); j++) { h = (h * BASE + w[j]) % MOD; sets[j].insert(h); // 注意 j 从 0 开始 } }
auto max_jump = [&](int i) -> int { // 开区间二分,left 一定满足要求,right 一定不满足要求 int left = 0, right = min(n - i, max_len) + 1; while (left + 1 < right) { int mid = (left + right) / 2; (sets[mid - 1].contains(sub_hash(i, i + mid)) ? left : right) = mid; } return left; };
int ans = 0; int cur_r = 0; // 已建造的桥的右端点 int nxt_r = 0; // 下一座桥的右端点的最大值 for (int i = 0; i < n; i++) { nxt_r = max(nxt_r, i + max_jump(i)); if (i == cur_r) { // 到达已建造的桥的右端点 if (i == nxt_r) { // 无论怎么造桥,都无法从 i 到 i+1 return-1; } cur_r = nxt_r; // 造一座桥 ans++; } } return ans; } };
复杂度分析
时间复杂度:O(L+nlogn),其中 n 是 target 的长度,L 是 words 中所有字符串的长度之和。
空间复杂度:O(L+n)。
写法二:双指针
用双指针更新代码中的 nxtR:
外层循环枚举 i,内层循环右移 nxtR。
对于 target 的下标从 i 到 nxtR 的子串,如果其哈希值在哈希表中,那么把 nxtR 加一。
classSolution { public: intminValidStrings(vector<string>& words, string target){ int n = target.length();
voidput(string& s){ auto cur = root; for (char b : s) { b -= 'a'; if (cur->son[b] == nullptr) { cur->son[b] = newNode(cur->len + 1); } cur = cur->son[b]; } }
voidbuild_fail(){ root->fail = root; queue<Node*> q; for (auto& son : root->son) { if (son == nullptr) { son = root; } else { son->fail = root; // 第一层的失配指针,都指向根节点 ∅ q.push(son); } } // BFS while (!q.empty()) { auto cur = q.front(); q.pop(); for (int i = 0; i < 26; i++) { auto& son = cur->son[i]; if (son == nullptr) { // 虚拟子节点 cur.son[i],和 cur.fail.son[i] 是同一个 // 方便失配时直接跳到下一个可能匹配的位置(但不一定是某个 words[k] 的最后一个字母) son = cur->fail->son[i]; continue; } son->fail = cur->fail->son[i]; // 计算失配位置 q.push(son); } } } };
classSolution { public: intminValidStrings(vector<string>& words, string target){ AhoCorasick ac; for (auto& w : words) { ac.put(w); } ac.build_fail();
int n = target.length(); vector<int> f(n + 1); auto cur = ac.root; for (int i = 0; i < n; i++) { // 如果没有匹配相当于移动到 fail 的 son[target[i]-'a'] cur = cur->son[target[i] - 'a']; // 没有任何字符串的前缀与 target[..i] 的后缀匹配 if (cur == ac.root) { return-1; } f[i + 1] = f[i + 1 - cur->len] + 1; } return f[n]; } };
复杂度分析
时间复杂度:O(L∣Σ∣+n),其中 n 是 target 的长度,L 是 words 中所有字符串的长度之和,∣Σ∣ 是字符集合的大小,本题字符均为小写字母,所以 ∣Σ∣=26。如果把数组替换成哈希表,可以做到 O(L+n) 的时间复杂度。