您的位置:首页 > 编程语言 > C语言/C++

和链表有关的算法题(C++)

2014-02-19 16:25 281 查看
链表操作在面试中经常出现,这一方面考察了对指针的使用,以下整理了一些关于链表的面试题以及注意事项。

首先对单链表进行定义。

struct ListNode{
int val;
ListNode *next;
};


关于链表,有以下几个方面需要注意:

1、指针方面。判断指针是否为NULL,何时判断等等。

2、不要贪图少指针,尽量清楚明了逻辑清晰,多几个指针也没关系。

3、一般单链表的题目较多,一定要注意单链表的单向特性,该保存指针就保存,同2,不要妄想就用一个指针解决问题。

4、指针作为实参传入时要注意,虽然指针可以改变所指向的地址的值,但是指针所保存的地址依然是作为按值传递进入函数的,所以若想使用最后的指针,需要使用指针的指针、引用或者利用函数返回指针,切记这三种方式。

接下来对几个常见问题进行介绍。

问题1:输入一个单向链表,输出该链表中倒数第k个结点

先第一个指针向前走k,然后第二个指针此时和第一个指针一起走即可。

情况:pHead=NULL、链表小于k、k输入为0;

ListNode* LastK(ListNode* pHead, unsigned int k)
{
// 保证输入的正确性;
if (pHead == NULL || k == 0)
return NULL;
ListNode *pAhead = pHead;
ListNode *pBehind = NULL;

for ( int i = 0; i < k - 1; i++)
{
if (pAhead->next != NULL)
pAhead = pAhead->next;
else
return NULL;
}

pBehind = pHead;
while ( pAhead != NULL)
{
pAhead = pAhead->next;
pBehind = pBehind->next;
}
return pBehind;
}


其他方法:先遍历一次得到总节点数。

不足:未考虑有环的情况。

问题2:如何判断一个链表有环

这个问题可以这么来分析我们可以设置两个指针(p1, p2),初始值都指向头,p1每次前进一步,p2每次前进二步,如果链表存在环,则p2先进入环,p1后进入环,两个指针在环中走动,必定相遇

bool isCircle(ListNode* pHead, ListNode** pNode)
{
if (pHead == NULL)
return NULL;

ListNode *fast = pHead;
ListNode *slow = pHead;

while(1)
{
if ( fast->next == NULL)
{
*pNode = fast;
return false;
}
fast = fast->next;
if ( fast->next == NULL)
{
*pNode = fast;
return false;
}
fast = fast->next;
if ( fast == slow)
{
*pNode = fast;
return true;
}

slow = slow->next;
}
}


附:若链表有环的情况下如何找出环的入口。

当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)。假设slow走了s步,则 fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:

2s = s + nr

s= nr

设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。

a + x = nr

a + x = (n – 1)r +r = (n-1)r + L - a

a = (n-1)r + (L – a – x)

(L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。

问题3:如何判断两个链表是否相交
分析:问题可以分为两种情况,第一种情况如果两个链表都没有环的话,那么两个链表要是相交,那么从他们相交的那一点开始到尾节点两个链表应该完全相同,这样我们就可以直接判断链表的尾节点是否相同来进行判断两链表是否相交来进行判断。第二种情况的话如果两个链表存在环的话,那么我们则应该判断一链表上俩指针相遇的那个节点,在不在另一条链表上,如果在,则相交,如果不在,则不相交。

bool isIntersect(ListNode* pHead1, ListNode* pHead2)
{
ListNode **Last1 = NULL;
ListNode **Last2 = NULL;
bool isCir1 = isCircle(pHead1, Last1);
bool isCir2 = isCircle(pHead2, Last2);
// 一个有环一个没有,肯定不相交;
if ( isCir1 != isCir2)
return false;
// 如果都无环,判断最后一个是否相同即可;
if ( isCir1 == false)
{
if ( *Last1 == *Last2)
return true;
else
return false;
}

ListNode *tmp = *Last2;
if ( tmp == *Last1)
return true;
tmp = tmp->next;
// tmp绕环一圈,寻找是否和第一个链表有相同节点;
while ( tmp != (*Last2))
{
if ( tmp == *Last1)
return true;
tmp = tmp->next;
}
return false;
}


问题4:反转链表

其实问题很简单,但是在代码的过程中要注意保存好相应的指针,以及考虑特殊的情况,如终止条件,特殊输入等。

void inver(ListNode **Head)
{
if ( Head == NULL || *Head == NULL || (*Head)->next == NULL)
return;

ListNode *p1 = NULL;
ListNode *p2 = *Head;

while( p2 != NULL)
{
*Head = p2;
p2 = (*Head)->next;
(*Head)->next = p1;
p1 = *Head;
}
}


问题5:合并两个排序的链表

该题需要对合并的过程考虑清楚,如何处理合并问题是关键,可以以采用递归算法解决,返回什么?输入什么?一旦这些考虑完善,写出来就容易很多。

void inver(ListNode **Head)
{
if ( Head == NULL || *Head == NULL || (*Head)->next == NULL)
return;

ListNode *p1 = NULL;
ListNode *p2 = *Head;

while( p2 != NULL)
{
*Head = p2;
p2 = (*Head)->next;
(*Head)->next = p1;
p1 = *Head;
}
}


问题6:在O(1)时间删除给点节点

由于节点没有前向节点的地址,所以遍历是需要O(n)时间。

转换思维,我们可以将后一节点的值赋给该节点,然后删去后一节点。

功能测试:链表只含一个节点;节点是链表最后节点。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: