In this assignment, your goal is to implement a queue using a singly linked list. Once again we'll provide you with some template code to get started. If you need help, you can refer to the walkthrough below.
class ListNode {
constructor(val = 0, next = null) {
this.val = val;
this.next = next;
}
}
class Queue {
constructor() {
this.front = null;
this.back = null;
}
peek() {
// Returns the value of the top most element without removing it.
// If the queue is empty, it returns `null`.
}
enqueue(value) {
// Adds an item to the queue
}
dequeue() {
// Removes the item from the queue and returns it
// If the queue is empty, it returns `null`.
}
}
const myQueue = new Queue();
myQueue.enqueue(1);
console.log('Front element:', myQueue.peek()); // logs 'Front element: 1'
myQueue.enqueue(2);
console.log('Front element:', myQueue.peek()); // logs 'Front element: 1'
myQueue.enqueue(3);
console.log('Front element:', myQueue.peek()); // logs 'Front element: 1'
myQueue.dequeue();
console.log('Front element after dequeue:', myQueue.peek()); // logs 'Front element after dequeue: 2'
myQueue.dequeue();
console.log('Front element after dequeue:', myQueue.peek()); // logs 'Front element after dequeue: 3'
myQueue.dequeue();
console.log('Peek on empty queue:', myQueue.peek()); // logs 'Peek on empty queue: null'
console.log('`back` on empty queue:', myQueue.back); // logs '`back` on empty queue: null'
As you might have already noticed from the template, you'll need to use two pointers to implement a queue using a singly linked list. One pointer will always point to the front of the queue, and the other will point to the back of the queue.
The peek
method in a queue is straightforward. It returns the value of the frontmost node or null
if the queue is empty.
// some code above
peek() {
return this.front ? this.front.val : null;
}
We use a ternary operator for simplicity. If the front element exists, this.front.val
is returned; otherwise, it returns null
. This can be rewritten with an if
statement for clarity:
// some code above
peek() {
if (this.front) {
return this.front.val;
}
return null;
}
The enqueue
method is more challenging.
ListNode
with the given value.
next
property of the current back
node to the new node.
back
to point to the new node.
Assuming we have created a queue, let's see how adding the first 3 elements would look visually:
myQueue.enqueue(1)
We start by creating a new ListNode
with 1
as the value. Because front
is null
when our queue is empty, we'll assign both front
and back
to our new node.
myQueue.enqueue(2)
Again, we start by creating our new node, newNode
. Now that our list is not empty, we'll assign the next
property of the back
node (the last node in our queue) to newNode
. This puts our new node 'behind' the end of our queue. We finish by assigning back
to the new end of our queue.
myQueue.enqueue(3)
Adding yet another node follows the same process as the previous call to enqueue
. We create a new node, update the next
pointer of back
, and reassign back
to newNode
.
// some code above
enqueue(value) {
const newNode = new ListNode(value);
if (!this.back) {
this.front = this.back = newNode;
} else {
this.back.next = newNode;
this.back = newNode;
}
}
The dequeue
method removes and returns the value from the front of the queue, returning null
if the queue is empty. If the queue is not empty we should:
front
node in a temporary variable.
front
pointer to point to the next node.
front
is null
, also set back
to null
(the queue is empty now).
Let's see how this looks visually:
myQueue.dequeue()
We start by storing the existing front
node (with a value of 1
) in a variable removedNode
. Now that we've saved the removed node, we can reassign front
to front.next
, effectively deleting the node with value 1
form our queue. Since front
references a node, and not null
, we can finish by returning the value of our stored removedNode
, 1
.
myQueue.dequeue()
This invocation is similar to the previous step. We save a reference to the node with a value of 2
, reassign front
to front.next
(node with value 3
) and can return the value of removedNode
, 2
.
myQueue.dequeue()
Our last invocation of dequeue
takes a bit more work. We can begin as normal, storing the node referenced by front
with our removedNode
variable. Then we reassign front
to front.next
, which is null
, because our queue only had 1 element. If we were to stop here and return removedNode.val
, we would return the correct value, but we would have a problem in that back
would still be pointing to our removed node. Since our queue is now empty, back
should be referencing null
, just like front
.
This is where our final check comes in. After reassigning front
to front.next
, we need to check if this new front
is null
. This time, it is. We must reassign back
to null
, and finally we can return removedNode.val
.
myQueue.dequeue()
When trying to dequeue
and empty queue
, we return null
because there's no front
node.
// some code above
dequeue() {
if (!this.front) return null;
const removedNode = this.front;
this.front = this.front.next;
if (!this.front) {
this.back = null;
}
return removedNode.val;
}
class ListNode {
constructor(val = 0, next = null) {
this.val = val;
this.next = next;
}
}
class Queue {
constructor() {
this.front = null;
this.back = null;
}
peek() {
return this.front ? this.front.val : null;
}
enqueue(value) {
const newNode = new ListNode(value);
if (!this.front) {
this.front = this.back = newNode;
} else {
this.back.next = newNode;
this.back = newNode;
}
}
dequeue() {
if (!this.front) return null;
const removedNode = this.front;
this.front = this.front.next;
if (!this.front) {
this.back = null;
}
return removedNode.val;
}
}
const myQueue = new Queue();
myQueue.enqueue(1);
console.log('Front element:', myQueue.peek()); // logs 'Front element: 1'
myQueue.enqueue(2);
console.log('Front element:', myQueue.peek()); // logs 'Front element: 1'
myQueue.enqueue(3);
console.log('Front element:', myQueue.peek()); // logs 'Front element: 1'
myQueue.dequeue();
console.log('Front element after dequeue:', myQueue.peek()); // logs 'Front element after dequeue: 2'
myQueue.dequeue();
console.log('Front element after dequeue:', myQueue.peek()); // logs 'Front element after dequeue: 3'
myQueue.dequeue();
console.log('Peek on empty queue:', myQueue.peek()); // logs 'Peek on empty queue: null'
console.log('`back` on empty queue:', myQueue.back); // logs '`back` on empty queue: null'