スライディング ウィンドウの問題分析

スライディング ウィンドウの問題分析

スライディング ウィンドウ フレームワークを使用して、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

おすすめ

転載: blog.csdn.net/O_1CxH/article/details/126813986