Sparround

Rapid-fire nəzəri suallar

Bir çox texniki müsahibə "rapid-fire" raundu ilə başlayır: 10-20 qısa sual, ardıcıl, hər birinə 20-30 saniyə. Məqsəd dərinlik yoxlamaq deyil — əsasların oturuşub-oturuşmadığını və sənin təmkinli olub-olmadığını görməkdir.

Cavab düsturu — üç hissə, 2-3 cümlə:

  • 1. Birbaşa cavab — sualı təkrarlamadan, girişsiz. "Hash map açarı hash funksiyası ilə massiv indeksinə çevirir."
  • 2. Bir sətirlik NİYƏ — mexanizm və ya səbəb. "Ona görə axtarış orta halda O(1)-dir."
  • 3. Bir konkret nümunə — bu, cavabı əzbərdən fərqləndirən hissədir. "Məsələn istifadəçini ID-yə görə tapmaq üçün ideal, amma 'ən kiçik ID' sualına cavab vermir."

Üç hissə birlikdə 20-25 saniyə çəkir. Nümunə əlavə edən namizədlə etməyən namizəd arasındakı fərq intervyuerin qeydlərində açıq görünür.

Nə etməməli:

  • Uzun cavab vermək — bu raundda mənfi siqnaldır: sualın sadə olduğunu görmədiyini göstərir. Dərinlik istəsələr, özləri soruşacaqlar. "İstəsəniz detala girə bilərəm" cümləsi bunu nəzakətlə həll edir
  • Uydurmaq — ən pis variantdır. İntervyuerlər bunu demək olar həmişə hiss edir və ondan sonra bütün cavablarına şübhə ilə yanaşırlar. "Bunu dəqiq bilmirəm, amma güman edirəm ki..." və ya sadəcə "Bilmirəm" qat-qat yaxşıdır
  • Sualı təkrarlayıb vaxt qazanmağa çalışmaq — "Yəni siz soruşursunuz ki, hash map necə işləyir?" Bu, hiss olunur

Ritm haqqında: 15 sualdan 2-3-nə "bilmirəm" demək tamamilə normaldır və heç kim 15-in 15-ni gözləmir. Gözlənilən — dürüstlük və təmkindir.

