单链表是最基本的数据结构,由于其简单的构造以及相关操作代码的简短,特别受面试官的青睐,面试官可以通过在短短的15分钟内让你写出单链表的操作,而对你的代码能力进行判断。所以掌握单链表是重中之重,对常考的知识点必须熟记于心,能够快速的写出来,并且bug-free。
单链表的定义
常见的定义方式如下:
// Definition for singly-linked list.
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
从上述代码中可以看出,单链表包含数据以及指向下一个节点的指针,形象化的表示如下图1所示:
图1: 单链表图(感谢第七城市提供)
单链表不一定有头结点,但是一定会有一个头指针head。
单链表常见面试题
面试题目录:
1:就地逆转单链表
2:检测单链表是否有环
3:判断两个单链表是否有交点,如果有交点,求交点
4:在O(1)时间内删除链表内的某一结点
5:如果单链表有环,求环的长度以及入环点
(一)就地逆转单链表
经典做法:设定三个指针,一个是pre(指向前一个结点),一个是current(指向当前结点),一个next(指向下一个结点),然后三个指针同时往右移,并且同时修改指针指向的位置
//reverse the single-list in place
//@author: zhang haibo
//@time: 2013-12-5
ListNode* Reverse_Single_List(ListNode* head)
{
//check the head is or not NULL
if(head == NULL)
return head;
//the head of the reverse list
ListNode* reverseHead = NULL;
//current listnode
ListNode* current = head;
//the previous listnode of current listnode
ListNode* pre = NULL;
while(current != NULL)
{
ListNode* next = current->next;
if(next == NULL)
reverseHead = current;
current->next = pre;
pre = current;
current = next;
}
return reverseHead;
}
(二)检测单链表是否有环
经典做法:设定两个指针,一个快指针,一个慢指针,快指针一次走两步,慢指针一次走一步,如果两个指针相遇,说明肯定存在环,如果没有环,则扫描到链表尾节点就退出了。
//check the singly-list whether or not has cycle
//@author: zhang haibo
//@time: 2013-12-5
bool Is_Exist_Cycle(ListNode* head)
{
ListNode* slow = head;
ListNode* fast = head;
while(fast != NULL && fast->next != NULL)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
return true;
}
return false;
}
(三)判断两个单链表是否有交点,如果有交点,求交点
经典做法:判断两个链表的尾节点是否相同,如果相同则必存在交点,如果不相同,则不存在交点。具体的做法是,指向两个链表的指针p1和p2分别走到末尾,并且求出两个链表的长度了L1和L2,判断p1和p2是否相同,不相同则没有交点,直接返回即可,如果相同,则存在交点,设定p1和p2指向头结点,长链表先走abs(L1-L2)步,然后两个链表共同走即可。
//check two lists have intersection or not.
//@author: zhang haibo
//@time: 2013-12-5
bool Is_Exist_Intersection(ListNode* list1, ListNode* list2, ListNode* &result)
{
if(list1 == NULL || list2 == NULL)
return false;
ListNode* p1 = list1;
ListNode* p2 = list2;
int length_of_list1 = 1;
int length_of_list2 = 1;
while(p1->next != NULL)
{
p1 = p1->next;
++length_of_list1;
}
while(p2->next != NULL)
{
p2 = p2->next;
++length_of_list2;
}
//not have the intersection
if(p1 != p2)
return false;
//have the intersection
p1 = list1, p2 = list2;
if(length_of_list1 < length_of_list2)
{
p1 = list2;
p2 = list1;
}
//p1 goes forward dis steps
int dis = abs(length_of_list1 - length_of_list2);
while(dis--)
p1 = p1->next;
//p1 and p2 goes forward in the same time
while(p1 != p2 )
{
p1 = p1->next;
p2 = p2->next;
}
//set the result
result = p1;
return true;
}
(四)在O(1)时间内删除链表内的某一结点
1)如果此节点是尾节点,则必须从头进行扫描到尾部节点前一个节点,进行删除,耗费N-1时间。
2)如果此节点是内部节点,则删除此节点的下一个节点,并且交换两个节点的数据即可,耗费1时间。
总体而言,假设链表的长度为N,总共的时间复杂度为(1/N)*(N-1)+(N-1/N)*1 = 1
//delete one node in the list
//@author: zhang haibo
//@time: 2013-12-5
ListNode* Delete_Node(ListNode* head, ListNode* deletedNode)
{
if(head == NULL || deletedNode == NULL)
return head;
//if deletedNode is the head of the list
if(head == deletedNode)
{
head = head->next;
return head;
}
//not the tail
if(deletedNode->next != NULL)
{
ListNode* next = deletedNode->next;
deletedNode->val = next->val;
deletedNode->next = next->next;
next = NULL;
}
else//the tail
{
ListNode* current = head;
while(current->next != deleteNode)
current = current->next;
current->next = NULL;
}
return head;
}
(五)如果单链表有环,求环的长度以及入环点
带环链表有如下几点性质,这里不再证明,请找相关资料查看:
1:快指针(每次走两步)和慢指针(每次走一步)肯定会在第一圈相遇
2:相遇点到入环点 和 链表头指针到入环点 的距离是相同的。
//find the entrance point and
// calculate the length of cycle
//@author: zhang haibo
//@time: 2013-12-5
ListNode* Find_Loop_Port(ListNode* head, int& length)
{
length = 0;
if(head == NULL)
return NULL;
ListNode* slow = head;
ListNode* fast = head;
while(fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
++length;
}
//no cycle
if(fast == NULL || fast->next == NULL)
{
length = 0;
return NULL;
}
//set slow point to the start head
slow = head;
while(slow != fast)
{
slow = slow->next;
fast = fast->next;
++length;
}
return slow;
}