Specimen 08 ยท O(n) time, O(n) space

1D Dynamic Programming

Climbing n stairs, one or two steps at a time โ€” how many distinct ways are there to reach the top? Rather than re-deriving the answer for every n from scratch, build a table left to right: the answer for n is just the answer for nโˆ’1 plus the answer for nโˆ’2, already sitting in the table. Once that idea is clear, the whole table collapses into two rolling variables โ€” same answer, constant space.

โ† Back to the Field Guide
08-namuna ยท O(n) vaqt, O(n) xotira

1D Dynamic Programming

n ta zinapoyaga bir yoki ikki qadamda chiqish โ€” tepaga chiqishning nechta xil yo'li bor? Har bir n uchun javobni qaytadan hisoblash o'rniga, jadvalni chapdan o'ngga to'ldiring: n uchun javob โ€” jadvalda allaqachon turgan nโˆ’1 va nโˆ’2 javoblarining yig'indisi, xolos. Bu g'oya aniq bo'lgach, butun jadval ikkita rolling o'zgaruvchiga siqiladi โ€” javob bir xil, xotira esa doimiy.

โ† Qo'llanmaga qaytish

Intuition

Watch the bars below: each new slot is built from the sum of the two bars just before it โ€” once a slot is filled, it's never recomputed, only reused.

Tushuncha (Intuition)

Quyidagi ustunlarga qarang: har bir yangi katakcha undan oldingi ikkita ustunning yig'indisidan quriladi โ€” bir marta to'ldirilgan katakcha hech qachon qayta hisoblanmaydi, faqat qayta ishlatiladi.

How It Works

  1. Recognize the recurrence: the number of ways to reach step i equals the number of ways to reach step iโˆ’1 (then take one more step) plus the number of ways to reach step iโˆ’2 (then take two more steps).
  2. Nail down the two base cases directly, since the recurrence needs both to get started: reaching step 1 has exactly 1 way (a single step), and reaching step 2 has exactly 2 ways (two single steps, or one double step).
  3. Create an array dp with one slot per step, from 0 up to n.
  4. Seed the table with the base cases: dp[1] = 1 and dp[2] = 2.
  5. Walk i from 3 up to n, filling the table strictly left to right, one slot at a time.
  6. At each i, compute dp[i] = dp[i-1] + dp[i-2]. Because the table is filled in increasing order of i, both dp[i-1] and dp[i-2] are already sitting in the table by the time dp[i] needs them โ€” nothing is recomputed, nothing is guessed.
  7. After the loop finishes, dp[n] holds the answer: the total number of distinct ways to climb all n stairs.
  8. Notice that filling dp[i] only ever touches the two most recent entries, dp[i-1] and dp[i-2] โ€” every older entry is never read again. That observation is what later motivates replacing the whole table with two rolling variables (see Complexity).

Qanday Ishlaydi

  1. Recurrence'ni tanib oling: i-zinapoyaga yetish yo'llari soni โ€” (iโˆ’1)-zinapoyaga yetish yo'llari soniga (keyin yana bitta qadam qo'yiladi) va (iโˆ’2)-zinapoyaga yetish yo'llari soniga (keyin yana ikkita qadam qo'yiladi) teng.
  2. Ikkita base case'ni aniq belgilang, chunki recurrence boshlanishi uchun ikkalasi ham kerak: 1-zinapoyaga yetish aynan 1 yo'lga ega (bitta qadam), 2-zinapoyaga yetish esa aynan 2 yo'lga ega (ikkita bitta qadam, yoki bitta ikkita qadam).
  3. Har bir zinapoya uchun bitta katakcha bo'lgan dp array yarating, 0'dan n'gacha.
  4. Jadvalni base case'lar bilan urug'lantiring: dp[1] = 1 va dp[2] = 2.
  5. ini 3'dan n'gacha yurgizing, jadvalni qat'iy ravishda chapdan o'ngga, bir vaqtning o'zida bitta katakcha to'ldirib boring.
  6. Har bir ida dp[i] = dp[i-1] + dp[i-2]ni hisoblang. Jadval ining o'sib boruvchi tartibida to'ldirilgani uchun, dp[i-1] va dp[i-2] ikkalasi ham dp[i]ga kerak bo'lgan paytda allaqachon jadvalda turibdi โ€” hech narsa qaytadan hisoblanmaydi, hech narsa taxmin qilinmaydi.
  7. Loop tugagach, dp[n] javobni saqlaydi: barcha n ta zinapoyaga chiqishning jami xil yo'llari soni.
  8. E'tibor bering: dp[i]ni to'ldirish faqat eng so'nggi ikkita yozuvga โ€” dp[i-1] va dp[i-2]ga โ€” tegadi, undan oldingi har qanday yozuv boshqa hech qachon o'qilmaydi. Aynan shu kuzatuv keyinchalik butun jadvalni ikkita rolling o'zgaruvchiga almashtirishga turtki beradi (Murakkablik bo'limiga qarang).

