Visual walkthrough — DP problems — Longest Increasing Subsequence (LIS) — O(n²) and O(n log n)
3.7.11 · D2· Coding › Algorithm Paradigms › DP problems — Longest Increasing Subsequence (LIS) — O(n²) a
Hum sab kuch ek running array pe banate hain: Kuch aur assume nahi kiya. Pehle apne words define karte hain.

Step 1 — Array ko heights ki tarah draw karo, aur ek increasing chain banao
KYA. Har value ko horizontal position aur height par ek dot ki tarah plot karo. Ek increasing subsequence koi bhi aisa set of dots hai jinhein tum arrows se connect kar sako jo hamesha right aur upar jaate hon.
KYUN. Height = value, position = index. "Increasing subsequence" ab ek aisi staircase ban jaati hai jo right move karte waqt sirf upar hi chadhti hai. Woh visual — ek path jo kabhi neeche nahi utarta — poora problem hai. Neeche sab kuch is baare mein hai ki sabse lambi aisi staircase kaise banayein bina kabhi bhi saare possible paths dekhein.
PICTURE. Upar Figure s01. Red arrows ko connect karte hain (length 4). Notice karo ki har arrow upar-aur-right point karta hai; yahi ek matra rule hai.
Step 2 — Ek bookkeeping array: tails
KYA. Hum tails naam ka ek single array introduce karte hain. Hum ise aise padhte hain:
Yahan se shuru hota hai, isliye tails[0] length-1 chains ke baare mein hai, tails[1] length-2 chains ke baare mein, aur aise aage.
"Smallest" word KYUN. Ek given length ki chain future ke liye tab hi useful hoti hai jab use extend karna aasaan ho. Chain ko ek naye value se extend karne ke liye, ko chain ki last value se bada hona chahiye. Toh woh last value (yani "tail") jitni chhoti hogi, utne zyada future attach ho sakenge. Ek hi length ki saari chains mein, sabse chhoti tail wali chain baaki sabse dominate karti hai — woh sab kuch accept karti hai jo doosri karti hain, aur kuch zyada bhi. Isliye hum hamesha sirf minimum tail per length record karte hain.
PICTURE. Figure s02 mein do alag length-2 chains hain jo aur par khatam hoti hain. par khatam hone wali (red) clearly better hai: koi bhi future value jo ke upar fit ho sakti thi, woh ke upar bhi fit hogi, par ulta nahi.

Step 3 — tails automatically sorted rehta hai (yahi cheez binary search allow karti hai)
KYA. Claim: tails strictly increasing hai, matlab — hamesha, chahe hum koi bhi array feed karein.
KYUN. Koi bhi chain lo length ki jo value par khatam hoti ho. Iska last element kaat do. Ab tumhare paas length ki chain hai jiska tail koi aisi value hai jo strictly less than hai (woh ek rising chain mein pehle aayi thi). Lekin sabse chhota possible length- tail hai, isliye Inequality chain karo: . Ho gaya — array chadhta hai.
PICTURE. Figure s03: length-4 red chain; uska top element hatao aur bachi hui length-3 chain ka tail provably length-4 tail se neeche hai. Isse tails-ki-staircase ko rise karne par majboor hona padta hai.

Step 4 — Ek naya value process karna: dhundho yeh kahan fit hota hai
KYA. Har incoming value ke liye, hum poochhte hain: sabse leftmost position kaunsi hai jahan ho? Ise operation ==lower_bound== kehte hain — "pehla slot jo se chhota nahi."
KYUN. Do cheezein ho sakti hain aur dono us position se decide hoti hain:
- Agar aisa exist karta hai, toh length- chains ke liye ek better (chhoti) tail ban sakta hai, isliye hum overwrite karte hain. Humne koi chain lambi nahi ki — humne ek existing length ko baad mein extend karna sasta kar diya.
- Agar koi slot nahi hai (matlab har tail se bada hai), toh currently-longest chain ke upar chadh jaata hai aur hum use append karte hain, record length ek se badh jaati hai.
kyun, nahi. Hum strictly increasing subsequences chahte hain. Pehle tail ko equal to replace karna ek duplicate value ko extension ki tarah treat hone se rokta hai (tie koi increase nahi hai). Agar problem non-decreasing runs allow karti, toh hum first tail strictly dhundhhte. Yeh ek comparison choice hi dono variants ka ek-matra difference hai.
PICTURE. Figure s04: sorted tails array ek number line ki tarah; red marker lower_bound ko pehle tail par land karte dikhata hai, saath mein "replace here" arrow.

