1.求链表中倒数第k个结点
分析:可以使用两个指针pre和post,同时指向正向数第一个结点,post指针先走,走到正数第k+1个(即是的post的节点序号-pre的节点序号=k),然后pre和post一起走,直到post走到链表结尾。
代码
Node pre = list.head;
Node post = pre;
int count = 0;
while(count<k && post!=null)
{
post = post.next;
count++;
}
while(post!=null)
{
post = post.next;
pre = pre.next;
}
System.out.println(pre.value);
2.求链表中的中间节点
分析:使用两个指针pre和post,同时指向正向数第一个结点。pre和post指针同时走,不过post一次走两步,pre一次走一步,直至post走到链表结尾。
代码
Node pre = list.head;
Node post = pre;
while(post!=null)
{
post = post.next;
if(post==null)
break;
else
post = post.next;
pre = pre.next;
}
System.out.println(pre.value);
由此,可以推广到求链表的第1/3、1/4.....个结点
3.判断链表中是否有环
分析:如果链表中有环,那么遍历链表就会无限循环下去。使用两个指针p、q,p一次走两步,q一次走一步,如果有环,那么p、q总会相遇
代码
Node p = list1.head;
Node q = p;
boolean hasCircle = false;
while(p!=null)
{
p = p.next;
if(p==null)
break;
p = p.next;
q = q.next;
if(p==q)
{
hasCircle = true;
break;
}
}
System.out.println(hasCircle);
3.判断两个链表中是否有公共结点
分析:如果两个链表中有公共结点,那么遍历链表必然呈“Y型”,而不可能是“X”型。最差情况下是最后一个结点为公共结点,所以第一次先遍历第一个链表,保存最后一个结点,再遍历第二个链表,保存最后一个结点。最后比较两个链表的最后一个结点是否相同,相同就有公共结点,否则没有
代码
Node p = list1.head;
Node q = list2.head;
while(p.next!=null)
{
p = p.next;
}
while(q.next!=null)
{
q = q.next;
}
if(q != p)
System.out.println("没有相交的点");
4.求两个链表中第一个公共结点
分析:如果两个链表中有公共结点,那么两个链表分开看必然是结尾对齐的,所以,如果两个链表长度不同,以结尾对齐,长的链表多出来的那部分必然不可能是公共结点,所以先将长链表的指针移动到长度相同的位置,然后再分别比较两个链表的结点
代码
Node p = list1.head;
Node q = list2.head;
while(p.next!=null)
{
p = p.next;
}
while(q.next!=null)
{
q = q.next;
}
if(q != p)
System.out.println("没有相交的点");
int len1 = list1.length();
int len2 = list2.length();
int count = len1>len2?len1-len2:len2-len1;
p = len1>len2?list1.head:list2.head;
q = p==list1.head?list2.head:list1.head;
while(count>0)
{
p = p.next;
count--;
}
while(p!=null)
{
if(p==q)
break;
else
{
p = p.next;
q = q.next;
}
}
//p就为公共结点
System.out.println(p.value);
5.含环链表中入环第一个结点
分析:先判断是否有环,如果有环,则通过环中的一个结点(通常为判断出有环的那个结点)处断开,那么这个点和链表起始结点就形成了两个链表,则问题就转化为求两个链表的第一个公共结点的问题
代码:综合前面代码即可
6.给出链表头结点和p,删除p指向的结点,要求时间复杂度为O(1)
分析:通常情况下都是通过p的前一个结点指向p的下一个结点来删除,但是要求时间复杂度为O(1),所以,可以使用后一个结点的值赋给p指向的结点,再删除p的下一个结点即可
代码:
p.value = p.next.value;
p.next = p.next.next;
7.从尾到头打印链表中数据
分析:对于这种顺序颠倒的问题,就应该使用栈或者递归
代码:
Node p = list1.head;
Stack<Integer> stack = new Stack<Integer>();
while(p!=null)
{
stack.push(p.value);
p = p.next;
}
while(!stack.isEmpty())
System.out.print(stack.pop()+" ");
使用递归
private static void reverse(Node p)
{
if(p==null)
return;
reverse(p.next);
System.out.print(p.value+" ");
}
8.链表反转
分析:从第二个结点开始,将结点直接变为头指针指向的对象(不含头结点的链表)
代码:
Node p = list1.head;
Node first = p;
Node q = list2.head;
if(p.next==null)
return;
p = p.next;
while(p!=null)
{
Node cur = p;
p = p.next;
cur.next = list1.head;
list1.head = cur;
}
first.next = null;
9.链表中环的长度
分析:先判断是否有环,如果有环,则从判断出有环的那个节点处开始,只移动一个指针一次一个节点,下一次两个指针再相同时就是环的长度了
10.O(1)时间复杂度删除链表中指定节点p
分析:一般情况下均为先获得p的前驱节点q,然后q->next = p->next。这里因为任意节点结构都是相同的,不同的只是其中的数值,所以可以将p后继节点的值赋值给p,然后删除后继节点即可
代码:
Node nextNode = p.next;
//p指向的不是最后一个节点
if(nextNode != null)
{
p.value = nextNode.value;
p.next = nextNode.next;
}
//p指向最后一个节点
else
p.next = nextNode.next;
11.调整数组,使得所有奇数排在所有偶数之前(数组顺序可以打乱)
分析:使用两个指针,分别指向数组的开始和结尾,类似快排的调整,直至两个指针相遇
代码:
int[] a = {1,2,3,4,5};
int begin = 0;
int end = a.length-1;
for(int x:a)
System.out.print(x+" ");
System.out.println();
while(begin<end)
{
while(a[begin]%2!=0 && begin<end)
begin++;
while(a[end]%2==0 && begin<end)
end--;
if(a[begin]%2==0 && begin<end)
{
int temp = a[begin];
a[begin] = a[end];
a[end] = temp;
}
}
12.已知集合A和B的元素分别用不含头结点的单链表存储,求A-B,并将结果保存在A中
例子:集合A={5,10,20,15,25,30},集合B={5,15,35,25},完成后A={10,20,30}
分析:即求在A且不在B中的元素,那么就循环B中所有元素,每个都在A中查找,如果找到则在A中删除即可。(这里使用O(1)删除链表的方法,除了最后一个节点,其余的均将后面节点的值幅值过来即可)
代码:
定义节点类型
private class Node
{
private int num;
private Node next;
public Node(int num)
{
super();
this.num = num;
this.next = null;
}
}
构造链表并求差集
Node na1 = new Node(5);
Node na2 = new Node(10);
Node na3 = new Node(20);
Node na4 = new Node(15);
Node na5 = new Node(25);
Node na6 = new Node(30);
na1.next = na2;
na2.next = na3;
na3.next = na4;
na4.next = na5;
na5.next = na6;
Node nb1 = new Node(5);
Node nb2 = new Node(15);
Node nb3 = new Node(35);
Node nb4 = new Node(25);
nb1.next = nb2;
nb2.next = nb3;
nb3.next = nb4;
for(Node pb=nb1; pb!=null; pb=pb.next)
{
boolean isEnd = false;//用来判断是不是A中最后一个元素
for(Node pa = na1; pa!=null; pa=pa.next)
{
if(pa.num == pb.num)
{
if(pa.next != null)//如果不是最后一个元素,那么直接O(1)删除
{
pa.num = pa.next.num;
pa.next = pa.next.next;
}
else
isEnd = true;//是A最后一个元素
break;
}
}
if(isEnd)//是A最后一个元素
{
Node pre = na1;
for(Node pa = na1; pa.next!=null; pa=pa.next)
{
pre = pa;
}
pre.next = null;
}
}
Node p = na1;
while(p!=null)
{
System.out.print(p.num+" ");
p = p.next;
}
结果为:
10 20 30
13.合并两个非递增链表
分析:首先定义新链表头指针,先确定头指针指向谁,然后使用一个cur指针,指向新链表的当前节点,依次更新即可
代码:
@Test
public void test()
{
LinkNode head1 = null;
LinkNode node1 = new LinkNode(1);
LinkNode node2 = new LinkNode(3);
LinkNode node3 = new LinkNode(5);
LinkNode node4 = new LinkNode(7);
LinkNode node5 = new LinkNode(9);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
head1 = node1;
LinkNode head2 = null;
LinkNode node6 = new LinkNode(2);
LinkNode node7 = new LinkNode(4);
LinkNode node8 = new LinkNode(6);
node6.next = node7;
node7.next = node8;
head2 = node6;
LinkNode newHead = null; //新链表的头结点
LinkNode cur = null; //新链表的当前节点
LinkNode pa = head1; //pa指向链表1的当前节点
LinkNode pb = head2; //pb指向链表2的当前节点
if(pa.val < pb.val) //首先先确定新链表头结点的指向
{
newHead = pa;
cur = pa;
pa = pa.next;
}
else
{
newHead = pb;
cur = pb;
pb = pb.next;
}
while(pa!=null && pb!=null) //依次递归,选择小的加入新链表
{
if(pa.val < pb.val)
{
cur.next = pa;
cur = pa;
pa = pa.next;
}
else
{
cur.next = pb;
cur = pb;
pb = pb.next;
}
}
if(pa!=null)
cur.next = pa;
if(pb!=null)
cur.next = pb;
while(newHead!=null)
{
System.out.print(newHead.val+" ");
newHead = newHead.next;
}
}