Introduction
Introduction
Warm-up problem:
Design a data structure SpecialStack that supports all the stack operations like push(), pop(), isEmpty(), top()
and an additional operation getMin() which should return minimum element from the SpecialStack. All these
operations of SpecialStack must be O(1). To design SpecialStack, you should only use standard Stack data
structure and no other data structure like arrays, list, etc
Growable Stack
Q. Suppose we use an array to implement a stack. What if the array is full and we need to push a new element?
A. We can allocate a new larger array, copy the old one over, and then go on from there.
Cost model: Let’s say that inserting into the array costs 1, taking an element out of the array costs 1, and the
cost of resizing the array is the number of elements moved.
Amortized cost: The amortized cost per operation for a sequence of n operations is the total cost of the
operations divided by n.
Q. What if when we resize we just increase the size by 1? Is that a good idea?
A. If our n operations consist of n pushes then we will incur a total cost 1 + 2 + 3 + 4 + . . . + n = n(n + 1)/2.
That’s an amortized cost of (n + 1)/2 per operation.
Q. What if we instead decide to double the size of the array when we resize?
A. In any sequence of n operations, the total cost for resizing is 1 + 2 + 4 + 8 + . . . + 2i for some 2i < n (if all
operations are pushes then 2i will be the largest power of 2 less than n). This sum is at most 2n − 1. Adding in
the additional cost of n for inserting/removing, we get a total cost < 3n, and so our amortized cost per
operation is < 3.
Stack Permutation
A stack permutation is a ordering of numbers from 1 to n that can be obtained from the
initial ordering 1, 2, ... 𝑛 by a sequence of stack operations
Let us denote push as 1 and pop as 0.
We get 325641 by the sequence of stack operations 111001101000
154623 is not a stack permutation.
Admissible sequence: a sequence of 𝑛 0’s and 𝑛 1’s in which the number of 0’s never exceeds the number of 1’s
If we read the sequence from left to right.
Inadmissible sequence: a sequence of 𝑛 0’s and 𝑛 1’s in which the number of 0’s exceeds the number of 1’s
If we read the sequence from left to right.
Counting the number of stack permutation:
2𝑛
There are sequences of 1’s and 0’s that contain 𝑛 of each. In any inadmissible sequence, locate the
𝑛
first 0 for which the 0’s outnumber the 1’s. Then interchange 1’s and 0’s in the partial sequence up to the
first violation. This results a sequence of (𝑛+1) 1’s and (𝑛-1) 0’s. Conversely, for every sequence of
(𝑛+1) 1’s and (𝑛-1) 0’s we can reverse the process and find the inadmissible sequence. For example, the
sequence 001011100111 must have come from 110100000111. Hence, the number of inadmissible
2𝑛
Sequence is
𝑛−1 2𝑛 2𝑛
#stack permutation = −
𝑛 𝑛−1
Fun Challenge
Given a large integer 𝑛, implement an array data structure 𝐴[1,…, 𝑛] of some type 𝑇, with the following
operations:
• init(𝑣): all elements of 𝐴 are defined to be 𝑣
• get(𝑖): return the value of 𝐴[𝑖], where 1 ≤ 𝑖 ≤ 𝑛
• set(𝑖, 𝑥): set 𝐴[𝑖] = 𝑥, where 1 ≤ 𝑖 ≤ 𝑛
Objective: All the above operations must run in time 𝑂(1), irrespective of the value of 𝑛.
Rules:
1. You may use additional arrays, but you cannot assume they are initialized
2. No fancy data structures other than arrays are allowed
3. No bit manipulation is allowed
Solution:
• Maintain a stack 𝑆 containing the indices of defined elements.
• Maintain a parallel array 𝐵[1,…, 𝑛], where 𝐵[𝑖] indicates the
element of the stack that witnesses that 𝐴[𝑖] is defined.
• Note that 𝐵 may contain garbage, but the stack validates its
“legitimate” entries.
Fun Challenge
1. The command init(𝑣) saves the value of 𝑣 and sets the stack empty (top ← 0).
2. When an entry 𝐴[𝑖] is first defined a value 𝑥, we push index 𝑖 onto the stack, signaling that this entry has been
initialized. We set 𝐵[𝑖] ← top, which validates this entry. (Note that, 1 ≤ 𝐵[𝑖] ≤ top and 𝑆[𝐵[𝑖]] = 𝑖.)
Finally, we set 𝐴[𝑖] ← 𝑥.
3. To test whether 𝐴[𝑖] is defined, test whether 1 ≤ 𝐵[𝑖] ≤ top and 𝑆[𝐵[𝑖]] = 𝑖.
4. The command set(𝑖, 𝑥) applies Step 3 to test whether 𝐴[𝑖] is already defined. If not, we apply Step 2 to define it.
If it was defined, we set 𝐴[𝑖] ← 𝑥
5. The command get(𝑥) applies Step 3 to test whether 𝐴[𝑖] is defined. If so, it returns 𝐴[𝑖]. Otherwise it returns the
default value 𝑣.
Linear List: Queue
• Queue -- a collection of elements that are inserted and removed according to the first-in
first-out (FIFO) principle. Elements enter a queue at the rear and are removed from the front.
−enqueue(e): Insert element e at the rear of the queue
−dequeue(): Remove and return from the queue the object at the front; an error occurs if the
queue is empty
−size(): Return the number of objects in the queue.
−isEmpty(): Return a Boolean value that indicates whether the queue is empty.
• Array-based implementation:
Queue is full 10 20 30 40 50
Front Rear
Initialization: 𝑓 = 𝑟 = 0
Dequeue(𝑥) Empty queue
Enqueue(𝑥) {
{ if (𝑓 == 𝑟)
if (𝑓 == (𝑟+1) % Size) {
{ Queue empty
Queue full Return
return }
} 𝑥 = array[𝑟]
array[𝑟] = 𝑥 𝑓 == (𝑓+1) % Size
𝑟 == (𝑟+1) % Size return 𝑥
} }
Full queue
Rear Front
Front/Rear