スライディング ウィンドウの問題分析
スライディング ウィンドウ フレームワークを使用して、Leetcode/Likou の古典的なアルゴリズムの問題を分析および解決します。
記事ディレクトリ
スライドウィンドウの基本操作
# the iterable (list, str, ...) is "it"
# define a sliding window [left, right)
left = 0
right = 0
# define window states
wnd_state_1 = ...
wnd_state_2 = ...
...
# define target states
tgt_state_1 = ...
tgt_state_2 = ...
...
# define resulting state
result = ...
# expand window (moving right pointer)
expand_elem = it[right]
right += 1
# shrink window (moving left pointer)
shrink_elem = it[left]
left += 1
# define get_expand_condition() and get_shrink_condition()
# framework
while get_expand_condition() and right < len(it):
expand_elem = it[right]
right += 1
# update window states (expand) with expand_elem
while get_shrink_condition() and left <= right and left < len(it):
shrink_elem = it[left]
left += 1
# update window states (shrink) with shrink_elem
# update window states (expand) with expand_elem
# update resulting state
LC-3
文字列 s が与えられた場合、繰り返し文字を含まない最長の部分文字列の長さを見つけてください。
分析:
ウィンドウの状態は、ウィンドウ内の文字セットであること、
拡張条件は無条件 (True)、
縮小条件は、ウィンドウに追加する文字がウィンドウの文字セットに既に表示されていること、文字がウィンドウ内になくなるまで縮小する必要があります 文字セットが入っている;
拡大するときにウィンドウの状態を更新する: 文字がセットに追加される;
縮小するときにウィンドウの状態を更新する: セットから文字を削除する;
def lengthOfLongestSubstring(self, s: str) -> int:
# define a sliding window [left, right)
left = 0
right = 0
# define window states
window = set()
# define resulting state
result = 0
# get_expand_condition() = True
while right < len(s):
# expand window (moving right pointer)
expand_elem = s[right]
right += 1
# get_shrink_condition() = expand_elem in window
while expand_elem in window and left <= right and left < len(s):
# shrink window (moving left pointer) with shrink_elem
shrink_elem = s[left]
left += 1
# update window states (shrink)
window.remove(shrink_elem)
# update window states (expand) with expand_elem
window.add(expand_elem)
# update resulting state
result = max(result, right - left)
return result
LC-438
2 つの文字列 s と p が与えられた場合、s 内の p のすべてのアナグラム部分文字列を見つけ、これらの部分文字列の開始インデックスを返します。回答が出力される順序は考慮されません。
アナグラムとは、同じ文字を並べ替えた文字列(同じ文字列を含む)を指します。
分析:
ウィンドウの状態はウィンドウ内の文字数とカウント、
ターゲット状態はパターン文字列 p の文字数とカウント、
展開条件は無条件 (True)、
収縮条件はウィンドウのサイズがそれを超えることです。与えられたパターン文字列 p の長さ, 右は一度に 1 スペースしか移動しないので, この条件は一度に 1 スペースしか移動しない. 拡張時にウィンドウの状態を更新する: キーのカウントに 1 を加えて, 作成する
.キーがない場合;
縮小時にウィンドウの状態を更新します: カウントを -1 に設定し、0 の場合は削除します。
def findAnagrams(self, s: str, p: str) -> List[int]:
string_length = len(s)
# define a sliding window [left, right)
left = 0
right = 0
# define window states
window_char_and_count = dict()
# define target states
target_char_and_count = dict()
for _ in p:
target_char_and_count[_] = target_char_and_count.setdefault(_, 0) + 1
# define resulting state
result = []
# get_expand_condition() = True
while right < string_length:
# expand window (moving right pointer)
expand_elem = s[right]
right += 1
# update window states (expand) with expand_elem
window_char_and_count[expand_elem] = window_char_and_count.setdefault(expand_elem, 0) + 1
# get_shrink_condition() => size of slideing window == len(p)
while right - left > len(p):
shrink_elem = s[left]
left += 1
# update window states (shrink) with shrink_elem
if shrink_elem in window_char_and_count:
window_char_and_count[shrink_elem] -= 1
if window_char_and_count[shrink_elem] == 0:
window_char_and_count.pop(shrink_elem)
# update resulting state
if window_char_and_count == target_char_and_count:
result.append(left)
return result
LC-76
文字列 s と文字列 t が与えられます。t のすべての文字をカバーする s の最小の部分文字列を返します。t のすべての文字をカバーする部分文字列が s にない場合は、空の文字列 "" を返します。
分析:
ウィンドウの状態は、ウィンドウ内の文字とカウント、およびターゲット条件を満たす文字の種類です。ターゲットの
状態は、パターン文字列 p の文字、カウント、および文字の種類です。縮約条件
は、文字の種類と数が要件を満たしている;
拡張時にウィンドウの状態を更新する: 文字数 +1、この文字がターゲット条件の要件を満たしている場合、ターゲット条件を満たしている文字の種類 +1;
の場合にウィンドウの状態を更新する縮小: 文字数 -1、この文字がターゲット条件の要件に戻る場合、ターゲット条件を満たす文字の種類 charkind-1;
def minWindow(self, s: str, t: str) -> str:
s_len = len(s)
# define a sliding window [left, right)
left = 0
right = 0
# define window states
window_char_and_count = dict()
window_valid_distinct_char_count = 0
# define target states
target_char_and_count = dict()
for ch in t:
target_char_and_count[ch] = target_char_and_count.setdefault(ch, 0) + 1
target_distinct_char_count = len(target_char_and_count)
# define resulting state
result = None
while right < s_len:
# expand the window
expand_elem = s[right]
right += 1
# update window state (expand) with expand_elem
if expand_elem in target_char_and_count:
window_char_and_count[expand_elem] = 1 + window_char_and_count.setdefault(expand_elem, 0)
if window_char_and_count[expand_elem] == target_char_and_count[expand_elem]:
window_valid_distinct_char_count += 1
# get_shrink_condition() => window_valid_distinct_char_count == target_distinct_char_count
while window_valid_distinct_char_count == target_distinct_char_count:
# update resulting state
if not result:
result = s[left:right]
else:
if len(result) > right - left:
result = s[left:right]
# shrink the window
shrink_elem = s[left]
left += 1
# update window state (shrink)
if shrink_elem in window_char_and_count:
if window_char_and_count[shrink_elem] == target_char_and_count[shrink_elem]:
window_valid_distinct_char_count -= 1
window_char_and_count[shrink_elem] -= 1
if window_char_and_count[shrink_elem] == 0:
window_char_and_count.pop(shrink_elem)
if result == None:
return ""
else:
return result
LC-713
整数配列 nums と整数 k が与えられた場合、部分配列内のすべての要素の積が厳密に k より小さい連続する部分配列の数を返してください。
分析:
ウィンドウの状態は、ウィンドウ内の数値の積です。
縮小条件は、積が指定された値以上であり、ウィンドウ内に少なくとも 1 つの要素があることです。
拡大時にウィンドウの状態を更新します。 : 製品の更新 *= 拡張要素;
縮小時にウィンドウの状態を更新: 製品の更新/ = 縮小要素;
def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
n_len = len(nums)
# define a sliding window [left, right)
left = 0
right = 0
# define window states
window = 1
# define resulting state
result = 0
while right < n_len:
# expand window
expand_elem = nums[right]
right += 1
# update window state
window *= expand_elem
while window >= k and right > left:
# shrink window
shrink_elem = nums[left]
left += 1
# update window state
window /= shrink_elem
# update resulting state
result += right - left
return result
LC-1438
整数配列 nums と制限を表す整数制限が与えられた場合、最長の連続部分配列の長さを返してください。部分配列内の任意の 2 つの要素間の絶対差は、制限以下でなければなりません。
分析:
ウィンドウの状態はウィンドウ内の最大値と最小値ですが、最大値と最小値はスライド処理中に更新する必要があるため、2 つの単調キューが必要です; 収縮条件は
絶対ウィンドウ内の最大値と最小値の差が制限より大きい;
拡張時にウィンドウ状態を更新する: 最新の最大値と最小値を取得するためにモノトニック キューを更新する;
縮小時にウィンドウ状態を更新する: 取得するためにモノトニック キューを更新する最新の最大値と最小値;
def longestSubarray(self, nums: List[int], limit: int) -> int:
nums_length = len(nums)
# define sliding window [left, right)
left = 0
right = 0
# define window states
window_min_queue = collections.deque()
window_max_queue = collections.deque()
# define resulting state
result = 0
while right < nums_length:
expand_elem = nums[right]
right += 1
# add to mono queue - min values
while window_min_queue.__len__() > 0 and window_min_queue[-1] > expand_elem:
window_min_queue.pop()
window_min_queue.append(expand_elem)
# add to mono queue - max values
while window_max_queue.__len__() > 0 and window_max_queue[-1] < expand_elem:
window_max_queue.pop()
window_max_queue.append(expand_elem)
while window_max_queue[0] - window_min_queue[0] > limit:
shrink_elem = nums[left]
left += 1
# remove from mono queue
if shrink_elem == window_min_queue[0]:
window_min_queue.popleft()
if shrink_elem == window_max_queue[0]:
window_max_queue.popleft()
result = max(result, right - left)
return result
LC-424
文字列 s と整数 k が与えられます。文字列内の任意の文字を選択して、他の大文字の英字に変更できます。この操作は最大 k 回実行できます。
上記の操作を実行した後、同じ文字を含む最長の部分文字列の長さを返します。
分析:
ウィンドウの状態は、ウィンドウ内の文字の種類と量の辞書です;
収縮条件は、ウィンドウ内の各文字 + k の種類がウィンドウの長さよりも小さいことです;
拡張時にウィンドウの状態を更新します: 文字数+1を更新、辞書項目が無ければ作成;
縮小時のウィンドウ状態を更新: 文字数-1を更新、0なら辞書項目を削除;
縮小条件が不明なので注意つまり、flag 変数を使用して個別に格納し、while 値で更新する必要があります。
def characterReplacement(self, s: str, k: int) -> int:
# sliding window [left,right)
left = 0
right = 0
s_len = len(s)
# window state is character and counts
window = dict()
# resulting state
result = 0
while right < s_len:
expand_elem = s[right]
window[expand_elem] = window.setdefault(expand_elem, 0) + 1
right += 1
# get_shrink_condition() is complex and should be replaced with flag
flag = False
while flag == False: # while False condition
window_length = right - left # get condition
for winKey in window:
if window_length - window[winKey] <= k:
flag = True
result = max(result, window_length)
# shrink window
if flag == False:
shrink_elem = s[left]
left += 1
window[shrink_elem] -= 1
if window[shrink_elem] == 0:
window.pop(shrink_elem)
return result
引き違い窓のバリエーション
# the iterable (list, str, ...) is "it"
# define two sliding windows [0:left) and (right:]
left = 0
right = 0
# expand window [0:left) (moving left pointer)
expand_elem = it[left]
left += 1
# shrink window [0:left) (moving left pointer)
left -= 1 # note that moving pointer first if move towards opposite side
shrink_elem = it[left]
# expand window (right:] (moving right pointer)
expand_elem = it[right]
right -= 1
# shrink window (right:] (moving right pointer)
right += 1 # note that moving pointer first if move towards opposite side
shrink_elem = it[right]
LC-1658
整数配列 nums と整数 x が与えられます。操作ごとに、配列 nums の左端または右端の要素を削除し、その要素の値を x から減算する必要があります。後続の操作のために配列を変更する必要があることに注意してください。
x を正確に 0 に減らすことができる場合は最小のオペランドを返し、それ以外の場合は -1 を返します。
分析:
実際、2 つのウィンドウ [0:左) と (右:] の値は合計されて x の最小ウィンドウ長になるだけなので、一般的な考え方は次のとおりです。
- 合計が x に達するかそれを超えるまで、最初に左側のウィンドウを拡大します
- 左ウィンドウを一度に 1 要素ずつ縮小する
- 合計が x に達するかそれを超えるまで、右側のウィンドウを拡張します
def minOperations(self, nums, x: int) -> int:
nums_length = len(nums)
# define two windows = [0:left) and (right:]
left = 0
right = nums_length - 1
# define window state (sum of nums in window)
window = 0
# define resulting state
result = nums_length + 1
#-----------expand [0:left) till sum reaches x---------
# condition: sum of window < x
# restriction: left < nums_length
while window < x and left < nums_length:
expand_elem = nums[left]
window += expand_elem
left += 1
# update resulting state
if window == x:
result = min(result, left)
#-------------shrink [0:left) and expand (right:]----------
# restriction: two windows cannot overlap (when left = right + 1, the two windows [0:right+1) and (right:] ==> [0:right] and (right:] ==> [0:]
while right >= left - 1:
expand_elem = nums[right]
window += expand_elem
right -= 1
# condition sum of window > x
# restriction: left > 0
while window > x and left > 0:
left -= 1 # shrink, deduce index first and then remove element
shrink_elem = nums[left]
window -= shrink_elem
# update resulting state
if window == x:
result = min(result, left + nums_length - 1 - right)
if result == nums_length + 1:
return -1
else:
return result
LC-209
n 個の正の整数の配列と正の整数のターゲットが与えられます。
この配列で合計 ≥ ターゲットを満たす最小の長さを持つ連続部分配列 [numsl, numsl+1, …, numsr-1, numsr] を見つけ、その長さを返します。一致する部分配列が存在しない場合は 0 を返します。
分析案1:
スライディングウィンドウの左側を固定し、右側を条件を満たすまで拡大し、左側を1グリッドずつ縮小し、右側を拡大し続ける
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
nums_length = len(nums)
# sliding window [left,right)
left = 0
right = 0
# window state = sum of nums in window
window = 0
# resulting state
result = nums_length + 1
while left <= right and left < nums_length:
# expand right side till sum>=target
while right < nums_length and window < target:
expand_elem = nums[right]
window += expand_elem
right += 1
if window >= target:
result = min(result, right - left)
# shrink left side
shrink_elem = nums[left]
window -= shrink_elem
left += 1
if result == nums_length + 1:
return 0
else:
return result
分析アイデア 2:
通常は右辺を拡張し、条件が満たされたときに左辺を縮小し始め、その都度結果を更新する
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
nums_length = len(nums)
left = 0
right = 0
window = 0
result = nums_length + 1
while right < nums_length:
expand_elem = nums[right]
window += expand_elem
right += 1
while window >= target:
result = min(result, right - left)
shrink_elem = nums[left]
window -= shrink_elem
left += 1
if result == nums_length + 1:
return 0
else:
return result