For this part, we will define some common data structures. In LeetCode, there are two kinds of List Node. The first one is the most common singly-linked list node,
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
The second one is a modified version which is a list node with a random pointer.
class RandomListNode {
int label;
RandomListNode next, random;
RandomListNode(int x) { this.label = x; }
};
Having defined these two structures, we can go through the problems on LeetCode now.
For Linked List, one point we need to remember, is "checking null pointer". Almost every bug in the problems of linked list is related to null pointer. So, we really need to be careful.
One technique we used most is fast-slow runner. Often, we have a loop like this
ListNode fast = head, slow = head;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
}
For each loop, we move fast two steps, while slow one step. Using this technique, we can check if there is a cycle in the linked list. For more proof, please refer to the book "Cracking the Coding Interview". Here, we just point out that if fast meets slow somewhere, then we can conclude that there is a cycle in the list. Otherwise, there isn't a cycle.
For the problem, Linked List Cycle, we can simply use this technique
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null)
return false;
if(head.next == head)
return true;
ListNode fast = head;
ListNode slow = head;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow)
return true;
}
return false;
}
Now, if we have already know there is a cycle in the list, we may want to find the first point of the cycle. This can also be solved by fast-slow runner. Actually, we can just keep fast unchanged, and move slow to the head, then move fast and slow with 1 step at the same time. The node they meet again is exactly the first node of the cycle. The problem Linked List Cycle II, is just a problem of this.
public ListNode detectCycle(ListNode head) {
if(head == null || head.next == null)
return null;
if(head.next == head)
return head;
//check if there is a cycle
boolean hasCycle = false;
ListNode fast = head;
ListNode slow = head;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow)
{
hasCycle = true;
break;
}
}
if(!hasCycle)
return null;
//return the start node
slow = head;
while(fast!=slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}
Fast-slow runner is a very important trick when we solve linked list problems. It can not only be used in problems of cycles, but also problems like "find the k-th to the last node", "find the mid-node of the list", etc.
Next, Let's take a look at how it solves the problem "Remove the Nth Node from the End of the List".
Since this is a linked list and we don't have neither an index nor the length of the list, we need to find the node using another way. As mentioned above, the technique we will use is still fat-slow runner.
We know that we need fast to be N step faster than slow, So, we can keep slow unchanged, and move fast by N step. It will be something like this
ListNode fast = head;
while(fast!=null&&n-->0){
fast = fast.next;
}
Now, if we move fast and slow with 1 step at the same time, we will get the Nth node to the end, which is slow. However, in this problem, we need to remove that node. Therefore, we should introduce another runner, previous, to help us to remove the current node.
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head==null || n==0)
return head;
if(head.next==null && n==1)
return null;
ListNode p = head;
while(p!=null&&n-->0){
p = p.next;
}
ListNode pre=null, cur=head;
while(p!=null){
pre = cur;
cur = cur.next;
p = p.next;
}
if(pre!=null){
pre.next = cur.next;
}else{
return cur.next;
}
return head;
}
With a little modification, it can solve another problem, which is rotation of the list. If we are given the input 1->2->3->4->5->NULL and k=2, output : 4->5->1->2->3->NULL. Obviously, if we can find the 2nd to the end of the list, this problem will be much easier.
public ListNode rotateRight(ListNode head, int n) {
if(head==null || head.next==null)
return head;
ListNode p = head;
int r_n = n;
int length = 1;
//For the case n > length
while(p!=null&&p.next!=null&&n>0){
p = p.next;
length++;
n--;
}
if(n!=0){
n = r_n % length;
p = head;
}
while(p!=null&&p.next!=null&&n>0){
p = p.next;
n--;
}
//When p hits the end of the list, q becomes the node just before the nth to the end
//Why isn't q the nth to the end? Because here p terminates when p becomes the last node
//not null
ListNode q = head;
while(p!=null&&p.next!=null){
q = q.next;
p = p.next;
}
p.next = head;
ListNode newHead = q.next;
q.next = null;
return newHead;
}
For a special case, what if when n = length/2. We may use this when we want to convert a sorted list into a binary search tree.
public TreeNode sortedListToBST(ListNode head) {
if(head==null)
return null;
if(head.next==null)
return new TreeNode(head.val);
ListNode fast = head, slow = head, pre = null;
while(fast!=null && fast.next!=null){
pre = slow;
fast = fast.next.next;
slow = slow.next;
}
//slow->root of the tree
if(slow!=null){
TreeNode root = new TreeNode(slow.val);
if(pre!=null)
pre.next = null;
root.left = sortedListToBST(head);
root.right = sortedListToBST(slow.next);
return root;
}
return null;
}
Although trivial in this problem, we still need to point out that using "fast!=null && fast.next!=null", if fast is null when terminates, slow will be the point [length/2](which indicates that the length of the list is an even number and slow is the last node in the first half). Otherwise, if fast is not null (the end node in the list), slow will be the exact the mid-node (which indicates that the length of the list is an odd number).
Then, what if we want to reverse a list from position m to n. For example, 1->2->3->4->5->null , m =2, n=4 and then we get 1->4->3->2->5->null.
We can think of this problem as a problem to reverse a whole linked list. The only difference is that here the list is actually a sublist of another longer list.
To reverse a list, I prefer the approach to use a pre cursor, a cur cursor and a next cursor. The key point here is the order of the statement which changes the next field of the nodes.
public ListNode reverseBetween(ListNode head, int m, int n) {
if(head==null || head.next==null || m<=0 || m>=n )
return head;
//(1-)->(2-)->(3-)->(4-)->(5-)->null
ListNode newHead = new ListNode(0);
newHead.next = head;
//new->(1-)->(2-)->(3-)->(4-)->(5-)->null
//pre p
int count = 1;
ListNode p = head, pre = newHead;
while(count<m && p!=null){
p=p.next;
pre=pre.next;
count++;
}
//m=2
//new->(1-)->(2-)->(3-)->(4-)->(5-)->null
// pre p
pre.next = null;
ListNode tail = p, q=p.next;
p.next = null;
while(count<n && q!=null){
ListNode next = q.next;
q.next = p;
p = q;
q = next;
count++;
}
tail.next = q;
pre.next = p;
return newHead.next;
}