Example
暴力
思路:
- 把所有字符串扫描一次。
- 判断是否包含所有字符, 是则记录信息。
- 扫描完所有字符串,比较所有记录, 最短那个就是最小子串。
伪代码
for i in range(0, len(S)):for j in range(1, len(S)):if S[i:j] contain T:记录i,j
分析
for i in range(0, len(S)):for j in range(1, len(S)):if S[i:j] contain T:记录i,jbreak
- 接下来的问题是,伪代码中的
contain怎么实现?contain操作的, 因此, 移动的过程中, 我们应该迭代的判断
是否包含所有元素。 即
每移动一次, 都有可能改变了
覆盖状态。那么, 什么时候状态会改变呢? 什么时候状态满足了覆盖呢?- 题目中等价条件是包含
T的一个全排列, 全排列有什么性质?元素个数和种类一致, 所以我们把T的全排列抽象出一个·target结构, 这个结构记录T中的排列特征, 即元素种类和对应的元素个数。显然,target使用映射类型合适。 - 相似的, 我们增加一个·有效窗口
window记录满足的元素, 因为这些元素才可能改变覆盖状态。
- 综上
target = {T元素种类: 元素个数}for i in range(0, len(S)):for j in range(1, len(S)):if S[j] in T:window[S[j]]++ # 改变windowif window == target: # S_ij 出现了全排列# 记录长度# 记录起始点break
- 现在又出现一个问题, 迭代过程中, 只对
所指的字符进行处理,
所指向的字符并未处理。最小字串覆盖两端必然都是
T中字符,所以要剔除左边不需要的字符, 即剔除多余字符 ```python target = {T元素种类: 元素个数}
for j in range(0, len(S)): if S[j] in T: window[S[j]]++ # 改变window if window == target: # S_ij 出现了全排列
# 记录长度# 记录起始点# S_ij是包含已经满足条件, 但是有多余字符while window == target:# 剔除多余字符i++ # 在这更新i, 外层循环就不要了,# 必要时更新windowif S[i] in T:window[S[i]]--
- 在比较 `window = target` 需要耗费很多时间, 因此使用 `flag` 来判断两个数组接近程度。再做一些细节修改, 就得到滑动窗口算法了。<a name="3FD67"></a># 滑动窗口- , 初始, 即长度为0- ```python"""给定字符串S, 目标字符串T,"""def min_substring(S: str, T: str) -> str:# 记录当前有效字符, 暂时称为有效窗口window = {}# 目标字符target = {}for c in T:temp = target.get(c, 0)target[c] = temp + 1# 窗口初始, [left, right)left:int = 0right:int = 0# 最小,全局最小min_start:int = 0min_length = len(S) + 1# 极小,局部最小local_start:int = 0local_length:int = min_length# flag, 记录匹配了几个字符。 left, right) 是一个覆盖窗口当且仅当windows = target, 即【left, right)覆盖 Tflag = 0while right < len(S):c = S[right]# 窗口右边扩大right += 1if c in target.keys(): # 当前字符时候是有效字符, 有效字符需要更新有效窗口temp = window.get(c, 0)window[c] = temp + 1# 前面更新了window有效窗口, 这时,需要判断是否为覆盖窗口if window[c] == target[c]: # 这个字符已经完成匹配, 包括个数flag += 1# 窗口扩大,是否是覆盖窗口,如果是覆盖窗口则进行收缩。 由覆盖 -> 极小覆盖while flag == len(target): # 已经覆盖, 现在求一个极小覆盖# 初始极小覆盖长度, 起点local_start = leftlocal_length = right - leftc = S[left]# 窗口左边缩小left += 1if c in target.keys():# 当前字符是有效字符, 需要更新有效窗口和flagif window[c] == target[c]: # 如果当前c刚好匹配, window变化将改变其flag,flag -= 1window[c] -= 1# 更新最小覆盖if local_length < min_length:min_start = local_startmin_length = local_lengthif min_length == len(S) + 1:return ''return min_start, min_lengthif __name__ == '__main__':print(min_substring('ccadddd', 'cca'))
总结
- 对于双重循环, 可以考虑双指针
flag更新规则,flag反映的时和
T接近程度- 滑动窗口常用来解决字串问题
