目录
1.如何从尾到头输出单链表
看到这个问题,最直接地想法就是先反转链表,再输出,在上一篇博客有实现这个方法,但需要额外的操作。
还有一种方法,就是利用栈。每经过一个节点,把该节点压入栈,遍历完链表后,从栈顶开始输出节点值。但此方法需要维护一个额外的空间,实现起来略显麻烦。
是否还有更高效的方法?我们想到了栈,自然也就想到递归本质上就是一个栈结构,可以用递归来实现。每访问一个节点,先递归输出它后面的节点,再输出该节点自身,达到从尾到头输出链表的效果。
public class printReversely {
public static void printListReversely(Node p){
if(p!=null){
printListReversely(p.next);
System.out.println(p.data);
}
}
public static void main(String[] args) {
Node a = new Node(1);
Node b = new Node(2);
Node c = new Node(3);
Node d = new Node(4);
Node e = new Node(5);
a.next=b;
b.next=c;
c.next=d;
d.next=e;
e.next=null;
printListReversely(a);
}
}
2.如何寻找单链表的中间节点
最容易想到的方法就是先求解单链表长度length,然后遍历length/2距离可查找到中间节点。但这种方法需要遍历两次链表。
如果是双向链表,可以利用两个指针一个从头到尾遍历,一个从尾到头遍历,当两个指针相遇,就找到中间元素。如果是单链表也可以采用双指针的方式实现查找中间节点:
具体步骤就是两个指针同时从头开始遍历,一个指针一次一步,另外一个指针一次两步,快指针先到达尾部,慢指针恰好到达链表中部。其中当链表长度为奇数,慢指针指向即是中间节点,若为偶数,慢指针指向以及慢指针下一节点都是中间节点。
public class SearchMid {
public static Node SearchMidNode(Node head){
Node p = head;
Node q = head;
while(p != null && p.next != null && p.next.next != null){
p = p.next.next;
q = q.next;
}
return q;
}
public static void main(String[] args) {
Node a = new Node(1);
Node b = new Node(2);
Node c = new Node(3);
Node d = new Node(4);
Node e = new Node(5);
a.next=b;
b.next=c;
c.next=d;
d.next=e;
e.next=null;
System.out.println(SearchMidNode(a).data);
}
}
结果: 3
3.如何检测一个链表是否有环
定义两个指针fast和slow,slow每次前进一步,fast每次前进两步,两个指针同时移动,快指针每次都要和慢指针比较,直到快指针等于慢指针,就证明是带环的单链表;如果fast先行到NULL,则为无环链表。
public class IsLoopChs {
public static boolean IsLoop(Node head){
Node fast = head;
Node slow = head;
if(fast == null)
return false;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow)
return true;
}
return !(fast == null || fast.next == null);
}
public static void main(String[] args) {
Node a = new Node(1);
Node b = new Node(2);
Node c = new Node(3);
Node d = new Node(4);
Node e = new Node(5);
a.next=b;
b.next=c;
c.next=d;
d.next=e;
e.next=a;
System.out.println(IsLoop(a));
}
}
结果:true
4.如何在不知道头指针的情况下删除指定节点
一般分两种情况讨论:
1)若待删除节点为尾节点,则无法删除,因为删除后无法使其前驱节点的next置为空
2)若待删除节点不是尾节点,则可以将后继节点值赋给当前节点,然后删除后继节点。
public class DeleteNode {
public static boolean deleteNode(Node p){
if(p == null || p.next == null)
return false;
p.data = p.next.data;
p.next=p.next.next;
return true;
}
public static void main(String[] args) {
Node a = new Node(1);
Node b = new Node(2);
Node c = new Node(3);
Node d = new Node(4);
Node e = new Node(5);
a.next=b;
b.next=c;
c.next=d;
d.next=e;
e.next=null;
System.out.println(deleteNode(e));
System.out.println(deleteNode(d));
Node chs = a;
while(chs!=null){
System.out.println(chs.data);
chs=chs.next;
}
}
}
结果:
5.如何判断两个链表是否相交
首先需要明确如果两个链表相交,那么一定有相同的尾节点,不可能出现X型交叉,因为每个节点只有一个指针域。因此可以分别遍历两个链表,记录尾节点,如果尾节点相同,那么两个链表相交。
public class IsIntersect {
public static boolean isIntersect(Node h1,Node h2){
if(h1==null||h2==null)
return false;
Node tail=h1;
while(tail.next!=null)
tail=tail.next;
Node tail2=h2;
while(tail2.next!=null)
tail2=tail2.next;
return tail==tail2;
}
public static void main(String[] args) {
Node a = new Node(1);
Node b = new Node(2);
Node c = new Node(3);
Node d = new Node(4);
Node e = new Node(5);
a.next=b;
b.next=c;
c.next=null;
d.next=b;
e.next=null;
System.out.println(isIntersect(a,d));
System.out.println(isIntersect(a,e));
}
}
结果:
引申:如果两个链表相交,如何找到它们相交的第一个节点?
可以先计算两个链表的长度len1和len2(假设len1>len2),对长链表先遍历(len1-len2)个节点,此时距离它们相交的节点距离相同,同时开始遍历,直到遇到相同的节点。注意查找相交节点的前提是先判断是否相交。
public static Node getFirstMeetNode(Node h1,Node h2){
if(h1==null||h2==null)
return null;
Node tail=h1;
int len = 1;
while(tail.next!=null){
tail=tail.next;
len++;
}
Node tail2=h2;
int len2=1;
while(tail2.next!=null){
tail2=tail2.next;
len2++;
}
if(tail!=tail2)
return null;
Node t1=h1;
Node t2=h2;
if(len>len2){
int d=len-len2;
while(d!=0){
t1=t1.next;
d--;
}
}
else{
int d=len2-len;
while(d!=0){
t2=t2.next;
d--;
}
}
while (t1!=t2){
t1=t1.next;
t2=t2.next;
}
return t1;
}