Sual — data strukturlarıModel cavab (20-30 saniyə)
Array ilə linked list fərqi?Array elementləri ardıcıl yaddaşda saxlayır — indekslə müraciət `O(1)`, amma ortadan əlavə/silmə `O(n)`, çünki elementlər sürüşür. Linked list-də hər node növbətinə göstərir — node əlindədirsə silmə `O(1)`, amma indekslə müraciət `O(n)`. Praktikada array cache-ə dost olduğuna görə çox vaxt daha sürətlidir.
Hash map necə işləyir?Açar hash funksiyasından keçirilir, nəticə massivdəki bucket indeksinə çevrilir və dəyər ora yazılır. Ona görə axtarış orta halda `O(1)`-dir. Məsələn ID-yə görə istifadəçi tapmaq üçün idealdır — amma sıra saxlamadığı üçün "ən kiçik ID" sualına `O(n)`-dən başqa cavabı yoxdur.
Hash collision nədir, necə həll olunur?İki fərqli açarın eyni bucket-ə düşməsidir — bucket sayı məhdud olduğuna görə qaçılmazdır. İki əsas həll: chaining (bucket-də siyahı və ya ağac saxlamaq) və open addressing (növbəti boş yeri tapmaq). Çox collision olanda `O(1)` `O(n)`-ə deqradasiya edir; ona görə müasir implementasiyalar yükü izləyib massivi böyüdür (resize).
Stack ilə queue fərqi?Stack LIFO-dur — sonuncu girən birinci çıxır; undo, mötərizə yoxlaması, rekursiyanın iterativ variantı. Queue FIFO-dur — birinci girən birinci çıxır; tapşırıq növbəsi, BFS. Hər ikisində əlavə/çıxarma `O(1)`; fərq yalnız hansı ucdan çıxarılmasındadır.
Heap nədir və nəyə lazımdır?Kökündə həmişə minimum (və ya maksimum) duran ağac formalı strukturdur; əlavə və çıxarma `O(log n)`, kökə baxmaq `O(1)`. Priority queue kimi istifadə olunur: top-K, Dijkstra, task scheduler. Vacib nüans: heap sıralanmış siyahı deyil — onu gəzsən elementlər sıralı gəlmir.
Set-i nə vaxt array əvəzinə seçirsən?"Bu dəyər var?" sualı təkrarlananda. Array-də bu yoxlama `O(n)`, set-də `O(1)`-dir. Klassik nümunə: massivdə təkrar tapmaq — hər element üçün set-də yoxlayırsan, ümumi `O(n)`, iç-içə döngəli `O(n²)` əvəzinə. Güzəşt: `O(n)` əlavə yaddaş və sıranın itməsi.
Binary search tree nədir və niyə balanslı olmalıdır?Hər node-un solunda ondan kiçik, sağında böyük dəyərlərin olduğu ağacdır; axtarış hər addımda yarısını atır — `O(log n)`. Amma sıralanmış data daxil edilsə, ağac zəncirə çevrilir və `O(n)` olur. Ona görə praktikada özünü balanslaşdıran variantlar (AVL, Red-Black) istifadə olunur — `TreeMap` və `SplayTreeMap` məhz bunlardır.
Hash map ilə tree map arasında nə vaxt tree map seçirsən?Sıra lazım olanda. Hash map `O(1)` verir, amma aralıq sorğusu, min/max, "ən yaxın dəyər" və sıralı gəzmə üçün yararsızdır. Tree map `O(log n)`-dir, amma bunların hamısını dəstəkləyir. Nümunə: reytinq cədvəlində "bu istifadəçidən yuxarıda kim var" sualı tree map istəyir.
Dinamik massivə əlavə niyə "amortized O(1)" adlanır?Adətən əlavə `O(1)`-dir, amma massiv dolanda yeni, iki dəfə böyük massiv ayrılır və hər şey köçürülür — o bir əməliyyat `O(n)`. Ölçü hər dəfə ikiqat artdığına görə belə köçürmələr getdikcə seyrəlir və `n` əlavənin ümumi dəyəri `O(n)` olur, yəni orta hesabla əməliyyat başına `O(1)`.
Trie nədir?Prefiks ağacıdır: hər node bir simvol, kökdən node-a qədər olan yol isə prefiksdir. Axtarış prefiksin uzunluğundan asılıdır, saxlanılan söz sayından yox. Avtotamamlama və lüğət yoxlaması üçün ideal; əvəzində yaddaş tələbkardır, ona görə kiçik lüğətdə sadə filtr daha praktikdir.
Stack yaddaş ilə heap yaddaş fərqi?Stack funksiya çağırışlarının çərçivələrini və lokal dəyişənləri saxlayır; ayırma çox sürətlidir və funksiya bitəndə avtomatik təmizlənir, amma ölçüsü məhduddur — dərin rekursiya stack overflow verir. Heap dinamik obyektlər üçündür: ayırma daha bahalıdır, ömrü uzundur, təmizləmə isə ya GC, ya da əl ilə olur. Fasiləsiz "data strukturu heap"i ilə qarışdırmamaq lazımdır — adları eynidir, məzmunları fərqli.
Sual — alqoritmlər və mürəkkəblikModel cavab (20-30 saniyə)
Big O nədir və niyə ən pis hal götürülür?Big O giriş böyüdükcə işin necə artdığını təsvir edir — sabitləri və kiçik hədləri atır, çünki miqyasda onlar əhəmiyyətini itirir. Ən pis hal götürülür, çünki zəmanət lazımdır: "orta halda sürətlidir" cümləsi 10 milyonluq girişdə düşən servisi izah etmir. Nümunə: quicksort orta halda `O(n log n)`, amma ən pis halda `O(n²)` — və məhz bu, onu real-time sistemdə riskli edir.
`O(n log n)` nə vaxt qaçılmazdır?Elementləri yalnız bir-biri ilə müqayisə edərək sıralayanda. `n` elementin `n!` düzülüşü var və ikili qərar ağacının hündürlüyü ən azı `log₂(n!) ≈ n log n` olmalıdır. Bu, məlumat nəzəriyyəsi həddidir. Həddi keçmək üçün müqayisədən imtina etmək lazımdır: counting sort `O(n + k)` verir, amma dəyərlərin dar diapazonda olmasını tələb edir.
BFS, yoxsa DFS — hansını, niyə?İkisi eyni alqoritmdir, sadəcə BFS queue, DFS stack işlədir. "Minimum addım sayı" və ya "ən yaxın" soruşulursa BFS, çünki təbəqə-təbəqə gedir və ilk çatdığı an ən qısadır. "Var/yoxdur", "neçə komponent", "cycle var?", "sıralama çıxar" — DFS. Hər ikisi `O(V + E)`.
Quicksort praktikada niyə merge sort-dan sürətlidir?Hər ikisi `O(n log n)` olsa da, quicksort yerində işləyir və yaddaşa ardıcıl müraciət edir — CPU cache-i yaxşı istifadə olunur. Merge sort isə hər səviyyədə əlavə massivə yazır. Big O eyni, sabit əmsal fərqlidir. Merge sort stabillik lazım olanda, linked list-də və xarici sorting-də qalib gəlir.
Sorting-də stabillik nədir?Bərabər açara malik elementlərin ilkin nisbi sırasının qorunmasıdır. Praktik nəticə: əvvəlcə ada, sonra şöbəyə görə sıralasan, hər şöbənin daxilində adlar əlifba sırasında qalır. Stabil: merge sort, insertion sort, TimSort. Stabil deyil: quicksort, heap sort. Dart-ın `List.sort`-u stabilliyə zəmanət vermir — bu, real bug mənbəyidir.
Binary search niyə sıralanmış giriş tələb edir?Çünki hər addımda yarısını atmaq qərarı sıraya əsaslanır: `a[mid]` hədəfdən kiçikdirsə, soldakıların hamısının da kiçik olduğunu bilirsən. Sıra yoxdursa, bu nəticə etibarsızdır. Daha ümumi ifadə: əsl şərt monotonluqdur — ona görə binary search massiv olmadan, birbaşa cavab diapazonunda da işləyə bilir.
Rekursiya ilə iterasiya arasında nə vaxt hansını seçirsən?Məsələ özü rekursivdirsə — ağac gəzişi, backtracking, böl-və-hökm et — rekursiya kodu qat-qat oxunaqlı edir. İterasiyanı isə dərinlik böyük ola biləndə seçirəm, çünki hər rekursiv çağırış stack çərçivəsi yeyir və 10⁵ dərinlikdə stack overflow olur. Bəzi dillərdə tail-call optimallaşdırması var, amma JavaScript-də praktikada ona güvənmək olmaz.
İç-içə döngəni (`O(n²)`) necə `O(n)`-ə endirirsən?Daxili döngənin nə etdiyinə baxıram: adətən "bu dəyər varmı" və ya "buna uyğun cütü tap" axtarışıdır. Həmin axtarışı hash map ilə `O(1)` edəndə ümumi mürəkkəblik `O(n)` olur. Klassik nümunə two sum-dır. Sıralanmış datada isə iç-içə döngəni two pointers əvəz edir — `O(n)`, üstəlik `O(1)` yaddaşla.
Memoization bir cümlə ilə nədir?Eyni arqumentlərlə təkrar çağırılan funksiyanın nəticəsini yadda saxlayıb ikinci dəfə hesablamamaqdır. Klassik nümunə: sadə rekursiv Fibonacci `O(2ⁿ)`-dir, çünki eyni alt-məsələləri dəfələrlə hesablayır; memoization onu `O(n)`-ə endirir. Bu, dynamic programming-in "yuxarıdan aşağı" formasıdır.
`O(1)` yaddaş dedikdə nə nəzərdə tutulur?Girişin ölçüsündən ASILI OLMAYAN əlavə yaddaş — bir neçə dəyişən, pointer, sayğac. Girişin özü sayılmır. Ona görə two pointers `O(1)`, hash map isə `O(n)`-dir. Diqqət: rekursiya da yaddaş yeyir — `O(log n)` dərinlikli rekursiya `O(1)` deyil, `O(log n)` yaddaşdır və bunu qeyd etmək dəqiqlik göstərir.
Dijkstra mənfi weight-lərdə niyə işləmir?Dijkstra node-u priority queue-dan çıxaran kimi onun məsafəsini yekun elan edir. Bu qərar yalnız edge-lər yolu uzada bilirsə təhlükəsizdir. Mənfi edge sonradan tapılan uzun yolun məsafəni azaltmasına imkan verir, amma node artıq bağlanıb. Nəticə səssizcə səhv olur — xəta atılmır. Bu halda `O(V × E)` olan Bellman-Ford işlədilir.

Interview məsləhəti: rapid-fire raundu üçün hazırlıq oxumaqla yox, ucadan danışmaqla olur. Yuxarıdakı cədvəllərin sual sütununu örtüb hər sualı taymerlə 30 saniyəyə cavabla — səssiz oxuyanda hər şey aydın görünür, amma danışanda cümlə qurmaq gözlənilməz dərəcədə çətindir. İkinci məsləhət: bilmədiyin sualda uydurmaq əvəzinə "Bunu dəqiq bilmirəm, amma məntiqən belə olmalıdır..." de — düzgün yönlü təxmin çox vaxt müsbət qiymətləndirilir, uydurma isə həmişə mənfi.