Lesson 64 of 83 intermediate

DSA II: Stack, Queue, Linked List, Binary Search & Heap

Linear and hierarchical structures that unlock a new class of interview problems

Open interactive version (quiz + challenge)

Real-world analogy

A Stack is a stack of plates — you add and remove from the top (LIFO). A Queue is a checkout line — first in, first out (FIFO). A LinkedList is a treasure hunt where each clue points to the next location. Binary search is like finding a word in a dictionary by opening to the middle, not page one. A Heap is a priority boarding gate — always processes the most important item next.

What is it?

Stack, Queue, LinkedList, Binary Search, and Heap are the second tier of DSA patterns. They appear in ~40% of medium interview problems. Stacks and queues are often implemented on ArrayDeque in Kotlin. Binary search extends far beyond sorted arrays to any monotonic decision function. Heaps provide an efficient way to maintain a sorted subset of data for streaming and top-K problems.

Real-world relevance

In the Tixio real-time task management platform, notification delivery priority was implemented using a PriorityQueue — critical alerts (deadline missed, blocker added) were assigned higher priority and processed first even when multiple notifications were queued simultaneously. This is the heap pattern applied directly: always serve the highest-priority item next in O(log n) time.

Key points

Code example

// ─── VALID PARENTHESES (Stack, O(n)) ───
fun isValid(s: String): Boolean {
    val stack = ArrayDeque<Char>()
    val matching = mapOf(')' to '(', ']' to '[', '}' to '{')
    for (ch in s) {
        if (ch in "([{") {
            stack.addLast(ch)
        } else {
            if (stack.isEmpty() || stack.last() != matching[ch]) return false
            stack.removeLast()
        }
    }
    return stack.isEmpty()
}

// ─── REVERSE LINKED LIST (Iterative, O(n), O(1) space) ───
class ListNode(var value: Int, var next: ListNode? = null)

fun reverseList(head: ListNode?): ListNode? {
    var prev: ListNode? = null
    var curr = head
    while (curr != null) {
        val next = curr.next  // save
        curr.next = prev      // reverse pointer
        prev = curr           // advance prev
        curr = next           // advance curr
    }
    return prev // prev is the new head when curr is null
}

// ─── DETECT CYCLE — FLOYD'S (O(n), O(1) space) ───
fun hasCycle(head: ListNode?): Boolean {
    var slow = head
    var fast = head
    while (fast != null && fast.next != null) {
        slow = slow?.next
        fast = fast.next?.next
        if (slow == fast) return true // fast caught slow inside cycle
    }
    return false
}

// ─── BINARY SEARCH — UNIVERSAL TEMPLATE ───
fun binarySearch(nums: IntArray, target: Int): Int {
    var left = 0
    var right = nums.size - 1
    while (left <= right) {
        val mid = left + (right - left) / 2
        when {
            nums[mid] == target -> return mid
            nums[mid] < target -> left = mid + 1
            else -> right = mid - 1
        }
    }
    return -1
}

// ─── TOP-K LARGEST ELEMENTS (Min-Heap, O(n log k)) ───
fun topKLargest(nums: IntArray, k: Int): List<Int> {
    val minHeap = PriorityQueue<Int>() // min-heap by default
    for (num in nums) {
        minHeap.offer(num)
        if (minHeap.size > k) minHeap.poll() // remove smallest, keep K largest
    }
    return minHeap.toList()
}

Line-by-line walkthrough

  1. 1. isValid uses a HashMap to map each closing bracket to its expected opening bracket — this avoids three separate if-checks and scales to any bracket type.
  2. 2. When a closing bracket is encountered, we check two conditions with OR: stack is empty (nothing to match against) OR the top does not match. Either means invalid.
  3. 3. reverseList saves curr.next before overwriting it — curr.next = prev breaks the forward link, so without saving, the rest of the list is lost.
  4. 4. After the while loop in reverseList, curr is null and prev points to the last original node — which is now the new head of the reversed list.
  5. 5. hasCycle initializes both slow and fast at head. The loop condition checks fast and fast.next both non-null because fast moves two steps — if fast.next were null, fast.next.next would throw a NullPointerException.
  6. 6. binarySearch uses left + (right - left) / 2 to avoid integer overflow. If left = 1_000_000_000 and right = 1_500_000_000, their sum overflows Int.MAX_VALUE.
  7. 7. topKLargest adds every element to the min-heap, then immediately removes the minimum if size exceeds k. After processing all elements, the heap contains exactly the k largest.
  8. 8. PriorityQueue.poll() is O(log n) because removing the root of a heap requires re-heapifying down the tree. This gives topKLargest O(n log k) total — O(log k) per element.

Spot the bug

fun reverseList(head: ListNode?): ListNode? {
    var prev: ListNode? = null
    var curr = head
    while (curr != null) {
        curr.next = prev
        prev = curr
        curr = curr.next
    }
    return prev
}
Need a hint?
There are a bug that causes an infinite loop or always returns a single-node list. Think carefully about the order of pointer assignments.
Show answer
Bug: curr.next = prev overwrites the next pointer BEFORE saving it. Then curr = curr.next reads the already-overwritten pointer — which now points to prev (the previous node), not the next node in the original list. This causes the traversal to go backward or get stuck. The fix: save the original next BEFORE overwriting: val next = curr.next. Then do curr.next = prev, prev = curr, curr = next. The saved 'next' variable preserves the forward link before the reversal.

Explain like I'm 5

Stack: imagine a pile of dirty dishes — you always wash and add to the top. Queue: imagine a line at a water park — first person in line goes down the slide first. Linked list: a scavenger hunt where each clue tells you where the next clue is hidden. Binary search: guessing a number between 1 and 100 by always guessing the middle — you find it in 7 guesses maximum. Heap: a queue where the most important person always cuts to the front.

Fun fact

Floyd's Cycle Detection algorithm was invented by Robert Floyd in 1967 and is also called the 'tortoise and hare' algorithm. It requires only O(1) extra space — no visited set needed. The same mathematical principle (modular arithmetic in a cycle) is used in Pollard's rho algorithm for integer factorization, which was used to crack RSA keys in cryptographic research.

Hands-on challenge

Implement 'Daily Temperatures': given an array of daily temperatures, return an array where answer[i] is the number of days until a warmer temperature. If no future day is warmer, answer[i] = 0. Example: [73,74,75,71,69,72,76,73] returns [1,1,4,2,1,1,0,0]. Trace through using a monotonic stack. What is the time and space complexity? Why is this O(n) and not O(n^2)?

More resources

Open interactive version (quiz + challenge) ← Back to course: Android Interview Mastery