LeetCode 1910 Remove All Occurrences of a Substring - Optimal Solution using stack (Med, Java, O(n))

Anni HuangAnni Huang
3 min read

Overall Strategy

The algorithm uses a stack simulation with a character array to efficiently remove all occurrences of a substring. It processes the string character by character and removes matching substrings as soon as they're detected.

Code Breakdown

Static Block (Warmup)

static {
    for (int i = 0; i < 100; i++) {
        removeOccurrences("daabcbaabcbc", "abc");
    }
}

This runs the method 100 times during class loading to "warm up" the JVM - optimizing performance for online judges that run multiple test cases.

Main Algorithm

1. Setup:

char[] pt = part.toCharArray();      // Convert substring to char array
char[] stack = new char[s.length()]; // Stack simulation (max size = s.length)
int m = pt.length, size = 0;         // m = substring length, size = stack size
char end = pt[m - 1];                // Last character of substring

2. Process each character:

for (char cur : s.toCharArray()) {
    stack[size++] = cur;  // Always add current character to stack

    // Check if we might have a complete match
    if (cur == end && size >= m) {
        // Potential match found - verify backwards
    }
}

3. Match verification (the tricky part):

if (cur == end && size >= m) {
    int j = m - 1, k = size - j - 1;  // j = pattern index, k = stack start position
    while (j >= 0 && stack[k + j] == pt[j])
        j--;
    if (j < 0) size = j + k + 1;  // Match found - "pop" the substring
}

How the Match Verification Works

Let's trace through an example: s = "daabcbaabcbc", part = "abc"

When we encounter 'c' (potential end of "abc"):

  • j = 2 (last index of "abc")
  • k = size - j - 1 = size - 3 (start position to check in stack)
  • Compare backwards: stack[k+2] == 'c', stack[k+1] == 'b', stack[k] == 'a'
  • If all match, j becomes -1, so we "pop" by setting size = j + k + 1 = k

Visual Example

s = "daabcbaabcbc", part = "abc"

Step by step:
stack: [d] -> [d,a] -> [d,a,a] -> [d,a,a,b] -> [d,a,a,b,c]
                                                    ↑
                                              'c' matches end of "abc"
                                              Check backwards: a,b,c ✓
                                              Remove 3 chars: stack = [d,a]

Continue: [d,a,b] -> [d,a,b,a] -> [d,a,b,a,a] -> [d,a,b,a,a,b] -> [d,a,b,a,a,b,c]
                                                                         ↑
                                                                   Check: a,b,c ✓
                                                                   Remove: [d,a,b,a]

Continue: [d,a,b,a,b] -> [d,a,b,a,b,c]
                              ↑
                        Check: a,b,c ✓
                        Remove: [d,a,b]

Final result: "dab"

Why It's Efficient

  1. Single Pass: O(n) time complexity - each character is processed once
  2. Space Efficient: Uses a character array instead of StringBuilder
  3. Early Detection: Only checks for matches when the last character matches
  4. No String Operations: Avoids expensive string creation during processing

This is much faster than the naive approach that repeatedly searches and replaces, especially for cases with many overlapping occurrences.

0
Subscribe to my newsletter

Read articles from Anni Huang directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Anni Huang
Anni Huang

I am Anni HUANG, a software engineer with 3 years of experience in IDE development and Chatbot.