链表相关的一些题目
将单向链表按某值划分成左边小、中间相等、右边大的形式:
【题目】
给定一个单向链表的头节点head,节点的值类型是整型,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点。除这个要求外,对调整后的节点顺序没有更多的要求。例如:链表9->0->4->5->1,pivot=3。调整后链表可以是1->0->4->9->5,也可以是0->1->9->5->4。总之,满足左部分都是小于3的节点,中间部分都是等于3的节点(本例中这个部分为空),右部分都是大于3的节点即可。对某部分内部的节点顺序不做要求。
【思路】
遍历链表,将链表按pivot值的大小划分成三个链表,分别为small表、equal表、big表,存储小于pivot的元素,等于pivot的元素,大于pivot的元素。划分完后将三个链表连接起来,返回头指针即可。
【Code】
public static class Node
{
public int value;
public Node next;
public Node(int value)
{
this.value = value;
}
}
public static Node SmallEqualBig(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;
//将原链表按pivot的值进行划分,成三个链表
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;
}
//将三个链表从小中大进行连接
//small连接equal表
if(sT!=null)
{
sT.next = eH;
eT = eT == null? sT:eT;
}
//equal表连接big表
if(eT!=null)
{
eT.next = bH;
}
//返回头指针
return sH!=null?sH:eH!=null?eH:bH;
}
复制含有随机指针节点的链表
【题目】
一种特殊的链表节点类描述如下:
public class Node {
public int value;
public Node next;
public Node rand;
public Node(int data) {
this.value = data;
}
}
Node类中的value是节点值,next指针和正常单链表中next指针的意义一样,都指向下一个节点,rand指针是Node类中新增的指针,这个指针可能指向链表中的任意一个节点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表中所有结构的复制,并返回复制的新链表的头节点。
【思路】
使用哈希表,为每个原节点都新建一个新节点作为映射,遍历链表,对每个节点n,在哈希表中找其next指针和rand指针所对应的新节点,将n所对应的新节点的next指针和rand指针替换为上述在哈希表中找到的指针,往下遍历,即可完成深度拷贝任务。哈希表的作用就是对老链表和新链表的每个节点建立联系。
【Code】
public static Node CopyListRandom(Node head)
{
//使用哈希表的方法,空间复杂度为O(N)
HashMap<Node, Node> map = new HashMap<Node,Node>();
Node curNode = head;
//建立映射
while(curNode!=null)
{
map.put(curNode, new Node(curNode.value));
curNode = curNode.next;
}
curNode = head;
while(curNode!=null)
{
//在哈希表中去找next指针和rand指针指向的节点所对应的拷贝节点,并将其赋给当前指针的拷贝节点
map.get(curNode).next = map.get(curNode.next);
map.get(curNode).rand = map.get(curNode.rand);
curNode=curNode.next;
}
return map.get(head);
}
【进阶】不使用额外的数据结构,只用有限几个变量,且在时间复杂度为O(N)内完成原问题要实现的函数。
【思路】
为每个节点产生一个新的替换节点,该节点即为上述哈希表中老节点对应的映射节点,是同种概念,然后将老节点的next指针指向新的节点,新节点的next指针再指向原本老节点的next指针所指向的节点,简单来说就是新建一个节点插入在老节点之后。
例如:1->2->4->3 为每个节点构造新的替换节点后,链表变为:1->1’->2->2’->4->4’->3->3’
第二步,拷贝rand指针。当1的rand指针指向2时,2的next指针指向的是2的拷贝2’,此时,将1‘的rand指针等于2的next指针,即1’->rand=1->rand->next,即可拷贝rand指针,依次往下即可拷贝全部节点的rand指针。
第三步。分离新链表和老链表(将1next指回2,1’next指回2’)
【Code】
public static Node copyListRandomByBetter(Node heaNode) {
//时间复杂度为O(N) 空间复杂度为O(1)
if(heaNode == null)
return null;
Node curNode = heaNode;
Node nextNode = null;
//第一步:拷贝节点并插入在每个老节点之后
while(curNode!=null)
{
nextNode = curNode.next;
curNode.next = new Node(curNode.value);
curNode.next.next = nextNode;
curNode = nextNode;
}
curNode = heaNode;
Node curCopy = null;
//第二步:拷贝每个老节点的rand指针
while(curNode!=null)
{
nextNode = curNode.next.next;
curCopy = curNode.next;
curCopy.rand = curNode.rand!=null? curNode.rand.next : null;
curNode = nextNode;
}
//第三步:分离链表中的新老节点
curNode = heaNode;
Node res = heaNode.next;
while(curNode!=null)
{
nextNode = curNode.next.next;
curCopy = curNode.next;
curNode.next = nextNode;
curCopy.next = nextNode!=null? nextNode:null;
curNode = nextNode;
}
return res;
}
【题目】
在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点head1和head2,这两个链表可能相交,也可能不相交。请实现一个函数,如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null即可。
要求:如果链表1的长度为N,链表2的长度为M,时间复杂度请达到O(N+M),额外空间复杂度请达到O(1)。
【提示】
当一个链表有环时,设定快指针和慢指针,快指针一次走两步,慢指针一次走一步,他们肯定会存在相遇的情况,且两指针指向的内存地址相同,此时再将快指针回到表头,一次走一步,慢指针在当前位置继续一次走一步,当俩指针相遇的地点即为入环的第一个节点。这是一个数学结论。
【思路1】
使用哈希set,将head1的所有节点放入,遍历head2的节点,在set中查找是否已经存在,若当前节点已在set中存在,则当前节点是相交的第一个节点。额外空间复杂度是n。不满足题意对空间复杂度的要求,故不出Code。
【思路2】
当两链表都无环时(用提示中的技术判断):
遍历两链表,获得两链表的长度和两个表尾节点,若两个表尾节点不相等,两链表绝不可能相交,因为是单向链表,只有一个next指针。
当表尾节点相同时,观察链表长度,获得长度差值len,让表长度较大一方先走len步,再让两表同时往下移动,当两表指针同时指向相同的内存空间时,此位置为相交的第一个节点。
当两链表 一个有环一个无环时,不可能相交。
当两链表都有环时
存在三种情况,当对两链表使用技术判别时返回的入环节点相同时,为情况2。不相同时为情况1或者3。不相同时,对入环节点1,继续往下走的过程中判别是否与节点2相等,如果相等即为情况3,不存在相等时为情况1。
情况1:不相交,各自成环,返回null
情况2:相交,相交点在环之前。此时将技术判别返回的入环节点作为中止节点,此时又是一个无环相交问题,复用上述代码即可。
情况3:相交,相交点在环中。两个入环节点都是相交节点,都可返回。
【Code】
public static Node getIntersectNode(Node head1,Node head2) {
if(head1==null || head2==null)
return null;
Node loop1 = getLoopNode(head1);
Node loop2 = getLoopNode(head2);
if(loop1==null && loop2 == null)
return NoLoopList(loop1,loop2);
if(loop1!=null && loop2!=null)
return BoothloopList(head1,head2,loop1, loop2);
return null;
}
public static Node getLoopNode(Node head) {
//满足下列条件时不可能存在环
if(head == null ||head.next == null || head.next.next == null)
return null;
Node fast = head.next.next;
Node slow = head.next;
//如果链表有环,按此规则他们一定会相遇,如无环则fast会走向null
while(fast!=slow)
{
if(fast.next==null||fast.next.next==null)
return null;
slow = slow.next;
fast = fast.next.next;
}
//判断fast
fast = head;
while(fast!=slow)
{
fast = fast.next;
slow = slow.next;
}
return fast;
}
public static Node NoLoopList(Node head1,Node head2) {
if(head1==null||head2==null)
return null;
//两链表都不存在环的情况下求相交
//先各自遍历到末端节点,并计算各自长度
Node last1 = head1;
Node last2 = head2;
int n =0;
while(last1.next!=null)
{
last1 = last1.next;
n++;
}
while(last2.next!=null)
{
last2 = last2.next;
n--;
}
//两单向链表如果相交,其终点必为相同
if(last1!=last2)
return null;
last1 = n>0?head1:head2;
last2 = last1 ==head1?head2:head1;
n = Math.abs(n);
//更长的链表先走差值n的步数
while(n!=0)
{
n--;
last1 = last1.next;
}
//再一起往下走,直到相遇
while(last1!=last2)
{
last1 = last1.next;
last2 = last2.next;
}
//跳出循环时的当前节点即为入环点
return last1;
}
public static Node BoothloopList(Node head1,Node head2,Node loop1,Node loop2) {
Node cur1 = null;
Node cur2 = null;
//入环点相同,证明相交点在环之前
if(loop1==loop2)
//此时将入环点作为末尾节点,复用noloop函数内容
{
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;
}
}
【题目】
分别实现反转单向链表和反转双向链表的函数。
【要求】
如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)
【Code】
public static Node reverseList(Node head) {
Node reverse = null;
Node next = null;
while(head!=null)
{
//头插法
next = head.next;
head.next = reverse;
reverse = head;
head = next;
}
return reverse;
}
public static class DoubleNode {
public int value;
public DoubleNode last;
public DoubleNode next;
public DoubleNode(int data) {
this.value = data;
}
}
public static DoubleNode reverseDobleList(DoubleNode head) {
DoubleNode pre = null;
DoubleNode next = null;
while(head!=null)
{
next = head.next;
head.next = pre;
head.last = next;
pre = head;
head = next;
}
return pre;
}

浙公网安备 33010602011771号