Texnikalar: two pointers, sliding window, prefix sum
Massiv masalalarini yechishda bir nechta shabloniy yondashuvlar bor. Yangi masalaga duch kelganingizda "buni qanday hal qilsam?" deb noldan fikrlashni boshlasiz. Lekin amalda ko'p masalalar o'xshash tuzilmaga ega — va bir marta bu shablonlarni tanib olsangiz, ularni keyinchalik tez aniqlay olasiz.
Bu qismda uchta kuchli texnikani chuqur ko'rib chiqamiz: ikki ko'rsatkich (two pointers), harakatlanuvchi oyna (sliding window) va oldindan hisoblangan yig'indilar (prefix sum). Ular massiv masalalarida qayta-qayta uchraydigan fundamental usullar.
Ikki ko'rsatkich — two pointers
G'oya
Ko'pgina massiv masalalarida bir vaqtda ikkita joyni kuzatish kerak bo'ladi. Masalan, massivni teskari ko'rinishiga aylantirish uchun boshidan va oxiridan bir vaqtda harakat qilish kerak. Yoki saralangan massivdan ikkita element topish kerak bo'lganda bitta elementi kichikroq, ikkinchisi kattaroq tomonga harakatlansa.
Ikki ko'rsatkich texnikasida massivda ayni vaqtda ikkita index saqlanadi. Ular muammoning mantiqiga qarab harakatlanadi. Bu ko'pincha ichma-ich ikkita takrorlash o'rniga bitta takrorlashda yechimga yetishga yordam beradi — O(n²) o'rniga O(n).
Ikki ko'rsatkichning asosan ikkita ko'rinishi bor:
- Qarama-qarshi uchlardan — biri boshdan, biri oxirdan;
- Bir yo'nalishda — ikkalasi ham boshdan, lekin har xil tezlikda.
Ko'rinish 1: qarama-qarshi uchlardan
Bu ko'rinishda biri 0-indexdan, ikkinchisi n-1-indexdan boshlab bir-biriga qarab harakatlanadi. Ular uchrashganda to'xtaydi.
Massivni joyida teskari aylantirish:
FUNCTION teskari_aylantir(array)
chap = 0
o'ng = array uzunligi - 1
WHILE chap < o'ng
array[chap] VA array[o'ng] JOYINI ALMASHTIR
chap = chap + 1
o'ng = o'ng - 1
Boshlang'ich: [1, 2, 3, 4, 5]
↑ ↑
chap o'ng
1-qadam: 1 va 5 almashtirildi → [5, 2, 3, 4, 1]
chap ↑ ↑ o'ng
2-qadam: 2 va 4 almashtirildi → [5, 4, 3, 2, 1]
↑
chap = o'ng, to'xtaymiz
Bu O(n) vaqt va O(1) xotira. Qo'shimcha massiv kerak emas.
Palindromni tekshirish:
Satr (yoki massiv) palindrommi? Boshdan va oxirdan bir vaqtda kelib, mos elementlarni taqqoslaydi.
FUNCTION palindrommi(array)
chap = 0
o'ng = array uzunligi - 1
WHILE chap < o'ng
IF array[chap] ≠ array[o'ng]
RETURN FALSE
chap = chap + 1
o'ng = o'ng - 1
RETURN TRUE
Saralangan massivdan yig'indisi berilgan ikkita sonni topish:
FUNCTION ikki_son_yigindisi(array, maqsad)
chap = 0
o'ng = array uzunligi - 1
WHILE chap < o'ng
yigindi = array[chap] + array[o'ng]
IF yigindi = maqsad
RETURN (chap, o'ng)
ELSE IF yigindi < maqsad
chap = chap + 1
ELSE
o'ng = o'ng - 1
RETURN topilmadi
Nima uchun bu ishlaydi? Massiv saralangan. Agar yig'indi maqsaddan kichik bo'lsa, kattaroq qiymat kerak — chap o'ngga suriladi. Agar yig'indi maqsaddan katta bo'lsa, kichikroq qiymat kerak — o'ng chapga suriladi. Har qadam birdaniga bir imkoniyatni yo'q qiladi.
[2, 7, 11, 15], maqsad = 9
chap=0, o'ng=3: 2 + 15 = 17 > 9 → o'ng--
chap=0, o'ng=2: 2 + 11 = 13 > 9 → o'ng--
chap=0, o'ng=1: 2 + 7 = 9 = maqsad → (0, 1) ✓
Ko'rinish 2: bir yo'nalishda (sekin va tez ko'rsatkich)
Bu ko'rinishda ikki ko'rsatkich ham boshdan harakat qiladi, lekin biri sekin, ikkinchisi tez. Ular bir-birlarining orasidagi oraliq yoki munosabatiga qarab ishlaydi.
Massivdan nollarni tashlab, tartibni saqlagan holda siqish:
Agar masalaga saralangan massiv sharti yo'q bo'lsa ham ikki ko'rsatkich yordam beradi. Siz nollarni olib, qolgan elementlarni old tomonga yig'moqchisiz:
FUNCTION nollarni_siqish(array)
yozish_joyi = 0
FOR o'qish = 0 DAN array uzunligi - 1 GACHA
IF array[o'qish] ≠ 0
array[yozish_joyi] = array[o'qish]
yozish_joyi = yozish_joyi + 1
WHILE yozish_joyi < array uzunligi
array[yozish_joyi] = 0
yozish_joyi = yozish_joyi + 1
[0, 1, 0, 3, 12]
↑ o'qish
↑ yozish_joyi
0 = 0 → o'tkazib yuborildi. o'qish olg'a.
1 ≠ 0 → yozish_joyiga yozildi: [1, 1, 0, 3, 12], yozish_joyi++
0 = 0 → o'tkazib yuborildi.
3 ≠ 0 → yozildi: [1, 3, 0, 3, 12], yozish_joyi++
12 ≠ 0 → yozildi: [1, 3, 12, 3, 12], yozish_joyi++
Qolgan joyga nol: [1, 3, 12, 0, 0] ✓
Takroriy elementlarni olib tashlash (saralangan massivda):
FUNCTION takrorlarni_olt(array)
IF array uzunligi = 0
RETURN 0
yozish_joyi = 1
FOR o'qish = 1 DAN array uzunligi - 1 GACHA
IF array[o'qish] ≠ array[o'qish - 1]
array[yozish_joyi] = array[o'qish]
yozish_joyi = yozish_joyi + 1
RETURN yozish_joyi
[1, 1, 2, 3, 3, 4]
o'qish=1: 1 = 1 → o'tkazib yuborildi
o'qish=2: 2 ≠ 1 → yozildi: [1, 2, 2, 3, 3, 4], yozish_joyi=2
o'qish=3: 3 ≠ 2 → yozildi: [1, 2, 3, 3, 3, 4], yozish_joyi=3
o'qish=4: 3 = 3 → o'tkazib yuborildi
o'qish=5: 4 ≠ 3 → yozildi: [1, 2, 3, 4, 3, 4], yozish_joyi=4
Natija: birinchi 4 ta element → [1, 2, 3, 4]
Qachon ikki ko'rsatkich ishlatish kerak?
- Saralangan massivda ikkita elementning yig'indisi, farqi yoki ko'paytmasi bo'yicha qidirish;
- Massivni joyida o'zgartirish (teskari aylantirish, siqish, takrorlarni olib tashlash);
- Muammo "ikkita indeksni boshqarish" shaklida ko'ringanda.
Agar muammo "bir vaqtda ikkita joyga nazar tashlash" talabi bilan kelsa — ikki ko'rsatkich ehtimol to'g'ri yo'l.
Harakatlanuvchi oyna — sliding window
G'oya
Massivning ketma-ket kelgan bir nechta elementlari ustida hisoblash kerak bo'lganda — masalan, har 5 ta kunning o'rtacha harorati, yoki uzunligi k bo'lgan bo'lakdagi eng katta yig'indi — to'g'ridan-to'g'ri yondashuv har bo'lak uchun qaytadan hisoblash talab qiladi.
Masalan, [5, 2, 7, 4, 6] massivida har 3 ta elementning yig'indisi:
Naiv yondashuv: har bo'lak uchun k ta element qo'shiladi. Agar massivda n ta element bo'lsa, bu O(n × k) bo'ladi.
Aqlliroq yol: oynani bir katak o'ngga surayotganda, faqat bitta yangi element qo'shiladi va bitta eski element ayiriladi. Avvalgi hisob qaytadan bajarilmaydi.
Bu sliding window — "harakatlanuvchi oyna". Oyna massiv ustida bitta katak o'ngga surilib boradi. Har surishda faqat ikki ta arifmetik amal: bir qo'shish va bir ayirish.
Ko'rinish 1: belgilangan o'lchamli oyna (fixed size)
Oyna o'lchami k oldindan ma'lum va o'zgarmaydi.
Uzunligi k bo'lgan bo'lakning eng katta yig'indisi:
FUNCTION eng_katta_yigindi_k(array, k)
n = array uzunligi
IF n < k
RETURN XATO
joriy_yigindi = 0
FOR i = 0 DAN k - 1 GACHA
joriy_yigindi = joriy_yigindi + array[i]
eng_katta = joriy_yigindi
FOR i = k DAN n - 1 GACHA
joriy_yigindi = joriy_yigindi + array[i] - array[i - k]
IF joriy_yigindi > eng_katta
eng_katta = joriy_yigindi
RETURN eng_katta
array = [2, 1, 5, 1, 3, 2], k = 3
1-oyna: 2 + 1 + 5 = 8
2-oyna: 8 + 1 - 2 = 7 (array[3] qo'sh, array[0] olib tashla)
3-oyna: 7 + 3 - 1 = 9
4-oyna: 9 + 2 - 5 = 6
eng_katta = 9 ✓
Murakkablik: O(n) vaqt, O(1) xotira. Naiv yondashuv O(n × k) bo'lardi.
Ko'rinish 2: o'zgaruvchan o'lchamli oyna (variable size)
Oyna o'lchami aniq emas — u muammo shartiga qarab kattalashadi yoki kichiklashadi.
Yig'indisi maqsaddan katta bo'lgan eng qisqa bo'lak:
FUNCTION eng_qisqa_bolak(array, maqsad)
chap = 0
joriy_yigindi = 0
eng_qisqa = CHEKSIZ
FOR o'ng = 0 DAN array uzunligi - 1 GACHA
joriy_yigindi = joriy_yigindi + array[o'ng]
WHILE joriy_yigindi >= maqsad
uzunlik = o'ng - chap + 1
IF uzunlik < eng_qisqa
eng_qisqa = uzunlik
joriy_yigindi = joriy_yigindi - array[chap]
chap = chap + 1
IF eng_qisqa = CHEKSIZ
RETURN 0
RETURN eng_qisqa
array = [2, 3, 1, 2, 4, 3], maqsad = 7
o'ng=0: yig'indi=2
o'ng=1: yig'indi=5
o'ng=2: yig'indi=6
o'ng=3: yig'indi=8 >= 7 → uzunlik=4, chap+1: yig'indi=6
o'ng=4: yig'indi=10 >= 7 → uzunlik=4, chap+1: yig'indi=7 >= 7 → uzunlik=3, chap+1: yig'indi=4
o'ng=5: yig'indi=7 >= 7 → uzunlik=2, chap+1: yig'indi=3
eng_qisqa = 2 ([4, 3]) ✓
Bu yondashuv O(n) da ishlaydi. chap va o'ng har biri massiv bo'ylab ko'pi bilan n marta siljiydi — jami O(2n) = O(n).
Sliding window uchun umumiy shablon
chap = 0
joriy_holat = bo'sh/nol
FOR o'ng = 0 DAN n - 1 GACHA
joriy_holatga array[o'ng] QO'SH
WHILE shart bajarilmayaptimi yoki o'lcham kattalashdi
joriy_holatdan array[chap] OLIB TASHLA
chap = chap + 1
NATIJANI YANGILASHTIR
Ushbu shablon ko'p variantda qo'llaniladi — qo'shiladigan va olinadigan qiymat sonlar, belgilar, tezlik yoki boshqa ko'rinishda bo'lishi mumkin.
Qachon sliding window ishlatish kerak?
- Ketma-ket elementlar bo'laklari bilan ishlash kerak bo'lganda;
- "Belgilangan o'lchamli oyna ichida ..." yoki "eng qisqa/uzun bo'lak ..." shaklidagi masalalarda;
- Ichma-ich ikkita takrorlash
O(n²)danO(n)ga tushirilishi kerak bo'lganda.
Agar "ketma-ket elementlar" so'zi masalada kelsa yoki "subarray" (qism massiv) bilan ishlash kerak bo'lsa — sliding window ko'p hollarda to'g'ri yo'l.
Oldindan hisoblangan yig'indilar — prefix sum
G'oya
Tez-tez bir xil massivning turli oralig'idagi yig'indi so'ralsa, har safar o'sha oraliq bo'ylab yurish O(n) vaqt oladi. Ko'p so'rov bo'lsa, bu qimmatga tushadi.
Prefix sum (oldindan hisoblangan yig'indilar) yondashuvi bir marta O(n) da barcha "boshlang'ichdan u gacha" yig'indilarini hisoblab, massivda saqlaydi. Keyin istalgan [i, j] oralig'i yig'indisi O(1) da topiladi.
Bu "bir marta hisob, ko'p marta foydalanish" g'oyasi.
Prefix massivini qurish
Asl massiv: [3, 5, 2, 4, 1]
prefix[i] — asl massivning 0-indexdan i-indexgacha (shu index ham kirib) bo'lgan elementlar yig'indisi.
Asl: [ 3, 5, 2, 4, 1]
Index: 0 1 2 3 4
prefix[0] = 3
prefix[1] = 3 + 5 = 8
prefix[2] = 3 + 5 + 2 = 10
prefix[3] = 3 + 5 + 2 + 4 = 14
prefix[4] = 3 + 5 + 2 + 4 + 1 = 15
Prefix: [ 3, 8, 10, 14, 15]
FUNCTION prefix_qur(array)
n = array uzunligi
prefix = n o'lchamli yangi massiv
prefix[0] = array[0]
FOR i = 1 DAN n - 1 GACHA
prefix[i] = prefix[i - 1] + array[i]
RETURN prefix
Oraliq yig'indisini so'rash
[i, j] oralig'i yig'indisi quyidagi formula bilan:
Nima uchun? prefix[j] — 0-dan j-gacha yig'indi. prefix[i-1] — 0-dan i-1-gacha yig'indi. Ikkinchisini birinchisidan ayirsak, i-dan j-gacha bo'lgan yig'indi qoladi.
Asl: [3, 5, 2, 4, 1]
Prefix: [3, 8, 10, 14, 15]
[1, 3] oralig'i yig'indisi — ya'ni 5 + 2 + 4 = 11:
prefix[3] - prefix[0] = 14 - 3 = 11 ✓
[2, 4] oralig'i yig'indisi — ya'ni 2 + 4 + 1 = 7:
prefix[4] - prefix[1] = 15 - 8 = 7 ✓
Amaliy misol: kunlik savdo hisobotlari
Server qaysi kunlardan qaysi kunlargacha savdo hajmini qaytarishi kerak. Savdo hajmlari massivda: [120, 340, 210, 450, 180, 270, 390].
Prefix summasi quriladi — bir marta. Keyin istalgan oraliq uchun savdo hajmi O(1) da hisob-kitob qilinadi. Agar bu so'rov ko'p bo'lsa — kuniga minglab hisobot — prefix sum katta tejamkorlik beradi.
Ikki o'lchamli prefix sum
Ikki o'lchamli massivda ham prefix sum ishlatiladi — masalan, rasmdagi to'rtburchak hududning piksel yig'indisini tez topish uchun.
Ikki o'lchamli prefix:
Qurilishi:
Oxirgi ayirish sababi: prefix[i-1][j] va prefix[i][j-1] birgalikda prefix[i-1][j-1] ni ikki marta hisoblaydi — shu sababli bir marta ayiriladi.
Bu rasmni qayta ishlash, mozaika yoki to'r formatidagi ma'lumotlarda foydali.
Murakkablik
| Bosqich | Vaqt | Xotira |
|---|---|---|
| Prefix massivini qurish | O(n) |
O(n) |
| Bitta oraliq so'rovi | O(1) |
— |
q ta so'rov |
O(n + q) |
O(n) |
Prefix sum O(n) oldingi hisob bilan har bir so'rovni O(1) ga tushiradi. Agar q ta so'rov bo'lsa va q katta bo'lsa, bu O(n × q) naiv yondashuvga qaraganda ancha tezroq.
Qachon prefix sum ishlatish kerak?
- Bir xil massivda ko'p marta oraliq yig'indisi so'ralganda;
- "Ushbu oraliqda nechta element shartni qanoatlantiradi?" shaklidagi masalalarda;
- Ikki o'lchamli massivda to'rtburchak hududlar bilan ishlashda.
Prefix sum yordamida oraliq yig'indisi so'rovlari O(n) dan O(1) ga tushiriladi — oldindan O(n) xarajat evaziga.
Uchala texnikani qiyosiy ko'rish
| Texnika | Asosiy g'oya | Klassik muammo | Vaqt |
|---|---|---|---|
| Two pointers | Ikkita joyni bir vaqtda kuzatish | Yig'indisi X bo'lgan ikki son |
O(n) |
| Sliding window | Oyna surilib boradi, qaytadan hisob yo'q | Uzunligi k bo'lgan eng katta bo'lak |
O(n) |
| Prefix sum | Oldindan hisoblab, tez so'rash | Ko'p oraliq yig'indisi so'rovi | O(n + q) |
Keng tarqalgan xatolar
Sliding windowda chapni surishni unutish
Oyna o'lchamini nazorat qilmasdan faqat o'ng ko'rsatkichni harakatlatish — oyna cheksiz kattalashib ketadi va noto'g'ri natija beradi. chap ko'rsatkichi belgilangan qoidaga muvofiq siljishi kerak.
Prefix sumda i = 0 holati
prefix[j] - prefix[i-1] formulasida i = 0 bo'lsa, prefix[-1] ga murojaat qilinadi — bu mavjud emas. Shu sababli i = 0 holati alohida ko'rib chiqilishi kerak: prefix[j] ni qaytarish yetarli.
Two pointersda cheksiz loop
chap va o'ng ko'rsatkichlari har qadam siljishi kerak — aks holda algoritm bir joyda aylanib qoladi. WHILE sikli shartiga qarab kamida bittasi siljishi kafolatlanishi kerak.
Naiv yondashuv bilan boshlash
Sliding window yoki two pointers zarur bo'lgan masalada to'g'ridan-to'g'ri ichma-ich ikkita takrorlash yozish O(n²) ga olib keladi. Agar masalada "ketma-ket elementlar" yoki "ikkita ko'rsatkich bo'yicha qidiruv" ko'rinsa, avval bu texnikalarni ko'rib chiqish kerak.
Qachon bu texnikalarni qo'llash kerak?
Two pointers: - Massiv saralangan va ikkita element o'rtasidagi munosabat kerak bo'lsa; - Massivni joyida o'zgartirish, siqish, ayirish kerak bo'lsa; - "Boshidan va oxiridan yuring" yoki "sekin va tez yuring" mantiqida masala bo'lsa.
Sliding window:
- "Uzunligi k bo'lgan bo'lak", "eng qisqa bo'lak, shartni qanoatlantiruvchi" kabi masalalar;
- Ketma-ket elementlar oralig'i bilan ishlash;
- Har bo'lak uchun qaytadan hisob keraksiz bo'lganda.
Prefix sum: - Bir massivda ko'p marta oraliq yig'indisi so'ralsa; - Hisoblash oldindan bajarilishi mumkin bo'lsa (massiv o'zgarmasa yoki kamdan-kam o'zgarse); - Ikki o'lchamli masalalar uchun ham prefix sum varianti bor.
Eng muhim xulosa
Uchala texnika ham bitta umumiy g'oyani turli shakllarda amalga oshiradi: avvalgi hisob natijasidan foydalanib, qaytadan ish qilmaslik. Buni "ortiqcha ishni yo'qotish" deb tasavvur qilish mumkin.
- Two pointers: bir vaqtda ikkita joyni kuzatib, taqqoslash sonini kamaytiradi.
- Sliding window: oynadagi o'zgarishni inkremental hisoblaydi, butun oynani qaytadan hisob qilmaydi.
- Prefix sum: yig'indilarni oldindan hisoblaydi, har so'rov uchun faqat ikki ta amal bajaradi.
Bu shablonlarni bilish massiv masalalarini ko'rganda "bu qanday ko'rinadi?" deb tezroq tanib olishga yordam beradi. Keyingi qismda ushbu texnikalarni amalda ishlatadigan klassik masalalarni ko'ramiz.