Exercises — Naive pattern matching — O(nm)
3.8.1 · D4· Coding › String Algorithms › Naive pattern matching — O(nm)
Quick symbol refresher, taaki pehli line readable ho:
Level 1 — Recognition
L1.1
Candidate shifts ki number batao "MISSISSIPPI" aur "ISS" ke liye, aur ek sentence mein explain karo ki count woh kyun hai jo hai.
Figure ruler idea ko lay out karta hai: har blue bracket ke liye ek legal starting slot hai; pink bracket woh pehla illegal wala hai jo end se hang off hota hai.
Recall Solution
Letters count karo: M-I-S-S-I-S-S-I-P-P-I mein hai; I-S-S mein hai. Candidate shifts se tak run karte hain, isliye hain. Exactly itne hi kyun: agar se baad start hota toh use positions par characters chahiye hote — lekin index par khatam hota hai, isliye end se run off ho jaata (figure mein pink bracket). Positions hi woh akele hain jahan saare characters abhi bhi ke andar fit hote hain.
L1.2
Fixed shift par trace ke liye, inner counter ki kaunsi value full match signal karti hai, aur kaunsi failure signal karti hai? Dono pseudocode while j < m and T[s+j] == P[j]: j += 1 ko refer karte hain.
Recall Solution
se start hota hai aur har matched character par ek baar increment hota hai.
- Full match: loop sirf isliye ruka kyunki
j < mfalse ho gayi, yaani poora tak climb kar gaya. Har character match hua. - Failure: loop isliye ruka kyunki
T[s+j] == P[j]false ho gayi jabki abhi bhi tha. Toh se neeche stuck hai — woh stuck value pehle mismatching character ki index hai. Yaad rakhne ki ek line: ⇒ report; ⇒ dead shift.
Level 2 — Application
L2.1
"ABABAB" (), "ABA" () par naive matching haath se chalao. Har valid shift list karo.
Figure pattern ko text ke neeche har shift par stack karta hai: blue cells matches hain, pink cells woh mismatch hai jo ek shift ko khatam karti hai. Do blue rows do hits hain.
Recall Solution
Shifts (kyunki ). Figure ko top se bottom tak padhte hain:
- : A=A, B=B, A=A → MATCH ✅
- : B≠A → fail at
- : A=A, B=B, A=A → MATCH ✅
- : B≠A → fail at
Valid shifts: . Note karo ki do matches overlap karte hain — positions aur index share karte hain. Naive matching khushi se overlapping matches dhundhta hai kyunki woh har baar ko scratch se restart karta hai.
L2.2
Usi run ke liye (L2.1), early break use karke exact number of character comparisons count karo.
Recall Solution
Comparisons per shift add up karo (figure mein coloured cells count karo — matches plus woh ek mismatch jo har row ko stop karti hai):
- : 3 comparisons (saari match).
- : 1 comparison (B≠A, immediately break).
- : 3 comparisons.
- : 1 comparison.
Total comparisons. bound ke comparison mein itne kam kyun? Bound sirf ek upper limit hai. Break ne do shifts ko ek-ek comparison tak cut kar diya, isliye humne kiya.
L2.3
"AAAAA" (), "AAB" () par naive matching chalao. Matches aur comparison count dhundho.
Figure dikhata hai ki har shift "AA" (blue) match karta hai aur phir hamesha "B" par fail hota hai (pink) — early break kabhi fire nahi hoti, isliye har row ki full cost hoti hai.
Recall Solution
Shifts , shape mein saare identical:
- : A=A, A=A, A≠B → fail at (3 comps)
- : A=A, A=A, A≠B → fail at (3 comps)
- : A=A, A=A, A≠B → fail at (3 comps)
Valid shifts: none. Comparisons . Har shift ki full cost kyun: pattern ke pehle do characters "AA" hamesha all-A text se match karte hain, isliye break last character "B" ke mismatch hone tak fire nahi ho sakti. Yeh miniature mein worst-case shape hai.
Level 3 — Analysis
L3.1
12 copies of "A" () aur "AAAB" () ke liye exact worst-case comparison count do (sirf nahi).
Recall Solution
Number of shifts . Har shift teen A's match karta hai phir B par fail hota hai — poore comparisons each, break kabhi early fire nahi hoti. Total . Yeh exact max kyun hai: koi shift se zyada comparisons cost nahi kar sakta, aur yahan har shift woh ceiling hit karta hai, isliye sum upper bound ke barabar hai. Yahi precisely ka matlab hai — bound achieve hota hai, sirf respect nahi kiya jaata.
L3.2
Parent note kehta hai naive matching "random text par ke close" hai. Explain karo kyun, expected comparisons per shift ke terms mein, aur binary alphabet (do equally likely letters) ke liye expected work estimate karo. Phir batao ki finite pattern length estimate ko kaise change karta hai.
Figure ke partial sums ko climb aur par flatten hote dikhata hai — yeh kyun infinite sum finite number par converge hoti hai, iska ek picture hai.
Recall Solution
Random text par, kya ek shift ke position par character match hoga, yeh probability ke saath ek coin flip hai (binary alphabet). Ek shift par comparisons ki number woh characters ki count hai jo first mismatch tak check hote hain — lekin yeh se zyada kabhi nahi ho sakti, kyunki pattern mein sirf characters hain.
Pehle, idealised (unbounded) estimate. Agar hum pretend karte ki pattern infinitely long hai, toh ek shift par comparison count satisfy karta , aur uska mean hota Yeh derive karna ki yeh equals karta hai: plain geometric series se start karo, kisi bhi ke liye valid, Dono sides ko ke respect mein differentiate karo (left par term-by-term): Apni sum ki shape match karne ke liye dono sides ko se multiply karo: set karo:
Ab finite- correction (edge case). Ek real pattern count ko par stop karta hai: jab bhi pehle characters saare match karte hain (probability ), shift exactly comparisons par khatam hoti hai rather than continue karne ke. par tail truncate karne se exact finite mean milta hai Pehla sum unbounded mean minus uski apni tail hai; truncation carry out karne par, yeh clean closed form par telescope karta hai Extremes check karo: deta hai (ek guaranteed comparison — correct); deta hai ; aur jaise jaise , , idealised value recover karta hai. Toh har finite ke liye true expected comparisons per shift strictly se below hai, se neeche se approach karta hai.
Bottom line: shifts ke saath, expected total work hai, se independent. Finite pattern ise sirf sasta banata hai, kabhi zyada nahi, isliye conclusion safe hai. kyun disappear hota hai: mismatches almost immediately hoti hain, isliye inner loop reach karne se bahut pehle mar jaata hai. Dreaded factor sirf tab appear hota hai jab text aur pattern conspire karte hain (all-same characters).
L3.3
Naive matching ki asymptotic cost ko KMP ke se compare karo. , , aur worst-case input ke liye, roughly kitne comparisons dono karte hain? Ratio do. Constant factors ke baare mein careful raho.
Recall Solution
- Naive worst case (exact): comparisons (approximately ).
- KMP: iska bound hai, lekin yeh ek constant factor hide karta hai — KMP prefix function ke saath wापस loop fall back hone par same text character se zyada baar compare kar sakta hai, aur standard worst-case comparison bound approximately text-character comparisons hai (plus prefix table build karne ke liye ). Toh ek fair worst-case comparison estimate hai
- Ratio (naive ÷ KMP): (roughly paanch-sau-guna). Gap kyun aur factor 2 kyun matter karta hai: naive har baar slide karne par already matched characters ko re-check karta hai — har shift mein tak. KMP ka prefix function use skip karne deta hai, har text character ko bounded number of times padhta hai (kabhi bhi se zyada nahi). Crude "" figure use karna ratio ko approximately overstate karta; honest constant-aware estimate wahi hai jo humne yahan use kiya. Growth rates ki bajaye actual counts chahiye hone par constants kyun matter karte hain, dekhо Big-O Notation.
Level 4 — Synthesis
L4.1
Pseudocode ko modify karo taaki woh sirf pehla match report kare aur immediately stop kare. Change batao aur ek pattern ke liye exact worst-case comparison count do jo kabhi occur nahi karta, lengths ke text/pattern par.
Recall Solution
Change: report match at s ke baad, return s add karo. Agar koi match nahi, return .
FIRST-MATCH(T, P):
n = len(T); m = len(P)
for s = 0 to n - m:
j = 0
while j < m and T[s+j] == P[j]:
j = j + 1
if j == m:
return s # stop at first hit
return -1Worst case jab pattern kabhi occur nahi karta: early return kabhi fire nahi hoti, isliye hum saare shifts scan karte hain, aur har shift mein comparisons tak cost ho sakti hai (jaise L2.3/L3.1 ki all-same-character shape mein). Exact worst-case count isliye poora product hai all-matches version ke identical. Concretely, agar toh yeh comparisons hai — wahi jaise L3.1 mein. "First match" modification sirf tab help karta hai jab ek match early exist karta hai; jab pattern kabhi occur nahi karta toh yeh kuch nahi kharidta, aur count exactly hai.
L4.2
Ek naive matcher design karo jo case-insensitive ho ("Abc" matches "aBC") poori text pehle copy ya lowercase kiye bina (memory save karne ke liye). Ek-line change do.
Recall Solution
Comparison T[s+j] == P[j] ko lower(T[s+j]) == lower(P[j]) se replace karo, jahan lower(c) ek uppercase letter ko on the fly lowercase mein map karta hai.
while j < m and lower(T[s+j]) == lower(P[j]):Yeh kyun kaam karta hai aur sasta rehta hai: hum individual characters ko sirf comparison ke moment par transform karte hain, isliye text ki koi extra copy nahi banti. Total extra cost har comparison par ek lower call hai — ek constant factor, isliye complexity par rehti hai.
L4.3
Naive matching extend karo taaki single wildcard ? wale pattern ke matches count kare, jo koi bhi character match karta hai. Example: "A?A" "ABA", "ACA", "AAA" match karta hai. Inner comparison likhо aur "ABACA" (), "A?A" () par trace karo.
Recall Solution
Pehle comparison operator precisely define karo. woh test hai jo hum har position par record karte hain: Pseudocode mein:
while j < m and (P[j] == '?' or T[s+j] == P[j]):Trace, shifts . Yahan "✓" ka matlab hai true return kiya; ek wildcard position hamesha true return karti hai:
- , window "ABA": ✓, ✓, ✓ → MATCH ✅
- , window "BAC": ✗ → fail at
- , window "ACA": ✓, ✓, ✓ → MATCH ✅
Match count ( aur par). Wildcard complexity kyun nahi change karta: har call abhi bhi hai; humne sirf kya equal count hota hai woh widen kiya. Worst case rehta hai.
Level 5 — Mastery
L5.1
Fixed text length ke liye, woh pattern length choose karo (with ) jo worst-case comparison count maximize kare. Phir woh smallest dhundho jiske liye woh maximum 100 exceed kare, aur maximizing report karo.
Recall Solution
Optimization criterion (explicitly stated): hum minimize kar rahe hain subject to . Har ke liye hum pehle best choose karte hain, phir ko upar scan karte hain.
Fixed ke liye best : function mein ek downward-opening parabola hai, vertex par maximize hota hai. Toh worst possible pattern length text ka roughly half hai — yeh biggest product deta hai. Kyunki ek integer hona chahiye, ko nearest integer par round karo, deta hai
ko upar scan karo, hamesha best use karte hue, jab tak max pehli baar exceed nahi karta:
- : , lo (ya ) → . (, enough nahi.)
- : , lo → . ( equals — problem strictly greater maangta hai, toh abhi bhi enough nahi.)
- : , lo (ya ) → . ( ✓ — pehli baar bar clear kiya.)
Answer: smallest text length hai, maximizing pattern length ke saath achieve hota hai (equivalently , parabola ki symmetry se, jo bhi deta hai), worst-case comparisons yield karta hai. kyun aur kyun nahi: ek huge pattern bahut kam shifts chhодता hai ( tiny hota hai), jabki ek tiny pattern per shift kam cost karta hai ( tiny hota hai). Product middle mein peak karta hai — yahi parabola ka vertex hai.
L5.2
Prove karo: agar pattern mein saare distinct characters hain (koi letter repeat nahi), toh naive matching total mein at most comparisons karta hai — yaani woh ki parwah kiye bina linear hai. (Yeh "reason hai kyun aise patterns ke liye KMP ka prefix function trivial hai.")
Recall Solution
Setup. Text mein ek single pointer track karo — currently compare ho raha text character ka index, . Saare comparisons ko do disjoint types mein split karo:
- Matching comparisons — wo jahan
T[s+j] == P[j]true hai. Har ek advance karta hai aur isliye ko ek se advance karta hai. - Mismatching comparisons — wo jahan woh false hai. Har ek ek shift end karta hai.
Matching comparisons bound karna. Yahan distinctness bite karta hai. Suppose ek shift ne ek block ko ke against match kiya aur phir mismatch kiya. Kyunki ke characters saare different hain, mein se koi bhi ke barabar nahi; aur wo text block jo humne abhi match kiya woh tha . Isliye us already-matched block ke andar koi doosri jagah nahi hai jahan line up ho sake. Agla shift jo possibly match kar sakta hai woh hai — algorithm ka agla successful match previous run ke stop hone ke text position par ya uske baad shuru hona chahiye. Consequently matching comparison se ever consume kiye gaye text positions ka set sirf aage move karta hai: poore run mein, har text index at most ek baar match hota hai. Yeh at most matching comparisons hai.
Mismatching comparisons bound karna. Har shift at most ek mismatch mein khatam hoti hai (loop pehle false test par break karta hai), aur shifts hain. Isliye at most mismatching comparisons.
Sum karna. Total comparisons . Hence Intuition kyun matter karta hai: distinctness us overlap ko kill karta hai jo re-checking cause karta hai — exactly woh redundancy jo KMP generally remove karta hai. Distinct patterns ke liye, naive already essentially KMP-fast hai.
Active Recall
Shifts for T length 11, P length 3, and why?
Does naive matching report overlapping occurrences?
Expected comparisons per shift on a random binary text (finite m)?
Smallest text length forcing more than 100 worst-case comparisons (best m each n)?
Worst-case comparison count for 12 A's, "AAAB"?
Why is naive matching linear when the pattern has all-distinct characters?
Connections
- Naive pattern matching — O(nm) — woh parent jise yeh drills exercise karte hain.
- KMP Algorithm — L3.3 aur L5.2 exactly us redundancy ki taraf point karte hain jo KMP remove karta hai.
- Boyer-Moore Algorithm — aage skip karta hai; naive ke step-by-one se contrast karo.
- Rabin-Karp Algorithm — windows hash karta hai; se pare ek aur route.
- Big-O Notation — L3 mein worst vs average case reasoning.
- Sliding Window Technique — "pattern slide karo" mental model.
- Substring Search Problem — umbrella problem.