Data Structures: Improving Time Complexity on Stacks and Queues
By: Samuel Guo
Tags: Programming, Arrays, JavaScript, Data Structures, Big O Notation

Stack and Queue Class Overview

Before we dig too much into the weeds and code, let’s provide a general overview of how we’re going to create the StackQueueArray class.

For the stack portion, since the time complexity is O(1) with out-of-the-box Javascript array methods, I decided to keep using the same methods (i.e., .push() and .pop() ). It doesn’t make sense to reinvent the wheel when it’s working perfectly fine, especially when its already optimized.

For the queue portion, that’s where I had to think outside the box. We will still be leveraging the .push() method but the .shift() method will need to change. After all, the .shift() method is the reason why the time complexity is O(n).

This is where leveraging principles from circular arrays come in. Based on my research, an implicit assumption made about circular arrays is that they have a fixed length.

Typically, a circular array has a pointer for the head and tail of the array. By keeping track of the pointers of where the head and tail are at all times, you can figure out if there is space in the array to add an additional element.

In other words, if the head and tail are not next to each other, then you can add the element.

However, in JavaScript, thanks to it being a dynamic language, we don’t have to worry about memory allocation. This dissuaded me from using circular arrays but inspired me to use a pointer to keep track of the head of the array.

We don’t need a pointer for the tail because the tail will always be the last element of the array.

So, how are we going to use a head pointer in conjunction with our custom .shift() method?

Let’s establish a new array with the StackQueueArray class and push some elements into them.

Don’t worry about the class methods for now as we will dive into detail about what those will be later. Recall that the .push() function will be the same as the out-of-box .push() function.

let array = new StackQueueArray()

array.push(10)

array.push(20)

array.push(30)

console.log(array) // [10, 20, 30]

In the StackQueueArray class, it will have an attribute called headIndex , which will be our head pointer. Currently, in array , the headIndex should be 0 because 10 was the first element push into the array as per the first-in principle.

console.log(array.headIndex) // 0

Now, with our custom .shift() method, we’re going to leverage another principle I learned from circular arrays.

In a circular array, whenever there was space between the head and tail pointers, the empty spaces were filled with null as placeholders. We’re going to do the same thing whenever we invoke our custom .shift() method.

When .shift() is invoked, it’ll swap the value at the headIndex with null , increase headIndex by 1 to assign the head pointer to the new head, and return the value.

So, in pseudo code, it will look like this:

console.log(array) // [10, 20, 30]

console.log(array.headIndex) // 0

array.shift()

console.log(array) // [null, 20, 30]

console.log(array.headIndex) // 1

Let’s really drive it home and invoke our custom .shift() method again to our current array to solidify what the method should be doing.

array.shift()

console.log(array) // [null, null, 30]

console.log(array.headIndex) // 2

Now that we have a more holistic understanding what the methods are supposed to do and the general idea of the class, let’s break it down into code.