Similar to the array, the linked list is also a linear data structure.
As you can see, each element in the linked list is actually a separate object while all the objects are linked together by the reference field in each element.
There are two types of linked list: singly linked list and doubly linked list.
// Definition for singly-linked list.
public class SinglyListNode {
int val;
SinglyListNode next;
SinglyListNode(int x) { val = x; }
}
If we want to add a new value after a given node prev, we should:
Unlike an array, we don’t need to move all elements past the inserted element. Therefore, you can insert a new node into a linked list in O(1) time complexity, which is very efficient.
For instance, Let’s insert a new value 9 after the second node 6.
We will first initialize a new node with value 9. Then link node 9 to node 15. Finally, link node 6 to node 9.
After insertion, our linked list will look like this:
As we know, we use the head node head to represent the whole list.
So it is essential to update head when adding a new node at the beginning of the list.
For example, let’s add a new node 9 at the beginning of the list.
If we want to delete an existing node cur from the singly linked list, we can do it in two steps:
In our first step, we need to find out prev and next. It is easy to find out next using the reference field of cur. However, we have to traverse the linked list from the head node to find out prev which will take O(N) time on average, where N is the length of the linked list. So the time complexity of deleting a node will be O(N).
The space complexity is O(1) because we only need constant space to store our pointers.
Let’s try to delete node 6 from the singly linked list above.
Traverse the linked list from the head until we find the previous node prev which is node 23
Link prev (node 23) with next (node 15)
Node 6 is not in our singly linked list now.
As we mentioned before, we use the head node head to represent a linked list. Our head is the black node 23 in the example below.
If we want to delete the first node, we can simply assign the next node to head. That is to say, our head will be node 6 after deletion.
The linked list begins at the head node, so node 23 is no longer in our linked list.
Wasn’t too bad for implementation of the algorithm. Some points needed to be specified here:
if (index < 0 || index >= this.size) {
return -1;
}
size == 0
and elseLet’s start with a classic problem:
Given a linked list, determine if it has a cycle in it.
That’s exactly what we will come across using two pointers with different speed in a linked list:
So the only remaining problem is:
What should be the proper speed for the two pointers?
It is a safe choice to move the slow pointer one step at a time while moving the fast pointer two steps at a time. For each iteration, the fast pointer will move one extra step. If the length of the cycle is M, after M iterations, the fast pointer will definitely move one more cycle and catch up with the slow pointer.
if slow == fast ultimately, then we will return true!
(x + y)
steps while fast traveled 2 * (x + y)
steps, and the extra distance (x + y)
must be a multiple of the circle length C
.
so we have x + y = N * C, let slow continue to travel from y and after x more steps, slow will return to the start of the cycle. At the same time, according to the definition of x, head will also reach the start of the cycle after moving x steps. so if head and slow start to move at the same time, they will meet at the start of the cycle, that is the answer.
Time Complexity: O(N), Space Complexity: O(1)
O(n) time O(1) space Just to make multiple loops until they meet
If headA reaches the end, make it the head of the other LinkedList If headB reaches the end, make it the head of the LinkedListA. From here, they will loop ahead and ultimately meet each other somewhere.
Here’s the template:
// Initialize slow & fast pointers
ListNode slow = head;
ListNode fast = head;
/**
* Change this condition to fit specific problem.
* Attention: remember to avoid null-pointer error
**/
while (slow != null && fast != null && fast.next != null) {
slow = slow.next; // move slow pointer one step each time
fast = fast.next.next; // move fast pointer two steps each time
if (slow == fast) { // change this condition to fit specific problem
return true;
}
}
return false; // change return value to fit specific problem
It is easy to analyze the space complexity. If you only use pointers without any other extra space, the space complexity will be O(1). However, it is more difficult to analyze the time complexity. In order to get the answer, we need to analyze how many times we will run our loop .
In our previous finding cycle example, let’s assume that we move the faster pointer 2 steps each time and move the slower pointer 1 step each time.
If there is no cycle, the fast pointer takes N/2 times to reach the end of the linked list, where N is the length of the linked list.
If there is a cycle, the fast pointer needs M times to catch up the slower pointer, where M is the length of the cycle in the list.
Obviously, M <= N. So we will run the loop up to N times. And for each loop, we only need constant time. So, the time complexity of this algorithm is O(N) in total.
We proceed by recursive method.
ListNode next_node = head.next;
head.next = prev;
prev = head;
head = next_node;
Time complexity : O(n). There are total nn nodes and we visit each node once.
Space complexity : O(1). All we need is the four pointers.
public ListNode oddEvenList(ListNode head) {
// head -> odd
// head.next -> even
// head.next.next -> odd
if (head == null) return null;
ListNode odd = head, even = head.next, evenHead = even;
while (even!= null && even.next != null){
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
odd.next = evenHead;
return head;
}
The doubly linked list works in a similar way but has one more reference field which is known as the “prev” field. With this extra field, you are able to know the previous node of the current node.
Let’s take a look at an example:
Here is a typical definition of the node structure in a doubly linked list:
class DoublyListNode {
int val;
DoublyListNode next, prev;
DoublyListNode(int x) {val = x;}
}
Similar to the singly linked list, we will use the head node to represent the whole list.
We can access data in the same exact way as in a singly linked list:
If we want to insert a new node cur after an existing node prev, we can divide this process into two steps:
Similar to the singly linked list, both the time and the space complexity of the add operation are O(1).
we can simply link its previous node prev with its next node next. Since we no longer need to traverse the linked list to get the previous node, both the time and space complexity are O(1).
They are similar in many operations:
After this comparison, it is not difficult to come up with our conclusion:
If you need to add or delete a node frequently, a linked list could be a good choice.
If you need to access an element by index often, an array might be a better choice than a linked list.
Initialize current node to dummy head of the returning list.
Since n may be a large number compared to the length of list. So we need to know the length of linked list. After that, move the list after the (l-n%l )th node to the front to finish the rotation.
Ex: {1,2,3} k=2 Move the list after the 1st node to the front
Ex: {1,2,3} k=5, In this case Move the list after (3-5%3=1)st node to the front.
Algorithm: