3.7.11 · D1 · Coding › Algorithm Paradigms › DP problems — Longest Increasing Subsequence (LIS) — O(n²) a
LIS ka matlab hai Longest Increasing Subsequence . Yeh poochta hai: numbers ki ek row mein se, kuch numbers ko cross out karo (baaki ka order mat badlo) taaki jo bachhe woh climbing karte rahein — woh climbing line kitni lambi ho sakti hai? Is topic ki baki saari cheezein bas do tarike hain us ek sawaal ka jawaab jaldi dene ke liye, instead of saare 2ⁿ cross-out patterns try karne ke.
Parent note ko fluently padhne se pehle, tumhe ideas ki ek chhoti si toolbox chahiye. Yeh page har ek idea ko zero se build karta hai, usi order mein jis order mein woh ek doosre par depend karte hain. Yahan kuch bhi assume nahi kiya gaya hai — agar parent ne koi symbol use kiya, toh hum use pehle yahan define karte hain.
Definition LIS — acronym ka poora naam
LIS = Longest Increasing Subsequence. "Longest" = hum count ko maximise karte hain; "Increasing" = values ko climb karna chahiye; "Subsequence" = hum unhe doosron ko cross out karke choose karte hain, reorder kiye bina. Teeno words dhyan mein rakho — har ek ek rule hai jise hum neeche precise karte hain.
Is topic ki har cheez ek array ke upar hoti hai — boxes ki ek row, har box mein ek number, aur har box ke neeche ek index (uski position number) hota hai.
A aur uski length n
Ek array values ki ek ordered row hoti hai. Hum ise A kehte hain. Length yeh hai ki usme kitne boxes hain, jise n likhte hain. Picture mein, n = 5 hai.
A [ 0 ] ka matlab hai "box number 0 mein jo value hai". (Hum 0 se count karte hain, isliye pehla box index 0 par hai, 1 par nahi.)
Har box ke neeche ka chhota number uska index hai.
Intuition 0 se count kyun shuru karte hain?
Almost har programming language aisa hi karti hai. Index 0 pehla element hai, aur last element index n − 1 par hota hai (n par nahi!). Yeh pakka kar lo: ek length-5 array ke valid indices 0 , 1 , 2 , 3 , 4 hain.
Common mistake Empty array (
n = 0 ) — ise skip mat karo.
Yeh ignore karna kyun tempting lagta hai: "kaun empty array dega?" — lekin edge cases real code ko crash kar dete hain. Rule yeh hai: agar A mein koi box nahi hai (n = 0 ), toh climb karne ke liye koi subsequence nahi hai, isliye LIS length 0 hai . Dono methods ise free mein handle karte hain: O ( n 2 ) loop kabhi run nahi karta toh empty d p table ka max 0 hoga, aur fast method ki tails array empty rehti hai toh uski length 0 hoti hai. Hamesha state karo: empty array → answer 0 .
Yahi THE distinction hai jis par poora topic tika hai.
Definition Subarray (contiguous)
Ek subarray ek connected window hoti hai — tum ek start box aur ek end box choose karte ho aur beech mein jo bhi hai sab le lete ho, koi gap nahi. Jaise ek picture frame row ke saath slide karna.
Definition Subsequence (gaps allowed)
Ek subsequence woh hai jo bachta hai jab tum kuch boxes ko cross out karo aur baaki ko unke original order mein rakho. Gaps allowed hain. Tum zero boxes cross out kar sakte ho, ya sab bhi.
Picture mein, [3, 4, 5] jo [3,1,4,1,5] se aaya hai, ek valid subsequence hai (humne do 1's cross out kiye) lekin subarray nahi hai (boxes ek doosre ke saath nahi hain).
Common mistake "Reordering allowed hai" — nahi.
Yeh tempting kyun lagta hai: aisa feel hota hai jaise tum bas numbers ka ek set pick kar rahe ho. Fix yeh hai: order lock hai. Tum sirf delete kar sakte ho, kabhi shuffle nahi. [5, 3], [3,1,4,1,5] ki subsequence NAHI hai kyunki original mein 5, 3 ke baad aata hai.
Recall Length-
n array mein kitni subsequences hoti hain?
2 n — n boxes mein se har ek independently "kept" ya "crossed out" hota hai. Yahi exactly reason hai ki brute force O ( 2 n ) hai aur hume kuch zyada smart chahiye.
Definition Strictly increasing
Ek sequence strictly increasing hoti hai jab har element apne pehle wale se bada ho — kabhi equal nahi, kabhi chhota nahi. Symbol: neighbours ke beech < sign.
A [ i 1 ] < A [ i 2 ] < ⋯ < A [ i L ]
Unke beech har gap strictly upar ka step hai.
< symbol aur "strict" kya exclude karta hai
x < y ka matlab hai "x , y se chhota hai". Strict ka matlab hai hum x = y forbid karte hain. Toh [2, 2, 3] strictly increasing NAHI hai (do 2's equal hain). Agar hum equals allow karte toh hum non-decreasing kehte aur ≤ ("less than or equal") likhte. Parent ka default LIS strict version hai.
< = strict = "must climb" , ≤ = loose = "ek step par rest kar sakta hai" . Parent note ka lower_bound vs upper_bound choice poori tarah isi do mein se kisi ek ke baare mein hai.
Definition mein i 1 < i 2 < ⋯ < i L use hota hai. Yahan do alag < signs chhupe hain — inhe confuse mat karo!
i 1 < i 2 < ⋯ < i L
Yeh i 's positions (box numbers) hain, values nahi. i 1 < i 2 ka matlab hai "position i 1 , row mein position i 2 se pehle aati hai". Yeh < order preserved enforce karta hai (no reordering).
Jabki A [ i 1 ] < A [ i 2 ] (un positions par values) climbing enforce karta hai.
Toh poori definition mein do rules packed hain:
Order: i 1 < i 2 < ⋯ < i L — positions left to right jaati hain.
Climb: A [ i 1 ] < A [ i 2 ] < ⋯ < A [ i L ] — values upar jaati hain.
L — woh length jise hum maximise karte hain
L chosen elements ki count hai — climbing line ki length. LIS sabse bada possible L chahta hai.
Parent ki recurrence yeh hai:
d p [ i ] = 1 + max ( { 0 } ∪ { d p [ j ] : j < i , A [ j ] < A [ i ]} ) .
Unpack karne ke liye notation ke teen pieces hain.
max
max ( S ) = collection S mein sabse bada number . Agar S empty hai, toh hum max as producing nothing maante hain — isliye { 0 } bolt on kiya gaya hai.
∪ (union) aur { 0 }
∪ ka matlab hai "dono collections ko ek mein daal do ". Yahan { 0 } ek set hai jismein sirf number 0 hai. Hum ise union karte hain taaki agar koi bhi earlier element qualify na kare , toh max ke paas phir bhi kuch ho — woh 0 return karta hai, jisse d p [ i ] = 1 milta hai (element akele khada hai). Yeh sabse pehle / sabse chhote elements ke liye safety net hai.
{ d p [ j ] : j < i , A [ j ] < A [ i ]}
Colon : ko "such that " padho. Yeh kehta hai: "har us j ke liye d p [ j ] collect karo such that j , i se pehle hai aur wahan ki value chhoti hai." Yeh un saari chains ka set hai jise hum A [ i ] ant mein lagakar legally extend kar sakte hain.
Worked example Poori recurrence ko English mein padhna
"Box i par khatam hone wali best climbing line yeh hai: kisi bhi earlier, smaller box par khatam hone wali best climbing line lo, aur box i ke liye 1 jodo — ya agar aisa koi box nahi hai, toh sirf box i akela hai (length 1)."
d p [ i ] — ek memory table
dp dynamic programming ka short form hai: chhote pieces solve karo, unke answers ek table mein store karo, unhe reuse karo. d p [ i ] us table ka ek cell hai. Yahan yeh store karta hai "sabse lambi climbing subsequence ki length jo exactly box i par khatam hoti hai." General pattern ke liye Dynamic Programming dekho. Kahan khatam hoti hai yeh pin karna woh trick hai jo ek global hunt ko ek simple left-to-right fill mein badal deti hai.
Definition Big-O notation
O ( ⋅ ) describe karta hai ki input bada hone par kaam kaise grow karta hai , constants ignore karke. O ( n 2 ) ≈ "n cheezein mein se har ek ke liye, n units of work karo" — n double karne par time chaar guna ho jaata hai. O ( n log n ) bahut gentle hai: n double karne par time barely se zyada double hota hai.
O ( n 2 ) kyun hai — loops count karo
d p [ i ] fill karne ke liye hume har earlier j < i dekhna padta hai valid predecessors (A [ j ] < A [ i ] ) dhundhne ke liye. Yeh n values tak ek inner loop hai. Hum ise n boxes mein se har ek ke liye repeat karte hain — ek outer loop. Do nested loops, har ek n tak, matlab roughly n × n = n 2 steps of work — isliye O ( n 2 ) . Fast method ka poora point us inner scan-all-j loop ko ek O ( log n ) binary search se replace karna hai.
log n — halving count
log 2 n ka jawaab hai: "n ko 1 tak pahunchne se pehle main kitni baar half kar sakta hun? " 1024 ki list ko sirf 10 halvings chahiye. Yeh chhota sa number hi reason hai kyun Binary Search (jo har step mein sorted range ko half karta hai) slow method ko fast banata hai.
tails array
tails O ( n log n ) method ke peeche bookkeeping array hai (dekho Patience Sorting ). ==tails[k] = ab tak milne wali length k + 1 ki kisi bhi increasing subsequence ki sabse chhoti possible last value.== Yeh hamesha sorted rakhi jaati hai (lambi chain ki tail badi hoti hai), isliye hum ise binary-search kar sakte hain. Ekdum ant mein, len(tails) LIS length ke barabar hoti hai — aur empty input ke liye tails empty rehti hai, jisse length 0 milti hai. (Warning: tails khud ek real subsequence nahi hai; sirf uski length meaningful hai.)
Ek sorted array par, lower_bound(x) pehle aise element ki position dhundta hai jo ≥ x ho (x se kam na ho). Yeh Binary Search se milta hai — har step mein range half karta hai, isliye O ( log n ) . Fast method yeh tails array par har element ke liye call karta hai yeh decide karne ke liye ki ek tail replace karni hai ya ek nayi append karni hai. upper_bound uska cousin hai: pehla element strictly greater (> x ).
Array A and index counting from 0
Subsequence delete keep order
Index chain i1 lt i2 order
max union set-builder notation
Dynamic Programming store subproblems
lower_bound and tails array
Seedha parent LIS note mein jaata hai aur aage Patience Sorting aur Greedy Algorithms se connect karta hai.
LIS acronym ka kya matlab hai? Longest Increasing Subsequence.
Length-5 array diya ho toh uske last element ka index kya hai? 4 (indices 0 se n − 1 tak jaate hain)
Empty array (n = 0 ) ka LIS kya hai? 0 — climb karne ke liye koi subsequence nahi hai.
Kya [5, 3], [3,1,4,1,5] ki subsequence hai? Kyun ya kyun nahi? Nahi — order lock hai; tum delete kar sakte ho lekin reorder nahi, aur original mein 5, 3 ke baad aata hai.
Length-n array mein kitni subsequences hoti hain? 2 n (har element kept ya crossed out).
Kya [2, 2, 3] strictly increasing hai? Nahi — strict equal neighbours forbid karta hai; yeh sirf non-decreasing hai.
i 1 < i 2 mein, kya hum positions compare kar rahe hain ya values?Positions (indices) — yeh original order enforce karta hai, value comparison A [ i 1 ] < A [ i 2 ] se alag.
dp table se overall LIS length kaise nikalte hain? Saare cells par max lo: LIS = max over 0 ≤ i < n of d p [ i ] , kyunki LIS kisi bhi index par khatam ho sakti hai.
O ( n 2 ) recurrence quadratic kyun hai?Ek inner loop har earlier j ko scan karta hai (up to n ) n boxes mein se har ek ke liye → n × n = n 2 steps.
Recurrence mein { 0 } union kyun kiya jaata hai? Taaki max tab bhi defined ho jab koi earlier smaller element exist na kare, jisse length 1 mile (element akela).
log 2 n kya count karta hai?Kitni baar tum n ko 1 tak pahunchne se pehle half kar sakte ho.
Fast method mein tails[k] kya store karta hai? Ab tak dekhi gayi length k + 1 ki kisi bhi increasing subsequence ki sabse chhoti possible last value.
Sorted array par lower_bound(x) kya return karta hai? Pehle aise element ki position jo ≥ x ho.