Complexity

Time: O(n). Whether the full table or just two rolling variables are kept, the work is the same: one pass over i from 3 up to n, doing one addition per step. There's no nested loop and no repeated recomputation, so the total work scales linearly with n.

Space: O(n) with the array, O(1) with rolling variables. The straightforward version above stores every dp[i] in an array of length n+1, so memory use grows linearly with n. But step 8 already pointed out that filling dp[i] only ever needs the two most recent entries โ€” everything before dp[i-2] is never touched again. So instead of an array, keep two plain variables, say prev2 and prev1, holding the last two answers, and slide them forward each iteration: the new value becomes prev1, and the old prev1 becomes the new prev2. That's the O(1)-space rolling-variable version โ€” same recurrence, same time complexity, but constant extra memory no matter how large n gets.

Murakkablik

Vaqt: O(n). To'liq jadval saqlansa ham, faqat ikkita rolling o'zgaruvchi saqlansa ham, ish bir xil: i bo'yicha 3'dan n'gacha bitta pass, har bir qadamda bitta qo'shish. Nested loop yo'q, qayta hisoblash yo'q, shuning uchun jami ish n'ga chiziqli proportsional o'sadi.

Xotira: array bilan O(n), rolling o'zgaruvchilar bilan O(1). Yuqoridagi to'g'ridan-to'g'ri versiya har bir dp[i]ni n+1 uzunlikdagi array'da saqlaydi, shuning uchun xotira sarfi n'ga chiziqli proportsional o'sadi. Lekin 8-qadamda allaqachon ta'kidlanganidek, dp[i]ni to'ldirish faqat eng so'nggi ikkita yozuvga muhtoj โ€” dp[i-2]dan oldingi hech narsa boshqa hech qachon ishlatilmaydi. Shuning uchun array o'rniga oxirgi ikkita javobni saqlaydigan ikkita oddiy o'zgaruvchi, aytaylik prev2 va prev1, tuting va ularni har bir iteratsiyada oldinga suring: yangi qiymat prev1ga aylanadi, eski prev1 esa yangi prev2ga aylanadi. Bu โ€” O(1)-xotirali rolling-o'zgaruvchi versiya: bir xil recurrence, bir xil vaqt murakkabligi, lekin n qanchalik katta bo'lmasin, doimiy qo'shimcha xotira.

