本篇包括:
- [最快时间内找到单链表的倒数第K个节点]
- [最快时间内删除单链表的倒数第K个节点]
- [判断单链表是否有环,环的入口,环的长度]
- [判断两个单链表是否相交,交点?]
- [单链表的逆置(头插法)]
- [单链表的反转]
- [ O(1)时间内删除单链表的节点]
- [合并两个有序的单链表为一个单链表]
关于单链表及结点类的定义、以及常用到的添加、删除、打印等方法,在上一篇博客中进行了简单的书写,详情点击:单链表常用方法
1. 最快时间内找到单链表的倒数第K个节点
要找到单链表的一个结点至少要遍历链表一遍,时间复杂度O(n)
这里假设K = 3;要找到单链表的倒数第三个结点,首先定义cursor1 和cursor2,让cursor1先遍历数组,走k-1步,然后cursor1 和cursor2一样的步进继续遍历链表,当cursor1遍历到链表的尾结点时,cursor1 刚好到链表的倒数第K个结点,如图:
编码:
//最短时间内找到单链表的倒数第K个结点
public T fidLastK(int k) {
if (!checkRange(k)) {
throw new UnsupportedOperationException("k不合法");
}
Entry<T> cursor1 = this.head;
Entry<T> cursor2 = this.head;
while (k-1 > 0) {
cursor1 = cursor1.next;
k--;
}
while (cursor1.next != null) {
cursor1 = cursor1.next;
cursor2 = cursor2.next;
}
return cursor2.data;
}
2. 最快时间内删除单链表的倒数第K个节点
要删除倒数第k个结点,就需要找到该节点的前驱,即倒数第k+1个结点,类似于上面寻找倒数第k个结点的方法,这次需要找的是倒数第k+1个结点;
相同的道理,定义cursor1 和cursor2,让cursor1先遍历数组,这次需要走k步,然后cursor1 和cursor2一样的步进继续遍历链表,当cursor1遍历到链表的尾结点时,cursor1 刚好到链表的倒数第k+1个结点,如图:
让cursor2.next = cursor.next.next就可以删除倒数第k个结点了;
编码:
public void removeLastK(int k) {
if (!checkRange(k)) {
throw new UnsupportedOperationException("k不合法");
}
Entry<T> cursor1 = this.head;
Entry<T> cursor2 = this.head;
while (k > 0) {
cursor1 = cursor1.next;
k--;
}
while (cursor1.next != null) {
cursor1 = cursor1.next;
cursor2 = cursor2.next;
}
cursor2.next = cursor2.next.next;
}
3. 判断单链表是否有环
//制造一个环
public void creatLoop() {
Entry cur = this.head;
while (cur.next != null) {
cur = cur.next; // 找到单链表的尾结点;
}
cur.next = this.head.next.next.next;
//这里可以随意.next,只要不要超过尾结点就ok;
}
如图,尾结点next指向了头结点后的第三个数据节点;
//判断单链表是否有环
public boolean isLoop() {
Entry<T> fast = this.head;
Entry<T> slow = this.head;
while (fast != null && fast.next != null) {
fast = fast.next.next; //fast一次遍历两个结点
slow = slow.next;
if (fast == slow) { //若fast和slow相遇,说明有环
return true;
}
}
return false;
}
环的入口
在上面提到的判断单链表是否有环时,当fast和slow相遇则说明有环,此时相遇的交点即是环的入口
public int EnterEntry() {
Entry fast = this.head;
Entry slow = this.head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast) {
break;
}
}
return fast.data;
}
环的长度
//环的长度
public int loopLength() {
Entry fast = this.head;
Entry slow = this.head;
boolean flg = false;
int len = 0;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast && flg == false) {
flg = true;
}
if (slow == fast && flg == true) {
break;
}
if (flg == true) {
len++;
}
}
return len;
}
4. 判断两个单链表是否相交
首先制造两个相交的单链表:
为了画图方便这里用一样的链表复制了下,看指向就行不用纠结是一个链表啦
//制造两个相交的单链表
public static void creatCut(TestLink link1,TestLink link2) {
TestLink.Entry head1 = link1.getHead();
TestLink.Entry head2 = link2.getHead();
head1.next.next = head2.next.next.next.next;
}
此时link1的长度为4,link2的长度为5,head1指向link2,head2指向link1,myLen等于1,先让head1遍历myLen个结点,然后两个指针一起遍历链表时最终会相遇;
public static boolean isCut(TestLink link1,TestLink link2) {
TestLink.Entry head1 = link1.getHead(); //用head1指向长的单链表
TestLink.Entry head2 = link2.getHead(); //用head2指向短的单链表
int len1 = link1.getLength(); //link1的链表长度
int len2 = link2.getLength(); //link2的链表长度
int myLen = len1 - len2; //两个链表长度之差
if (myLen < 0) { //若myLen < 0则说明head1指向的是短链表,此时交换head1与head2的指向
head1 = link2.getHead();
head2 = link1.getHead();
myLen = len2 - len1;
}
int count = 0;
while (count < myLen) { //让指向长链表的head1先遍历myLen个结点
head1 = head1.next;
count++;
}
//此时link1剩余的链表长度与link2的长度相等
//同时开始遍历链表,若相交则head1与head2一定会相遇
while (head1.next != null && head2.next != null) {
head1 = head1.next;
head2 = head2.next;
if (head1 == head2) {
return true;
}
}
return false;
}
测试及结果:
public static void main(String[] args) {
TestLink link1 = new TestLink();
TestLink link2 = new TestLink();
for (int i = 0;i < 5;i++) {
link1.insertHead(i);
}
for (int i = 2;i < 7;i++) {
link2.insertTail(i);
}
creatCut(link1,link2);
System.out.println(isCut(link1,link2));
}
交点?
在上面的描述中可以看出,Link1 == link2时说明两个链表相交,此时link1/link2就是两个链表的交点;
5. 单链表的逆置(头插法)
定义cursor为第一个数据节点,将头结点和数据节点断开,curNext是cursor的后继节点,然后用头插法将cursor插入head结点的后面,再将curNext赋给cursor以此类推,直到将之前链表的尾结点插入新的链表中,即完成了链表的逆置;
编码:
public void reverseLink() {
Entry cur = this.head.next;
Entry curNext = null;
this.head.next = null;
while (cur != null) {
curNext = cur.next;
cur.next = this.head.next;
this.head.next = cur;
cur = curNext;
}
}
6. 单链表的反转
//单链表的反转
public Entry reverseLink1() {
Entry cur = this.head;
Entry curNext = null;
Entry pre = null;
Entry reverHead = null; //反转后的头节点
while (cur != null) {
curNext = cur.next;
if (curNext == null) {
reverHead = cur;
}
cur.next = pre;
pre = cur;
cur = curNext;
}
return reverHead;
}
7. O(1)时间内删除单链表的节点
//O(1)时间内删除单链表的节点
public void deleteEntry(Entry entryStart,Entry entryDelete) {
Entry entryDeleteNext;
if (entryDelete.next != null) { //如果删除的节点不是尾节点
entryDeleteNext = entryDelete.next;
entryDelete.data = entryDeleteNext.data;
entryDelete.next = entryDeleteNext.next;
entryDeleteNext.next = null;
} else {
while (entryStart.next != entryDelete) {
if (entryStart.next.next != null) {
entryStart = entryStart.next;
}
entryStart.next = null;
entryDelete = null;
}
}
}
8. 合并两个有序的单链表为一个单链表
默认两个链表的数据都是从小到大排列;
emmmmm不晓得注释这样写小伙伴能更好理解嘛
//合并两个有序单链表
public static TestLink.Entry mergeLink(TestLink link1, TestLink link2) {
TestLink.Entry newHead = null; // 定义一个空的头结点
TestLink.Entry p1 = link1.getHead(); //p1指向link1的头结点
TestLink.Entry p2 = link2.getHead();//p2指向link2的头结点
//判断两个链表第一个数据节点值的大小
//合并后的链表的头结点 指向 第一个数据节点值 较小的链表的头结点
if (p1.next.data < p2.next.data) {
newHead = link1.getHead();
} else {
newHead = link2.getHead();
}
//让P1,p2指向各自链表的第一个数据节点
p1 = p1.next;
p2 = p2.next;
//将头结点newHead作为该函数的返回值返回,此处重新给一个等于newHead的头结点
TestLink.Entry tmpHead = newHead;
while (p1 != null && p2 != null) { //在两个链表都没有遍历完时
//让temHead指向小的结点,p继续向后遍历原来的结点
//然后tmpHead指向新链表的下一个结点,此时tmpHead为新链表的尾结点
if (p1.data < p2.data) {
tmpHead.next = p1;
p1 = p1.next;
} else {
tmpHead.next = p2;
p2 = p2.next;
}
tmpHead = tmpHead.next;
}
//退出循环则说明有一个链表已经遍历完成,此时将另一个没遍历完的链表剩余部分跟新链表的尾结点连接起来
if (p1 == null) {
tmpHead.next = p2;
}
if (p2 == null) {
tmpHead.next = p1;
}
return newHead; //返回合并后新链表的头结点
}
调用上面的函数会返回一个TestLink.Entry类型的头结点,此时需要重新写一个打印的函数;
public static void showMerge(TestLink.Entry entry) {
TestLink.Entry cur = entry.next;
while (cur != null) {
System.out.print(cur.data + " ");
cur = cur.next;
}
System.out.println();
}
测试及结果:
public static void main(String[] args) {
TestLink link1 = new TestLink();
TestLink link2 = new TestLink();
for (int i = 0;i < 5;i++) {
link1.insertTail(i);
}
for (int i = 2;i < 7;i++) {
link2.insertTail(i);
}
link1.show();
link2.show();
showMerge(mergeLink(link1,link2));
}