Sliding Window Questions Part 1 ================================ 146. (LC 567) Permutation in String ---------------------------------------- `567. Permutation in String `_ Medium | **Solution 1** [:ref:`7 `] | Key points: | 1)Sliding window | 2)Hash tables, just compare hash tables. | -we make hash tables using collections.Counter() | Of the full s1. | Of the part of s2, window size, which is len of s1. | -we can compare dicts! | if cnt1 == cnt2: | -Move the window. Iterate starting from len(s1). | (Modify hash tables. Simply +=1 to the value at rp, -= to the key in hash at lp) :: from collections import Counter class Solution: def checkInclusion(self, s1: str, s2: str) -> bool: n = len(s1) cnt1 = Counter(s1) cnt2 = Counter(s2[:n]) if cnt1 == cnt2: return True for i in range(n, len(s2)): cnt2[s2[i]] += 1 cnt2[s2[i - n]] -= 1 if cnt1 == cnt2: return True return False ### My remake def f(s1, s2): if len(s1) > len(s2): return False cnts1 = collections.Counter(s1) cnts2 = collections.Counter(s2[: len(s1)]) if cnts1 == cnts2: return True lp = 0 for rp in range(len(s1), len(s2)): cnts2[s2[rp]] += 1 cnts2[s2[lp]] -= 1 lp += 1 if cnts1 == cnts2: return True return False | **Note** | we use special properties of dicts, created by collections.Counter(iterable) | 1) | cnts2[s2[rp]] += 1 | Counter makes a dict with properties like defaultdict. So we can add to non-existent keys, | which doesn't work in a regular dict. >>> import collections >>> s='abc' >>> cnt=collections.Counter(s) >>> cnt['f']+=1 #'f' is not in cnt, but we add to it OK >>> cnt Counter({'a': 1, 'b': 1, 'c': 1, 'f': 1}) >>> d={'b':1} >>> d['c']+=1 #Nope in reg dict Traceback (most recent call last): File "", line 1, in KeyError: 'c' | 2) | ``if cnts1 == cnts2:`` | cnts like cnt1={'a':1, 'd':0}, cnt2={'a':1} will be equal, | again which is not the case for normal dict. | **Solution 2** (If you don't want to use Python tricks.) [:ref:`10 `] | -What is your sliding window going to be? | The size of your window in s2 (longer string) is the len of s1 (shorter string). | We will have 2 hash maps: for chars in s1 and those in s2. | Arrays are used here: [0]*26 | In each hash/array we will be tracking the counts for all 26 chars. | No char in string will have count 0, char present, count=1 | Variable matches=0 | E.g. | s1='abc', s2='baxyzabc' | window 1 = 'bax' | matches=24 | window 2 = 'axy' | matches=22 | ..Stop when we have matches=26 :: class Solution: def checkInclusion(self, s1: str, s2: str) -> bool: if len(s1) > len(s2): return False s1Count, s2Count = [0] * 26, [0] * 26 for i in range(len(s1)): s1Count[ord(s1[i]) - ord("a")] += 1 #fills in hash/array for s1 s2Count[ord(s2[i]) - ord("a")] += 1 #fills in for window1 of s2 matches = 0 for i in range(26): #matches for window1,yes iterate through all 26 values matches += 1 if s1Count[i] == s2Count[i] else 0 l = 0 for r in range(len(s1), len(s2)): #<==Move RP if matches == 26: return True index = ord(s2[r]) - ord("a") #BLOCK 1 for rp s2Count[index] += 1 if s1Count[index] == s2Count[index]: matches += 1 elif s1Count[index] + 1 == s2Count[index]: #if in s1 'a'=0, in s2 'a'=1 matches -= 1 index = ord(s2[l]) - ord("a") #BLOCK 2 for lp s2Count[index] -= 1 if s1Count[index] == s2Count[index]: matches += 1 elif s1Count[index] - 1 == s2Count[index]: #if in s1 we had 'a'=1, now in s2 'a'=0 matches -= 1 l += 1 #<==Move LP return matches == 26 ### My V (LC accepted 78,84%) def f(s1, s2): d = {} for c in s1: d[c] = 1 + d.get(c, 0) total_cnt = sum([v for v in d.values()]) lp = rp = 0 # for rp in range(len(s2)): NOPE while rp < len(s2): if s2[rp] in d and d[s2[rp]] > 0: d[s2[rp]] -= 1 total_cnt -= 1 if total_cnt == 0: return True else: if s2[rp] not in d: #s2[rp] not in D, build from scratch while lp != rp: d[s2[lp]] += 1 lp += 1 total_cnt += 1 else: #s2[rp] in D, don't build from scratch, just try moving lp d[s2[lp]] += 1 total_cnt += 1 rp -= 1 #Move rp BACK and try again lp += 1 rp += 1 #DONT FORGET return False | Seems to be a classic sliding window. | One tricky edge case: | s1 = "adc" | s2 = "dcda" #True has s1 substring :: # d c d a # L R | Case 1) value at R in hash and D[s2[R]]> 0, so L stays, R +=1 | Case 2) value at R not in hash, just move L all the way to R (L=R), start looking from scratch. | -> spec. case 3) value at R in hash, but D[s2[R]]=0. | Then L+1, R-1 and try again. 147 (LC 239) Sliding Window Maximum ------------------------------------- `239. Sliding Window Maximum `_ Hard | The example given in the task is misleading (makes you think that mono increasing stack is enough). | A better one that covers more cases: | nums = [0, 5, 3, 2, 1, 2, 4, 4] | **Keys** | -use dequeue, mono decreasing, store (value, index) | E.g.: q = [(5,1), (3,2), (2,3)] (so the greatest value is always at q[0]) | -pop from dequeue if current value greater than previous values, q[-1] | cur=2, q=[(2,3), (1,4)], pop val=1 => q=[(2,3), [2,5]] | -popleft if index of the value on the left of q (q[0] i.e. greatest) is beyond our k window | cur i = 4, q = [(5,1), (3,2), (2,3)], pop (5,1) | -append to ans value at q[0], first value in q is always the greatest. QUEUE (my V):: ### My V (LC accepted 30, 50) class Solution: def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: ans = [] q = collections.deque() for i, n in enumerate(nums): while q and q[-1][0] < n: q.pop() while q and q[0][1] <= i-k: q.popleft() q.append((n, i)) if i >= k-1: ans.append(q[0][0]) return ans | HEAP | -Using min heap, store (value, index) pairs. | Because we may have a situation like this: | nums = [9,10,9,-7,-4,-8,2,-6] | k = 5 | Storing just values in heap, would give output: [10,10,9,9] instead of [10,10,9,2]. | (We never deleted 2nd to greatest (9) that we've passed.) | To delete all values that we've passed, store also indices in heap. :: ### My V (LC accepted 10, 5) class Solution: def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: q = [(-n, i) for i, n in enumerate(nums[:k-1])] heapq.heapify(q) ans = [] for i in range(k-1, len(nums)): heapq.heappush(q, (-nums[i], i)) lp = i+1-k while q[0][1] < lp: heapq.heappop(q) ans.append(-q[0][0]) return ans Solution 2 [:ref:`7 `]:: class Solution: def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: q = [(-v, i) for i, v in enumerate(nums[: k - 1])] heapify(q) ans = [] for i in range(k - 1, len(nums)): heappush(q, (-nums[i], i)) while q[0][1] <= i - k: heappop(q) ans.append(-q[0][0]) return ans 148 (LC 904) Fruit Into Baskets ---------------------------------- `LC 904. Fruit Into Baskets `_ Medium | **Keys** | -note, 2 kinds of fruit, so when dict has more than 2 keys: collect result, pop from | dict value at lp. | -Thanks to defaultdict(int), we can add value at rp to dict every time without testing | for presence. | -Then subtract values at key a[lp], when d[a[lp]]==0, just pop it entirely from the dict. Solution 1 [:ref:`10 `] LC accepted :: import collections def f(a): d = collections.defaultdict(int) lp = 0 cur_len = 0 max_len = 0 for rp in range(len(a)): d[a[rp]] += 1 cur_len += 1 while len(d) > 2: value = a[lp] d[value] -= 1 cur_len -= 1 lp += 1 if d[value] == 0: d.pop(value) max_len = max(max_len, cur_len) return max_len 149 (LC 1456) Maximum Number of Vowels in a Substring of Given Length ------------------------------------------------------------------------- `1456. Maximum Number of Vowels in a Substring of Given Length `_ Medium :: # My V (LC accepted 75,90%) class Solution: def maxVowels(self, s: str, k: int) -> int: vowels = {'a': 1, 'e': 1, 'i': 1, 'o':1, 'u':1} L, cnt, cnt_cur = 0,0,0 for R in range(len(s)): if s[R] in vowels: cnt_cur+=1 cnt = max(cnt, cnt_cur) if (R-L+1) == k: if s[L] in vowels: cnt_cur -=1 L+=1 return cnt Solution 1 [:ref:`10 `] :: class Solution: def maxVowels(self, s: str, k: int) -> int: l, res, total = 0, 0, 0 vowels = "aeiou" for r in range(len(s)): if s[r] in vowels: total += 1 if (r - l + 1) > k: if s[l] in vowels: total -= 1 l += 1 res = max(res, total) return res 150. (LC 1004) Max Consecutive Ones III ----------------------------------------- `1004. Max Consecutive Ones III `_ Medium | **Keys** | -Sliding window. | -When p2 is at the last item, be it 0 or 1, flips being == to k or not, calculate max_len always. | (Also note that then len=p2-p1+1.) :: ### My V2 (LC accepted 72,93%) def max_consequtive_ones(nums, k): p1 = p2 = 0 max_len = 0 flips = 0 for p2 in range(len(nums)): if nums[p2] == 0: if flips < k: flips += 1 elif flips == k: max_len = max(max_len, p2 - p1) # flips+=1 while nums[p1] == 1: p1 += 1 p1 += 1 # flips -= 1 if p2 == len(nums) - 1: #important to have IF here not elif, when k is very large max_len = max(max_len, p2 - p1 + 1) # p2 - p1 + 1 return max_len **Solution 1** [:ref:`7 `] :: class Solution: def longestOnes(self, nums: List[int], k: int) -> int: ans = 0 cnt = j = 0 for i, v in enumerate(nums): if v == 0: cnt += 1 while cnt > k: if nums[j] == 0: cnt -= 1 j += 1 ans = max(ans, i - j + 1) return ans 151. (LC 1493) Longest Subarray of 1's After Deleting One Element --------------------------------------------------------------------- `1493. Longest Subarray of 1's After Deleting One Element `_ Medium | My note: | it's not that you are allowed to delete one element, you must, even if it is a 1. | **Solution 1** | (Recording zi, index at which we encounter 0 each time. To then move the lp there.) :: def f(a): lp, ans, zi = 0, 0, -1 for rp in range(len(a)): if a[rp] == 0: lp = zi + 1 zi = rp ans = max(ans, rp - lp) #** return ans | #**calculate here to take care of all the edge cases: | -array ends with 1s, | -there are no 0s in array. | | nums = [0, 1, 1, 1, 0, 1, 1, 0, 1] #5 | nums = [0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1] #6 | nums = [1, 1, 1] #2 | print(f(nums)) **Solution 2** (Keep a count of how many zeros we encountered. When cnt > 1, shrink the window, i.e. move lp till cnt is again == 1. Because we can have one 0 in the window.) :: def f(a): lp, ans, zcnt = 0, 0, 0 for rp in range(len(a)): if a[rp] == 0: zcnt += 1 while zcnt > 1: if a[lp] == 0: zcnt -= 1 lp += 1 ans = max(ans, rp - lp) return ans