Common Mistakes

  • Getting the base cases wrong โ€” e.g. setting dp[1] = dp[2] = 1 instead of dp[1] = 1, dp[2] = 2, or forgetting that reaching step 2 has two ways, not one. Fix: work out the first two answers by hand before writing any loop, and seed the table with exactly those values โ€” every later entry inherits any mistake made here.
  • Writing the naive recursive version straight from the recurrence โ€” ways(n) = ways(n-1) + ways(n-2) โ€” with no memoization. It's correct, but it recomputes the same smaller subproblems over and over (the same call to ways(n-2) happens once inside ways(n-1)'s work and again as a direct call), so runtime blows up exponentially. Fix: either cache results with memoization, or build the answer bottom-up with a table so each subproblem is solved exactly once.
  • Off-by-one errors in the loop bound, e.g. looping with range(3, n) instead of range(3, n+1). Fix: since range excludes its endpoint, the loop must go up to and include n, so the last table slot actually gets filled โ€” otherwise dp[n] is left untouched at its initial value of 0, and the function returns 0 instead of the real answer.
  • Updating the two rolling variables in the wrong order, e.g. writing prev2 = prev1 on one line and prev1 = prev2 + prev1 on the next. The second line now reads the already-overwritten prev2 (which is really the old prev1), so the new prev1 ends up double the old one instead of following the real recurrence. Fix: compute both new values from the old ones in a single simultaneous step, e.g. prev2, prev1 = prev1, prev2 + prev1, so nothing gets overwritten before it's read.

Ko'p Uchraydigan Xatolar

  • Base case'larni noto'g'ri belgilash โ€” masalan, dp[1] = 1, dp[2] = 2 o'rniga dp[1] = dp[2] = 1 deb qo'yish, yoki 2-zinapoyaga yetishning ikkita yo'li borligini, bittasi emasligini unutish. Yechim: har qanday loop yozishdan oldin birinchi ikkita javobni qo'lda hisoblab chiqing va jadvalni aynan shu qiymatlar bilan urug'lantiring โ€” keyingi har bir yozuv shu yerda qilingan har qanday xatoni meros qilib oladi.
  • Recurrence'dan to'g'ridan-to'g'ri naive recursive versiyani yozish โ€” ways(n) = ways(n-1) + ways(n-2) โ€” memoization'siz. Bu to'g'ri, lekin bir xil kichikroq subproblem'larni qayta-qayta hisoblaydi (ways(n-2)ga bo'lgan bir xil chaqiruv bir marta ways(n-1)ning ishi ichida, yana bir marta to'g'ridan-to'g'ri chaqiruv sifatida sodir bo'ladi), shuning uchun runtime eksponensial darajada o'sib ketadi. Yechim: natijalarni memoization bilan cache qiling, yoki jadval yordamida javobni pastdan boshlab quring, shunda har bir subproblem aynan bir marta yechiladi.
  • Loop chegarasida off-by-one xatosi, masalan range(3, n+1) o'rniga range(3, n) bilan loop qilish. Yechim: range o'z oxirgi nuqtasini o'z ichiga olmagani uchun, loop n'gacha va n'ni ham o'z ichiga olgan holda borishi kerak, shunda jadvalning oxirgi katakchasi haqiqatan ham to'ldiriladi โ€” aks holda dp[n] boshlang'ich 0 qiymatida qolib ketadi, va funksiya haqiqiy javob o'rniga 0 qaytaradi.
  • Ikkita rolling o'zgaruvchini noto'g'ri tartibda yangilash, masalan bir qatorda prev2 = prev1, keyingi qatorda prev1 = prev2 + prev1 deb yozish. Ikkinchi qator endi allaqachon qayta yozilgan prev2ni (bu aslida eski prev1) o'qiydi, shuning uchun yangi prev1 haqiqiy recurrence'ga ergashish o'rniga eskisining ikki barobariga aylanadi. Yechim: ikkala yangi qiymatni eskilaridan bitta bir vaqtdagi qadamda hisoblang, masalan prev2, prev1 = prev1, prev2 + prev1, shunda hech narsa o'qilishidan oldin qayta yozilmaydi.

When to Use It

  • The answer for position n can be written as a formula using the answers for a small, fixed number of earlier positions (like nโˆ’1 and nโˆ’2) โ€” common in "count the ways," "minimum cost to reach," or "maximum value ending at" problems over a sequence.
  • The problem has overlapping subproblems: solved recursively without caching, the same smaller subproblem gets recomputed many times. If a plain recursive solution feels correct but suspiciously slow, that's the signal to tabulate instead.
  • The problem is framed over a single sequence, index, or count (a line of stairs, a row of houses, a length n) rather than over two separate arrays or a grid โ€” that's the "1D" in 1D DP, as opposed to a 2D table for problems like edit distance.
  • The recurrence only reaches back a constant number of steps (like iโˆ’1 and iโˆ’2, not "all previous i"). That's the cue that the full table isn't actually needed โ€” two or three rolling variables can replace it for O(1) space.

