Design your implementation of the linked list. You can choose to use a singly or doubly linked list.
A node in a singly linked list should have two attributes: val
and next
. val
is the value of the current node, and next
is a pointer/reference to the next node.
If you want to use the doubly linked list, you will need one more attribute prev
to indicate the previous node in the linked list. Assume all nodes in the linked list are 0-indexed.
Implement the MyLinkedList
class:
MyLinkedList()
Initializes the MyLinkedList
object.int get(int index)
Get the value of the indexth
node in the linked list. If the index is invalid, return -1
.void addAtHead(int val)
Add a node of value val
before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.void addAtTail(int val)
Append a node of value val
as the last element of the linked list.void addAtIndex(int index, int val)
Add a node of value val
before the indexth
node in the linked list. If index
equals the length of the linked list, the node will be appended to the end of the linked list. If index
is greater than the length, the node will not be inserted.void deleteAtIndex(int index)
Delete the indexth
node in the linked list, if the index is valid.
Example 1:
Input ["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"] [[], [1], [3], [1, 2], [1], [1], [1]] Output [null, null, null, null, 2, null, 3] Explanation MyLinkedList myLinkedList = new MyLinkedList(); myLinkedList.addAtHead(1); myLinkedList.addAtTail(3); myLinkedList.addAtIndex(1, 2); // linked list becomes 1->2->3 myLinkedList.get(1); // return 2 myLinkedList.deleteAtIndex(1); // now the linked list is 1->3 myLinkedList.get(1); // return 3
Constraints:
0 <= index, val <= 1000
2000
calls will be made to get
, addAtHead
, addAtTail
, addAtIndex
and deleteAtIndex
.This problem requires designing a linked list data structure. Two solutions are provided: one using a singly linked list and another using an array-based implementation that simulates a linked list.
This solution uses a singly linked list, where each node contains a value (val
) and a pointer to the next node (next
). A dummy node is used at the head to simplify the operations.
Data Structure:
dummy
: A dummy node at the beginning of the list. This simplifies the add and delete operations at the head because you don't have to check for an empty list.cnt
: Keeps track of the number of elements in the linked list.Methods:
get(index)
: Iterates through the list until the index
th node is found and returns its value. Returns -1 if the index is out of bounds.addAtHead(val)
: Adds a new node with the given value to the beginning of the list.addAtTail(val)
: Adds a new node with the given value to the end of the list.addAtIndex(index, val)
: Adds a new node with the given value before the index
th node. Handles edge cases where index
is 0 (add to head) or equal to cnt
(add to tail). Does nothing if index
is greater than cnt
.deleteAtIndex(index)
: Deletes the index
th node from the list. Handles edge cases where index
is 0 (delete from head) or out of bounds.Time Complexity:
get(index)
: O(index) - Linear time because it might need to traverse up to index
nodes.addAtHead(val)
: O(1) - Constant time as it adds to the head.addAtTail(val)
: O(n) - Linear time because it needs to iterate to the tail.addAtIndex(index, val)
: O(index) - Linear time because it might need to traverse up to index
nodes.deleteAtIndex(index)
: O(index) - Linear time because it might need to traverse up to index
nodes.Space Complexity: O(n) - Linear space due to the linked list storing n
nodes.
This solution simulates a linked list using arrays. e
stores the values, ne
stores the index of the next node, head
stores the index of the first node, idx
stores the index of the next available slot in the arrays, and cnt
stores the number of nodes.
Data Structures:
e
: Array to store the values of the nodes.ne
: Array to store the index of the next node for each node.head
: Index of the first node in the list.idx
: Index of the next available slot in the arrays.cnt
: Number of nodes in the list.Methods: The methods are analogous to those in Solution 1, but they use array indices to represent the links between nodes.
Time Complexity:
get(index)
: O(index)addAtHead(val)
: O(1)addAtTail(val)
: O(n) - In the worst case (adding to the end), it might have to traverse the entire array to find the tail.addAtIndex(index, val)
: O(index)deleteAtIndex(index)
: O(index)Space Complexity: O(1010) - Constant space because the arrays are pre-allocated. While technically not strictly constant, the space is fixed and doesn't depend on the number of nodes, making it effectively constant within the constraint of at most 2000 calls.
Choosing between Solutions 1 and 2:
Solution 1 (singly linked list) is generally preferred for its dynamic memory management and scalability. Solution 2 (array simulation) is provided as an alternative approach that can be more efficient in terms of memory in some scenarios if the maximum number of nodes is known and relatively small (as in this problem's constraints). For very large linked lists where dynamic allocation becomes expensive, an array-based approach might be slightly better. But for most practical applications, Solution 1 is better because it can handle many more nodes without constraints.