Why this is categorized as difficult: It deals with overlapping substring matches and edge-case word boundaries that are highly relevant to QA automation log parsers.
Category: Difficult | Concepts used: Word frequency counting, substring counting — very relevant for QA log analysis!
Problem Statement
Given a block of text (e.g., a log file), count how many times a specific word or substring appears — and, more generally, build a frequency count of all words.
Input : "ERROR: timeout ERROR: connection lost ERROR: timeout", find "ERROR"
Output: 3
Note: This problem is directly relevant to QA/automation work — counting error occurrences in logs, tracking how often specific events appear in test output, etc.
Examples (with edge scenarios)
| # | Text | Target | Output | Why |
|---|---|---|---|---|
| 1 | "ERROR: timeout ERROR: connection" | "ERROR" | 2 | Appears twice as a distinct word |
| 2 | "ERRORERROR" (no separators) | "ERROR" | Depends — is this 1 “word” or 2 substring occurrences? | Clarify: whole-word matching vs. raw substring search |
| 3 | "" (empty) | "ERROR" | 0 | Nothing to search |
| 4 | "error Error ERROR" (case variations) | "error" | 3 (if case-insensitive) or 1 (if case-sensitive) | Clarify case sensitivity |
| 5 | Overlapping substrings, e.g., text="aaa", target="aa" | 2 (overlapping) or 1 (non-overlapping)? | Substring counting has its own overlap ambiguity — clarify! |
Common Fresher Mistake
Mistake What happens Fix Using simple substring search ( String.contains()in a loop) without considering word boundaries"ERROR"would also match inside"ERRORCODE"— may not be intendedClarify: exact whole-word match, or any substring occurrence? Use regex word boundaries ( \bword\b) for whole-word matchingNot handling overlapping substring occurrences correctly Miscounted results for patterns like counting "aa"in"aaa"Decide and clearly state whether overlapping matches should be counted
Before You Code: Clarify the Contract
Before choosing an algorithm, confirm how null and empty strings should behave, whether comparison is case-sensitive, and whether spaces or punctuation count. Java char values are UTF-16 code units, not always complete human-visible Unicode characters, so international text may require code points or grapheme-aware libraries.
Analogy: Highlighter Matching
Imagine you are scanning a printed document looking for the word "cat":
- Solution 1 (Whole Word Match - Scissors & Sorting): You take a pair of scissors and cut out every word individually. If a word has punctuation attached (like
"cat!"), you trim the punctuation away so it is just"cat". You then go through the cut-out words and count how many times the card"cat"appears."category"is ignored because it’s a completely different card. - Solution 2 (Substring Search - Highlighter Slide): You use a yellow highlighting tape of exactly 3 characters wide:
- Non-overlapping: You place the tape over
"cat". Once highlighted, you slide the tape completely past it (index += target.length()) to start highlighting from the next character."category"gets highlighted once because it contains"cat". - Overlapping: If you were checking for
"aa"in"aaa", after highlighting the first"aa"at indices 0-1, you slide the tape by just one letter (index += 1) to highlight the second"aa"at indices 1-2.
- Non-overlapping: You place the tape over
Solution 1 — Count a Specific Word (Whole-Word Match) Using Split + Frequency Map
Intuition
If we want to count a word as a distinct word (not just any substring occurrence), the safest approach is the same one used for “count words in a sentence” (Q15) and “duplicate words” (Q43): split the text into individual words (normalizing case/punctuation), then simply count how many of those words match our target exactly.
import java.util.HashMap;
public class CountSpecificWord {
public static int countWord(String text, String target) {
String[] words = text.toLowerCase().replaceAll("[^a-zA-Z0-9\\s]", "").split("\\s+");
String normalizedTarget = target.toLowerCase();
int count = 0;
for (String word : words) {
if (word.equals(normalizedTarget)) {
count++;
}
}
return count;
}
public static void main(String[] args) {
String log = "ERROR: timeout ERROR: connection lost ERROR: timeout";
System.out.println(countWord(log, "ERROR")); // 3
System.out.println(countWord(log, "timeout")); // 2
}
}
Output:
3
2
Dry Run (text = “ERROR: timeout ERROR:”, target = “ERROR”)
Lowercased & punctuation stripped: "error timeout error"
Split: ["error", "timeout", "error"]
normalizedTarget = "error"
word="error" -> matches -> count=1
word="timeout" -> no match
word="error" -> matches -> count=2
Final: 2
Interviewer’s take
This is the expected, correct approach for whole-word counting — reusing the same “normalize, split, count” pattern from earlier word-based problems. Stripping punctuation with regex ([^a-zA-Z0-9\\s]) prevents "ERROR:" and "ERROR" from being treated as different tokens, which is an important, easy-to-miss detail.
Follow-up questions you might get:
- “What if you want to count occurrences of ALL words, not just one target?” → Build a full frequency map (
HashMap<String, Integer>) exactly like Q43’s duplicate-words logic, then look up any word’s count directly, or list all counts. - “How does this be useful for QA/log analysis?” → Directly applicable to counting error types in a log file, tracking how often specific test failure messages appear, etc.
Solution 2 — Count Raw Substring Occurrences (Including Partial Matches, Not Just Whole Words)
Intuition
If we instead want to count every place the target text appears as a substring (even inside other words, like "ERROR" inside "ERRORCODE"), we scan through the text looking for the pattern starting at every position, and use Java’s indexOf() repeatedly, advancing our search start point past each found match.
public class CountSubstringOccurrences {
public static int countSubstring(String text, String target) {
if (target.isEmpty()) {
return 0; // avoid infinite loop on empty target
}
int count = 0;
int index = 0;
while ((index = text.indexOf(target, index)) != -1) {
count++;
index += target.length(); // move past this match (non-overlapping count)
}
return count;
}
public static void main(String[] args) {
System.out.println(countSubstring("ERRORERRORCODE", "ERROR")); // 2
System.out.println(countSubstring("aaa", "aa")); // 1 (non-overlapping)
}
}
Output:
2
1
Dry Run (text = “aaa”, target = “aa”, non-overlapping)
index=0
text.indexOf("aa", 0) = 0 -> count=1, index=0+2=2
text.indexOf("aa", 2) = -1 (not enough characters left starting at index 2) -> loop ends
Final: 1 (non-overlapping count — the second possible "aa" at index 1 is skipped since it overlaps the first match)
Interviewer’s take
This is the correct approach for substring (not whole-word) counting — and it’s important to explicitly clarify (and mention in your answer) whether you’re counting overlapping or non-overlapping occurrences, since text.indexOf(target, index) with index += target.length() specifically produces non-overlapping counts. If overlapping matches should be counted instead, you’d only advance index by 1 each time instead of by the full target length.
Follow-up questions you might get:
- “How would you count OVERLAPPING occurrences instead?” → Change
index += target.length()toindex += 1— this re-checks starting from just one position later, allowing overlapping matches to be found. - “Which version is more relevant for log analysis — whole-word or substring?” → Depends on context: searching for a specific error code or exact word usually wants whole-word matching (Solution 1); searching for a general pattern that might appear as part of larger tokens might want substring matching (Solution 2).
📊 Visual Flowchart
graph TD
Start["Input Text, Target T"] --> Decide{"Match Type?"}
Decide -->|Whole Word Match| Path1["Normalize & strip punctuation<br>Split text by whitespace into words"]
Path1 --> Loop1{"For each word"}
Loop1 --> Check1{"word.equals(target)?"}
Check1 -->|Yes| Inc1["count++"]
Inc1 --> Loop1
Check1 -->|No| Loop1
Loop1 -->|Done| End1["Return count"]
Decide -->|Raw Substring Search| Path2["Initialize index = 0, count = 0"]
Path2 --> Loop2{"indexOf(T, index) != -1?"}
Loop2 -->|Yes| Inc2["count++"]
Inc2 --> DecideOverlap{"Allow Overlaps?"}
DecideOverlap -->|Yes| Slide1["index = index + 1"]
DecideOverlap -->|No| Slide2["index = index + T.length"]
Slide1 --> Loop2
Slide2 --> Loop2
Loop2 -->|No| End2["Return count"]
Final Verdict — Which Solution Should You Give?
- Clarifying whole-word vs. substring matching (and overlap behavior, if substring) is the most important step — both solutions are simple and correct once the exact requirement is understood.
Quick Recap
| Approach | Matches | Time | Space |
|---|---|---|---|
| Split + compare | Whole words only | O(n) | O(n) for word array |
indexOf() loop | Any substring (overlap configurable) | O(n·m) worst case (m = target length) | O(1) |
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed