Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Remember that each data has its own trade-offs. And you need to pay attention mo
* `B` [Doubly Linked List](src/data-structures/doubly-linked-list)
* `B` [Queue](src/data-structures/queue)
* `B` [Stack](src/data-structures/stack)
* `B` [Deque](src/data-structures/deque) - double-ended queue
* `B` [Hash Table](src/data-structures/hash-table)
* `B` [Heap](src/data-structures/heap) - max and min heap versions
* `B` [Priority Queue](src/data-structures/priority-queue)
Expand Down
113 changes: 113 additions & 0 deletions src/data-structures/deque/Deque.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import DoublyLinkedList from '../doubly-linked-list/DoublyLinkedList';

export default class Deque {
constructor() {
// We use a doubly linked list internally so that both front and back
// operations (add, remove and peek) run in O(1) time.
this.linkedList = new DoublyLinkedList();

// Keep a running element count so that `size` stays O(1).
this.length = 0;
}

/**
* Check if the deque is empty.
* @return {boolean}
*/
isEmpty() {
return !this.linkedList.head;
}

/**
* Read the element at the front of the deque without removing it.
* @return {*}
*/
peekFront() {
if (!this.linkedList.head) {
return null;
}
return this.linkedList.head.value;
}

/**
* Read the element at the back of the deque without removing it.
* @return {*}
*/
peekBack() {
if (!this.linkedList.tail) {
return null;
}
return this.linkedList.tail.value;
}

/**
* Add a new element to the front (head) of the deque.
* @param {*} value
*/
addFront(value) {
this.linkedList.prepend(value);
this.length += 1;
}

/**
* Add a new element to the back (tail) of the deque.
* @param {*} value
*/
addBack(value) {
this.linkedList.append(value);
this.length += 1;
}

/**
* Remove the element from the front (head) of the deque.
* @return {*}
*/
removeFront() {
const removedHead = this.linkedList.deleteHead();
if (!removedHead) {
return null;
}
this.length -= 1;
return removedHead.value;
}

/**
* Remove the element from the back (tail) of the deque.
* @return {*}
*/
removeBack() {
const removedTail = this.linkedList.deleteTail();
if (!removedTail) {
return null;
}
this.length -= 1;
return removedTail.value;
}

/**
* Return the number of elements in the deque.
* @return {number}
*/
get size() {
return this.length;
}

/**
* Convert the deque to an array (front to back).
* @return {*[]}
*/
toArray() {
return this.linkedList
.toArray()
.map((linkedListNode) => linkedListNode.value);
}

/**
* Return a string representation of the deque.
* @param {function} [callback]
* @return {string}
*/
toString(callback) {
return this.linkedList.toString(callback);
}
}
75 changes: 75 additions & 0 deletions src/data-structures/deque/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Deque (Double-Ended Queue)

A **deque** (pronounced "deck", short for **double-ended queue**) is a linear data
structure that generalizes both a stack and a queue. Elements can be added or
removed from **either end** — the front (head) or the back (tail) — in **O(1)** time.

```
addFront(3) addBack(4)
↓ ↓
┌───────┬───────┬───────┬───────┐
│ 3 │ 1 │ 2 │ 4 │ ← internal doubly linked list
└───────┴───────┴───────┴───────┘
↑ ↑
removeFront() removeBack()
```

## Operations

| Method | Description | Time |
| --------------- | -------------------------------------------- | ---- |
| `addFront(v)` | Insert element `v` at the front | O(1) |
| `addBack(v)` | Insert element `v` at the back | O(1) |
| `removeFront()` | Remove and return the front element | O(1) |
| `removeBack()` | Remove and return the back element | O(1) |
| `peekFront()` | Return the front element without removing it | O(1) |
| `peekBack()` | Return the back element without removing it | O(1) |
| `isEmpty()` | Return `true` if the deque has no elements | O(1) |
| `size` | Return the number of elements | O(1) |

> **Note:** `size` is O(1) because the deque keeps a running element count that
> is updated on every add and remove operation.

## Complexity

| Operation | Time |
| ---------------------------- | ---- |
| `addFront` / `addBack` | O(1) |
| `removeFront` / `removeBack` | O(1) |
| `peekFront` / `peekBack` | O(1) |
| `isEmpty` / `size` | O(1) |
| Space | O(n) |

## Use Cases

A deque is the right tool when you need **O(1) access at both ends**:

