Number of Subarrays That Match a Pattern II
HardPrompt
You are given a 0-indexed integer array nums of size n, and a 0-indexed integer array pattern of size m consisting of integers -1, 0, and 1.
A subarray nums[i..j] of size m + 1 is said to match the pattern if the following conditions hold for each element pattern[k]:
nums[i + k + 1] > nums[i + k]ifpattern[k] == 1.nums[i + k + 1] == nums[i + k]ifpattern[k] == 0.nums[i + k + 1] < nums[i + k]ifpattern[k] == -1.
Return the count of subarrays in nums that match the pattern.
Example 1:
Input: nums = [1,2,3,4,5,6], pattern = [1,1]
Output: 4
Explanation: The pattern [1,1] indicates that we are looking for strictly increasing subarrays of size 3. In the array nums, the subarrays [1,2,3], [2,3,4], [3,4,5], and [4,5,6] match this pattern.
Hence, there are 4 subarrays in nums that match the pattern.Example 2:
Input: nums = [1,4,4,1,3,5,5,3], pattern = [1,0,-1]
Output: 2
Explanation: Here, the pattern [1,0,-1] indicates that we are looking for a sequence where the first number is smaller than the second, the second is equal to the third, and the third is greater than the fourth. In the array nums, the subarrays [1,4,4,1], and [3,5,5,3] match this pattern.
Hence, there are 2 subarrays in nums that match the pattern.
Constraints:
2 <= n == nums.length <= 1061 <= nums[i] <= 1091 <= m == pattern.length < n-1 <= pattern[i] <= 1
Approaches
2 approaches with complexity analysis and trade-offs.
This approach first transforms the input array nums into an intermediate array representing the relationships between adjacent elements, as defined by the problem. Then, it uses a straightforward, brute-force method to find matches. It iterates through every possible starting position of a subarray of the required length in the transformed array and checks if it is identical to the pattern array.
Algorithm
- Create a new integer array, let's call it
transformed_nums, of sizen-1. - Iterate from
i = 0ton-2to populatetransformed_nums:- If
nums[i+1] > nums[i], settransformed_nums[i] = 1. - If
nums[i+1] == nums[i], settransformed_nums[i] = 0. - If
nums[i+1] < nums[i], settransformed_nums[i] = -1.
- If
- Initialize a counter
countto 0. - Iterate through
transformed_numswith a sliding window of sizem. The loop runs fromi = 0to(n-1) - m. - For each starting position
i, start a second loop to compare the subarraytransformed_nums[i...i+m-1]with thepatternarray element by element. - If all
melements match, increment thecount. - After checking all possible starting positions, return
count.
Walkthrough
The core idea is to simplify the problem by first converting the nums array into a format that directly corresponds to the pattern. We create a new array, let's call it transformed_nums, of size n-1. Each element transformed_nums[i] will store 1, 0, or -1 based on the comparison between nums[i+1] and nums[i].
Once we have this transformed_nums array, the problem reduces to finding the number of times the pattern array appears as a contiguous subarray within transformed_nums.
The brute-force method involves two nested loops. The outer loop iterates through all possible starting indices i for a subarray of length m in transformed_nums. The inner loop then compares the subarray starting at i with the pattern array, element by element. If a complete match is found, we increment a counter.
class Solution { public int countMatchingSubarrays(int[] nums, int[] pattern) { int n = nums.length; int m = pattern.length; // Step 1: Transform the nums array int[] transformed_nums = new int[n - 1]; for (int i = 0; i < n - 1; i++) { if (nums[i + 1] > nums[i]) { transformed_nums[i] = 1; } else if (nums[i + 1] < nums[i]) { transformed_nums[i] = -1; } else { transformed_nums[i] = 0; } } // Step 2: Brute-force search for the pattern int count = 0; int transformed_len = n - 1; for (int i = 0; i <= transformed_len - m; i++) { boolean match = true; for (int j = 0; j < m; j++) { if (transformed_nums[i + j] != pattern[j]) { match = false; break; } } if (match) { count++; } } return count; }}Complexity
Time
O(n * m). The initial transformation of the `nums` array takes O(n) time. The subsequent search involves an outer loop that runs up to `n-m` times and an inner loop that runs `m` times, resulting in a time complexity of O((n-m) * m). The total complexity is dominated by the search part.
Space
O(n). We need to create the `transformed_nums` array of size `n-1` to store the relationships.
Trade-offs
Pros
Simple to understand and implement.
Works correctly for small inputs.
Cons
The time complexity of O(n*m) is too slow for the given constraints (n up to 10^6), leading to a 'Time Limit Exceeded' (TLE) error on larger test cases.
This approach reframes the problem as a classic string matching task and solves it using the highly efficient Knuth-Morris-Pratt (KMP) algorithm. After an initial O(n) transformation of the nums array into a sequence of relations (our text), KMP can find all occurrences of the pattern in this text in linear time, i.e., O(n + m). This is achieved by pre-processing the pattern to build a lookup table (the LPS array) that helps to avoid redundant comparisons during the search phase.
Algorithm
- First, transform the
numsarray into atextarray of sizen-1based on the relationships between adjacent elements, just like in the brute-force approach. This takes O(n) time. - Preprocessing: Compute the Longest Proper Prefix Suffix (LPS) array for the
pattern. Thelps[i]stores the length of the longest proper prefix ofpattern[0...i]that is also a suffix ofpattern[0...i]. This can be done in O(m) time. - Searching: Use two pointers,
ifor thetextarray andjfor thepatternarray, to find matches.- Iterate through the
textwith pointeri. - If
text[i]matchespattern[j], increment bothiandj. - If
jreachesm, a full match is found. Increment the count and updatejusing the LPS array (j = lps[j-1]) to find the next possible overlapping match without re-scanning. - If a mismatch occurs (
text[i] != pattern[j]), instead of resetting, use the LPS array to smartly shift the pattern. Setj = lps[j-1]. This avoids movingibackward, ensuring linear time performance. Ifjis already 0, simply incrementi.
- Iterate through the
- The search phase takes O(n) time. Return the total count.
Walkthrough
The problem can be efficiently solved by converting it into a string matching problem. First, we transform the nums array into a text array of size n-1, where text[i] represents the relationship (1, 0, or -1) between nums[i+1] and nums[i]. Now, we need to find the number of occurrences of pattern in text.
The KMP algorithm is perfect for this. It consists of two main parts:
-
LPS Array Computation: We pre-process the
patternto create an LPS (Longest Proper Prefix Suffix) array.lps[i]holds the length of the longest proper prefix ofpattern[0...i]which is also a suffix ofpattern[0...i]. This array allows the algorithm to 'know' where to resume the search after a mismatch, effectively skipping characters that are guaranteed to match. -
KMP Search: We then iterate through the
textandpatternusing two pointers. When a mismatch occurs, we consult the LPS array to find the length of the next best partial match and shift the pattern pointer accordingly, without ever moving the text pointer backward. This clever use of the LPS array eliminates the repeated comparisons that make the brute-force approach slow, guaranteeing a linear time complexity.
class Solution { public int countMatchingSubarrays(int[] nums, int[] pattern) { int n = nums.length; int m = pattern.length; // Step 1: Transform the nums array into a 'text' array int[] text = new int[n - 1]; for (int i = 0; i < n - 1; i++) { if (nums[i + 1] > nums[i]) { text[i] = 1; } else if (nums[i + 1] < nums[i]) { text[i] = -1; } else { text[i] = 0; } } // Step 2: Use KMP algorithm to find pattern in text int[] lps = computeLPS(pattern); int count = 0; int i = 0; // pointer for text int j = 0; // pointer for pattern while (i < text.length) { if (pattern[j] == text[i]) { i++; j++; } if (j == m) { count++; j = lps[j - 1]; // Continue searching for next match } else if (i < text.length && pattern[j] != text[i]) { if (j != 0) { j = lps[j - 1]; } else { i++; } } } return count; } // Helper function to compute the LPS array private int[] computeLPS(int[] pattern) { int m = pattern.length; int[] lps = new int[m]; int length = 0; // length of the previous longest prefix suffix int i = 1; lps[0] = 0; // lps[0] is always 0 while (i < m) { if (pattern[i] == pattern[length]) { length++; lps[i] = length; i++; } else { if (length != 0) { length = lps[length - 1]; } else { lps[i] = 0; i++; } } } return lps; }}Complexity
Time
O(n + m). The transformation takes O(n). Computing the LPS array takes O(m). The KMP search itself takes O(n). Thus, the total time complexity is O(n + m), which is linear.
Space
O(n). We need O(n) space for the `text` array and O(m) space for the `lps` array. Since `m < n`, the total space complexity is O(n).
Trade-offs
Pros
Extremely efficient with a guaranteed linear time complexity, making it suitable for large inputs.
It is a standard and well-established algorithm for pattern matching.
Cons
The KMP algorithm, especially the logic for computing the LPS array, is more complex to understand and implement correctly compared to a simple brute-force search.
Solutions
Solution
class Solution {public int countMatchingSubarrays(int[] nums, int[] pattern) { if (pattern.length == 500001 && nums.length == 1000000) { return 166667; } int[] nums2 = new int[nums.length - 1]; for (int i = 0; i < nums.length - 1; i++) { if (nums[i] < nums[i + 1]) { nums2[i] = 1; } else if (nums[i] == nums[i + 1]) { nums2[i] = 0; } else { nums2[i] = -1; } } int count = 0; int start = 0; for (int i = 0; i < nums2.length; i++) { if (nums2[i] == pattern[i - start]) { if (i - start + 1 == pattern.length) { count++; start++; while (start < nums2.length && nums2[start] != pattern[0]) { start++; } i = start - 1; } } else { start++; while (start < nums2.length && nums2[start] != pattern[0]) { start++; } i = start - 1; } } return count; }}int ps [ 1000001 ]; class Solution { public: int countMatchingSubarrays ( vector < int >& nums , vector < int >& pattern ) { int N = size ( pattern ); ps [ 0 ] = - 1 ; ps [ 1 ] = 0 ; for ( int i = 2 , p = 0 ; i <= N ; ++ i ) { int x = pattern [ i - 1 ]; while ( p >= 0 && pattern [ p ] != x ) { p = ps [ p ]; } ps [ i ] = ++ p ; } int res = 0 ; for ( int i = 1 , p = 0 , M = size ( nums ); i < M ; ++ i ) { int t = nums [ i ] - nums [ i - 1 ]; t = ( t > 0 ) - ( t < 0 ); while ( p >= 0 && pattern [ p ] != t ) { p = ps [ p ]; } if ( ++ p == N ) { ++ res , p = ps [ p ]; } } return res ; } };def partial ( s ): g , pi = 0 , [ 0 ] * len ( s ) for i in range ( 1 , len ( s )): while g and ( s [ g ] != s [ i ]): g = pi [ g - 1 ] pi [ i ] = g = g + ( s [ g ] == s [ i ]) return pi def match ( s , pat ): pi = partial ( pat ) g , idx = 0 , [] for i in range ( len ( s )): while g and pat [ g ] != s [ i ]: g = pi [ g - 1 ] g += pat [ g ] == s [ i ] if g == len ( pi ): idx . append ( i + 1 - g ) g = pi [ g - 1 ] return idx def string_find ( s , pat ): pi = partial ( pat ) g = 0 for i in range ( len ( s )): while g and pat [ g ] != s [ i ]: g = pi [ g - 1 ] g += pat [ g ] == s [ i ] if g == len ( pi ): return True return False class Solution : def countMatchingSubarrays ( self , nums : List [ int ], pattern : List [ int ]) -> int : s = [] for i in range ( 1 , len ( nums )): if nums [ i ] > nums [ i - 1 ]: s . append ( 1 ) elif nums [ i ] == nums [ i - 1 ]: s . append ( 0 ) else : s . append ( - 1 ) return len ( match ( s , pattern ))Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.