# Design Singly Linked List
Design a Singly Linked List class.
Your LinkedList class should support the following operations:
* LinkedList() will initialize an empty linked list.
* int get(int i) will return the value of the ith node (0-indexed). If the index is out of bounds, return -1.
* void insertHead(int val) will insert a node with val at the head of the list.
* void insertTail(int val) will insert a node with val at the tail of the list.
* int remove(int i) will remove the ith node (0-indexed). If the index is out of bounds, return false, otherwise return true.
* int[] getValues() return an array of all the values in the linked list, ordered from head to tail.
Example 1:
> Input:
> ["insertHead", 1, "insertTail", 2, "insertHead", 0, "remove", 1, "getValues"]
>
> Output:
> [null, null, null, true, [0, 2]]
Example 2:
> Input:
> ["insertHead", 1, "insertHead", 2, "get", 5]
>
> Output:
> [null, null, -1]
**Note:**
* The index int i provided to get(int i) and remove(int i) is guranteed to be greater than or equal to 0.
---
## 1. Create a ListNode
```python
class ListNode:
def __init__(self, val):
self.val = val
self.next = None
```
## 2. Create a Singly Linked List
Note: initialize the linked list with a **dummy node(sentinel node)** will make edge cases easier
```python
class SinglyLinkedList:
def __init__ (self):
self.head = ListNode(-1) # sentinel node
self.tail = self.head
```
## 3. get i-th node
```python
def get(self, index: int) -> int:
curr = self.head.next
i = 0
# iteration
while curr:
# found
if i == index:
return curr.val
# next node and increment i
i += 1
curr = curr.next
# not found
return -1
```
Steps:
1. create a pointer(curr), and points to the head.next
2. initiate variable i = 0
3. iteration through the list (standard start: **while curr:**)
4. if i == index, then the node is found. Return the value of the node.
5. iterate to the next node and increment i by 1
6. return -1 if not found
## 4. insertHead
```python
def insertHead(self, val: int) -> None:
new_node = ListNode(val)
new_node.next = self.head.next
self.head.next = new_node
# handle self.tail
# if list is empty before insertion
if not new_node.next:
self.tail = new_node
```
Steps:
1. create a new node
2. point new node's next to the head.next
3. move head to point to new node
4. handle edge case: if the list is empty before insertion, then new node should be tail. Therefore, if new node's next points to null, it means the node is the first node. Then, move the tail to point to the new node.
## 5. insertTail
```python
def insertTail(self, val: int) -> None:
self.tail.next = ListNode(val)
self.tail = self.tail.next
```
easy, not explanation needed
## 6. remove
**This one is tricky, be careful**
```python
def remove(self, index: int) -> bool:
curr = self.head
i = 0
# start the iteration
while i < index and curr:
i += 1
curr = curr.next
if curr and curr.next:
# if target(curr.next) is the last node
if curr.next == self.tail:
# set tail to curr(previous node)
self.tail = curr
# remove the node
curr.next = curr.next.next
return True
return False # list out of bounds
```
Steps:
1. create a pointer that points to the previous node of every node (i.e. first point at the dummy(head))
2. initiate variable i = 0
3. start iteration. **Condition: i < index and curr exist**. The condition "i < index" will ensure curr pointer stop at the previous node before our target node.
4. execute iteration and increment i by 1
5. **check curr and curr.next exist**
6. **handle tail pointer: if target node is the tail**, then curr(the previous node) should be the new tail
**7. remove the node: curr.next = curr.next.next (should memorize this)**
8. return
## get values
```python
def getValues(self) -> List[int]:
result = []
curr = self.head.next
while curr:
result.append(curr.val)
curr = curr.next
return result
```
easy, no explanation