面试中常见链表题型如下:
1.单链表的反序
2.给单链表建环
3.检测单链表是否有环,返回入口点
4.给单链表解环
5.检测两条链表是否相交,返回交点
6.不输入头节点,删除单链表的指定节点(只给定待删除节点指针)
7.合并两个有序链表
1、单链表的逆序
//逆转链表,并返回逆转后的头节点 node* reverse(node *head) { if(head == NULL || head->next == NULL) { return head; } node *cur = head; node *pre = NULL; node *tmp; while(cur->next) { tmp = pre; pre = cur; cur = cur->next; pre->next = tmp; //操作pre的next逆转 } cur->next = pre; //结束时,操作cur的next逆转 return cur; }
//方法二 node *reverse(node *head) { node *p, *q, *r; p = head; q = p->next; while(q != NULL) { r = q->next; q->next = p; p = q; q = r; } head->next = NULL; head = p; return head; }
2、给单链表建环
//给单链表建环,让尾指针,指向第num个节点,若没有,返回false bool bulid_looplink(node *head, int num) { node *cur = head; node *tail = NULL; int i = 0; if(num <= 0 || head == NULL) { return false; } for(i = 1; i < num; ++i) { if(cur == NULL) { return false; } cur = cur->next; } tail = cur; while(tail->next) { tail = tail->next; } tail->next = cur; return true; }
3、检测单链表是否有环,如果有则返回入口结点
算法思想:用两个指针p1,p2同时指向链表的头部,p1一次移动一步,p2一次移动两步,如果最终p1和p2重合则说明链表有环,如果p2走到空指针(链表的结尾)则说明链表无环。如果最终p1和p2重合,使p2重新指向链表的头结点,然后p1和p2同时一次移动一步,当p1和p2再次重合时该节点指针就是环的入口节点指针。
证明思路:在p2和p1第一次相遇的时候,假定p1走了n步,环路的入口是在p步的时候经过的,那么有p1走的路径: p+c = n;(c为p1和p2相交点,距离环路入口的距离)p2走的路径: p+c+k*L = 2*n;(L为环路的周长,k是整数)显然,如果从p+c点开始,p1再走n步骤的话,还可以回到p+c这个点,同时p2从头开始走的话,经过n步,也会达到p+c这点。显然在这个步骤当中p1和p2只有前p步骤走的路径不同,所以当p1和p2再次重合的时候,必然是在链表的环路入口点上。
判断链表是否有环
bool IsExitsLoop(slist * head) { slist * slow = head , * fast = head; while ( fast && fast -> next ) { slow = slow -> next; fast = fast -> next -> next; if ( slow == fast ) break ; } return ! (fast == NULL || fast -> next == NULL); }
如果有环返回入口结点
slist * FindLoopPort(slist * head) { slist * slow = head, * fast = head; while ( fast && fast -> next ) { slow = slow -> next; fast = fast -> next -> next; if ( slow == fast ) break ; } if (fast == NULL || fast -> next == NULL) return NULL; slow = head; while (slow != fast) { slow = slow -> next; fast = fast -> next; } return slow; }
4、给单链表建环
//找到有环节点,并解环,返回true,无环,返回false //思路:先找到环节点:被2个节点指向的节点(一定有环的条件)ps:不考虑中间环,因为只有一个next节点,只可能是尾环 bool unloop_link(node *head) { set<node *> node_bitmap; //node的地址位图 unsigned int num = 0; node *cur = head, *pre = NULL; while(cur != NULL) { if(!node_bitmap.count(cur) ) //该节点未被遍历过 { node_bitmap.insert(cur); ++num; } else //指向已被遍历过的节点,此时pre节点为尾节点 { pre->next = NULL; return true; } pre = cur; cur = cur->next; } return false; }
5、检测两条链表是否相交,有则返回交点
常见思路:
一、将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。
二、如果两个链表相交,那个两个链表从相交点到链表结束都是相同的节点,我们可以先遍历一个链表,直到尾部,再遍历另外一个链表,如果也可以走到同样的结尾点,则两个链表相交。这时我们记下两个链表length,再遍历一次,长链表节点先出发前进(lengthMax-lengthMin)步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。
#include <iostream> using namespace std; /*节点的类定义*/ class Node { public: int data; Node * next; Node(int data) { this->data=data; } }; /*链表的类定义*/ class List { public: Node * head;//头结点指针 Node * tail;//尾结点指针 /*用一个整形数组作为参数的构造函数*/ List(int array[],int length) { head=new Node(array[0]); Node * temp=head; int i; for(i=1;i<length;i++) { temp->next=new Node(array[i]); temp=temp->next; } temp->next=NULL; tail=temp; } /*查找指定位置的节点,并返回指向该节点的指针*/ Node * FindNode(int index) { Node * temp=head; while(--index) temp=temp->next; return temp; } }; /*判断链表是否有环,如果有环则返回环的首结点指针,否则返回NULL值*/ Node * FindCircle(List list) { Node * p1,* p2; p1=list.head; p2=list.head; /*判断链表是否有环,当p1=p2时说明链表有环,程序跳出循环。如果p2一直走到链表尽头则说明没有环。*/ do { if(p2->next!=NULL&&p2->next->next!=NULL) { p2=p2->next->next; p1=p1->next; } else return NULL; } while(p1!=p2); /*求出环的起点节点,并将其返回*/ p2=list.head; while(p1!=p2) { p2=p2->next; p1=p1->next; } return p1; } /*判断两个链表是否交叉,如果交叉返回交叉节点,否则返回NULL。*/ Node * FindCross(List list1,List list2) { list1.tail->next=list1.head;//将list1变成有环链表 Node * p1,* p2; p1=list2.head; p2=list2.head; /*判断链表是否有环,当p1=p2时说明链表有环,程序跳出循环。如果p2一直走到链表尽头则说明没有环。*/ do { if(p2->next!=NULL&&p2->next->next!=NULL) { p2=p2->next->next; p1=p1->next; } else return NULL; } while(p1!=p2); /*求出环的起点节点,并将其返回*/ p2=list2.head; while(p1!=p2) { p2=p2->next; p1=p1->next; } return p1; } int main() { /*构造一个有环链表,并查找环的起始节点*/ int array[8]={1,2,3,4,5,6,7,8}; List list(array,sizeof(array)/sizeof(int));//构造一个链表 list.tail->next=list.FindNode(5);//将该链表构造成一个有环链表 Node * temp=FindCircle(list);//查找环的起始节点 if(temp==NULL) cout<<"No cirle exists in the list."<<endl; else cout<<"There is a circle in the list , and the value of the join_point is "<<temp->data<<endl; /*构造两个链表,然后使这两个链表交叉。最后查找交叉节点。*/ int array1[8]={1,2,3,4,5,6,7,8}; int array2[5]={9,10,11,12,13}; List list1(array1,sizeof(array1)/sizeof(int));//构造链表list1 List list2(array2,sizeof(array2)/sizeof(int));//构造链表list2 list2.tail->next=list.FindNode(3);//使这两个链表交叉 temp=FindCross(list,list2);//查找这两个链表的交叉节点 if(temp==NULL) cout<<"These two lists dose not cross."<<endl; else cout<<"These two lists cross with each other , and the value of the corss_point is "<<temp->data<<endl; return 0; }
//检测两条链表是否相交,是则返回第一个交点,否则返回NULL //思路:把2个链表各遍历一遍,记下长度length1和length2,若2者的尾节点指针相等,则相交。 // 之后再把长的链表从abs(len1-len2)的位置开始遍历,第一个相等的指针为目标节点 node* detect_intersect_links(node *first_link, node *second_link) { int legnth1 = 1, length2 = 1, pos = 0; node *cur = NULL, *longer_link = first_link, *shorter_link = second_link; if(first_link == NULL || second_link == NULL) { return NULL; } while(first_link->next || second_link->next) //遍历2个链表 { if(first_link->next) { first_link = first_link->next; ++legnth1; } if(second_link->next) { second_link = second_link->next; ++length2; } } if(first_link != second_link) //比较尾节点 { return NULL; } pos = legnth1 - length2; if(legnth1 < length2) //保证 longer_link为长链表 { pos = length2 - legnth1; cur = longer_link; longer_link = shorter_link; shorter_link = cur; } while(pos-- > 0) longer_link = longer_link->next; while(longer_link || shorter_link) { if(longer_link == shorter_link) //找到第一个交点 { return longer_link; } longer_link = longer_link->next; shorter_link = shorter_link->next; } return NULL; }
6、不输入头节点,删除单链表的指定节点(只给定待删除节点指针)
//无头节点,随机给出单链表中一个非头节点,删除该节点,当传入空节点,或者尾节点时,返回false //思路:由于没有头节点,非循环单链表,无法获取目标节点的前节点,所以只能把它的next节点数据前移,并删除next节点 //ps:当传入节点为尾节点,无法用此方法删除 bool withouthead_delete_node(node *target_node) { node *cur = NULL; if(target_node == NULL || target_node->next == NULL) //空节点或者尾节点,失败 { return false; } cur = target_node->next; target_node->name = cur->name; target_node->next = cur->next; delete cur; return true; }
7、合并两个有序链表
递归实现:
①算法思想:
递归终止条件:若head1为空,返回head2指针(head);若head2为空,返回head1指针(head)
递归过程:
若head1->data>head2->data; head 指针应该指向head2所指向的节点,而且head->next应该指向head1和head2->next两个链表的合成序列的头指针;
否则head 指针应该指向head1所指向的节点,而且head->next应该指向head->next和head2两个链表的合成序列的头指针;
*/
#include <iostream>
using namespace std;
/*节点的类定义*/
class Node
{
public:
int data;
Node *next;
Node(int data)
{
this->data = data;
}
};
/*链表的类定义*/
class LinkedList
{
public:
Node *head;
/*用一个整形数组作为参数的构造函数*/
LinkedList(int array[])
{
head = new Node(array[0]);
Node *temp = head;
int i;
for( i = 1; i < 3; i++ )
{
temp->next = new Node(array[i]);
temp = temp->next;
}
temp->next = NULL;
}
};
/*递归的合并两个有序链表*/
Node * mergeLinkedList(Node *head1, Node *head2)
{
Node *p = NULL;
if(head1 == NULL && head2 == NULL)
return p;
else if(head1 == NULL)
return head2;
else if(head2 == NULL)
return head1;
else
{
if(head1->data < head2->data)
{
p = head1;
p->next = mergeLinkedList(head1->next, head2);
}
else
{
p = head2;
p->next = mergeLinkedList(head1, head2->next);
}
return p;
}
}
/*打印链表的所有元素*/
void printList(Node *head)
{
Node *temp = head;
while(temp != NULL)
{
cout<<temp->data<<"";
temp = temp->next;
}
}
int main()
{
int array1[3] = {2,5,8};
int array2[3] = {1,6,7};
/*构造两个有序链表--list1和list2*/
LinkedList list1(array1);
LinkedList list2(array2);
/*递归的将这两个有序链表合并成一个有序链表*/
Node *new_head = mergeLinkedList(list1.head, list2.head);
/*打印有序链表*/
printList(new_head);
return 0;
}