链表的基本操作
题目来自hackrank
定义链表节点Node
单向链表,每个节点除了有自己相应的数据,还有一个下一个节点的引用。class Node { int data; Node next; }
遍历打印单向链表
利用递归void Print(Node head) { if(head == null) return; else{ System.out.println(head.data); Print(head.next); } }
利用迭代
void Print(Node head) { while(head != null){ System.out.println(head.data); head = head.next; } }
在链表末端插入新的节点
分情况讨论如下:
a. 链表为空
创建新节点,并把它作为新的head返回。
b. 链表非空
通过遍历链表,找到最后一个非空节点,插入新节点。Node Insert(Node head,int data) { //链表为空 if(head == null){ head = new Node(); head.data = data; return head; } //链表非空,通过tmp去遍历 Node tmp = head; while(tmp.next != null){ tmp = tmp.next; } tmp.next = new Node(); tmp.next.data = data; return head; }
在链表头部插入新节点
Node Insert(Node head,int x) { Node tmp = new Node(); tmp.data = x; tmp.next = head; return tmp; }
在链表任意位(不超过链表长度)置插入新节点
Node InsertNth(Node head, int data, int position) { //判断是否在头部加 if(position == 0){ Node tmp = new Node(); tmp.data = data; tmp.next = head; return tmp; } //通过tmp去找插入的位置 Node tmp = head; while(--position > 0) tmp = tmp.next; Node newNode = new Node(); newNode.data = data; newNode.next = tmp.next; tmp.next = newNode; return head; }
删除指定位置(在链表中)节点
分情况讨论是否删除的是head节点Node Delete(Node head, int position) { //删除head节点 if(position == 0) return head.next; //删除一般节点 Node tmp = head; while(--position > 0) tmp = tmp.next; if(tmp.next != null){ tmp.next = tmp.next.next; } return head; }
反向打印链表
利用递归void ReversePrint(Node head) { if(head == null) return; ReversePrint(head.next); System.out.println(head.data); }
链表反向
利用递归的思想。Node Reverse(Node head) { //递归终止条件 if(head == null || head.next == null) return head; //得到以head.next为头节点的反向链表 Node ret = Reverse(head.next); //将head插入到最后 head.next.next = head; head.next = null; return ret; }
比较两个链表是否相同
按顺序比较就行//递归 int CompareLists(Node headA, Node headB) { if((headA == null) && (headB == null)) return 1; if((headA == null) || (headB ==null)) return 0; if(headA.data == headB.data) return CompareLists(headA.next, headB.next); else return 0; } //循环 int CompareLists(Node headA, Node headB) { while((headA != null) && (headB != null)){ if(headA.data != headB.data) return 0; headA = headA.next; headB = headB.next; } return ((headA == null) && (headB == null)) ? 1 : 0; }
合并两个有序的链表
//循环 Node mergeLists(Node headA, Node headB) { Node preHead = new Node(); Node tmp = preHead; for(;;){ if(headA == null){ tmp.next = headB; break; } if(headB == null){ tmp.next = headA; break; } if(headA.data < headB.data){ tmp.next = headA; headA = headA.next; } else{ tmp.next = headB; headB = headB.next; } tmp = tmp.next; } return preHead.next; } //递归 Node mergeLists(Node headA, Node headB) { if(headA == null) return headB; if(headB == null) return headA; Node ret = null; if(headA.data < headB.data){ ret = headA; headA = headA.next; } else{ ret = headB; headB = headB.next; } ret.next = mergeLists(headA, headB); return ret; }
链表倒数第n个节点
利用双指针,一开始都指向head,一个指针p1先走n步,然后两个一起走,当p1走到末尾的时候,p2就走到了倒数第n个节点int GetNode(Node head,int n) { Node p1 = head; Node p2 = head; while(n-- > 0) p1 = p1.next; while(p1.next != null){ p1 = p1.next; p2 = p2.next; } return p2.data; }
判断一个链表是否有环
同样利用双指针,p1、p2都先指向head,每次p1走一步,p2走两步。如果链表里面有环,则p1和p2最终会相遇。boolean hasCycle(Node head) { if(head == null) return false; Node p1 = head; Node p2 = head; for(;;){ if(p1.next != null) p1 = p1.next; else return false; if(p1.next != null) p1 = p1.next; else return false; p2 = p2.next; //相遇表示链表有环 if(p1 == p2) return true; } }
如果发现链表有环,那么怎么找到这个环的起点呢?
我们先假设我们现在知道了这个环的长度为 d ,那我们可以通过如下方法找到环的起点:
p1 先走d 步,然后p1,p2同时走,当p1和p2相遇的时候,这个点就是环的起点。因为p1一直比p2多走一个环的长度。
那么我们要怎么得到环的长度呢?
我们还是像判断链表是否有环一样,利用两个指针一个一次走一步、另外一个一次走两步。当他们相遇以后,让他们继续走,直到他们第二次相遇。这样就可以得到环的长度了。找到两个链表相交的点
还是利用双指针,p1从headA走, 走到头了再从headB走; 同样地,p2从headB走,走到头了再从headA走。p1、p2同时走,直到它们两个相遇,这是就是两个链表相交的点。(p1,p2走的总路程一直保持相同)int FindMergeNode(Node headA, Node headB) { Node p1 = headA; Node p2 = headB; while(p1 != p2){ p1 = p1.next; if(p1 == null) p1 = headB; p2 = p2.next; if(p2 == null) p2 = headA; } return p1.data; }