链表相关的面试问题极其常见,注意单向链表和双向链表的区别,在解答这一章的问题之前,节点的创建、链表的增删查改等操作应该熟记于心。
以单向节点为例,基本的代码实现如下:
class listNode
{
public:
listNode(int value=0):_value(value){_pNext = NULL;}
~listNode(){_pNext = NULL;}
//int data(){return this->_value;}
//listNode* next(){return this->_pNext;}
void appendToTail(int value){
listNode* end = new listNode(value);
listNode* p = this;
while(p->_pNext!=NULL)
p = p->_pNext;
p->_pNext = end;
}
public:
int _value;
listNode* _pNext;
};
/*
@ delete a listNode with value=d, return head of listNode.
*/
listNode* deleteListNode(listNode* head, int d)
{
if(head==NULL)
return NULL;
listNode* pNode = head;
if(head->_value == d){//if it occurs in head
pNode = pNode->_pNext;
delete head;
head = NULL;
return pNode;
}
while(pNode->_pNext){
if(pNode->_pNext->_value==d){
listNode* pTmpNode = pNode->_pNext;
pNode->_pNext = pNode->_pNext->_pNext;
delete pTmpNode;
pTmpNode = NULL;
return head;
}
pNode = pNode->_pNext;
}
return head;//if NOT found
}
/*
@ delete Node from list
@ Make sure to handle the case if pNode is the last node in the linked list
*/
bool deleteListNode(listNode* pNode)
{
if(pNode==NULL || pNode->_pNext == NULL)
return false;
listNode* pNext = pNode->_pNext;
pNode->_value = pNext->_value;
pNode->_pNext = pNext->_pNext;
delete pNext;
return true;
}
void displayList(listNode* head)
{
if(head==NULL)
cout << "Invalid list." << endl;
listNode* pNode = head;
while(pNode){
cout << setw(5) << pNode->_value;
pNode = pNode->_pNext;
}
cout << endl;
}
方法1.借助辅助buffer和hashTable,假设节点中的数据为整型且在[0,256)之间
/*
@ 删除列表中的重复节点
@ 假定节点的数据为int,且取值为【0,256】
*/
void deleteDups(listNode* head)
{
if(head==NULL)
return;
bool hashTable[256] = {false};
listNode* pNode = head;
hashTable[pNode->_value%256] = true;
while(pNode->_pNext){
if(hashTable[pNode->_pNext->_value%256]==false){
hashTable[pNode->_pNext->_value%256] = true;
pNode = pNode->_pNext;
}
else{
listNode* pNodeTmp = pNode->_pNext;
pNode->_pNext = pNode->_pNext->_pNext;
delete pNodeTmp;
pNodeTmp = NULL;
}
}
}
方法2. 使用两个指针进行迭代比较,current从头到尾遍历链表,previous指向以处理完链表片段的末尾节点。对于每一个current指向的节点,使用runner遍历[head, previous]以检测是否有重复的节点。
void deleteDups2(listNode* head)
{
if(head == NULL)
return;
listNode* previous = head;
listNode* current = head->_pNext;
while(current!=NULL)
{
listNode* Runner = head;
while(Runner != current){//在[head, previous]区间内寻找和current相等的节点
if(Runner->_value == current->_value){//如果存在雷同节点
listNode* pTmp = current;//则删除current节点
previous->_pNext = current->_pNext;//已处理完区间的下一个节点位置后移
current = current->_pNext;
delete pTmp; pTmp = 0;
break;
}
Runner = Runner->_pNext;
}
if(Runner==current){//遍历完后并未发现相同节点,则将current节点移入已处理节点
previous = current;
current = current->_pNext;//往后遍历节点
}
}
}
2.2. 查找距离单向链表最后节点距离为n的节点
方法1. 首先确定首尾节点距离length,然后再次首节点出发后移length-n个节点即可。最坏情况下,需要遍历链表两次。(代码略)
方法2. 双指针迭代法,1次遍历即可。步骤如下:1)创建p1,p2均指向head;2) 向后移动p2,使得p1和p2间隔n个节点;3) 同步递增p1和p2,直到p2->next==NULL,返回p1。代码如下:
void deleteDups2(listNode* head)
{
if(head == NULL)
return;
listNode* previous = head;
listNode* current = head->_pNext;
while(current!=NULL)
{
listNode* Runner = head;
while(Runner != current){//在[head, previous]区间内寻找和current相等的节点
if(Runner->_value == current->_value){//如果存在雷同节点
listNode* pTmp = current;//则删除current节点
previous->_pNext = current->_pNext;//已处理完区间的下一个节点位置后移
current = current->_pNext;
delete pTmp; pTmp = 0;
break;
}
Runner = Runner->_pNext;
}
if(Runner==current){//遍历完后并未发现相同节点,则将current节点移入已处理节点
previous = current;
current = current->_pNext;//往后遍历节点
}
}
}
2.3. 删除单链表中的某个节点,有一个约束,能且仅能操作链表的待删除节点。
有一点需要注意的是,如果待删除节点位于链表的最后一个节点,如何处理?删除最后一个节点会导致指针悬空,这种情况下需要特除处理,要么不删除,要不将该节点的数据置为无效。代码实现见链表的实现。
2.4. 两个单链表表征的数,每个数字由一个节点表示,且低位在前、高位在后,如果315表示为(3->1->5)。编码实现两个数的相加操作,如(3->1->5) + (5->9->2) = (8->0->8)。
思路:递归实现
/*
@ recursion method to add lists
*/
listNode* addLists(listNode* l1, listNode* l2, int carry)
{
if(l1==NULL || l2==NULL)
return NULL;
int value = carry;
if(l1!=NULL)
value += l1->_value;
if(l2!=NULL)
value += l2->_value;
listNode* pNode = new listNode(value%10);
listNode* pMore = addLists(l1->_pNext, l2->_pNext, value/10);
pNode->_pNext = pMore;
return pNode;
}
2.5. 找出环形单向链表中环的起始位置。如有单向链表 A->B->C->D->E->F->C,输出为C。
思路:两倍速追赶法
listNode* findBeginning(listNode* head)
{
if(head==NULL || head->_pNext==NULL || head->_pNext->_pNext==NULL)
return NULL;
listNode* p1 = head;
listNode* p2 = head;
while(p2=p2->_pNext->_pNext){//找到相遇点
p1=p1->_pNext;
if(p1==p2)
break;
}
if(p2==NULL)//如果没有相遇点,则表明不存在环
return NULL;
p1 = head;//将p1移到头部,p2保持在相遇点
while(p1!=p2){//同步移动p1和p2,直到他们相遇,此时的相遇点为环的起始位置
p1 = p1->_pNext;
p2 = p2->_pNext;
}
return p2;
}