Qachon Ishlatish Kerak

  • n-pozitsiya uchun javobni oldingi kichik, belgilangan sondagi pozitsiyalar (masalan, nโˆ’1 va nโˆ’2) javoblari orqali formula sifatida yozish mumkin โ€” bu ketma-ketlik ustidagi "yo'llar sonini sanash", "yetib borishning minimal narxi", yoki "biror joyda tugaydigan maksimal qiymat" kabi masalalarda keng uchraydi.
  • Masalada overlapping subproblem'lar bor: cache'siz rekursiv yechilsa, bir xil kichikroq subproblem ko'p marta qayta hisoblanadi. Agar oddiy rekursiv yechim to'g'riday tuyulsa-yu, shubhali darajada sekin ishlasa โ€” bu jadvallashtirish kerakligining belgisi.
  • Masala ikkita alohida array yoki grid ustida emas, balki bitta ketma-ketlik, indeks yoki son (zinapoyalar qatori, uylar qatori, n uzunlik) ustida qurilgan โ€” bu "1D" so'zining ma'nosi 1D DP'da, edit distance kabi masalalar uchun kerak bo'ladigan 2D jadvaldan farqli o'laroq.
  • Recurrence faqat doimiy sondagi qadamlarga orqaga qaraydi (masalan, iโˆ’1 va iโˆ’2, "barcha oldingi i" emas). Bu โ€” to'liq jadvalning aslida kerak emasligining belgisi: ikkita yoki uchta rolling o'zgaruvchi uni O(1) xotira uchun almashtirishi mumkin.

LeetCode Practice

70. Climbing Stairs โ†—

Restated: given an integer n representing a staircase of n steps, and a rule that each move climbs either 1 or 2 steps, return the number of distinct sequences of moves that reach exactly the top (step n).

  1. Spot the shape. The last move to reach step n was either a single step from nโˆ’1 or a double step from nโˆ’2, and no other option exists โ€” so ways(n) = ways(n-1) + ways(n-2). That's a recurrence hiding inside a staircase story.
  2. Try the direct recursive translation. ways(n) calling ways(n-1) and ways(n-2) mirrors the recurrence exactly and gives a correct answer, but without caching, the same smaller values (like ways(n-2)) get recomputed from scratch every time they're needed โ€” the call tree grows exponentially, roughly O(2โฟ).
  3. Tabulate bottom-up instead. Build a dp array, seed the two base cases (dp[1] = 1, dp[2] = 2), and fill the rest left to right with dp[i] = dp[i-1] + dp[i-2]. Every subproblem is now solved exactly once โ€” O(n) time, O(n) space.
  4. Notice the table is bigger than it needs to be. Filling dp[i] only ever reads dp[i-1] and dp[i-2] โ€” nothing further back is ever touched again, so keeping the whole array is wasted memory.
  5. Collapse it to two rolling variables. Replace the array with prev2 and prev1, sliding them forward each iteration. Same recurrence, same O(n) time, but O(1) space.

The bottom-up array version first, matching the table-filling idea above:

def climb_stairs(n):
    # base cases: 1 step has exactly 1 way, 2 steps have exactly 2 ways
    if n <= 2:
        return n

    # dp[i] = number of distinct ways to reach step i
    dp = [0] * (n + 1)
    dp[1], dp[2] = 1, 2

    # fill the table left to right -- each answer reuses the two before it
    for i in range(3, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]

    return dp[n]

The optimized, final solution โ€” same logic, O(1) space via two rolling variables:

