Bird
Raised Fist0
DSA Goprogramming~15 mins

Substring Search Patterns in DSA Go - Deep Dive

Choose your learning style10 modes available
Overview - Substring Search Patterns
What is it?
Substring search patterns are methods to find if a smaller string (called a pattern) exists inside a larger string (called text). It helps locate where the pattern starts in the text or if it is present at all. This is useful in many areas like searching words in documents, DNA sequences, or computer code. The goal is to do this search efficiently without checking every possible position one by one.
Why it matters
Without substring search patterns, computers would waste a lot of time checking every position in a text to find a pattern, which is slow for large texts. Efficient substring search saves time and resources, making software faster and more responsive. This is important in search engines, spell checkers, and even security systems that scan for harmful code. Without these patterns, many everyday technologies would be much slower or less reliable.
Where it fits
Before learning substring search patterns, you should understand basic strings and how to compare characters. After this, you can learn advanced string algorithms like suffix trees or automata. This topic fits in the middle of string processing and algorithm optimization in your learning journey.
Mental Model
Core Idea
Substring search patterns find a smaller string inside a bigger one by smartly skipping unnecessary checks to save time.
Think of it like...
Imagine looking for a word in a book by flipping pages and scanning lines, but instead of reading every word, you use clues to skip pages that can't have the word.
Text:    ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
         │ h e l l o   w o r l d       │
Pattern:           ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                  │ w o r l d │
Search:  Start at each position -> skip ahead when mismatch found -> match found at position 6
Build-Up - 7 Steps
1
FoundationUnderstanding Basic String Matching
šŸ¤”
Concept: Learn how to check if a pattern matches the text starting at a specific position.
To find a pattern in text, start at the first character of the text and compare each character with the pattern. If all characters match in order, the pattern is found. If a mismatch occurs, move to the next position in the text and try again.
Result
You can find the pattern by checking each position one by one, but this is slow for large texts.
Understanding the simplest way to match strings helps you see why smarter methods are needed to avoid repeated work.
2
FoundationNaive Search Algorithm Explained
šŸ¤”
Concept: The naive search tries every possible position in the text to find the pattern.
For each position in the text from 0 to (text length - pattern length), compare the pattern characters one by one. If all match, return the position. Otherwise, move to the next position and repeat.
Result
The naive search works but can be very slow, especially if the text and pattern are large.
Knowing the naive approach sets a baseline to appreciate faster algorithms that reduce unnecessary comparisons.
3
IntermediateKnuth-Morris-Pratt (KMP) Algorithm Basics
šŸ¤”Before reading on: do you think KMP checks every character in the text multiple times or only once? Commit to your answer.
Concept: KMP uses information from the pattern itself to skip some comparisons, avoiding re-checking characters.
KMP builds a table (called the longest prefix suffix or LPS array) that tells how far to jump in the pattern when a mismatch happens. This way, it never moves backward in the text, checking each character only once.
Result
KMP finds the pattern in linear time, much faster than naive search for large inputs.
Understanding how KMP uses the pattern's own structure to skip ahead is key to efficient substring search.
4
IntermediateBuilding the LPS Array in KMP
šŸ¤”Before reading on: do you think the LPS array stores positions of mismatches or lengths of matching prefixes? Commit to your answer.
Concept: The LPS array stores lengths of the longest proper prefix which is also a suffix for each prefix of the pattern.
For each position in the pattern, calculate the longest prefix that matches a suffix ending there. This helps decide how far to jump when a mismatch occurs during search.
Result
The LPS array guides the search to avoid re-checking characters unnecessarily.
Knowing how to build and use the LPS array unlocks the power of KMP's efficiency.
5
IntermediateRabin-Karp Algorithm with Hashing
šŸ¤”Before reading on: do you think Rabin-Karp compares characters directly or uses a numeric fingerprint? Commit to your answer.
Concept: Rabin-Karp uses a hash function to convert strings into numbers and compares these numbers to find matches quickly.
Calculate a hash for the pattern and for each substring of the text of the same length. If hashes match, compare characters to confirm. This reduces the number of direct comparisons.
Result
Rabin-Karp is efficient for multiple pattern searches and can quickly skip non-matching positions.
Understanding hashing in substring search shows how math can speed up string matching.
6
AdvancedBoyer-Moore Algorithm and Bad Character Rule
šŸ¤”Before reading on: do you think Boyer-Moore starts matching from the start or end of the pattern? Commit to your answer.
Concept: Boyer-Moore starts matching from the end of the pattern and uses rules to skip ahead when mismatches occur.
When a mismatch happens, use the bad character rule to jump the pattern forward past the mismatched character's last occurrence in the pattern. This can skip large parts of the text.
Result
Boyer-Moore often runs faster than KMP in practice, especially on large alphabets.
Knowing how to match from the end and skip ahead smartly makes substring search much faster in real-world cases.
7
ExpertCombining Good Suffix and Bad Character Rules
šŸ¤”Before reading on: do you think combining rules in Boyer-Moore always improves speed or can sometimes slow it down? Commit to your answer.
Concept: Boyer-Moore uses both bad character and good suffix rules to decide the largest safe jump after a mismatch.
The good suffix rule looks at matched suffixes and finds where they appear again in the pattern to jump forward. Combining both rules chooses the maximum jump to skip unnecessary checks.
Result
This combination makes Boyer-Moore one of the fastest substring search algorithms in practice.
Understanding the interplay of these rules reveals the clever design behind high-performance substring search.
Under the Hood
Substring search algorithms work by comparing characters of the pattern and text, but they differ in how they avoid repeating comparisons. KMP precomputes a table to know how far to jump on mismatch, Rabin-Karp uses rolling hash to quickly compare substrings, and Boyer-Moore uses pattern information to jump ahead from the end. Internally, these algorithms maintain indexes and auxiliary data structures to track progress and decide jumps, minimizing redundant work.
Why designed this way?
Early substring search was slow because it checked every position fully. Researchers designed these algorithms to use pattern properties and math (like hashing) to skip unnecessary checks. The tradeoff was extra memory or preprocessing time for big gains in search speed. Alternatives like brute force were too slow, and more complex structures like suffix trees are heavier, so these algorithms balance speed and simplicity.
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│   Text        │
│ h e l l o ... │
ā””ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
      │
      ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Pattern       │
