Difficulty: Medium, Asked-in: Amazon, Microsoft, Goldman Sachs, Yahoo.
Key takeaway: An excellent problem to visualise the use of the stack operations in problem-solving.
Given a stack, write a program to sort the stack in ascending order. We are not allowed to make any assumptions about how the stack is implemented. To implement, we need to use following stack operations:
Input stack: [4, 3, 6, 5, 1, 2] -> top, Output stack: [6, 5, 4, 3, 2, 1] -> top.
Input stack: [4, 3, 2, 1] ->top, Output stack: [4, 3, 2, 1] -> top.
Input stack: [5, 2, 8, 1, 9] ->top, Output stack: [9, 8, 5, 2, 1] -> top.
A stack is a LIFO (last-in, first-out) data structure, where the last element inserted is the first one removed. The critical question is: Can we sort a stack in place? The answer is no, because we cannot directly access or insert an element at an intermediate position without first popping the elements above it. So we need extra space to store the popped elements. Why should that extra space be another stack? An extra stack helps us maintain the order of elements during push and pop operations.
We move elements from the input stack to a temporary stack until we find an element x that is smaller than the top of the temporary stack. To insert x in sorted order, we move every larger element from the temporary stack back to the input stack. Then we push x onto the temporary stack. This places x above all smaller elements and below all larger ones.
We run a outer loop until input stack is empty. At each iteration: We pop an element from the input stack and store it in a variable currentElement.
sortStack(inputStack)
create an empty temporary stack tempStack
while inputStack is not empty
currentElement = top of inputStack
pop inputStack
// Move larger elements back until currentElement
// can be placed in sorted order.
while tempStack is not empty and top of tempStack > currentElement
move top of tempStack to inputStack
push currentElement onto tempStack
// Move the sorted elements back to inputStack.
while tempStack is not empty
move top of tempStack to inputStackWe pop each element from the tempStack repeatedly to insert a new element. So we need to identify the worst-case scenario for which the above algorithm performs the maximum number of stack operations. If we observe, such a scenario will occur when the input stack is sorted in decreasing order. Why? Let's think!
Another analysis idea: When the input stack is sorted in decreasing order, we need to perform 1 push and pop operation in the 1st iteration, 3 push and pop operations in the 2nd iteration, 5 push and pop operations in the 3rd iteration, and so on. In the last iteration, (n - 1) sorted elements are present in the tempStack and 1 element in the input stack. So, we need to perform 2(n - 1) + 1 push and pop operations to place the remaining element to the tempStack in the sorted order.
Space complexity = O(n) as we are using a tempStack to store the sorted elements.
This idea is: We pop the top element from the stack and sort the remaining (n - 1) size stack recursively using the same function. After sorting the (n - 1) size stack, we use another helper function insertTop(s, topElement), to insert the top element into the current position in the sorted stack.
Implementation of the insertTop(s, topElement): If the stack is empty or topElement is less than the top, we push it into the stack and return. Otherwise, we insert the topElement into the correct position of the sorted stack. For this, we recursively pop out each stack element and call the same function to insert the elements again in the stack in sorted order.
insertTop(s, topElement)
if s is empty or topElement < top of s
push topElement onto s
return
temp = top of s
pop s
insertTop(s, topElement)
push temp onto s
sortStack(s)
if s is not empty
topElement = top of s
pop s
sortStack(s)
insertTop(s, topElement)We are solving the sorting problem of size n by recursively solving the sorting problem of size n-1 and inserting the top element into the n-1 size sorted stack. In the worst case, the time complexity of inserting the top element would be O(n).
Recurrence relation: T(n) = T(n-1) + O(n). This is similar to the recurrence relation of recursive insertion sort or the worst-case time complexity of the quicksort. So time complexity = O(n^2).
This algorithm will use the recursion call stack of size O(n) to maintain the sorted order of input. Space complexity = O(n).
Enjoy learning, Enjoy Algorithms