Skip to content
dsa450for five year olds

Stacks & Queues · 5 of 38

Implement N stacks in an Array

Let many piles share one row of lockers, where any pile can take any empty locker and every locker remembers which one is under it.

Simple way
time O(1) per operation
room O(n + k), but each stack gets only n/k
Clever way
time O(1) per operation
room O(n + k)

The problem, simply

You get one array with n boxes. Build k stacks inside it. You need push(s, x), pop(s) and peek(s) for any stack number s.

The rule: no stack may say "full" while there is still an empty box anywhere in the array. One busy stack should be able to use almost the whole array if the others are small.

A story to remember it

Picture it

arrayarr
0123451545111739free
arraynext
012345-10-1-12-1
arraytop
012431
stackstack 0
1139top
stackstack 1
17top
stackstack 2
1545top

Six boxes shared by three stacks. Stack 0 is 11 then 39, stack 1 is 17, stack 2 is 15 then 45. The next row says which box is under each one. free points at box 5, the only empty box.

Examples

A normal run

In
n = 6, k = 3: push(2,15), push(2,45), push(0,11), push(1,17), push(0,39), pop(2)
Out
45

45 was the last push into stack 2, so it comes out first. The other stacks do not notice.

A reused box

In
the run above, then push(1,49), peek(1)
Out
49, stored in box 1

Box 1 held 45 a moment ago. After the pop, it went back on the empty list, so stack 1 grabs it next.

One stack takes it all

In
n = 3, k = 3: push(0,1), push(0,2), push(0,3)
Out
all three pushes work

Stack 0 uses every box. If each stack had its own fixed block of n/k = 1 box, the second push would already fail.

Full and empty

In
after the example above: push(1,9), then pop(2)
Out
error: overflow, then error: stack 2 underflow

No box is free anywhere, so the push fails. Stack 2 never got anything, so the pop fails too.

Way 1: the simple way

Cut the array into k equal blocks of n/k boxes. Stack s lives in boxes s * n/k to (s + 1) * n/k - 1 and works like a normal array stack inside its block.

  1. Keep tops[s], starting at s * size - 1 (one before the block).
  2. Push: if tops[s] is at the end of the block, overflow. Else arr[++tops[s]] = x.
  3. Pop: if tops[s] is before the block, underflow. Else return arr[tops[s]--].
array
012345stack 0 block1139top0

n = 6, k = 3, so each stack gets 2 boxes. Stack 0 is full and cannot take box 2, 3, 4 or 5, even though they are empty.

  • rejected
  • ignored
java
class KStacksBlocks {
    private final int[] arr, tops;
    private final int size;                 // boxes per stack

    KStacksBlocks(int n, int k) {
        arr = new int[n];
        tops = new int[k];
        size = n / k;
        for (int s = 0; s < k; s++) tops[s] = s * size - 1;
    }

    void push(int s, int x) {
        if (tops[s] == (s + 1) * size - 1) throw new RuntimeException("stack " + s + " overflow");
        arr[++tops[s]] = x;
    }

    int pop(int s) {
        if (tops[s] < s * size) throw new RuntimeException("stack " + s + " underflow");
        return arr[tops[s]--];
    }
}

How long it takes: O(1) per action. Extra room: the array plus k tops, O(n + k). But a stack overflows after n/k items even when the rest of the array is empty.

Way 2: the clever way

Treat each box like a locker with a sticky note. Use three arrays:

  • arr[i]: the value in box i.
  • next[i]: for a used box, the box under it in the same stack. For an empty box, the next empty box.
  • top[s]: the box on top of stack s, or -1 if it is empty.

And one number, free, the first empty box. At the start every box is empty and they are chained 0, 1, 2, ....

  1. push(s, x): if free == -1, overflow. Else take box i = free, move free = next[i], write arr[i] = x, set next[i] = top[s], then top[s] = i.
  2. pop(s): if top[s] == -1, underflow. Else take i = top[s], set top[s] = next[i], then put box i back at the front of the empty chain: next[i] = free, free = i. Return arr[i].

The empty boxes form a stack of their own. Push moves a box from the free stack to stack s, and pop moves it back.

Three stacks sharing six boxes

01 / 10

arrayarr
012345free
arraynext
01234512345-1
arraytop
012-1-1-1
stackstack 0
empty
stackstack 1
empty
stackstack 2
empty

Start. Every box is empty. The free chain goes 0, 1, 2, 3, 4, 5. All three tops are -1.

  • looking now
  • done
  • rejected
  • just added
java
class KStacks {
    private final int[] arr, next, top;
    private int free;                       // first empty box, -1 if none

    KStacks(int n, int k) {
        arr = new int[n];
        next = new int[n];
        top = new int[k];
        Arrays.fill(top, -1);
        for (int i = 0; i < n - 1; i++) next[i] = i + 1;  // chain all empty boxes
        next[n - 1] = -1;
        free = 0;
    }

    void push(int s, int x) {
        if (free == -1) throw new RuntimeException("overflow");
        int i = free;
        free = next[i];         // take box i off the empty chain
        arr[i] = x;
        next[i] = top[s];       // the old top is now under box i
        top[s] = i;
    }

    int pop(int s) {
        if (top[s] == -1) throw new RuntimeException("stack " + s + " underflow");
        int i = top[s];
        top[s] = next[i];       // the box under becomes the top
        next[i] = free;         // box i goes back on the empty chain
        free = i;
        return arr[i];
    }

    int peek(int s) {
        if (top[s] == -1) throw new RuntimeException("stack " + s + " is empty");
        return arr[top[s]];
    }

    boolean isEmpty(int s) { return top[s] == -1; }

    boolean isFull() { return free == -1; }
}

How long it takes: every action changes a few numbers, O(1). Extra room: arr and next of size n, plus top of size k, so O(n + k). Any stack can grow until the whole array is used.

Watch out for

  • In push, read free = next[i] before writing next[i] = top[s], for the same reason.
  • The next array means two things: "box under me" for used boxes and "next empty box" for empty ones. A box is only ever on one chain, so that is safe.
  • Check free == -1 before pushing, and top[s] == -1 before popping.
  • The old value stays in arr after a pop. That is fine, because nothing points to it.

What interviewers ask next

  • Do the same for k queues. Keep front[q] and rear[q], and link each new box after rear[q]. The free chain works the same way.
  • Why not use k separate ArrayLists? In real code you would. This problem is about memory tricks for systems with one fixed block of memory, like an embedded device or an allocator.
  • What is the free chain called? A free list. Memory allocators and object pools use it to hand out and take back fixed size blocks in O(1).
  • Can you give the size of each stack in O(1)? Keep a count[s] array and update it in push and pop.

Remember this

Grown-up words: the lockers are array slots, the sticky notes are the next array (an index based linked list), the list of empty lockers is a free list, and top[s] is the head of stack s.