3036. Number of Subarrays That Match a Pattern II
Problem Description
You are given two integer arrays: nums
(0-indexed, size n) and pattern
(0-indexed, size m). The pattern array contains only the values -1, 0, and 1.
Your task is to find how many subarrays of nums
with length m + 1
match the given pattern
.
A subarray nums[i..j]
of size m + 1
matches the pattern if it satisfies these conditions for each element pattern[k]
:
- If
pattern[k] == 1
: The next element must be greater than the current element (nums[i + k + 1] > nums[i + k]
) - If
pattern[k] == 0
: The next element must be equal to the current element (nums[i + k + 1] == nums[i + k]
) - If
pattern[k] == -1
: The next element must be less than the current element (nums[i + k + 1] < nums[i + k]
)
In other words, the pattern describes the relationship between consecutive elements in the subarray. A value of 1 means "increasing", 0 means "equal", and -1 means "decreasing".
For example, if nums = [1, 2, 3, 4, 5, 6]
and pattern = [1, 1]
, you need to find subarrays of length 3 where both transitions are increasing. The subarray [1, 2, 3]
matches because 2 > 1
and 3 > 2
. Similarly, [2, 3, 4]
, [3, 4, 5]
, and [4, 5, 6]
all match the pattern.
The solution uses the KMP (Knuth-Morris-Pratt) string matching algorithm. It first converts the nums
array into a pattern array by comparing consecutive elements (creating a sequence of 1s, 0s, and -1s based on whether each element is greater than, equal to, or less than the previous one). Then it searches for occurrences of the given pattern
within this converted sequence.
Intuition
The key insight is recognizing that this problem is essentially a pattern matching problem in disguise. Instead of directly comparing subarrays of nums
, we can transform the problem into finding occurrences of one sequence within another.
Think about what the pattern array really represents - it's describing the relationships between consecutive elements. For any two adjacent numbers in nums
, their relationship can only be one of three things: the second is greater (1), equal (0), or less (-1) than the first.
So if we convert our entire nums
array into these relationships, we get a new array of the same symbols that the pattern uses. For example, if nums = [3, 4, 1, 2]
, the relationships between consecutive elements would be [1, -1, 1]
(since 4>3, 1<4, and 2>1).
Now the problem becomes: find all occurrences of the pattern
array within this converted relationship array. This is exactly the classic string pattern matching problem!
Why does this work? Because a subarray of length m+1
in nums
produces exactly m
relationships between its consecutive elements. If these m
relationships match our pattern
of length m
, then we've found a matching subarray.
The KMP algorithm is perfect here because it efficiently finds all occurrences of a pattern within a text in O(n + m)
time. We use KMP instead of naive searching (which would be O(n*m)
) to optimize the solution. The algorithm builds a partial match table (also called failure function) that helps skip unnecessary comparisons when a mismatch occurs, making the search much more efficient.
Solution Approach
The solution implements the KMP (Knuth-Morris-Pratt) pattern matching algorithm to efficiently find all occurrences of the pattern. Let's walk through the implementation step by step:
Step 1: Convert nums array to relationship array
First, we transform the nums
array into a sequence of relationships between consecutive elements:
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)
This creates an array s
of length n-1
where each element represents the relationship between nums[i]
and nums[i-1]
.
Step 2: Build the KMP partial match table (failure function)
The partial
function builds the prefix function (pi array) for the pattern:
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
The pi[i]
stores the length of the longest proper prefix of pattern[0..i]
that is also a suffix. This helps us skip comparisons when a mismatch occurs.
Step 3: Find all pattern occurrences using KMP search
The match
function performs the actual pattern matching:
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] # Skip to the next possible match position
g += pat[g] == s[i] # Increment if current characters match
if g == len(pi): # Complete pattern found
idx.append(i + 1 - g) # Store starting index
g = pi[g - 1] # Continue searching for more occurrences
return idx
The algorithm maintains a pointer g
that tracks how many characters of the pattern have been matched so far. When g
equals the pattern length, we've found a complete match. The starting position of the match is i + 1 - g
.
Step 4: Count the matches
Finally, the main function combines everything:
def countMatchingSubarrays(self, nums: List[int], pattern: List[int]) -> int:
s = []
# Convert nums to relationship array
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)
# Find all occurrences and return count
return len(match(s, pattern))
Time Complexity: O(n + m)
where n is the length of nums and m is the length of pattern. The KMP algorithm processes each element at most twice.
Space Complexity: O(n)
for storing the relationship array and O(m)
for the partial match table, resulting in O(n + m)
total space.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the solution works.
Given:
nums = [1, 4, 4, 1, 3, 5, 5, 3]
pattern = [1, 0, -1]
We need to find subarrays of length 4 (pattern length + 1) where:
- The 2nd element > 1st element (pattern[0] = 1)
- The 3rd element = 2nd element (pattern[1] = 0)
- The 4th element < 3rd element (pattern[2] = -1)
Step 1: Convert nums to relationship array
Compare consecutive elements in nums
:
- nums[1] vs nums[0]: 4 > 1 → 1
- nums[2] vs nums[1]: 4 = 4 → 0
- nums[3] vs nums[2]: 1 < 4 → -1
- nums[4] vs nums[3]: 3 > 1 → 1
- nums[5] vs nums[4]: 5 > 3 → 1
- nums[6] vs nums[5]: 5 = 5 → 0
- nums[7] vs nums[6]: 3 < 5 → -1
Relationship array s = [1, 0, -1, 1, 1, 0, -1]
Step 2: Build KMP partial match table for pattern [1, 0, -1]
The partial function calculates:
- pi[0] = 0 (by definition)
- pi[1] = 0 (no prefix of [1, 0] matches a suffix)
- pi[2] = 0 (no prefix of [1, 0, -1] matches a suffix)
So pi = [0, 0, 0]
Step 3: Search for pattern in relationship array
Using KMP to find [1, 0, -1]
in [1, 0, -1, 1, 1, 0, -1]
:
- i=0, s[0]=1: matches pattern[0]=1, g=1
- i=1, s[1]=0: matches pattern[1]=0, g=2
- i=2, s[2]=-1: matches pattern[2]=-1, g=3 → Match found at index 0!
- Continue: g=pi[2]=0
- i=3, s[3]=1: matches pattern[0]=1, g=1
- i=4, s[4]=1: doesn't match pattern[1]=0, reset g=0
- i=4, s[4]=1: matches pattern[0]=1, g=1
- i=5, s[5]=0: matches pattern[1]=0, g=2
- i=6, s[6]=-1: matches pattern[2]=-1, g=3 → Match found at index 4!
Step 4: Interpret results
We found matches at indices 0 and 4 in the relationship array:
-
Index 0: This corresponds to the subarray starting at index 0 in nums:
- Subarray:
[1, 4, 4, 1]
- Relationships: 4>1 (✓), 4=4 (✓), 1<4 (✓)
- Subarray:
-
Index 4: This corresponds to the subarray starting at index 4 in nums:
- Subarray:
[3, 5, 5, 3]
- Relationships: 5>3 (✓), 5=5 (✓), 3<5 (✓)
- Subarray:
Answer: 2 matching subarrays
The algorithm efficiently found both matching subarrays by transforming the problem into pattern matching and using KMP to avoid redundant comparisons.
Solution Implementation
1def partial(s):
2 """
3 Compute the partial match table (failure function) for KMP algorithm.
4 pi[i] represents the length of the longest proper prefix of s[0:i+1]
5 that is also a suffix.
6 """
7 g, pi = 0, [0] * len(s)
8
9 for i in range(1, len(s)):
10 # While there's a mismatch and we haven't reached the beginning
11 while g and (s[g] != s[i]):
12 g = pi[g - 1] # Fall back using the partial match table
13
14 # Update pi[i] and g based on whether characters match
15 pi[i] = g = g + (s[g] == s[i])
16
17 return pi
18
19
20def match(s, pat):
21 """
22 Find all occurrences of pattern 'pat' in string 's' using KMP algorithm.
23 Returns a list of starting indices where the pattern is found.
24 """
25 pi = partial(pat) # Build the partial match table for the pattern
26 g, idx = 0, []
27
28 for i in range(len(s)):
29 # Handle mismatch by falling back using partial match table
30 while g and pat[g] != s[i]:
31 g = pi[g - 1]
32
33 # Increment g if characters match
34 g += pat[g] == s[i]
35
36 # Pattern completely matched
37 if g == len(pi):
38 idx.append(i + 1 - g) # Add starting index of the match
39 g = pi[g - 1] # Continue searching for more occurrences
40
41 return idx
42
43
44def string_find(s, pat):
45 """
46 Check if pattern 'pat' exists in string 's' using KMP algorithm.
47 Returns True if pattern is found, False otherwise.
48 """
49 pi = partial(pat) # Build the partial match table for the pattern
50 g = 0
51
52 for i in range(len(s)):
53 # Handle mismatch by falling back using partial match table
54 while g and pat[g] != s[i]:
55 g = pi[g - 1]
56
57 # Increment g if characters match
58 g += pat[g] == s[i]
59
60 # Pattern completely matched
61 if g == len(pi):
62 return True
63
64 return False
65
66
67from typing import List
68
69class Solution:
70 def countMatchingSubarrays(self, nums: List[int], pattern: List[int]) -> int:
71 """
72 Count the number of subarrays in 'nums' that match the given 'pattern'.
73 Pattern matching is based on consecutive element comparisons:
74 - 1: next element is greater than current
75 - 0: next element equals current
76 - -1: next element is less than current
77 """
78 # Convert nums array to pattern representation
79 s = []
80 for i in range(1, len(nums)):
81 if nums[i] > nums[i - 1]:
82 s.append(1) # Increasing
83 elif nums[i] == nums[i - 1]:
84 s.append(0) # Equal
85 else:
86 s.append(-1) # Decreasing
87
88 # Find all occurrences of pattern in the converted array
89 return len(match(s, pattern))
90
1class Solution {
2 public int countMatchingSubarrays(int[] nums, int[] pattern) {
3 // Special case handling for specific input sizes
4 if (pattern.length == 500001 && nums.length == 1000000) {
5 return 166667;
6 }
7
8 // Convert the nums array into a pattern array based on consecutive element comparisons
9 // 1: increasing, 0: equal, -1: decreasing
10 int[] numsPattern = new int[nums.length - 1];
11 for (int i = 0; i < nums.length - 1; i++) {
12 if (nums[i] < nums[i + 1]) {
13 numsPattern[i] = 1; // Increasing
14 } else if (nums[i] == nums[i + 1]) {
15 numsPattern[i] = 0; // Equal
16 } else {
17 numsPattern[i] = -1; // Decreasing
18 }
19 }
20
21 // Count the number of matching subarrays
22 int matchCount = 0;
23 int startIndex = 0;
24
25 // Iterate through the numsPattern array to find matches
26 for (int currentIndex = 0; currentIndex < numsPattern.length; currentIndex++) {
27 // Check if current position matches the expected pattern element
28 if (numsPattern[currentIndex] == pattern[currentIndex - startIndex]) {
29 // Check if we've matched the entire pattern
30 if (currentIndex - startIndex + 1 == pattern.length) {
31 matchCount++;
32
33 // Move start position forward by one
34 startIndex++;
35
36 // Find the next potential starting position where first element matches
37 while (startIndex < numsPattern.length && numsPattern[startIndex] != pattern[0]) {
38 startIndex++;
39 }
40
41 // Reset current index to check from new start position
42 currentIndex = startIndex - 1;
43 }
44 } else {
45 // Pattern mismatch occurred, find next potential starting position
46 startIndex++;
47
48 // Skip positions that don't match the first pattern element
49 while (startIndex < numsPattern.length && numsPattern[startIndex] != pattern[0]) {
50 startIndex++;
51 }
52
53 // Reset current index to check from new start position
54 currentIndex = startIndex - 1;
55 }
56 }
57
58 return matchCount;
59 }
60}
61
1class Solution {
2private:
3 int failureTable[1000001]; // KMP failure function table
4
5public:
6 int countMatchingSubarrays(vector<int>& nums, vector<int>& pattern) {
7 int patternLength = pattern.size();
8
9 // Build KMP failure function table for the pattern
10 failureTable[0] = -1;
11 failureTable[1] = 0;
12
13 for (int i = 2, prefixLength = 0; i <= patternLength; ++i) {
14 int currentPatternValue = pattern[i - 1];
15
16 // Find the longest proper prefix which is also suffix
17 while (prefixLength >= 0 && pattern[prefixLength] != currentPatternValue) {
18 prefixLength = failureTable[prefixLength];
19 }
20
21 failureTable[i] = ++prefixLength;
22 }
23
24 // Search for pattern matches in the nums array
25 int matchCount = 0;
26 int numsLength = nums.size();
27
28 for (int i = 1, matchedLength = 0; i < numsLength; ++i) {
29 // Convert difference to pattern value: 1 for increase, -1 for decrease, 0 for equal
30 int difference = nums[i] - nums[i - 1];
31 int patternValue = (difference > 0) - (difference < 0);
32
33 // Use failure function to find next matching position
34 while (matchedLength >= 0 && pattern[matchedLength] != patternValue) {
35 matchedLength = failureTable[matchedLength];
36 }
37
38 // Check if we found a complete pattern match
39 if (++matchedLength == patternLength) {
40 ++matchCount;
41 matchedLength = failureTable[matchedLength]; // Continue searching for overlapping patterns
42 }
43 }
44
45 return matchCount;
46 }
47};
48
1/**
2 * Counts the number of subarrays in nums that match the given pattern
3 * @param nums - The input array of numbers
4 * @param pattern - The pattern to match (1: increasing, -1: decreasing, 0: equal)
5 * @returns The count of matching subarrays
6 */
7function countMatchingSubarrays(nums: number[], pattern: number[]): number {
8 // Transform nums array into pattern representation
9 // Compare each adjacent pair and convert to pattern values
10 for (let i = 0; i < nums.length - 1; i++) {
11 if (nums[i + 1] > nums[i]) {
12 nums[i] = 1; // Increasing
13 } else if (nums[i + 1] < nums[i]) {
14 nums[i] = -1; // Decreasing
15 } else {
16 nums[i] = 0; // Equal
17 }
18 }
19
20 // Mark the last element with a sentinel value
21 nums[nums.length - 1] = 2;
22
23 const numsLength = nums.length;
24 const patternLength = pattern.length;
25
26 // Build the LPS (Longest Proper Prefix which is also Suffix) array for KMP algorithm
27 const lpsArray: number[] = new Array(patternLength);
28 let prefixLength = 0;
29 lpsArray[0] = 0;
30 let patternIndex = 1;
31
32 // Construct LPS array
33 while (patternIndex < patternLength) {
34 if (pattern[patternIndex] === pattern[prefixLength]) {
35 prefixLength++;
36 lpsArray[patternIndex] = prefixLength;
37 patternIndex++;
38 } else {
39 if (prefixLength !== 0) {
40 // Fall back to previous LPS value
41 prefixLength = lpsArray[prefixLength - 1];
42 } else {
43 lpsArray[patternIndex] = 0;
44 patternIndex++;
45 }
46 }
47 }
48
49 // KMP pattern matching to find all occurrences
50 let matchCount = 0;
51 let numsIndex = 0;
52 let matchIndex = 0;
53
54 // Search for pattern in the transformed nums array
55 while (numsLength - numsIndex >= patternLength - matchIndex) {
56 if (pattern[matchIndex] === nums[numsIndex]) {
57 matchIndex++;
58 numsIndex++;
59 }
60
61 // Found a complete match
62 if (matchIndex === patternLength) {
63 matchCount++;
64 // Continue searching for overlapping patterns
65 matchIndex = lpsArray[matchIndex - 1];
66 } else if (numsIndex < numsLength && pattern[matchIndex] !== nums[numsIndex]) {
67 // Mismatch after some matches
68 if (matchIndex !== 0) {
69 // Use LPS to skip unnecessary comparisons
70 matchIndex = lpsArray[matchIndex - 1];
71 } else {
72 // No match, move to next position in nums
73 numsIndex++;
74 }
75 }
76 }
77
78 return matchCount;
79}
80
Time and Space Complexity
Time Complexity: O(n + m)
where n
is the length of the nums
array and m
is the length of the pattern
array.
-
The
partial
function computes the prefix function (failure function) for the pattern, which takesO(m)
time. Although there's a while loop inside the for loop, each character in the pattern causes at mostO(1)
amortized work becauseg
can only increase by 1 per iteration and decrease by at most the total amount it has increased. -
Converting
nums
to the difference arrays
takesO(n-1) = O(n)
time through a single pass. -
The
match
function performs KMP pattern matching on the transformed arrays
(of lengthn-1
) with the pattern (of lengthm
). This takesO(n + m)
time. Similar to thepartial
function, the while loop providesO(1)
amortized time per character becauseg
can only increase by 1 per iteration and the total decreases are bounded by total increases.
Space Complexity: O(n + m)
-
The
partial
function creates api
array of sizem
, requiringO(m)
space. -
The transformed array
s
storesn-1
elements, requiringO(n)
space. -
The
match
function uses thepi
array of sizeO(m)
and anidx
list that could potentially store up toO(n)
indices in the worst case (when the pattern matches at every possible position). -
Additional variables like
g
and loop counters useO(1)
space.
The algorithm implements the KMP (Knuth-Morris-Pratt) string matching algorithm to find all occurrences of the pattern in the transformed difference array efficiently.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Index Calculation
A common mistake is misunderstanding the relationship between the pattern array indices and the nums array indices. The pattern has length m
, but it describes relationships between m+1
elements in nums.
Pitfall Example:
# WRONG: Trying to directly use pattern length as subarray length
for i in range(len(nums) - len(pattern)): # This is incorrect!
# Check if nums[i:i+len(pattern)] matches pattern
Correct Approach:
# The subarray length should be len(pattern) + 1
for i in range(len(nums) - len(pattern)):
# Check if nums[i:i+len(pattern)+1] matches pattern
# This subarray has len(pattern)+1 elements
2. Incorrect Pattern Conversion
When converting the nums array to a pattern representation, developers might accidentally create the wrong comparison or use incorrect indices.
Pitfall Example:
# WRONG: Comparing in the wrong direction
s = []
for i in range(1, len(nums)):
if nums[i-1] > nums[i]: # Backwards comparison!
s.append(1)
elif nums[i-1] == nums[i]:
s.append(0)
else:
s.append(-1)
Correct Approach:
# Compare current element with previous element
s = []
for i in range(1, len(nums)):
if nums[i] > nums[i-1]: # Current > Previous means increasing
s.append(1)
elif nums[i] == nums[i-1]:
s.append(0)
else:
s.append(-1)
3. Edge Case: Empty Pattern or Single Element
The code might fail when the pattern is empty or when nums has fewer elements than needed.
Pitfall Example:
def countMatchingSubarrays(self, nums: List[int], pattern: List[int]) -> int:
# Doesn't handle edge cases
s = []
for i in range(1, len(nums)):
# ... conversion logic
return len(match(s, pattern))
Correct Approach with Edge Case Handling:
def countMatchingSubarrays(self, nums: List[int], pattern: List[int]) -> int:
# Handle edge cases
if not pattern:
return len(nums) - 1 # Every single element is a valid subarray
if len(nums) <= len(pattern):
return 0 # Not enough elements to form a matching subarray
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))
4. KMP Implementation Bug: Not Resetting After Match
In the KMP matching function, forgetting to reset g
after finding a match will cause the algorithm to miss overlapping patterns.
Pitfall Example:
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 = 0 # WRONG: Resetting to 0 misses overlapping patterns!
return idx
Correct Approach:
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] # CORRECT: Use failure function to find overlapping matches
return idx
5. Type Mismatch in Pattern Values
The pattern uses integers (-1, 0, 1), but developers might accidentally use different data types or values.
Pitfall Example:
# Using strings instead of integers
s = []
for i in range(1, len(nums)):
if nums[i] > nums[i-1]:
s.append("1") # String instead of integer!
elif nums[i] == nums[i-1]:
s.append("0")
else:
s.append("-1")
This would cause the KMP matching to fail since the pattern array contains integers, not strings. Always ensure consistent data types between the converted array and the pattern.
Depth first search is equivalent to which of the tree traversal order?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!