📅  最后修改于: 2023-12-03 15:37:01.566000             🧑  作者: Mango
给定一个字符串数组,我们要找出一个字符串,使其包含另一个字符串作为其子字符串,且其字典序最小或最大。
本题的解法可以借助于字典树和后缀数组两种数据结构。
字典树可以高效地查询一个字符串是否包含另一个字符串作为其子字符串。对于每个输入的字符串,我们在字典树上插入其所有可能的子串,在插入过程中标记该点是否是某个字符串的末尾。查询一个字符串是否包含另一个字符串作为其子字符串,则可以在字典树上查找该字符串的每个子串是否都可以从根节点到达,且其中某个子串标记为某个字符串的末尾。
具体实现时,我们可以使用线段树+字典树的方式来维护字典树上的节点信息,并使用深度优先遍历来查询每个字符串是否包含另一个字符串作为其子字符串。
另外一种解法是使用后缀数组。后缀数组可以高效地查询一个字符串的所有后缀是否包含另一个字符串作为其前缀。对于每个输入的字符串,我们将其所有后缀插入到后缀数组中,并使用二分查找来查询其中是否有子串包含另一个字符串。具体的查询过程中,我们可以先找到该另一个字符串在后缀数组中的位置区间,然后在该区间内二分查找是否存在以该另一个字符串为前缀的后缀。
class SegmentTree:
def __init__(self, n, init_val=0):
self.n = n
self.tree = [init_val] * (4 * n)
def update(self, idx, val):
def _update(node, l, r):
if l == r:
self.tree[node] = val
return
mid = (l + r) // 2
if idx <= mid:
_update(node * 2, l, mid)
else:
_update(node * 2 + 1, mid + 1, r)
self.tree[node] = min(self.tree[node * 2], self.tree[node * 2 + 1])
_update(1, 1, self.n)
def query(self, qs, qe):
def _query(node, l, r):
if qs > r or qe < l:
return float('inf')
if qs <= l and qe >= r:
return self.tree[node]
mid = (l + r) // 2
return min(_query(node * 2, l, mid), _query(node * 2 + 1, mid + 1, r))
return _query(1, 1, self.n)
class Trie:
def __init__(self):
self.root = {}
self.idx = 0
def insert(self, s):
node = self.root
for ch in s:
if ch not in node:
self.idx += 1
node[ch] = self.idx
node = self.get_node(node[ch])
node['$'] = True
def get_node(self, idx):
return self.nodes[idx // leaf_size][idx % leaf_size]
def set_node(self, idx, val):
self.nodes[idx // leaf_size][idx % leaf_size] = val
def build(self):
leaves = ((self.idx - 1) // leaf_size + 1) * leaf_size
self.nodes = [[0] * leaf_size + [float('inf')] * (leaves - self.idx) for _ in range((leaves - 1) // leaf_size + 1)]
self.set_node(0, {})
for ch, idx in self.root.items():
self.set_node(idx, {})
for node_idx, ch in enumerate(self.root.keys()):
self.build_node(node_idx, self.get_node(self.root[ch]), ch)
def build_node(self, node_idx, node, ch):
self.set_node(node_idx, node)
for i in range(len(self.nodes)):
self.nodes[i][node_idx % leaf_size] = min(self.nodes[i][node_idx % leaf_size], node_idx)
for ch, idx in node.items():
self.build_node(idx, self.get_node(idx), ch)
def query(self, s):
node = self.get_node(0)
for ch in s:
if ch not in node:
return None
node = self.get_node(node[ch])
return self.query_subtree(node)
def query_subtree(self, node):
if '$' in node:
return node['$']
min_idx = float('inf')
for ch, idx in node.items():
if ch == '$':
continue
if idx < min_idx:
min_idx = idx
if min_idx == float('inf'):
return None
return self.query_subtree(self.get_node(min_idx))
def __str__(self):
res = []
queue = [(0, self.root)]
while queue:
level, node = queue.pop(0)
res.append(f'{level}: {node}')
for ch, idx in node.items():
if ch == '$':
res.append(f'{level+1}: $')
continue
queue.append((level+1, self.get_node(idx)))
return '\n'.join(res)
def min_max_string(puzzle: List[str]) -> List[str]:
n = len(puzzle)
t = Trie()
for s in puzzle:
t.insert(s[::-1] + '$')
t.insert(s)
t.build()
res_min = []
res_max = []
for s in puzzle:
prefix = ''
node = t.get_node(0)
for ch in s:
prefix += ch
if t.query(prefix[::-1]) is not None:
break
if ch not in node:
break
node = t.get_node(node[ch])
res_min.append(prefix[::-1])
if None in [t.query(p[::-1]) for p in puzzle]:
res_max.append(s)
else:
suffix = ''
node = t.get_node(0)
for ch in s[::-1]:
suffix = ch + suffix
if t.query(suffix) is not None:
break
if ch not in node:
break
node = t.get_node(node[ch])
res_max.append(suffix)
return [res_min, res_max]
def build_suffix_array(s):
n = len(s)
sa = list(range(n))
rank = [ord(s[i]) for i in sa]
k = 1
while k < n:
tmp = [(rank[sa[i]], rank[sa[i] + k] if sa[i] + k < n else -1, i) for i in range(n)]
tmp.sort()
rank[sa[0]] = 0
for i in range(1, n):
rank[sa[i]] = rank[sa[i - 1]] + (tmp[i - 1] != tmp[i])
k *= 2
if rank[sa[-1]] == n - 1:
break
return sa
def min_max_string(puzzle: List[str]) -> List[str]:
n = len(puzzle)
s = ''.join(chr(i + ord('a')) for i in range(26))
pos = len(s)
sa = build_suffix_array(s + '#' + '#'.join(puzzle) + '#' + s[::-1])
lcp = [0] * (len(sa) - 1)
for i in range(len(sa) - 1):
j = sa[i]
k = sa[i + 1]
while j < len(s) + 2 * n + 1 and k < len(s) + 2 * n + 1 and s[j] == s[k]:
j += 1
k += 1
lcp[i] = j - sa[i]
stack = []
res_min = []
res_max = []
for i in range(2, n + 2):
while stack and lcp[stack[-1]] > lcp[i]:
stack.pop()
if not stack:
res_min.append(puzzle[i - 2][:lcp[i]])
stack.append(i)
for i in range(n + 1, 2 * n + 1):
while len(stack) > 1 and lcp[stack[-1]] > lcp[i]:
stack.pop()
if lcp[stack[-1]] == len(puzzle[stack[-2] - 1]) and not (i - n - 1 == stack[-2] - 1):
continue
if lcp[stack[-1]] == len(puzzle[stack[-2] - 1]) and (i - n - 1 == stack[-2] - 1):
cur = puzzle[i - n - 2]
for j in range(lcp[stack[-1]], len(cur)):
if cur[j] < puzzle[stack[-2] - 1][j]:
break
if cur[j] > puzzle[stack[-2] - 1][j]:
res_max.append(cur[:j])
break
else:
res_max.append(cur[:lcp[stack[-1]]])
continue
if not stack:
continue
res_max.append(puzzle[stack[-2] - 1][:lcp[stack[-1]]])
return [res_min, res_max]
本题采用了不同的数据结构来解决,分别是使用字典树和后缀数组。字典树思路较直观,但是细节较多;后缀数组思路经典,但是实现较为复杂。为了更好地理解本题的解法,在实现时需要细致地调试和分析,尤其是在实现后缀数组求解过程中。