Searching
Sliding Window
Overview
When to Use This Pattern
- When asked to find the minimum/maximum subarray that meets certain requirements, a brute-force approach will have to look at O(N^2) subarrays. Also, checking whether a subarray meets the requirement in the inner loop can also take O(N).
- However, when requirement has the property, that if subarray [i:j] does not meet the requirements, then for all k <= i, l >= j subarray [k:l] also fails to meet the requirements, we can apply the sliding window approach to reduce time complexity.
- First, we set left=0, right=0. We expand the subarray to the right, until subarray [left, right] meets the requirement and [left, right-1] does not. Then we shrink the subarray from left. In a brute-force approach, each time we increment
left
, we need to look at all the subarray starting withleft
. But because [left, right-1] does not meet the requirement, for any j < right, [left, j] fails to meet the requirement, and for any j > j, the length of [i, k] is shorter than the previous subarray that meets the requirement - First, we set i=0, j=0; We expand the subarray to the right, until [i,j] doesn’t meet the requirement and [i, j-1] does. Then we shrink the subarray from left. In a brute-force approach, each time we increment
i
, we need to look at all the subarray. But if [i, j] does not meet the requirement, for any k > j, [i, k] fails to meet the requirement, and for any k < j, the length of [i, k] is shorter than the previous subarray that meets the requirement. So we can just keep increment i without making change to j, until subarray [i, j] meets the requirement again. - Furthermore, when using a sliding window approach, , and the requirement checking time for each subarray can be reduced to O(1)
- Time Complexity:
Code Pattern 1: Update maxlen on the first invalid subarray
left = 0
maxlen = 0
for (right = 0; right < nums.length; right++) // keep incrementing right if [left, right] is valid
if [left, right] is not valid // then [left, right - 1] is the longest valid subarray starting from the current left
maxlen = max(maxlen, left - right)
while ([left, right] is not valid) // keep incrementing left until [left, right] is valid again
left++;
maxlen = max(maxlen, left - right) // when right is out of bound, [left, right-1] is valid, but might not yet be updated to maxlen, so update it to maxlen now
Code Pattern 2: Update maxlen on every valid subarray
left = 0
maxlen = 0
for (right = 0; right < nums.length; right++)
while ([left, right] is not valid)
left++;
maxlen = max(maxlen, left - right + 1) // we reach this statement for every right with its leftmost left that [left, right] is valid
Code pattern 3: non-decreasing window
- Since we are asked for subarray of maximum length, we can only pay attention to subarray which length is larger than the current maximum length. So when we trying to expand the window from the right, if it is still valid, we add max length by 1, and if it is invalid, we can simply shrink the window from left window once to maintain its window size, and try to expand the window again. And we can even get rid of the left pointer, because it equals to right - max_window_size.
#+BEGIN_SRC maxlen = 0 for (right = 0; right < nums.length; right++) if subarray is valid: left++ maxlen++ if subaary is not valid remove (right - maxlen) from subarray // we do not need to shrink the window to be valid since it will be smaller than the current maximum window anyway
More Efficient Shrinking
Longest window
Longest Subarray of Consecutive 1s after Replacing k 0’s
- Checking validity: Use an counter to track the numbers of 0’s in the subarray
- Non-decreasing window: we can use the Code pattern 3: non-decreasing window trick to reduce times of shrinking
Longest Substring with At Most K Distinct Characters
- Sliding Window property: If a substring has more than K distinct characters, then any other substring containing the substring also has more than K distint characters
- Checking validity: Use an hashmap to store the occurrences and check
map.size() <= k
to decide whether the window is valid - Non-decreasing window: we can use the Code pattern 3: non-decreasing window trick.
Longest Substring Without Repeating Characters
- Description: Given a string
s
, return ind the length of the longest substring without repeating characters. - Sliding window property: If a substring has repeating characters, then any other substring containing the substring also has repeating characters
- Checking validity: Use a hashset to store characters in the current substring. When expanding, if the character to add is in the hashset, then the new substring will have repeating characters and we should start shrinking. When shrinking, increment the left pointer and remove characters from the subset until the repeating character is removed.
- More efficient shrinking: In the previous approach we need to remove characters one by one until the repeating character is removed, but if you use a hashmap to store the index of the last occurrence of each character, you can move the left pointer straight to the next position after the last occurrence of the repeating character when shrinking. And we don’t need to explicitly remove characters from the hashmap, because having its index less than the left pointer implies not being in the current substring.
Longest Substring with the Same Character After K Replacement
- Description: Given a string
s
and an integerk
, return the length of the longest substring containing the same characters afterk
replacement - What it means: contain the same letter after k replacement means that for c that has max frequency, frequency(c) >= length(substring) - k~
- Sliding window property: If a substring has
length - maxFrequency > k
, then for any substring containing the substring: 1. If the character having the max frequency remains the same, because the length is longer,newLength - maxFrequency
is sill larger thank
; 2. If another character has overtaken the previous to have the max frequency, then the remaining characters at least contains all the characters in the previous substring other than the new max frequency letter which is larger thank
. - Checking validity: We use a hash map to keep track of the frequency of each in the window, and
maxFreqency
to keep track of the maximum frequency in the window. Because we need a largermaxFrequency
to get largermaxlen
, we can only updatemaxFrequency
when we encountered a larger frequency of a letter. - Checking validity: we can use the Code pattern 3: non-decreasing window trick. The final code can look like
maxlen = 0 for right = 0 to len(s) - 1 do freq[s[right]] += 1 max_freq = max(max_freq, freq[s[right]] if (maxlen + 1 - max_freq <= k) maxlen += 1 else freq[s[right - maxlen]] -= 1
Exercise
- Leetcode - 1004. Max Consecutive Ones III (Longest Subarray of Consecutive 1s after Replacin k 0’s)
- Leetcode - 1493. Longest Subarray of 1’s After Deleting One Element (Exactly same as the previous one with k=1, and we need to return maxlen-1 since it ask us to delete rather than replace)
- Leetcode - 340. Longest Substring with At Most K Distinct Characters
- Leetcode - 904. Fruit Into Baskets (Longest Subarray with At Most 2 Distinct Elements)
- Leetcode - 3. Longest Substring Without Repeating Characters
- Leetcode - 424. Longest Repeating Character Replacement (Longest Substring with the Same Character After K Replacement)
Minimum window
Minimum Size Subarray with a Given Sum
- Description: Given an array of positive integers
nums
and a positive integertarget
Return the minimal length of a subarray whose sum is greater or equal to target
- Because all nums[i] > 0, if sum([i, j]) < target, then for all k >= i, l <= j, sum[k, l] < target. With this property, we can use the sliding window approach to find the minimal length.
Minimum Substring that Contains Every Character of Another String
Exercise
- Leetcode - 209. Minimum Size Subarray Sum
- Leetcode - 76. Minimum Window Substring Grok
- Leetcode - 30. Substring with Concatenation of All Words Grok
Fixed Size Window
- Leetcode - 643. Maximum Average Subarray I
- Leetcode - 1456. Maximum Number of Vowels in a Substring of Given Length
- Leetcode - 567. Permutation in String link Grok
- Leetcode - 438. Find All Anagrams in a String link Grok
Two Pointers
Find numbers in a array that sums to a target
2-Sum (Sorted Array)
- Solution: Because the array is in ascending order, if the sum of the two pointers is less than the target value, only the left pointer can move to the right. If the sum is greater than the target value, only the right pointer can move to the left. Neither pointer needs to backtrack, so the search time is O(n).
2-Sum (Unsorted)
- Sort the array, then perform Two Sum (Sorted Array). Sorting takes O(nlogn), thus the time complexity is O(nlogn)
- We can also use a hashset to solve this problem. Iterate through the
Number of 2-sum-pairs we can take from an array
- Same as previous, but when
nums[left] + nums[right]
equalstarget
, we also increment thecounter
.
3-Sum
- Description: Given an integer array
nums
. Find all non-duplicate triplets[nums[i], nums[j], nums[k]]
such thati != j
,i != k
, andj != k
, andnums[i] + nums[j] + nums[k]
== 0 - First sort the array, iterate through the array with variale
i
, then you can transform this problem to the 2 Sum problem onnums[i+1:]
to findj
andk
- Avoiding duplicates: When iterating though the array and moving the left pointer, check whether the number is distint from the previous number before proceed
- Time Complexity: Sort O(N), 2Sum O(N), O(nlogn) + N*O(N) = O(N^2)
3-sum-closest
- Same as 3-sum, we increment
j
on the sum is less thantarget
and decrementk
on the sum is grater thantarget
. But each time we computediff
asabs(nums[i] + nums[j] + nums[k] - target)
, and ifdiff
is less thanminDiff
, we updateres
asnums[i] + nums[j] + nums[k]
.
Number of 3-sum-smaller-triples exists in an array
4-sum
- 2-sum plus backtracking
Exercise
- Leetcode - 167. Two Sum II - Input Array Is Sorted (Find a pair)
- Leetcode - 1679. Max Number of K-Sum Pairs (Find number of pairs we can take from the array)
- Leetcode - 15. 3Sum
- Leetcode - 259. 3Sum Smaller
- Leetcode - 16. 3Sum Closest
- Leetcode - 18. 4Sum grok
Sort mountain array in O(n)
Sort Squares of a Sorted Array in O(n)
- Description: Given a non-decreasing array nums, return the array of the squares of each number sorted in non-decreasing order
Exercise
- Leetcode - 977. Squares of a Sorted Array
When the value we want to optimize is limited by one of two pointers
Container With Most Water
- Description: Given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the ith line are (i, 0) and (i, height[i]). Find two lines that together with the x-axis form a container, such that the container contains the most water. Return the maximum amount of water a container can store.
- We observe that the maximum amount of water it can hold is limited by the shorter line. And for the same shorter line, two lines closer to each other holds less water. So we can start with two pointers at 0 and height.length - 1. Identify the pointer at the shorter line, and move the pointer towards the other pointer until it is longer than the line the other pointer is at, because by then it is possible to holder more water.
Exercise
- Leetcode - 11. Container With Most Water
In-place rearrangement of array/string
Move zeros elements to the end
- Traverse through the array, keep a pointer at the place non-zero elements should be placed next:
for i = 0 to nums.length - 1 do if nums[i] != 0 ~nums[nonZeroDest++] = nums[i]
. Then we fill the rest of the array nums[nonZeroDest:] with 0s. - We can also do a swapping approach, which automatically moves all zeros to the end.
Remove duplicate elements in a sorted array
- Traverse through the array, keep a pointer at the place each unique elements should be placed next:
dest = 1; for i = 1 to nums.length - 1 do if nums[i] != nums[i-1] ~nums[dest++] = nums[i]
.
Exercise
- Leetcode - 283. Move Zeroes
- Leetcode - 26. Remove Duplicates from Sorted Array
Matching
Is Subsequence
s
is a subsequence oft
if all elements of s have occurred in order int
. We use a pointerp
to track up to which index ins
we has matched, and we traverse thought
with indexi
, incrementp
if we have matcheds[p]
andt[i]
. And if we matched all elements int
, it is a subsequence oft
, otherwise it is not.
Is Palindrome
Exercise
- Leetcode - 392. Is Subsequence
- Leetcode - 125. Valid Palindrome
TODO grok
- Leetcode - 713. Subarray Product Less Than K link grok
- Leetcode - 75. Sort Colors grok
- Leetcode - 844. Backspace String Compare link grok
- Leetcode - 581. Shortest Unsorted Continuous Subarray link grok
Binary Search
Normal Binary Search
Algorithm
int lo = 0, hi = nums.length;
while (lo <= hi) {
int mid = (lo + hi) / 2;
if (nums[mid] == target)
return mid;
if (target > nums[mid])
lo = mid + 1;
else
hi = mid - 1;
}
return -1;
Proof
- We maintain a loop variant that
nums[0..lo-1] < target
, andnums[hi+1..nums.length-1] > target
. So whenlo>hi
and the loop terminates,(lo-1)+1<=(hi+1)
, so elements the entire array are either less than or greater thantarget
. Sotarget
is not in the array.
Exercise
- Leetcode - 704. Binary Search link
- Leetcode - 374. Guess Number Higher or Lower
Find Largest-Smaller/Smallest-Greater Element / Find First/Last Position of Element
Find the smallest greater element
- Desciption: Given an array of non-decreasing numbers, and a target. Return the smallest number in the array that is greater than the target.
- Solution: In normal binary search we maintain a loop invariant that
nums[0..lo-1] < target
, andnums[hi+1..nums.length-1] > target
, and returns when when encounternums[mid] = target
. In this case, we are not looking fortarget
, but the smallest number that is greater thantarget
, so we can setlo = mid + 1
whennums[mid] = target
, then our loop invariant becomesnums[0..lo-1] <= target
. So when the loop terminates,nums[0..lo-1] <= target
,num[hi+1..] > target
. Also we can proof thatlo = hi + 1
, sonum[lo]
will be the smallest greater element.
Find the last position of element
- Solution: Follow the same approach, but return
nums[hi]
Find the largest smaller element
- Solution: Follow the similar approach, but set
hi = mid - 1
whennums[mid] = target
and returnnums[hi]
Find the first position of element
- Solution: Follow the same approach, but return
nums[lo]
Find the first and last position of element
- Solution: We can find the first and last position separately. We can also find one element first using binary search, and find the first position on
[lo, mid]
and the last position on[mid, hi]
Serach insert position
- Solution:
Exercise
- Leetcode - 744. Find Smallest Letter Greater Than Target link
- Leetcode - 35. Search Insert Position (Find First Position)
- Leetcode - 34. Find First and Last Position of Element in Sorted Array
Peak Element in a Mountain Array / Peak Element in a Array
Description
Solution
- In this problem we want to rule out [0..lo-1] that is ascending, and [hi+1..nums.length-1] that is descending.
Find in a Mountain Array
Find in a Mountain Array: Early Stopping and Cache
Exercise
- Leetcode - 852. Peak Index in a Mountain Array: link
- Leetcode - 1095. Find in Mountain Array: link
- Leetcode - 162. Find Peak Element
Rotated array
Exercise
- Leetcode - 153. Find Minimum in Rotated Sorted Array
- Leetcode - 154. Find Minimum in Rotated Sorted Array II
- Leetcode - 33. Search in Rotated Sorted Array
K Closest Elements
- Leetcode - 658. Find K Closest Elements: link grok (with K=1)
Sorted Array with Unknown Size
Exercise
Others
- 53.2 0 ~ n-1中缺失的数字 268. Missing Number
- 53.3 排序数组中数值和下标相等的元素
Cyclic Sort
Overview
Missing Number
Solution: Cyclic Sort
Solution: Bitwise XOR
Exercise
TODO Grokking 未学习
- 448. Find All Numbers Disappeared in an Array link grok
- 287. Find the Duplicate Number grok
- 442. Find All Duplicates in an Array link grok
- Find the Corrupt Pair # Combine of “Find the Duplicate Number” and “Missing Number”
- 41. First Missing Positive grok
- 1539. Kth Missing Positive Number link grok