Step 5 — Dekhte hain yeh chalta kaise hai, frame by frame
KYA. Hum ke saare elements rule se push karte hain aur har step ke baad tails record karte hain.
KYUN. Tracing abstract rule ko muscle memory mein badal deti hai, aur yeh do interesting moves surface karta hai — ek replace jo ek tail ko lower karta hai aur ek append jo length badhata hai.
| leftmost tail ? | action | tails baad mein |
|
|---|---|---|---|
| 3 | koi nahi | append | [3] |
| 1 | at | replace | [1] |
| 4 | koi nahi | append | [1,4] |
| 1 | at | replace | [1,4] |
| 5 | koi nahi | append | [1,4,5] |
| 9 | koi nahi | append | [1,4,5,9] |
| 2 | at | replace | [1,2,5,9] |
| 6 | at | replace | [1,2,5,6] |
Final length . ✓
PICTURE. Figure s05 in saare frames mein baadhte tails staircase ko overlay karta hai; red segment woh value hai jo har moment mein replace/append ho rahi hai.

Step 6 — Edge aur degenerate cases (reader ko kabhi stranded mat chhodo)
KYA + KYUN + PICTURE, ek-ek row. Figure s06 mein saare chaar side by side hain.
- Empty array
[]: loop kabhi chalta hi nahi,tails[]rehta hai, length . Sahi — koi elements nahi, koi subsequence nahi. - Strictly decreasing
[5,4,3,2,1]: har ,tailsmein sab kuch se hai, isliye har step position replace karta hai.tailslength se zyada kabhi nahi badhta → . Sahi: sabse lambi rising chain koi bhi single element hai. - Strictly increasing
[1,2,3,4,5]: koi bhi cheez kabhi nahi hoti jo block kar sake, har append hota hai →tailshar step mein badhta hai → . Sahi: poora array pehle se hi rise karta hai. - Sab equal
[7,7,7,7]: strict LIS ke liye hum use karte hain, isliye har existing ko par dhundhhta hai aur replace karta hai, kabhi append nahi → . Sahi: equal values koi increase nahi hain. (Non-decreasing rules ke saath use karte toh, yeh deta — exactly isliye vs choice matter karti hai.)

Recall Decreasing case mein kabhi append kyun nahi hoti?
Append tab hoti hai jab saare current tails se zyada ho. Decreasing array mein har naya saari dekhi gayi cheezein se chhota hota hai, isliye lower_bound hamesha position par land karti hai aur hum replace karte hain — length par rehti hai.
Ek-picture summary

Yeh final figure poori derivation ko fuse karta hai: raw array heights ki tarah (upar), sorted tails staircase jo hum maintain karte hain (beech mein, red = smallest-tail invariant), aur ek single decision box (neeche) — lower_bound, phir replace-or-append. Jitni baar hum kabhi append karte hain, woh count ke barabar hai, aur kyunki tails force-sorted hai (Step 3) isliye har decision sirf cost karta hai.
Recall Feynman: poora walkthrough kisi 12-saal ke bacche ko batao
Apne cards left to right line up karo. Tum piles ki ek row rakhte ho. Jab naya card aata hai, apni piles ko left se chalo aur card ko pehli aisi pile par daal do jiska top tumhare card se chhota nahi — tum us top ko apne (chhote-ya-barabar) card se cover karte ho. Agar koi pile nahi le sakti (tumhara card har top ko beat karta hai), right par ek bilkul naya pile shuru karo. Chhote card se kyun cover karte ho? Kyunki ek chhota top future cards ke liye beat karna aasaan hai, isliye zyada cards baad mein pile up ho sakte hain. Ant mein, apni piles gino — woh count hi tumhare deck mein chhipi hamesha-chadhhti cards ki line ki length hai. "Left se pehli pile" bas sorted row of pile-tops par Binary Search hai, aur yahi poora trick hai — yeh card game Patience Sorting hai.
Connections
- Binary Search — sorted-
tailsinvariant (Step 3) woh cheez hai jolower_boundko legal banata hai. - Patience Sorting — Feynman retelling mein card-pile picture.
- Dynamic Programming — woh cousin jise yeh fast method optimize karta hai.
- Greedy Algorithms — "keep smallest tails" ek greedy exchange argument hai, Step 2 mein prove kiya gaya.
- Longest Common Subsequence (LCS) — ek related subsequence problem.
- Russian Doll Envelopes — LIS ke upar layered ek 2D application.