│   w o r l d   │
ā””ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
      │
      ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Search Algorithm             │
│ - Compare chars             │
│ - Use LPS / Hash / Rules    │
│ - Decide next position      │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
              │
              ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Result                      │
│ - Pattern found at index i  │
│ - Or not found              │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
Myth Busters - 3 Common Misconceptions
Quick: Does KMP check every character in the text multiple times? Commit to yes or no.
Common Belief:KMP still checks some characters in the text more than once because it jumps back in the pattern.
Tap to reveal reality
Reality:KMP never moves backward in the text; it checks each character at most once by smartly shifting the pattern.
Why it matters:Believing KMP rechecks text characters can lead to underestimating its efficiency and missing its advantage over naive search.
Quick: Does Rabin-Karp guarantee no false matches? Commit to yes or no.
Common Belief:Rabin-Karp always finds exact matches without errors because it uses hashing.
Tap to reveal reality
Reality:Rabin-Karp can have hash collisions causing false positives, so it must verify matches by direct character comparison.
Why it matters:Ignoring collisions can cause incorrect matches, leading to bugs in applications like plagiarism detection or virus scanning.
Quick: Does Boyer-Moore always outperform KMP? Commit to yes or no.
Common Belief:Boyer-Moore is always faster than KMP because it skips more characters.
Tap to reveal reality
Reality:Boyer-Moore is often faster but can be slower on small alphabets or very short patterns where overhead outweighs benefits.
Why it matters:Assuming Boyer-Moore is always best can lead to poor algorithm choice in some real-world scenarios.
Expert Zone
1
The LPS array in KMP can be reused to solve other problems like finding the shortest palindrome by prefix extension.
2
Boyer-Moore's performance heavily depends on the alphabet size and pattern length; tuning preprocessing can optimize it further.
3
Rabin-Karp's rolling hash can be adapted for multiple pattern searches simultaneously, useful in network intrusion detection.
When NOT to use
Avoid naive substring search on large texts or patterns due to inefficiency. Boyer-Moore is less effective on small alphabets or very short patterns; KMP or Rabin-Karp may be better. For very large datasets or complex queries, suffix trees or suffix arrays are preferred despite higher memory use.
Production Patterns
Search engines use substring search patterns to quickly find keywords in documents. Antivirus software uses Rabin-Karp for scanning multiple virus signatures. Text editors implement KMP or Boyer-Moore for fast find-and-replace features. Network security tools use rolling hash variants for real-time pattern detection.
Connections
Finite Automata
Substring search algorithms like KMP can be represented as finite automata that track pattern matching states.
Understanding automata theory helps grasp how substring search algorithms maintain and transition states efficiently.
Cryptographic Hash Functions
Rabin-Karp uses hashing similar to cryptographic hashes but simpler and faster for substring matching.
Knowing hash functions in security deepens understanding of how hashing can speed up string comparisons while managing collisions.
Human Pattern Recognition
Humans also skip irrelevant parts when searching text, similar to how algorithms skip characters using pattern knowledge.
Recognizing this connection shows how algorithms mimic efficient human search strategies to save time.
Common Pitfalls
#1Checking every position without skipping on mismatch.
Wrong approach:for i := 0; i <= len(text)-len(pattern); i++ { match := true for j := 0; j < len(pattern); j++ { if text[i+j] != pattern[j] { match = false break } } if match { fmt.Println("Found at", i) } }
Correct approach:Use KMP or Boyer-Moore to skip unnecessary checks instead of naive nested loops.
Root cause:Not using pattern information to avoid redundant comparisons leads to slow search.
#2Ignoring hash collisions in Rabin-Karp and trusting hash matches blindly.
Wrong approach:if hashText == hashPattern { fmt.Println("Pattern found") }
Correct approach:if hashText == hashPattern { // Verify characters one by one if text[i:i+len(pattern)] == pattern { fmt.Println("Pattern found") } }
Root cause:Assuming hash equality guarantees string equality causes false positives.
#3Starting Boyer-Moore matching from the pattern start instead of the end.
Wrong approach:for i := 0; i <= len(text)-len(pattern); i++ { for j := 0; j < len(pattern); j++ { if text[i+j] != pattern[j] { break } } }
Correct approach:for i := 0; i <= len(text)-len(pattern); { j := len(pattern)-1 for j >= 0 && text[i+j] == pattern[j] { j-- } if j < 0 { fmt.Println("Found at", i) i += shift } else { i += max(badCharShift, goodSuffixShift) } }
Root cause:Not leveraging pattern end matching misses Boyer-Moore's main speed advantage.
Key Takeaways
Substring search patterns help find smaller strings inside bigger ones efficiently by avoiding unnecessary checks.
Naive search is simple but slow; advanced algorithms like KMP, Rabin-Karp, and Boyer-Moore use pattern knowledge or hashing to speed up search.
KMP uses a precomputed table to skip backward jumps in the pattern without rechecking text characters.
Rabin-Karp uses hashing to quickly compare substrings but must verify matches to avoid false positives.
Boyer-Moore matches from the pattern end and uses rules to jump ahead, often making it the fastest in practice.

Practice

(1/5)
1. What does the strings.Contains(s, substr) function in Go do?
easy
A. Returns the index of substr in s
B. Checks if substr is present anywhere in s
C. Splits s by substr
D. Replaces substr with another string in s

Solution

  1. Step 1: Understand the function purpose

    strings.Contains checks if one string is inside another.
  2. Step 2: Check the return type

    It returns true if substr is found anywhere in s, otherwise false.
  3. Final Answer:

    Checks if substr is present anywhere in s -> Option B
  4. Quick Check:

    Contains means presence check = A [OK]
Hint: Contains means checking presence, not position or replacement [OK]
Common Mistakes:
  • Confusing Contains with Index or Replace functions
  • Thinking it returns position instead of boolean
  • Assuming it modifies the original string
2. Which of the following is the correct syntax to find the index of substring "go" in string s using Go's standard library?
easy
A. index := strings.Index(s, "go")
B. index := s.IndexOf("go")
C. index := s.find("go")
D. index := strings.Find(s, "go")

Solution

  1. Step 1: Recall Go's strings package functions

    The correct function to find substring index is strings.Index.
  2. Step 2: Check syntax correctness

    The function is called as strings.Index(s, "go"), returning the first index or -1 if not found.
  3. Final Answer:

    index := strings.Index(s, "go") -> Option A
  4. Quick Check:

    Index function in strings package = B [OK]
Hint: Use strings.Index for substring position in Go [OK]
Common Mistakes:
  • Using methods on string variable directly (strings are not objects)
  • Using Python or JavaScript syntax like find or IndexOf
  • Calling non-existent functions like strings.Find
3. What will be the output of the following Go code?
package main
import (
  "fmt"
  "strings"
)
func main() {
  s := "hello golang"
  pos := strings.Index(s, "go")
  fmt.Println(pos)
}
medium
A. 6
B. -1
C. 0
D. Error

Solution

  1. Step 1: Understand strings.Index behavior

    It returns the first index where substring "go" appears in "hello golang".
  2. Step 2: Find substring position

    Counting from 0: h(0) e(1) l(2) l(3) o(4) ' '(5) g(6) o(7)... So "go" starts at index 6.
  3. Final Answer:

    6 -> Option A
  4. Quick Check:

    Index of "go" in "hello golang" = 6 [OK]
Hint: Count characters from zero to find substring index [OK]
Common Mistakes:
  • Counting from 1 instead of 0
  • Assuming substring not found returns 0
  • Confusing Index with Contains boolean
4. Identify the error in the following Go code snippet for substring search:
package main
import (
  "fmt"
  "strings"
)
func main() {
  s := "golang"
  if s.Contains("go") {
    fmt.Println("Found")
  }
}
medium
A. Incorrect string literal syntax
B. Missing import for fmt package
C. No error, code runs fine
D. strings package has no Contains method on string variable

Solution

  1. Step 1: Check method call on string variable

    In Go, strings are not objects and do not have methods like Contains directly.
  2. Step 2: Correct usage of Contains function

    The correct call is strings.Contains(s, "go") from the strings package, not s.Contains("go").
  3. Final Answer:

    strings package has no Contains method on string variable -> Option D
  4. Quick Check:

    Use strings.Contains(s, substr), not s.Contains(substr) [OK]
Hint: Call Contains from strings package, not on string variable [OK]
Common Mistakes:
  • Calling methods on string variables as if they were objects
  • Forgetting to import strings package
  • Assuming Contains returns index
5. Given a list of strings words := []string{"go", "golang", "gopher", "gone"}, which code snippet correctly finds all words containing substring "go" and stores them in result?
hard
A. result := []string{} for _, w := range words { if strings.Index(w, "go") > 5 { result = append(result, w) } }
B. var result []string for i := 0; i < len(words); i++ { if words[i].Contains("go") { result = append(result, words[i]) } }
C. var result []string for _, w := range words { if strings.Contains(w, "go") { result = append(result, w) } }
D. result := []string{} for _, w := range words { if strings.Index(w, "go") == -1 { result = append(result, w) } }

Solution

  1. Step 1: Identify correct substring check

    Use strings.Contains(w, "go") to check if substring exists.
  2. Step 2: Append matching words to result slice

    For each word containing "go", append it to result slice.
  3. Final Answer:

    var result []string for _, w := range words { if strings.Contains(w, "go") { result = append(result, w) } } -> Option C
  4. Quick Check:

    Contains + append in loop = D [OK]
Hint: Use strings.Contains inside loop and append matches [OK]
Common Mistakes:
  • Calling Contains as method on string variable
  • Appending words that do NOT contain substring
  • Checking Index == -1 instead of != -1
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard