一、打印两个有序链表的公共部分,给定两个有序链表的头指针head1和head2,打印两个链表的公共部分。
package FaceQuestion.链表; /** * 打印两个有序链表的公共部分 * 链表1的头结点head1,链表2的头结点为head2 * head指针每次移动一步前,都要判断两个链表元素的大小,哪个元素小,哪个指针就走一步,另一个指针不动,如果两个元素相等,打印,并且两个指针都走一步 */ public class PrintCommonPart { /** * 链表节点类 */ public static class Node{ public int value; public Node next; public Node(int data){ this.value=data; } } public static void printCommmonPart(Node head1,Node head2){ System.out.print("公共部分为:"); while (head1!=null&&head2!=null){//判断两个链表都不能为空 if (head1.value<head2.value){//如果head1的值小于head2的值 head1=head1.next;//head1走一步 }else if (head1.value>head2.value){//同理 head2=head2.next; }else {//两者相等 System.out.print(head1.value+" "); //head1和2都走一步 head1=head1.next; head2=head2.next; } } System.out.println(); } }二、判断一个链表是否为回文结构
给定一个链表的头节点head,请判断该链表是否为回文结构。
例如:
1->2->1,返回true。
1->2->2->1,返回true。
15->6->15,返回true。
1->2->3,返回false。
进阶:
如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1)。
三种方法:
1.使用一个栈,将链表元素压入栈,然后不断弹出元素与链表元素比较,需要额外O(N)的空间
2.使用快慢指针,找到链表中点,将链表中点后的部分压入栈中,需要额外O(N/2)的空间
3.使用快慢指针找到链表中点,然后将右边部分反转,再和左边部分比较
package FaceQuestion.链表; import java.util.Stack; /** * 判断链表是否回文 * 1.使用一个栈,将链表元素压入栈,然后不断弹出元素与链表元素比较,需要额外O(N)的空间 * 2.使用快慢指针,找到链表中点,将链表中点后的部分压入栈中,需要额外O(N/2)的空间 * 3.使用快慢指针找到链表中点,然后将右边部分反转,再和左边部分比较 */ public class IsPalindromeList { public static class Node { public int value; public Node next; public Node(int data) { this.value = data; } } /** * 第一种方法,使用一个栈 * @param head * @return */ public boolean isPalindrome1(Node head){ Stack<Node> stack =new Stack<>(); Node cur=head; while (cur!=null){ stack.push(cur); cur=cur.next; } while (head!=null){ if (head.value!=stack.pop().value){ return false; } head=head.next; } return true; } /** * 第二种方法,使用快慢指针,找到链表中点,将链表中点后的部分压入栈中 * @param head * @return */ public boolean isPalindrome2(Node head){ if (head==null||head.next==null){ return true; } Node right=head.next; Node cur=head; while (cur.next!=null&&cur.next.next!=null){ right=right.next; cur=cur.next.next; } Stack<Node> stack=new Stack<>(); while (right!=null){ stack.push(right); right=right.next; } while (!stack.isEmpty()){ if (head.value!=stack.pop().value){ return false; } head=head.next; } return true; } /** * 第三种方法:使用快慢指针找到链表中点,然后将右边部分反转,再和左边部分比较 * @param head * @return */ public boolean isPalindrome3(Node head){ if (head ==null||head.next==null){ return true; } Node n1=head; Node n2=head; while (n2.next!=null&&n2.next.next!=null){ n1=n1.next; n2=n2.next.next; } n2=n1.next; n1.next=null; Node n3=null; while (n2!=null){//中点右边反转 n3=n2.next;//保存下一个节点 n3.next=n1;//反转节点 n1=n2;//n1移动 n2=n3;//n2移动 } n3=n1; n2=head; boolean res=true; while (n1!=null&&n2!=null){ if (n1.value!=n2.value){ res=false; break; } n1=n1.next; n2=n2.next; } //将反转的链表恢复 n1=n3.next; n3.next=null; while (n1!=null){ n2=n1.next; n1.next=n3; n3=n1; n1=n2; } return res; } }
三、将单向链表按某值划分成左边小、中间相等、右边大的形式
package FaceQuestion.链表; /** * 将单向链表按某值划分成左边小、中间相等、右边大的形式 * 1.参照快速排序的方法去进行区域的划分 * 2.提前设置三个区域smaller,equal,bigger,遍历链表,将对应元素添加到对应区域中 */ public class SmallerEqualBigger { public static class Node { public int value; public Node next; public Node(int data) { this.value = data; } } /** * 第一种方法:参照快速排序的方法去进行区域的划分,将链表节点存储到数组中,按照节点的Value进行划分 * @param head * @param pivot * @return */ public static Node listPartition1(Node head, int pivot) { if (head == null) { return head; } //获取链表总长度 Node cur = head; int i = 0; while (cur != null) { i++; cur = cur.next; } //创建一个存储链表节点的数组 Node[] nodeArr = new Node[i]; i = 0; cur = head; //将链表元素存储到数组中 for (i = 0; i != nodeArr.length; i++) { nodeArr[i] = cur; cur = cur.next; } //划分区域 arrPartition(nodeArr, pivot); for (i = 1; i != nodeArr.length; i++) { nodeArr[i - 1].next = nodeArr[i]; } nodeArr[i - 1].next = null; return nodeArr[0]; } /** * 划分区域的方法,参照快排的思想 * @param nodeArr * @param pivot */ public static void arrPartition(Node[] nodeArr, int pivot) { int small = -1; int big = nodeArr.length; int index = 0; while (index != big) { if (nodeArr[index].value < pivot) { swap(nodeArr, ++small, index++); } else if (nodeArr[index].value == pivot) { index++; } else { swap(nodeArr, --big, index); } } } public static void swap(Node[] nodeArr, int a, int b) { Node tmp = nodeArr[a]; nodeArr[a] = nodeArr[b]; nodeArr[b] = tmp; } public Node listPartition2(Node head,int pivot){ Node sH=null;//小于区域的头结点 Node sT=null;//小于区域的尾节点 Node eH=null;//等与区域的头结点 Node eT=null;//等于区域的尾节点 Node bH=null;//大于区域的头节点 Node bT=null;//大于区域的尾节点 Node next=null;//用于保存下一个节点 while (head!=null){ next=head.next; head.next=null; if (head.value<pivot){//当前元素小于划分值 if (sH==null){ sH=head; sT=head; }else { sT.next=head; sT=head; } }else if (head.value==pivot){//当前元素等于划分值 if (eH==null){ eH=head; eT=head; }else { eT.next=head; eT=head; } }else {//当前元素大于划分值 if (bH==null){ bH=head; bT=head; }else { bT.next=head; bT=head; } } head=next; } //连接三个区域 if (sT!=null){ sT.next=eH; eT=eT==null?sT:eT; } if (eT!=null){ eT.next=bH; } return sH!=null?sH:eH!=null?eH:bH; } }
四、链表相交的一系列问题(重点)
在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点head1和head2,这两个链表可能相交,也可能不相交。请实现一个函数,如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null即可。
要求:如果链表1的长度为N,链表2的长度为M,时间复杂度请达到O(N+M),额外空间复杂度请达到O(1)。
首先,需要判断一个链表是否有环,有环的话返回环的第一个节点;这个问题的思路网上一抓一大把,大体思路就是,使用快慢指针,快指针一次走两步,慢指针一次走一步,两个指针一定会在环上的某一个节点相遇(关于这一点的理解,可以参照两个人跑步,跑的快的人和跑的慢的人一定会相遇),当相遇之后,将快指针指向链表头,快慢指针此时同时都直走一步,再次相遇时就是环的第一个节点;
/** * 判断链表是否有环,如果链表有环,那么返回环的第一个节点,如果没有换,直接返回空 * @param head * @return */ public Node getLoopNode(Node head){ if (head==null||head.next==null||head.next.next==null){ return null; } Node n1=head.next;//慢指针 Node n2=head.next.next;//快指针 while (n1!=n2){//当快慢指针相遇是结束循环 if (n2.next==null||n2.next.next==null){ return null; } n2=n2.next.next;//快指针一次走两步 n1=n1.next;//慢指针一次走一步 } //快慢指针相遇,将快指针指向链表头 n2=head; while (n1!=n2){ //快慢指针一次都直走一步,再次相遇时就是环的第一个节点 n1=n1.next; n2=n2.next; } return n1; }
有了getLoopNode方法,我们可以判断一个链表是否有环了,下一步我们需要考虑的就是两个链表是否相交的问题,这时,我们需要分三种情况进行考虑:
1.两个链表都无环
2.两个链表都有环
3.一个有环一个无环
首先我们排除第三种情况,一个有环一个无环是不可能相交的;这个可以直接排除;
所以我们现在对1,2两种情况进行分析;
当两个链表都无环时,我们可以计算两个链表的长度差,然后让长链表的头指针想走完这个长度差,这时两个链表的头结点的对应位置就相等了,然后就可以让两个头结点一起走,当两个指针指向的节点相等时(这里的相等指的是地址相同,也就是同一个节点,并不仅仅是值相等),这个节点就是相交的第一个节点
/** * 两个链表都无环时,判断是否相交 * @param head1 * @param head2 * @return */ public static Node noLoop(Node head1, Node head2) { if (head1 == null || head2 == null) { return null; } Node cur1 = head1; Node cur2 = head2; int n = 0;//两条链表的长度差 while (cur1.next != null) { n++; cur1 = cur1.next; } while (cur2.next != null) { n--; cur2 = cur2.next; } if (cur1 != cur2) { return null; } cur1 = n > 0 ? head1 : head2; cur2 = cur1 == head1 ? head2 : head1; n = Math.abs(n); while (n != 0) { n--; cur1 = cur1.next; } while (cur1 != cur2) { cur1 = cur1.next; cur2 = cur2.next; } return cur1; }
当两个链表都有环时,还要分两种情况:
1.在环上相交
2.不在环上相交
如果是第二种情况,不在环上相交,那么就相当于两条无环链表相交问题,只不过尾节点改为环的起始点;
如果时第一种情况,在环上相交,返回环的起始点就可以了
/** * 两个链表都无环时,判断相交问题 * @param head1 * @param loop1 * @param head2 * @param loop2 * @return */ public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) { Node cur1 = null; Node cur2 = null; if (loop1 == loop2) {//如果不在环上相交 cur1 = head1; cur2 = head2; int n = 0; while (cur1 != loop1) { n++; cur1 = cur1.next; } while (cur2 != loop2) { n--; cur2 = cur2.next; } cur1 = n > 0 ? head1 : head2; cur2 = cur1 == head1 ? head2 : head1; n = Math.abs(n); while (n != 0) { n--; cur1 = cur1.next; } while (cur1 != cur2) { cur1 = cur1.next; cur2 = cur2.next; } return cur1; } else {//在环上相交 cur1 = loop1.next; while (cur1 != loop1) { if (cur1 == loop2) { return loop1; } cur1 = cur1.next; } return null; } }
整体代码如下:
package FaceQuestion.链表; /** * 两个链表相交的一系列问题 */ public class FindFirstIntersectNode { public static class Node { public int value; public Node next; public Node(int data) { this.value = data; } } /** * 判断链表是否有环,如果链表有环,那么返回环的第一个节点,如果没有换,直接返回空 * @param head * @return */ public Node getLoopNode(Node head){ if (head==null||head.next==null||head.next.next==null){ return null; } Node n1=head.next;//慢指针 Node n2=head.next.next;//快指针 while (n1!=n2){//当快慢指针相遇是结束循环 if (n2.next==null||n2.next.next==null){ return null; } n2=n2.next.next;//快指针一次走两步 n1=n1.next;//慢指针一次走一步 } //快慢指针相遇,将快指针指向链表头 n2=head; while (n1!=n2){ //快慢指针一次都直走一步,再次相遇时就是环的第一个节点 n1=n1.next; n2=n2.next; } return n1; } /** * 两个链表都无环时,判断是否相交 * @param head1 * @param head2 * @return */ public static Node noLoop(Node head1, Node head2) { if (head1 == null || head2 == null) { return null; } Node cur1 = head1; Node cur2 = head2; int n = 0;//两条链表的长度差 while (cur1.next != null) { n++; cur1 = cur1.next; } while (cur2.next != null) { n--; cur2 = cur2.next; } if (cur1 != cur2) { return null; } cur1 = n > 0 ? head1 : head2; cur2 = cur1 == head1 ? head2 : head1; n = Math.abs(n); while (n != 0) { n--; cur1 = cur1.next; } while (cur1 != cur2) { cur1 = cur1.next; cur2 = cur2.next; } return cur1; } /** * 两个链表都无环时,判断相交问题 * @param head1 * @param loop1 * @param head2 * @param loop2 * @return */ public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) { Node cur1 = null; Node cur2 = null; if (loop1 == loop2) {//如果不在环上相交 cur1 = head1; cur2 = head2; int n = 0; while (cur1 != loop1) { n++; cur1 = cur1.next; } while (cur2 != loop2) { n--; cur2 = cur2.next; } cur1 = n > 0 ? head1 : head2; cur2 = cur1 == head1 ? head2 : head1; n = Math.abs(n); while (n != 0) { n--; cur1 = cur1.next; } while (cur1 != cur2) { cur1 = cur1.next; cur2 = cur2.next; } return cur1; } else {//在环上相交 cur1 = loop1.next; while (cur1 != loop1) { if (cur1 == loop2) { return loop1; } cur1 = cur1.next; } return null; } } }