前言:链表由于其本身的灵活性,很能考察程序员的编程功底,在面试中被问到链表相关的知识的概率很高,所以很值得我们去学习汇总,下面的内容非原创,而是自己在复习链表的过程中从别处摘抄而来,若想查看原文内容,可以看文章末尾的链接。
下面是本文所要用到的链表节点的定义:
struct Node{ int data; Node* next; };
题目描述:给定链表的头指针和一个节点指针,在O(1)时间删除该节点。[Google面试题]
分析:本题与《编程之美》上的 从无头单链表中删除节点
类似。主要思想都是 狸猫换太子
,即用下一个节点数据覆盖要删除的节点,然后删除下一个节点。但是如果节点是尾节点时,该方法就行不通了。
//O(1)时间删除链表节点,从无头单链表中删除节点 void deleteRandomNode(Node *cur) { assert(cur != NULL); assert(cur->next != NULL); //不能是尾节点 Node* pNext = cur->next; cur->data = pNext->data; cur->next = pNext->next; delete pNext; }
题目描述:输入一个单向链表,输出逆序反转后的链表。
分析:链表的转置是一个很常见、很基础的数据结构题了,非递归的算法很简单,用三个临时指针 pre、head、next 在链表上循环一遍即可。递归算法也是比较简单的,但是如果思路不清晰估计一时半会儿也写不出来吧。
循环版本
和 递归版本
的链表转置代码如下:
//单链表的转置,循环方法 Node* reverseByLoop(Node *head) { if(head == NULL || head->next == NULL) return head; Node *pre = NULL; Node *next = NULL; while(head != NULL) { next = head->next; head->next = pre; pre = head; head = next; } return pre; } //单链表的转置,递归方法 Node* reverseByRecursion(Node *head) { //第一个条件是判断异常,第二个条件是结束判断 if(head == NULL || head->next == NULL) return head; Node *newHead = reverseByRecursion(head->next); head->next->next = head; head->next = NULL; return newHead; //返回新链表的头指针 }
题目描述:输入一个单向链表,输出该链表中倒数第k个节点,链表的倒数第0个节点为链表的尾指针。
分析:设置两个指针 p1、p2,首先 p1 和 p2 都指向 head,然后 p2 向前走 k 步,这样 p1 和 p2 之间就间隔 k 个节点,最后 p1 和 p2 同时向前移动,直至 p2 走到链表末尾。
//倒数第k个节点 Node* theKthNode(Node *head,int k) { if(k < 0) return NULL; //异常判断 Node *slow,*fast; slow = fast = head; int i = k; for(;i>0 && fast!=NULL;i--) { fast = fast->next; } if(i > 0) return NULL; //考虑k大于链表长度的case while(fast != NULL) { slow = slow->next; fast = fast->next; } return slow; }
题目描述:求链表的中间节点,如果链表的长度为偶数,返回中间两个节点的任意一个,若为奇数,则返回中间节点。
分析:此题的解决思路和第3题「求链表的倒数第 k 个节点」很相似。可以先求链表的长度,然后计算出中间节点所在链表顺序的位置。但是如果要求只能扫描一遍链表,如何解决呢?最高效的解法和第3题一样,通过两个指针来完成。用两个指针从链表头节点开始,一个指针每次向后移动两步,一个每次移动一步,直到快指针移到到尾节点,那么慢指针即是所求。
//求链表的中间节点 Node* theMiddleNode(Node *head) { if(head == NULL) return NULL; Node *slow,*fast; slow = fast = head; //如果要求在链表长度为偶数的情况下,返回中间两个节点的第一个,可以用下面的循环条件 //while(fast && fast->next != NULL && fast->next->next != NULL) while(fast != NULL && fast->next != NULL) { fast = fast->next->next; slow = slow->next; } return slow; }
题目描述:输入一个单向链表,判断链表是否有环?
分析:通过两个指针,分别从链表的头节点出发,一个每次向后移动一步,另一个移动两步,两个指针移动速度不一样,如果存在环,那么两个指针一定会在环里相遇。
//判断单链表是否存在环,参数circleNode是环内节点,后面的题目会用到 bool hasCircle(Node *head,Node *&circleNode) { Node *slow,*fast; slow = fast = head; while(fast != NULL && fast->next != NULL) { fast = fast->next->next; slow = slow->next; if(fast == slow) { circleNode = fast; return true; } } return false; }
题目描述:输入一个单向链表,判断链表是否有环。如果链表存在环,如何找到环的入口点?
解题思路:由上题可知,按照 p2 每次两步,p1 每次一步的方式走,发现 p2 和 p1 重合,确定了单向链表有环路了。接下来,让p2回到链表的头部,重新走,每次步长不是走2了,而是走1,那么当 p1 和 p2 再次相遇的时候,就是环路的入口了。
为什么?:假定起点到环入口点的距离为 a,p1 和 p2 的相交点M与环入口点的距离为b,环路的周长为L,当 p1 和 p2 第一次相遇的时候,假定 p1 走了 n 步。那么有:
p1走的路径: a+b = n
;
p2走的路径: a+b+k*L = 2*n
; p2 比 p1 多走了k圈环路,总路程是p1的2倍;
根据上述公式可以得到 k*L=a+b=n
显然,如果从相遇点M开始,p1 再走 n 步的话,还可以再回到相遇点,同时p2从头开始走的话,经过n步,也会达到相遇点M。
显然在这个步骤当中 p1 和 p2 只有前 a 步走的路径不同,所以当 p1 和 p2 再次重合的时候,必然是在链表的环路入口点上。
//找到环的入口点 Node* findLoopPort(Node *head) { //如果head为空,或者为单结点,则不存在环 if(head == NULL || head->next == NULL) return NULL; Node *slow,*fast; slow = fast = head; //先判断是否存在环 while(fast != NULL && fast->next != NULL) { fast = fast->next->next; slow = slow->next; if(fast == slow) break; } if(fast != slow) return NULL; //不存在环 fast = head; //快指针从头开始走,步长变为1 while(fast != slow) //两者相遇即为入口点 { fast = fast->next; slow = slow->next; } return fast; }
题目描述:给出两个单向链表的头指针(如下图所示):
比如h1、h2,判断这两个链表是否相交。这里为了简化问题,我们假设两个链表均不带环。
//判断两个链表是否相交 bool isIntersect(Node *h1,Node *h2) { if(h1 == NULL || h2 == NULL) return false; //异常判断 while(h1->next != NULL) { h1 = h1->next; } while(h2->next != NULL) { h2 = h2->next; } if(h1 == h2) return true; //尾节点是否相同 else return false; }
题目描述:上面的问题都是针对链表无环的,那么如果现在,链表是有环的呢?上面的方法还同样有效么?
分析:如果有环且两个链表相交,则两个链表都有共同一个环,即环上的任意一个节点都存在于两个链表上。因此,就可以判断一链表上俩指针相遇的那个节点,在不在另一条链表上。
//判断两个带环链表是否相交 bool isIntersectWithLoop(Node *h1,Node *h2) { Node *circleNode1,*circleNode2; if(!hasCircle(h1,circleNode1)) //判断链表带不带环,并保存环内节点 return false; //不带环,异常退出 if(!hasCircle(h2,circleNode2)) return false; Node *temp = circleNode2->next; while(temp != circleNode2) { if(temp == circleNode1) return true; temp = temp->next; } return false; }
题目描述:如果两个无环单链表相交,怎么求出他们相交的第一个节点呢?
分析:采用对齐的思想。计算两个链表的长度 L1 , L2,分别用两个指针 p1 , p2 指向两个链表的头,然后将较长链表的 p1(假设为 p1)向后移动L2 - L1个节点,然后再同时向后移动p1 , p2,直到 p1 = p2。相遇的点就是相交的第一个节点。
//求两链表相交的第一个公共节点 Node* findIntersectNode(Node *h1,Node *h2) { int len1 = listLength(h1); //求链表长度 int len2 = listLength(h2); //对齐两个链表 if(len1 > len2) { for(int i=0;i<len1-len2;i++) h1=h1->next; } else { for(int i=0;i<len2-len1;i++) h2=h2->next; } while(h1 != NULL) { if(h1 == h2) return h1; h1 = h1->next; h2 = h2->next; } return NULL; }
原文链接,请戳 这里 。