- **Sliding window maximum/minimum** — maintain candidates in a monotonic deque
so each element is pushed and popped at most once (overall O(n)).
- **Browser history** — navigate backward (`removeFront`) and forward
(`removeBack`) through pages.
- **Undo / redo stacks** — push actions to the back, undo from the back,
redo from the front.
- **Palindrome checking** — compare characters from both ends simultaneously.
- **Work-stealing schedulers** (e.g. Java's `ForkJoinPool`) — threads push/pop
from their own back, while idle threads steal from another thread's front.

## Implementation Note

This implementation is backed by the project's existing
[`DoublyLinkedList`](../doubly-linked-list). Because every node keeps a reference
to both its previous and next neighbours, this gives O(1) `prepend` (for
`addFront`), O(1) `deleteHead` (for `removeFront`) and O(1) `append` /
`deleteTail` (for `addBack` / `removeBack`), with no need to shift array
elements. The number of elements is tracked in a counter, so `size` is O(1) too.

An alternative implementation using a circular buffer (fixed-size array) offers
better cache locality but requires resizing logic. The linked-list approach is
chosen here to stay consistent with the other data structures in this project.

## References

- [Deque — Wikipedia](https://en.wikipedia.org/wiki/Double-ended_queue)
- [Deque Data Structure — GeeksForGeeks](https://www.geeksforgeeks.org/deque-set-1-introduction-applications/)
- [▶ Deque (Double-Ended Queue) — YouTube](https://www.youtube.com/watch?v=pqg0SOPRlJ4)
- [▶ Sliding Window Maximum using Deque — YouTube](https://www.youtube.com/watch?v=2SXqBsTR6a8)
190 changes: 190 additions & 0 deletions src/data-structures/deque/__test__/Deque.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import Deque from '../Deque';

describe('Deque', () => {
it('should create an empty deque', () => {
const deque = new Deque();

expect(deque).not.toBeNull();
expect(deque.isEmpty()).toBe(true);
expect(deque.size).toBe(0);
});

it('should peek at the front and back of an empty deque', () => {
const deque = new Deque();

expect(deque.peekFront()).toBeNull();
expect(deque.peekBack()).toBeNull();
});

it('should return null when removing from an empty deque', () => {
const deque = new Deque();

expect(deque.removeFront()).toBeNull();
expect(deque.removeBack()).toBeNull();
});

it('should add elements to the back and remove from the front (queue behaviour)', () => {
const deque = new Deque();

deque.addBack(1);
deque.addBack(2);
deque.addBack(3);

expect(deque.isEmpty()).toBe(false);
expect(deque.size).toBe(3);
expect(deque.peekFront()).toBe(1);
expect(deque.peekBack()).toBe(3);

expect(deque.removeFront()).toBe(1);
expect(deque.removeFront()).toBe(2);
expect(deque.removeFront()).toBe(3);
expect(deque.removeFront()).toBeNull();
expect(deque.isEmpty()).toBe(true);
});

it('should add elements to the front and remove from the back (reversed queue)', () => {
const deque = new Deque();

deque.addFront(1);
deque.addFront(2);
deque.addFront(3);

expect(deque.size).toBe(3);
expect(deque.peekFront()).toBe(3);
expect(deque.peekBack()).toBe(1);

expect(deque.removeBack()).toBe(1);
expect(deque.removeBack()).toBe(2);
expect(deque.removeBack()).toBe(3);
expect(deque.removeBack()).toBeNull();
expect(deque.isEmpty()).toBe(true);
});

it('should add elements to the front and remove from the front (stack behaviour)', () => {
const deque = new Deque();

deque.addFront('a');
deque.addFront('b');
deque.addFront('c');

expect(deque.peekFront()).toBe('c');
expect(deque.removeFront()).toBe('c');
expect(deque.removeFront()).toBe('b');
expect(deque.removeFront()).toBe('a');
expect(deque.isEmpty()).toBe(true);
});

it('should support mixed addFront and addBack operations', () => {
const deque = new Deque();

// Build: [3, 1, 2, 4]
deque.addBack(1);
deque.addBack(2);
deque.addFront(3);
deque.addBack(4);

expect(deque.size).toBe(4);
expect(deque.peekFront()).toBe(3);
expect(deque.peekBack()).toBe(4);
expect(deque.toArray()).toEqual([3, 1, 2, 4]);
});

it('should support mixed removeFront and removeBack operations', () => {
const deque = new Deque();

deque.addBack(1);
deque.addBack(2);
deque.addBack(3);
deque.addBack(4);

expect(deque.removeFront()).toBe(1);
expect(deque.removeBack()).toBe(4);
expect(deque.removeFront()).toBe(2);
expect(deque.removeBack()).toBe(3);
expect(deque.isEmpty()).toBe(true);
});

it('should handle a single element correctly', () => {
const deque = new Deque();

deque.addBack(42);

expect(deque.size).toBe(1);
expect(deque.peekFront()).toBe(42);
expect(deque.peekBack()).toBe(42);

expect(deque.removeFront()).toBe(42);
expect(deque.isEmpty()).toBe(true);
expect(deque.peekFront()).toBeNull();
expect(deque.peekBack()).toBeNull();
});

it('should handle object values', () => {
const deque = new Deque();

const obj1 = { key: 'value1' };
const obj2 = { key: 'value2' };

deque.addBack(obj1);
deque.addFront(obj2);

expect(deque.peekFront()).toEqual({ key: 'value2' });
expect(deque.peekBack()).toEqual({ key: 'value1' });
expect(deque.removeFront()).toEqual({ key: 'value2' });
expect(deque.removeFront()).toEqual({ key: 'value1' });
});

it('should convert to array correctly', () => {
const deque = new Deque();

expect(deque.toArray()).toEqual([]);

deque.addBack(1);
deque.addBack(2);
deque.addFront(0);

expect(deque.toArray()).toEqual([0, 1, 2]);
});

it('should convert to string correctly', () => {
const deque = new Deque();

deque.addBack(1);
deque.addBack(2);
deque.addBack(3);

expect(deque.toString()).toBe('1,2,3');
});

it('should convert to string with a custom callback', () => {
const deque = new Deque();

deque.addBack({ value: 1, key: 'test1' });
deque.addBack({ value: 2, key: 'test2' });

const toString = (value) => `${value.key}:${value.value}`;

expect(deque.toString(toString)).toBe('test1:1,test2:2');
});

it('should track size correctly after many operations', () => {
const deque = new Deque();

expect(deque.size).toBe(0);

deque.addBack(1);
expect(deque.size).toBe(1);

deque.addFront(0);
expect(deque.size).toBe(2);

deque.removeFront();
expect(deque.size).toBe(1);

deque.removeBack();
expect(deque.size).toBe(0);

deque.removeBack();
expect(deque.size).toBe(0);
});
});
Loading