Problem#
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.
Examples#
Example 1#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
** 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
Please do not use the built-in LinkedList library.
At most 2000
calls will be made to get
, addAtHead
, addAtTail
, addAtIndex
and deleteAtIndex
.
Solution#
Method 1 – Doubly Linked List with Dummy Head and Tail#
Intuition#
A doubly linked list with dummy head and tail nodes simplifies edge cases for insertions and deletions at the head and tail. We maintain a size variable for O(1) length checks and index validation.
Approach#
Define a doubly linked list node with val
, prev
, and next
.
Use dummy head and tail nodes to avoid null checks for head/tail operations.
Maintain a size
variable to track the number of elements.
For get(index)
, traverse from head or tail depending on which is closer.
For addAtHead(val)
, insert after dummy head.
For addAtTail(val)
, insert before dummy tail.
For addAtIndex(index, val)
, insert at the correct position if index is valid.
For deleteAtIndex(index)
, remove the node at the index if valid.
Code#
Cpp
Go
Java
Kotlin
Python
Rust
Typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class MyLinkedList {
struct Node {
int val;
Node * prev, * next;
Node(int v) : val(v), prev(nullptr ), next(nullptr ) {}
} * head, * tail;
int sz;
public :
MyLinkedList() {
head = new Node(0 );
tail = new Node(0 );
head-> next = tail;
tail-> prev = head;
sz = 0 ;
}
int get (int idx) {
if (idx < 0 || idx >= sz) return - 1 ;
Node* cur;
if (idx + 1 < sz - idx) {
cur = head-> next;
for (int i = 0 ; i < idx; ++ i) cur = cur-> next;
} else {
cur = tail;
for (int i = 0 ; i < sz - idx; ++ i) cur = cur-> prev;
}
return cur-> val;
}
void addAtHead (int val) {
Node* n = new Node(val);
n-> next = head-> next;
n-> prev = head;
head-> next-> prev = n;
head-> next = n;
sz++ ;
}
void addAtTail (int val) {
Node* n = new Node(val);
n-> prev = tail-> prev;
n-> next = tail;
tail-> prev-> next = n;
tail-> prev = n;
sz++ ;
}
void addAtIndex (int idx, int val) {
if (idx < 0 || idx > sz) return ;
Node* cur;
if (idx < sz - idx) {
cur = head;
for (int i = 0 ; i < idx; ++ i) cur = cur-> next;
} else {
cur = tail;
for (int i = 0 ; i < sz - idx + 1 ; ++ i) cur = cur-> prev;
}
Node* n = new Node(val);
n-> next = cur-> next;
n-> prev = cur;
cur-> next-> prev = n;
cur-> next = n;
sz++ ;
}
void deleteAtIndex (int idx) {
if (idx < 0 || idx >= sz) return ;
Node* cur;
if (idx < sz - idx) {
cur = head;
for (int i = 0 ; i < idx; ++ i) cur = cur-> next;
} else {
cur = tail;
for (int i = 0 ; i < sz - idx; ++ i) cur = cur-> prev;
}
Node* toDel = cur-> next;
cur-> next = toDel-> next;
toDel-> next-> prev = cur;
delete toDel;
sz-- ;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
type Node struct {
val int
prev * Node
next * Node
}
type MyLinkedList struct {
head , tail * Node
sz int
}
func Constructor () MyLinkedList {
head := & Node {}
tail := & Node {}
head .next = tail
tail .prev = head
return MyLinkedList {head , tail , 0 }
}
func (l * MyLinkedList ) Get (idx int ) int {
if idx < 0 || idx >= l .sz {
return - 1
}
var cur * Node
if idx + 1 < l .sz - idx {
cur = l .head .next
for i := 0 ; i < idx ; i ++ {
cur = cur .next
}
} else {
cur = l .tail
for i := 0 ; i < l .sz - idx ; i ++ {
cur = cur .prev
}
}
return cur .val
}
func (l * MyLinkedList ) AddAtHead (val int ) {
n := & Node {val : val }
n .next = l .head .next
n .prev = l .head
l .head .next .prev = n
l .head .next = n
l .sz ++
}
func (l * MyLinkedList ) AddAtTail (val int ) {
n := & Node {val : val }
n .prev = l .tail .prev
n .next = l .tail
l .tail .prev .next = n
l .tail .prev = n
l .sz ++
}
func (l * MyLinkedList ) AddAtIndex (idx , val int ) {
if idx < 0 || idx > l .sz {
return
}
var cur * Node
if idx < l .sz - idx {
cur = l .head
for i := 0 ; i < idx ; i ++ {
cur = cur .next
}
} else {
cur = l .tail
for i := 0 ; i < l .sz - idx + 1 ; i ++ {
cur = cur .prev
}
}
n := & Node {val : val }
n .next = cur .next
n .prev = cur
cur .next .prev = n
cur .next = n
l .sz ++
}
func (l * MyLinkedList ) DeleteAtIndex (idx int ) {
if idx < 0 || idx >= l .sz {
return
}
var cur * Node
if idx < l .sz - idx {
cur = l .head
for i := 0 ; i < idx ; i ++ {
cur = cur .next
}
} else {
cur = l .tail
for i := 0 ; i < l .sz - idx ; i ++ {
cur = cur .prev
}
}
toDel := cur .next
cur .next = toDel .next
toDel .next .prev = cur
l .sz --
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class MyLinkedList {
class Node {
int val;
Node prev, next;
Node(int v) { val = v; }
}
Node head, tail;
int sz;
public MyLinkedList () {
head = new Node(0);
tail = new Node(0);
head.next = tail;
tail.prev = head;
sz = 0;
}
public int get (int idx) {
if (idx < 0 || idx >= sz) return - 1;
Node cur;
if (idx + 1 < sz - idx) {
cur = head.next ;
for (int i = 0; i < idx; ++ i) cur = cur.next ;
} else {
cur = tail;
for (int i = 0; i < sz - idx; ++ i) cur = cur.prev ;
}
return cur.val ;
}
public void addAtHead (int val) {
Node n = new Node(val);
n.next = head.next ;
n.prev = head;
head.next .prev = n;
head.next = n;
sz++ ;
}
public void addAtTail (int val) {
Node n = new Node(val);
n.prev = tail.prev ;
n.next = tail;
tail.prev .next = n;
tail.prev = n;
sz++ ;
}
public void addAtIndex (int idx, int val) {
if (idx < 0 || idx > sz) return ;
Node cur;
if (idx < sz - idx) {
cur = head;
for (int i = 0; i < idx; ++ i) cur = cur.next ;
} else {
cur = tail;
for (int i = 0; i < sz - idx + 1; ++ i) cur = cur.prev ;
}
Node n = new Node(val);
n.next = cur.next ;
n.prev = cur;
cur.next .prev = n;
cur.next = n;
sz++ ;
}
public void deleteAtIndex (int idx) {
if (idx < 0 || idx >= sz) return ;
Node cur;
if (idx < sz - idx) {
cur = head;
for (int i = 0; i < idx; ++ i) cur = cur.next ;
} else {
cur = tail;
for (int i = 0; i < sz - idx; ++ i) cur = cur.prev ;
}
Node toDel = cur.next ;
cur.next = toDel.next ;
toDel.next .prev = cur;
sz-- ;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class MyLinkedList {
class Node (var v: Int) {
var prev: Node? = null
var next: Node? = null
}
private val head = Node(0 )
private val tail = Node(0 )
private var sz = 0
init {
head.next = tail
tail.prev = head
}
fun get (idx: Int): Int {
if (idx < 0 || idx >= sz) return -1
var cur: Node?
if (idx + 1 < sz - idx) {
cur = head.next
repeat(idx) { cur = cur?. next }
} else {
cur = tail
repeat(sz - idx) { cur = cur?. prev }
}
return cur?. v ?: -1
}
fun addAtHead (v: Int) {
val n = Node(v)
n.next = head.next
n.prev = head
head.next?. prev = n
head.next = n
sz++
}
fun addAtTail (v: Int) {
val n = Node(v)
n.prev = tail.prev
n.next = tail
tail.prev?. next = n
tail.prev = n
sz++
}
fun addAtIndex (idx: Int, v: Int) {
if (idx < 0 || idx > sz) return
var cur: Node?
if (idx < sz - idx) {
cur = head
repeat(idx) { cur = cur?. next }
} else {
cur = tail
repeat(sz - idx + 1 ) { cur = cur?. prev }
}
val n = Node(v)
n.next = cur?. next
n.prev = cur
cur?. next?. prev = n
cur?. next = n
sz++
}
fun deleteAtIndex (idx: Int) {
if (idx < 0 || idx >= sz) return
var cur: Node?
if (idx < sz - idx) {
cur = head
repeat(idx) { cur = cur?. next }
} else {
cur = tail
repeat(sz - idx) { cur = cur?. prev }
}
val toDel = cur?. next
cur?. next = toDel?. next
toDel?. next?. prev = cur
sz--
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class Node :
def __init__ (self, v: int):
self. v = v
self. prev: Node | None = None
self. next: Node | None = None
class MyLinkedList :
def __init__ (self):
self. head = Node(0 )
self. tail = Node(0 )
self. head. next = self. tail
self. tail. prev = self. head
self. sz = 0
def get (self, idx: int) -> int:
if idx < 0 or idx >= self. sz:
return - 1
if idx + 1 < self. sz - idx:
cur = self. head. next
for _ in range(idx):
cur = cur. next
else :
cur = self. tail
for _ in range(self. sz - idx):
cur = cur. prev
return cur. v
def addAtHead (self, v: int) -> None :
n = Node(v)
n. next = self. head. next
n. prev = self. head
self. head. next. prev = n
self. head. next = n
self. sz += 1
def addAtTail (self, v: int) -> None :
n = Node(v)
n. prev = self. tail. prev
n. next = self. tail
self. tail. prev. next = n
self. tail. prev = n
self. sz += 1
def addAtIndex (self, idx: int, v: int) -> None :
if idx < 0 or idx > self. sz:
return
if idx < self. sz - idx:
cur = self. head
for _ in range(idx):
cur = cur. next
else :
cur = self. tail
for _ in range(self. sz - idx + 1 ):
cur = cur. prev
n = Node(v)
n. next = cur. next
n. prev = cur
cur. next. prev = n
cur. next = n
self. sz += 1
def deleteAtIndex (self, idx: int) -> None :
if idx < 0 or idx >= self. sz:
return
if idx < self. sz - idx:
cur = self. head
for _ in range(idx):
cur = cur. next
else :
cur = self. tail
for _ in range(self. sz - idx):
cur = cur. prev
toDel = cur. next
cur. next = toDel. next
toDel. next. prev = cur
self. sz -= 1
1
2
3
4
5
6
7
8
9
10
11
struct Node {
v: i32 ,
prev: Option<* mut Node> ,
next: Option<* mut Node> ,
}
struct MyLinkedList {
head: * mut Node,
tail: * mut Node,
sz: usize ,
}
// Rust implementation would use unsafe pointers for doubly linked list.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class Node {
v : number ;
prev : Node | null = null ;
next : Node | null = null ;
constructor (v : number ) { this .v = v ; }
}
class MyLinkedList {
private head = new Node (0 );
private tail = new Node (0 );
private sz = 0 ;
constructor () {
this .head .next = this .tail ;
this .tail .prev = this .head ;
}
get (idx : number ): number {
if (idx < 0 || idx >= this .sz ) return - 1 ;
let cur : Node ;
if (idx + 1 < this .sz - idx ) {
cur = this .head .next ! ;
for (let i = 0 ; i < idx ; ++ i ) cur = cur .next ! ;
} else {
cur = this .tail ;
for (let i = 0 ; i < this .sz - idx ; ++ i ) cur = cur .prev ! ;
}
return cur .v ;
}
addAtHead (v : number ): void {
const n = new Node (v );
n .next = this .head .next ;
n .prev = this .head ;
this .head .next ! .prev = n ;
this .head .next = n ;
this .sz ++ ;
}
addAtTail (v : number ): void {
const n = new Node (v );
n .prev = this .tail .prev ;
n .next = this .tail ;
this .tail .prev ! .next = n ;
this .tail .prev = n ;
this .sz ++ ;
}
addAtIndex (idx : number , v : number ): void {
if (idx < 0 || idx > this .sz ) return ;
let cur : Node ;
if (idx < this .sz - idx ) {
cur = this .head ;
for (let i = 0 ; i < idx ; ++ i ) cur = cur .next ! ;
} else {
cur = this .tail ;
for (let i = 0 ; i < this .sz - idx + 1 ; ++ i ) cur = cur .prev ! ;
}
const n = new Node (v );
n .next = cur .next ;
n .prev = cur ;
cur .next ! .prev = n ;
cur .next = n ;
this .sz ++ ;
}
deleteAtIndex (idx : number ): void {
if (idx < 0 || idx >= this .sz ) return ;
let cur : Node ;
if (idx < this .sz - idx ) {
cur = this .head ;
for (let i = 0 ; i < idx ; ++ i ) cur = cur .next ! ;
} else {
cur = this .tail ;
for (let i = 0 ; i < this .sz - idx ; ++ i ) cur = cur .prev ! ;
}
const toDel = cur .next ! ;
cur .next = toDel .next ;
toDel .next ! .prev = cur ;
this .sz -- ;
}
}
Complexity#
⏰ Time complexity: O(min(k, n-k))
for get, add, and delete operations, where k
is the index and n
is the list size.
🧺 Space complexity: O(n)
, where n
is the number of nodes in the list.