以下习题中的单链表以不带头节点的单链表为例。
1.输出倒数第k个节点的值
思路: 统计链表节点的个数length,让p从头节点遍历走length-k步,即得到倒数第k个节点的位置
- 代码如下:
public int getLength(){
int count=0;
for(Entry p=headEntry;p!=null;p=p.getNext()){
count++;
}
return count;
}
public static <E>E findEntry(Link<E> link, int k) {
if(k>link.getLength()|| k<0){
return null;
}
int length=link.getLength();
int len=length-k;
Entry<E> p=link.headEntry;
for(;--len>=0;p=p.getNext()){
;
}
return p.getValue();
}
2.单链表逆置
思路: 定义三个变量p、q、s,其中p、q用来逆置,s用来标记节点;
①将q的next域指向p的地址
②p走到q的位置③q走到原来s的位置④s后移
继续以上步骤,直到q != null或p.next != null 结束循环,最后更新头尾节点
- 代码如下:
public static <E extends Comparable<E>>void reverse(Link<E> link){
if(link.getLength()==1){
return;
}
Entry<E> p=link.headEntry,q=p.getNext(),s=q.getNext();
while(q!=null){
q.setNext(p);
p=q;
q=s;
if (s != null) {
s=s.getNext();
}
}
link.tailEntry=link.headEntry;//原来的头部是新尾部
link.headEntry=p;//p为新头部
link.tailEntry.setNext(null);//将新尾部节点next域置为空
}
测试1,2题:
public static void main(String[] args){
Link<Integer> link1 = new Link<>();
Link<Integer> link2 = new Link<>();
//构建link1
link1.addHead(3);//3
link1.addHead(9);//9 3
link1.addTail(5);//9 3 5
link1.addHead(16);//16 9 3 5
//构建link2
link2.addHead(2);//2
link2.addHead(5);//5 2
link2.addTail(4);//5 2 4
link2.addTail(1);//5 2 4 1
link2.addTail(7);//5 2 4 1 7
link2.addHead(9);//9 5 2 4 1 7
link2.addTail(8);//9 5 2 4 1 7 8
System.out.println("链表1:");
link1.show();
System.out.println();
System.out.println("链表2:");
link2.show();
System.out.println();
//第1题
System.out.println("倒数第k个节点为:"+link2.findEntry(link2,4));
//第2题
System.out.println("逆置链表后");
Link.reverse(link1);//16 9 3 5 --> 5 3 9 16
link1.show();
}
运行结果:
3.若两链表相交,输出相交节点
思路: ①分别统计两个链表的节点个数length1,length2
②通过比较length,得到长短链表LongLink、ShortLink,让长链表先走差值步
③此时让LongLink、ShortLink同时出发,当两个节点的地址相同时,找到相交节点
注意:此时需要设计一下两个链表让其相交:
如:链表一:5 2 4 1 7,链表二:3 5,假设我们想要实现如图所示链表的相交,首先需要找到链表二中5所在节点位置,其次将其next域指向链表一中1所在节点的地址
- 代码如下:
//找节点
public Entry getEntry(E value){
for(Entry<E> p=headEntry;p!=null;p=p.getNext()){
if(p.getValue().compareTo(value)==0){
return p;
}
}
return null;
}
public static <E>E getMeetEntry(Link<E> link1, Link<E> link2){
//1.统计链表长度
int length1=link1.getLength();
int length2=link2.getLength();
int difference=Math.abs(length1-length2); //获得差值
//2.长链表先走差值步
Entry<E> longLink=length1>length2?link1.headEntry:link2.headEntry;
Entry<E> shortLink=length1<=length2?link1.headEntry:link2.headEntry;
for(;--difference>=0;longLink=longLink.getNext()){
;
}
//3.长短链表同时向后走
for(;longLink!=null;longLink=longLink.getNext(),shortLink=shortLink.getNext()){
if(longLink==shortLink){
return longLink.getValue();
}
}
return null;
}
- 测试第3题:
public static void main(String[] args) {
Link<Integer> link1 = new Link<>();
Link<Integer> link2 = new Link<>();
//构建link1
link1.addHead(3);
link1.addHead(9);
link1.addTail(5);
link1.addHead(16);//16 9 3 5
//构建link2
link2.addHead(2);
link2.addHead(5);
link2.addTail(4);
link2.addTail(1);
link2.addTail(7);
link2.addHead(9);
link2.addTail(8);//9 5 2 4 1 7 8
System.out.println("链表1:");
link2.show();
System.out.println();
System.out.println("链表2:");
link1.show();
System.out.println();
//获取两链表的相交节点
Entry<Integer> meetEntry = link2.getEntry(4);
if(meetEntry==null)
return;
//获取相交节点前一个节点
Entry<Integer> linkList=link1.getEntry(5);
//构建相交链表
linkList.setNext(meetEntry);
System.out.println("两链表相交入口点为:"+ Link.getMeetEntry(link2,link1));
System.out.println("构建得到的新链表:");
link1.show();
}
运行结果:
4.判断单链表是否有环
思路: 快慢引用
定义fast、slow两个变量,slow走一步,fast走两步,若有环,则fast与slow必相遇
- 代码如下:
public Entry<E> isCircle(){
Entry<E> fast=headEntry;
Entry<E> slow=headEntry;
do{ //为使循环能顺利进入采用do...while循环语句
if(fast==null || fast.getNext()==null){
return null;
}
slow=slow.getNext();
fast=fast.getNext().getNext();
}while(slow!=fast);
return fast;
}
5.若有环,输出环的入口节点
思路: 若有环,则必有相遇点
若将该环链从入口点切开,起始点到入口点的距离为x,入口点到相遇点的距离为y,相遇点到入口点的距离为z;
则根据 “快慢引用原理” 可知:fast走过的距离=2×slow走过的距离,则x+y+z+y=2(x+y),推导得:x=z;即从起始点到入口点的距离=从相遇点到入口点的距离。故在起始点定义变量p指向头节点,在相遇点定义q,同时出发,直到p、q相遇;
- 代码如下:
public E getCircleMeetEntry(){
Entry<E> meet=isCircle(); //获得相遇节点
if(meet==null){
return null;
}
Entry<E> p=headEntry;
Entry<E> q=meet;
while(p!=q){
p=p.getNext();
q=q.getNext();
}
return p.getValue();
}
测试4,5题:
public static void main(String[] args) {
Link<Integer> link2=new Link<Integer>();
link2.addHead(2);//2
link2.addHead(5);//5 2
link2.addTail(4);//5 2 4
link2.addTail(1);//5 2 4 1
link2.addTail(7);//5 2 4 1 7
//构建环
Entry<Integer> m=link2.getEntry(7);
Entry<Integer> n=link2.getEntry(2);
m.setNext(n);
System.out.println("入口点为:"+link2.getCircleMeetEntry());
}
运行结果:
6.两个有序链表合并为一个有序链表
思路: ①比较两个链表头节点p1,p2的大小,用该节点标记新链表尾部(假设找到的较小节点为p1)
②p1后移,接着比较p1与p2的大小,将值小的链接到p1尾部
③循环上两步,直到p1或p2走为空,停止循环
④更新尾部节点
- 代码如下:
public Entry<E> mergeLink(Link<E> link){
if(link == null || link.headEntry==null)
return this.headEntry;
Entry<E> p1 = this.headEntry;//p1遍历当前链表
Entry<E> p2 = link.headEntry;//p2遍历link链表
//新链表头:
Entry<E> newHeadEntry = p1.getValue().compareTo(p2.getValue())>0 ? p2 : p1;
Entry<E> tailNewEntry = newHeadEntry;//标记新链表尾部
if(newHeadEntry == p1){
p1 = p1.getNext();
}else{
p2 = p2.getNext();
}
//比较p1 和 p2节点所对的值,值小的连接到新链表尾部
while(p1!=null && p2!=null){
if(p1.getValue().compareTo(p2.getValue()) < 0){
tailNewEntry.setNext(p1);
tailNewEntry = p1;//p1节点作为新链表尾部
p1 = p1.getNext();
}else{
tailNewEntry.setNext(p2);
tailNewEntry = p2;//p2节点作为新链表尾部
p2 = p2.getNext();
}
}
//p1走完
if(p1 == null) {
tailNewEntry.setNext(p2);
}
//p2走完
if(p2 == null) {
tailNewEntry.setNext(p1);
}
return newHeadEntry;
}
测试:
public static void main(String[] args) {
Link<Integer> link1=new Link();
Link<Integer> link2=new Link();
link1.addTail(1);
link1.addTail(3);
link1.addTail(8);
link1.addTail(12);
link1.addTail(13);
link1.addTail(16);
link2.addTail(2);
link2.addTail(5);
link2.addTail(6);
link2.addTail(14);
Entry<Integer> f=link1.mergeLink(link2);
for(;f!=null;f=f.getNext()){
System.out.print(f.getValue()+" ");
}
}
运行结果:
7.复杂链表的拷贝问题
首先明确什么是复杂链表:
复杂链表区别于普通单链表和双向链表。
所谓复杂链表,即每个节点中包含节点值以及两个指针:
其中next指针指向下一个节点,另一个特殊指针random随机指向任意一个节点
形如:(其中random指针我随意指的)
思路:
1.复制原链表,在原链表的每个节点后复制该节点。比如:原链表为:A-B-C,复制后为:A-A’-B-B’-C-C’
2.设置random指针的指向。复制节点的random指针指向原节点random指针的下一个节点
由上图可知,B’.random=B.random.next
3.拆分链表
- 代码如下:
public class CopyComplexListTest<T> {
static class Node<E>{
E value;
Node next;
Node random;
public Node() {
}
public Node(E value) {
this.value = value;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("value = " + value);
sb.append(", next = " + (next == null ? "null" : next.value));
sb.append(", random = " + (random == null ? "null" : random.value));
return sb.toString();
}
}
//复制原链表
public void copyList(Node<T> head){
Node<T> node = head;
while(node != null){
Node copyNode = new Node();
copyNode.value = node.value;
copyNode.next = node.next;
copyNode.random = null;
node.next = copyNode;
node = copyNode.next;
}
}
//设置random指针
public void setRandom(Node<T> head){
Node<T> node = head;
while(node != null){
Node<T> copyNode = node.next;
if(node.random != null){
copyNode.random = node.random.next;
}
node = copyNode.next;
}
}
//拆分链表
public Node<T> disconnectList(Node<T> head){
Node<T> node = head;
Node<T> copyHead = null;
Node<T> copyNode = null;
if(node != null){
copyHead = node.next;
copyNode = node.next;
node.next = copyNode.next;
node = node.next;
}
while(node != null){
copyNode.next = node.next;
copyNode = copyNode.next;
node.next = copyNode.next;
node = node.next;
}
return copyHead;
}
public Node<T> copy(Node<T> head){
copyList(head);
setRandom(head);
return disconnectList(head);
}
public static void main(String[] args) {
CopyComplexListTest<Integer> test = new CopyComplexListTest<>();
//构建复杂链表
Node<Integer> head = new Node<>(1);
Node<Integer> node2 = new Node<>(2);
Node<Integer> node3 = new Node<>(3);
Node<Integer> node4 = new Node<>(4);
Node<Integer> node5 = new Node<>(5);
head.next = node2;
head.random = node3;
node2.next = node3;
node2.random = node5;
node3.next = node4;
node4.next = node5;
node4.random = node2;
Node<Integer> copyHead = test.copy(head);
Node<Integer> node = copyHead;
for(;node!=null;node=node.next){
System.out.println(node);
}
}
}
运行结果: