Skip to content
ΣDSA Patterns
Menu
Language

KMP / String Matching

Guide 6 of 6 · Path 6 of 6

Interactive

Mental model

A worked animation for this problem. Scrub steps or press space to pause; re-tell the invariant out loud.

Step 1 of 6
1
2
3
1
2
3

pattern = [1,1]

Count windows whose consecutive signs match pattern [1,1] (two rises).

Demo preview: These solutions pass the automated test suite but have not been human-reviewed. Treat trade-offs and prose as draft.

Number of Subarrays That Match a Pattern II

Problem (restated)

pattern[j] is -1, 0, or 1 meaning the next value should fall, stay, or rise. Count subarrays of nums whose consecutive differences match the whole pattern. n can be 1e6, so linear time.

Intuition

Turn nums into a sign stream of length n-1. Count how often pattern occurs in that stream. That is KMP with a count (on match, j = lps[j-1] and increment, do not stop).

Approaches

KMP on sign stream

Unverified
Time O(n+m)Space O(n)

Idea. text[i] = sign(nums[i+1]-nums[i]). Build LPS of pattern. Scan text; every time j == m, ans++ and j = lps[j-1].

Walkthrough. nums=[1,2,3,1,2,3], pattern=[1,1] (two rises). Two windows: [1,2,3] twice. Overlapping matches are allowed.

Trade-offs. Naive windows TLE at 1e6. Same KMP as LC 28, counting instead of returning the first index.

Solution
export function countMatchingSubarrays(nums: number[], pattern: number[]): number {
  const text: number[] = [];
  for (let i = 1; i < nums.length; i++) {
    const d = nums[i]! - nums[i - 1]!;
    text.push(d > 0 ? 1 : d < 0 ? -1 : 0);
  }
  const m = pattern.length;
  if (m === 0) return text.length + 1;
  const lps = new Array<number>(m).fill(0);
  let length = 0;
  for (let i = 1; i < m; i++) {
    while (length > 0 && pattern[i] !== pattern[length]) length = lps[length - 1]!;
    if (pattern[i] === pattern[length]) length++;
    lps[i] = length;
  }
  let j = 0, ans = 0;
  for (let i = 0; i < text.length; i++) {
    while (j > 0 && text[i] !== pattern[j]) j = lps[j - 1]!;
    if (text[i] === pattern[j]) j++;
    if (j === m) {
      ans++;
      j = lps[j - 1]!;
    }
  }
  return ans;
}
export function countMatchingSubarrays(nums: number[], pattern: number[]): number {
  const text: number[] = [];
  for (let i = 1; i < nums.length; i++) {
    const d = nums[i]! - nums[i - 1]!;
    text.push(d > 0 ? 1 : d < 0 ? -1 : 0);
  }
  const m = pattern.length;
  if (m === 0) return text.length + 1;
  const lps = new Array<number>(m).fill(0);
  let length = 0;
  for (let i = 1; i < m; i++) {
    while (length > 0 && pattern[i] !== pattern[length]) length = lps[length - 1]!;
    if (pattern[i] === pattern[length]) length++;
    lps[i] = length;
  }
  let j = 0, ans = 0;
  for (let i = 0; i < text.length; i++) {
    while (j > 0 && text[i] !== pattern[j]) j = lps[j - 1]!;
    if (text[i] === pattern[j]) j++;
    if (j === m) {
      ans++;
      j = lps[j - 1]!;
    }
  }
  return ans;
}

Template connection

KMP exact match on a derived alphabet {-1,0,1}. Count-all variant of LC 28.

Reflection