def climb_stairs(n):
    # base cases: 1 step has exactly 1 way, 2 steps have exactly 2 ways
    if n <= 2:
        return n

    # prev2 = ways to reach two steps back, prev1 = ways to reach one step back
    prev2, prev1 = 1, 2

    for i in range(3, n + 1):
        # compute both new values from the old ones in one step, so
        # neither variable is overwritten before it's read
        prev2, prev1 = prev1, prev2 + prev1

    # prev1 now holds the answer for step n
    return prev1

LeetCode Amaliyoti

70. Climbing Stairs โ†—

Qayta bayon: n ta zinapoyadan iborat zinapoyani ifodalovchi butun son n berilgan, har bir harakat 1 yoki 2 qadam ko'tariladi โ€” tepaga (n-zinapoyaga) aynan yetib boradigan xil harakatlar ketma-ketliklari sonini qaytaring.

  1. Shaklni tanib oling. n-zinapoyaga yetgan oxirgi harakat โ€” yo (nโˆ’1)dan bitta qadam, yoki (nโˆ’2)dan ikkita qadam, boshqa variant yo'q โ€” shuning uchun ways(n) = ways(n-1) + ways(n-2). Bu zinapoya hikoyasi ichiga yashiringan recurrence.
  2. To'g'ridan-to'g'ri rekursiv tarjimani sinab ko'ring. ways(n)ning ways(n-1) va ways(n-2)ni chaqirishi recurrence'ni aynan aks ettiradi va to'g'ri javob beradi, lekin cache'siz, bir xil kichikroq qiymatlar (masalan, ways(n-2)) har safar kerak bo'lganda qaytadan boshidan hisoblanadi โ€” chaqiruvlar daraxti eksponensial o'sadi, taxminan O(2โฟ).
  3. Buning o'rniga pastdan boshlab jadvallang. dp array quring, ikkita base case'ni urug'lantiring (dp[1] = 1, dp[2] = 2), va qolganini chapdan o'ngga dp[i] = dp[i-1] + dp[i-2] bilan to'ldiring. Endi har bir subproblem aynan bir marta yechiladi โ€” O(n) vaqt, O(n) xotira.
  4. Jadval kerakidan katta ekanligiga e'tibor bering. dp[i]ni to'ldirish faqat dp[i-1] va dp[i-2]ni o'qiydi โ€” undan uzoqroqdagi hech narsa boshqa hech qachon ishlatilmaydi, shuning uchun butun array'ni saqlash behuda xotira sarfi.
  5. Uni ikkita rolling o'zgaruvchiga siqing. Array'ni prev2 va prev1 bilan almashtiring, har bir iteratsiyada ularni oldinga suring. Bir xil recurrence, bir xil O(n) vaqt, lekin O(1) xotira.

Avval bottom-up array versiyasi, yuqoridagi jadval-to'ldirish g'oyasiga mos ravishda:

def climb_stairs(n):
    # base cases: 1 step has exactly 1 way, 2 steps have exactly 2 ways
    if n <= 2:
        return n

    # dp[i] = number of distinct ways to reach step i
    dp = [0] * (n + 1)
    dp[1], dp[2] = 1, 2

    # fill the table left to right -- each answer reuses the two before it
    for i in range(3, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]

    return dp[n]

Optimallashtirilgan, yakuniy yechim โ€” bir xil mantiq, ikkita rolling o'zgaruvchi orqali O(1) xotira:

def climb_stairs(n):
    # base cases: 1 step has exactly 1 way, 2 steps have exactly 2 ways
    if n <= 2:
        return n

    # prev2 = ways to reach two steps back, prev1 = ways to reach one step back
    prev2, prev1 = 1, 2

    for i in range(3, n + 1):
        # compute both new values from the old ones in one step, so
        # neither variable is overwritten before it's read
        prev2, prev1 = prev1, prev2 + prev1

    # prev1 now holds the answer for step n
    return prev1

Check Yourself

O'zingizni Sinang

ยฉ 2026 Davronbek