滑动窗口经典问题整理
经典
class Solution: def lengthOfLongestSubstring(self, s: str) -> int: m = defaultdict(int) maxl, j = 0, 0 for i, c in enumerate(s): m[c] += 1 while j <= i and m[c] > 1: m[s[j]] -= 1 j += 1 if i - j + 1 > maxl: maxl = i - j + 1 return maxl
class Solution: def numberOfSubstrings(self, s: str) -> int: m = defaultdict(int) j, n = 0, len(s) res = 0 for i, c in enumerate(s): m[c] += 1 while m['a'] >= 1 and m['b'] >= 1 and m['c'] >= 1: res += n - i m[s[j]] -= 1 j += 1 return res
3、最长优雅子数组
class Solution: def longestNiceSubarray(self, nums: List[int]) -> int: j, s, n = 0, 0, len(nums) maxl = 1 for i in range(len(nums)): while i >= j: if (nums[i] & s) == 0: s |= nums[i] break s ^= nums[j] j += 1 maxl = max(maxl, i - j + 1) return maxl
转换成为求解「最多存在 K 个不同整数的子区间的个数」与 「最多存在 K−1 个不同整数的子区间的个数」
class Solution: def subarraysWithKDistinct(self, nums: List[int], k: int) -> int: def slidingwindow(k): m, j = {}, 0 res = 0 for i, num in enumerate(nums): if num not in m: m[num] = 0 m[num] += 1 while j <= i and len(m) > k: m[nums[j]] -= 1 if m[nums[j]] == 0: del m[nums[j]] j += 1 res += i - j + 1 return res return slidingwindow(k) - slidingwindow(k - 1)
5、倒序缩小 -- 最小窗口子序列
class Solution: def minWindow(self, s1: str, s2: str) -> str: n = len(s1) minl = "9" * (n + 1) i, k = 0, 0 while i < n: if s1[i] == s2[k]: k += 1 if k == len(s2): left, p = i, len(s2) - 1 while p >= 0: if s2[p] == s1[left]: p -= 1 left -= 1 left += 1 if len(s1[left: i + 1]) < len(minl): minl = s1[left:i + 1] i, k = left, 0 i += 1 return minl if minl != "9" * (n + 1) else ""
class Solution: def minOperations(self, nums: List[int]) -> int: n = len(nums) nums = sorted(set(nums)) j, minl = 0, inf for i, num in enumerate(nums): while j <= i and num - nums[j] + 1 > n: j += 1 minl = min(minl, n - (i - j + 1)) return minl
7、无限数组的最短子数组 -- (乘二倍,解决循环问题,类似于可见点的最大数目)
class Solution: def minSizeSubarray(self, nums: List[int], target: int) -> int: j, s = 0, 0 sum_nums = sum(nums) double_nums = nums * 2 minl = inf for i, num in enumerate(double_nums): s = s + num while i >= j and s > target % sum_nums: s = s - double_nums[j] j += 1 if s == target % sum_nums: minl = min(minl, i - j + 1 + len(nums) * (target // sum_nums)) return minl if minl < inf else -1
8、滑动窗口 + 中位数贪心(通过首尾配对证明) (执行操作使频率分数最大)
class Solution: def maxFrequencyScore(self, nums: List[int], k: int) -> int: nums.sort() pre = [0] for num in nums: pre.append(pre[-1] + num) def get_cost(i, j): s = i - j + 1 mid = nums[(i + j) // 2] idx = bisect_right(nums, mid) right = (pre[i + 1] - pre[idx]) - mid * (i + 1 - idx) left = mid * (idx - j) - (pre[idx] - pre[j]) return left + right j, maxl = 0, 0 for i, num in enumerate(nums): while get_cost(i, j) > k: j += 1 maxl = max(maxl, i - j + 1) return maxl
class Solution: def countSubarrays(self, nums: List[int], k: int) -> int: mx = max(nums) ans = cnt_mx = left = 0 for x in nums: if x == mx: cnt_mx += 1 while cnt_mx == k: if nums[left] == mx: cnt_mx -= 1 left += 1 ans += left return ans
注:如果元素存在负数时,窗口的滑动就没有单向性了,所以不适用滑动窗口了,例如和至少为 K 的最短子数组,最佳做法时利用前缀和 + 单调队列,代码如下:
class Solution: def shortestSubarray(self, nums: List[int], k: int) -> int: preSumArr = [0] res = len(nums) + 1 for num in nums: preSumArr.append(preSumArr[-1] + num) q = deque() for i, curSum in enumerate(preSumArr): while q and curSum - preSumArr[q[0]] >= k: res = min(res, i - q.popleft()) while q and preSumArr[q[-1]] >= curSum: q.pop() q.append(i) return res if res < len(nums) + 1 else -1
当子数组的右侧端点固定时,当左侧端点向左移动时函数值不变或减少,当左侧端点向右移动时函数值不变或增加。当左侧端点向左移动时,函数值的二进制表示的每一位只可能不变或从 1 变成 0,不可能从 0 变成 1,因此当子数组的右侧端点 r 固定时,不同的函数值个数不超过 O(lognums[r]),所以可以采用二分法确定子数组为K的左侧端点的起始点和终止点
由于元素值只会减少,所以当 i 增大时,左侧端点的起始点和终止点位置不会向变大的方向移动,有了单调性的保证,所以可以使用滑动窗口,代码如下:
class Solution: def countSubarrays(self, nums: List[int], k: int) -> int: ans = left = right = 0 for i, x in enumerate(nums): for j in range(i - 1, -1, -1): if nums[j] & x == nums[j]: break nums[j] &= x while left <= i and nums[left] < k: left += 1 while right <= i and nums[right] <= k: right += 1 ans += right - left return ans
滑动窗口与有序列表结合
class Solution: def containsNearbyAlmostDuplicate(self, nums: List[int], indexDiff: int, valueDiff: int) -> bool: from sortedcontainers import SortedList n = len(nums) s = SortedList() for i, num in enumerate(nums): idx = s.bisect_left(num - valueDiff) if idx < len(s) and s[idx] <= num + valueDiff: return True s.add(num) if i >= indexDiff: s.remove(nums[i - indexDiff]) return False
该方法还可以用桶排序的方法去做:
class Solution: def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: m, size = {}, t + 1 getID = lambda num: num // size for i, num in enumerate(nums): nid = getID(num) if nid in m: return True if (nid - 1) in m and m[nid - 1] + t >= num: return True if (nid + 1) in m and m[nid + 1] - t <= num: return True m[nid] = num if i >= k: del m[getID(nums[i - k])] return False
滑动窗口与单调队列结合
class Solution: def maximumRobots(self, chargeTimes: List[int], runningCosts: List[int], budget: int) -> int: ans = s = left = 0 q = deque() # 枚举区间右端点 right,计算区间左端点 left 的最小值 for right, (t, c) in enumerate(zip(chargeTimes, runningCosts)): # 及时清除队列中的无用数据,保证队列的单调性 while q and t >= chargeTimes[q[-1]]: q.pop() q.append(right) s += c # 如果左端点 left 不满足要求,就不断右移 left while q and chargeTimes[q[0]] + (right - left + 1) * s > budget: # 及时清除队列中的无用数据,保证队列的单调性 if q[0] == left: q.popleft() s -= runningCosts[left] left += 1 ans = max(ans, right - left + 1) return ans
哈希值 + 滑动窗口 (Rabin-Karp + 二分搜索)
1、最长重复子串
class Solution: def longestDupSubstring(self, s: str) -> str: mod = 10 ** 9 + 7 def check(m): t = 0 for i in range(m): t = (t * 26 + ord(s[i]) - ord('a')) % mod st = set([t]) for i in range(m, len(s)): t = (t * 26 + ord(s[i]) - ord('a') - pow(26, m, mod) * (ord(s[i - m]) - ord('a'))) % mod if t in st: tt = s[i - m + 1: i + 1] if s.find(tt) != i - m + 1: return True, tt st.add(t) return False, "" l, r = 0, len(s) res = "" while l < r: mid = (l + r) // 2 flag, true_s = check(mid) if flag: l = mid + 1 res = true_s else: r = mid return res
该题还可以用后缀数组去完成
from array import array L_TYPE = ord('L') S_TYPE = ord('S') def is_lms(i, t): """Returns whether the suffix/ character at index i is a leftmost S-type""" return t[i] == S_TYPE and t[i - 1] == L_TYPE def print_types(data: bytearray): """Simple method to the types of the characters of T""" print(data.decode('ascii')) print("".join( "^" if is_lms(i, data) else " " for i in range(len(data)) )) def classify(text, n) -> bytearray: """Classifies the suffixes in text as either S-type, or L-type This method can be merged with find_lms_suffixes but I have not done so for readability Args: text: the input string/array to be classified n: the length of text Returns: t: a bytearray object, where t[i] contains the type of text[i] """ t = bytearray(n) t[-1] = S_TYPE for i in range(n - 2, -1, -1): if text[i] == text[i + 1]: t[i] = t[i + 1] else: if text[i] > text[i + 1]: t[i] = L_TYPE else: t[i] = S_TYPE return t def find_lms_suffixes(t, n): """Finds the positions of all lms_suffixes Args: t: the type array n: the length of text and t """ pos = array('l') for i in range(n): if t[i] == S_TYPE and t[i - 1] == L_TYPE: pos.append(i) return pos def print_buckets(bucks): """Simple method to print bucket sizes""" res = '[ ' for b in bucks: if b != 0: res += str(b) res += ' ' res += ']' print(res) def buckets(text, sigma): """Find the alphabet and the sizes of the bucket for each character in the text""" alpha = [] bucket_sizes = array('L', [0] * sigma) for c in text: bucket_sizes[c] += 1 for i in range(sigma): if bucket_sizes[i] != 0: alpha.append(i) # print_buckets(bucket_sizes) return alpha, bucket_sizes def bucket_intervals(alpha, bucket_sizes, sigma): """Computes the bucket intervals, i.e heads and tails""" heads = array('l', [0] * sigma) tails = array('l', [0] * sigma) j = 0 for i in range(len(alpha)): heads[alpha[i]] = j j += bucket_sizes[alpha[i]] tails[alpha[i]] = j - 1 # print_buckets(heads) # print_buckets(tails) return heads, tails def induced_sorting(lms, tails, heads, SA, type_suffix, text, n, m, alpha, bucket_sizes, sigma): """Inductively creates the suffix array based on LMS Args: lms: an array indicating the positions of LMS Blocks/Suffixes in text tails: an array indexed by the characters in T which tells the ends of the buckets heads: an array indexed by the characters in T which tells the fronts of the buckets of those characters SA: an empty array to be filled during the creation of the suffix array type_suffix: an array in which type_suffix[i] tells the type of text[i] text: the input whose suffix array is to be created n: the length of the input 'text' alpha: an array of the alphabet of T in sorted order bucket_sizes: an array containing the sizes of each bucket: Used in resetting heads, tails """ for i in range(m-1, -1, -1): # place LMS suffixes at the end of their buckets nfs = tails[text[lms[i]]] SA[nfs] = lms[i] tails[text[lms[i]]] -= 1 for i in range(n): # place the L-type suffixes at the fronts of their buckets if SA[i] > 0 and type_suffix[SA[i] - 1] == L_TYPE: nfs = heads[text[SA[i] - 1]] SA[nfs] = SA[i] - 1 heads[text[SA[i] - 1]] += 1 # reset bucket counters heads, tails = bucket_intervals(alpha, bucket_sizes, sigma) for i in range(n-1, -1, -1): # place the S-type suffixes at the ends of their buckets if SA[i] > 0 and type_suffix[SA[i] - 1] == S_TYPE: nfs = tails[text[SA[i] - 1]] SA[nfs] = SA[i] - 1 tails[text[SA[i] - 1]] -= 1 def blocks_are_equal(i, j, types, text, n): """Testing for the equality of two blocks""" while i < n and j < n: if text[i] == text[j]: if is_lms(i, types) and is_lms(j, types): return True else: i += 1 j += 1 else: return False return False def get_reduced_substring(types, SA, lms, ordered_lms, text, n, m): """Finds the reduced substring""" j = 0 for i in range(n): if is_lms(SA[i], types): ordered_lms[j] = SA[i] j += 1 # number the lms blocks and form the reduced substring pIS = array('l', [0] * m) k, i = 1, 1 pIS[0] = 0 for i in range(1, m): if text[ordered_lms[i]] == text[ordered_lms[i - 1]] and \ blocks_are_equal(ordered_lms[i] + 1, ordered_lms[i - 1] + 1, types, text, n): pIS[i] = pIS[i - 1] else: pIS[i] = k k += 1 # form the reduced substring inverse_lms = array('l', [0] * n) for i in range(m): inverse_lms[ordered_lms[i]] = pIS[i] for i in range(m): pIS[i] = inverse_lms[lms[i]] return pIS, k == m, k + 1 def construct_suffix_array(T, SA, n, sigma): """Constructs the suffix array of T and stores it in SA Args: T: the text whose suffix array is to be built SA: the array to be filled n: the length of T and SA sigma: the size of the alphabet of T, i.e the largest value in T """ if len(T) == 1: # special case SA[0] = 0 return SA t = classify(T, n) # step 1: classification lms = find_lms_suffixes(t, n) # step 2: finding the indices of LMS suffixes m = len(lms) # print_types(t) alpha, sizes = buckets(T, sigma) # finding the bucket sizes and alphabet of T heads, tails = bucket_intervals(alpha, sizes, sigma) induced_sorting(lms, tails, heads, SA, t, T, n, m, alpha, sizes, sigma) # first induced sort ordered_lms = array('L', [0] * len(lms)) reduced_text, blocks_unique, sigma_reduced = get_reduced_substring(t, SA, lms, ordered_lms, T, n, m) reduced_SA = array('l', [-1] * m) # reduced SA if blocks_unique: # base case # compute suffix array manually for i in range(m): reduced_SA[reduced_text[i]] = i else: construct_suffix_array(reduced_text, reduced_SA, m, sigma_reduced) # use the suffix array to sort the LMS suffixes for i in range(m): ordered_lms[i] = lms[reduced_SA[i]] heads, tails = bucket_intervals(alpha, sizes, sigma) # reset bucket tails and heads for i in range(n): SA[i] = 0 # clear suffix array induced_sorting(ordered_lms, tails, heads, SA, t, T, n, m, alpha, sizes, sigma) def bwt(T, SA: array, BWT: bytearray, n: int): """If SA[i] = 0 then T[SA[i] - 1] = T[0 - 1] = T[-1] = '$""" for i in range(n): BWT[i] = T[SA[i] - 1] def isa(SA, ISA, n): """Constructs an inverse suffix array""" for i in range(n): ISA[SA[i]] = i def fm_index(SA, ISA, LF, n): """Constructs a last-to-first column mapping in linear time""" for i in range(n): if SA[i] == 0: LF[i] = 0 else: LF[i] = ISA[SA[i] - 1] def naive_suffix_array(s, n): """Naive suffix array implementation, just as a sanity check""" sa_tuple = sorted([(s[i:], i) for i in range(n)]) return array('l', map(lambda x: x[1], sa_tuple)) ''' text: str return: sa ''' def SAIS_sa(text): text += '$' text = [ord(c) for c in text] sigma = max(text) + 1 n = len(text) SA = array('l', [-1] * n) construct_suffix_array(text, SA, n, sigma) bt = bytearray(n) bwt(text, SA, bt, n) return SA.tolist()[1:] ''' text: str return: sa,rk,h ''' def SAIS_sa_rk_h(text): sa = SAIS_sa(text) n,k = len(sa),0 rk,h = [0]*n,[0]*n for i,sa_i in enumerate(sa): rk[sa_i] = i for i in range(n): if k>0: k-=1 while i+k<n and sa[rk[i]-1]+k<n and text[i+k]==text[sa[rk[i]-1]+k]: k+=1 h[rk[i]] = k return sa,rk,h class Solution: def longestDupSubstring(self, T: str) -> str: sa,rk,h = SAIS_sa_rk_h(T) max_h = max(h) start_index = sa[h.index(max_h)] return T[start_index:start_index+max_h]
ST表解法
模板
from typing import Callable, Generic, List, TypeVar E = TypeVar("E") class SlidingWindowAggregation(Generic[E]): """SlidingWindowAggregation Api: 1. append value to tail,O(1). 2. pop value from head,O(1). 3. query aggregated value in window,O(1). """ __slots__ = ["_stack0", "_stack1", "_stack2", "_stack3", "_e0", "_e1", "_size", "_op", "_e"] def __init__(self, e: Callable[[], E], op: Callable[[E, E], E]): """ Args: e: unit element op: merge function """ self._stack0 = [] self._stack1 = [] self._stack2 = [] self._stack3 = [] self._e = e self._e0 = e() self._e1 = e() self._size = 0 self._op = op def append(self, value: E) -> None: if not self._stack0: self._push0(value) self._transfer() else: self._push1(value) self._size += 1 def popleft(self) -> None: if not self._size: return if not self._stack0: self._transfer() self._stack0.pop() self._stack2.pop() self._e0 = self._stack2[-1] if self._stack2 else self._e() self._size -= 1 def query(self) -> E: return self._op(self._e0, self._e1) def _push0(self, value): self._stack0.append(value) self._e0 = self._op(value, self._e0) self._stack2.append(self._e0) def _push1(self, value): self._stack1.append(value) self._e1 = self._op(self._e1, value) self._stack3.append(self._e1) def _transfer(self): while self._stack1: self._push0(self._stack1.pop()) while self._stack3: self._stack3.pop() self._e1 = self._e() def __len__(self): return self._size
class Solution: def smallestSubarrays(self, nums: List[int]) -> List[int]: n = len(nums) Sm = SlidingWindowAggregation(lambda: 0, lambda a, b: a | b) for i in range(n): Sm.append(nums[i]) res, j = [0] * n, 0 S = SlidingWindowAggregation(lambda: 0, lambda a, b: a | b) for i in range(n): S.append(nums[i]) while j <= i and S and S.query() == Sm.query(): res[j] = i - j + 1 S.popleft() Sm.popleft() j += 1 return res
class Solution: def minOperations(self, nums: List[int]) -> int: if gcd(*nums) != 1: return -1 if 1 in nums: return len(nums) - nums.count(1) return minLen(nums) - 1 + len(nums) - 1 def minLen(nums: List[int]) -> int: """gcd为1的最短子数组.不存在返回INF.""" n = len(nums) S = SlidingWindowAggregation(lambda: 0, gcd) res, n = inf, len(nums) for right in range(n): S.append(nums[right]) while S and S.query() == 1: res = min(res, len(S)) S.popleft() return res