题目:
输入一个链表,输出该链表中倒数第k个结点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点。例如一个链表有6个结点,从头结点开始它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个结点是值为4的结点。
为了得到倒数第k个结点,很自然的想法是先走到链表的尾端,再从尾端回溯k步。可是我们从链表结点的定义可以看出本题中的链表是单向链表,单向链表的结点只有从前往后的指针而没有从后往前的指针,因此这种思路行不通。
既然不能从尾结点开始遍历这个链表,我们还是把思路回到头结点上来。假设整个链表有n个结点,那么倒数第k个结点就是从头结点开始的第 n-k+1个结点。如果我们能够得到链表中结点的个数n,那我们只要从头结点开始往后走 n-k+1步就可以了。如何得到结点数 n?这个不难,只需要从头开始遍历链表,每经过一个结点,计数器加1就行了。
这种计数的方法就必须得先遍历一遍链表,一共遍历两遍,有没有什么更好的办法
当然有! ——快慢指针
我们设置两个指向头节点的指针,让一个指针先向后走k步,然后让两个指针同时向后走,当快指针走到链表末尾时,慢指针的位置刚好就是我们所需要的
开始实现:
typedef struct list
{
struct list* _next;
int _val;
}list;
尾插和打印
void push_back(list*phead,int val)
{
list* cur = phead;
while (cur->_next)
{
cur = cur->_next;
}
list* newnode = new list;
newnode->_next = nullptr;
newnode->_val = val;
cur->_next = newnode;
}
void print(list*phead)
{
assert(phead);
list* cur = phead->_next;
while (cur)
{
cout << cur->_val << " ";
cur = cur->_next;
}
cout << endl;
}
实现算法
list* FindKthToTail(list* phead, int k)
{
assert(phead);
assert(k > 0);
list* fast = phead->_next;
list* slow = phead->_next;
for (int i = 0; i < k-1; i++)
{
if (fast->_next == nullptr)
{
return nullptr;
}
else
{
fast = fast->_next;
}
}
while (fast->_next)
{
fast = fast->_next;
slow = slow->_next;
}
return slow;
}
测试:
void test()
{
list* head;
head = new list;
head->_next = nullptr;
push_back(head, 1);
push_back(head, 2);
push_back(head, 3);
push_back(head, 4);
push_back(head, 5);
push_back(head, 6);
push_back(head, 7);
push_back(head, 8);
push_back(head, 9);
push_back(head, 10);
print(head);
list* ret;
ret = FindKthToTail(head, 11);//预计出错
if (ret == nullptr)
{
perror("ret==NULL");
}
else
{
cout << ret->_val << endl;
}
ret = FindKthToTail(head, 10);//倒数第十个数,预计1
if (ret == nullptr)
{
perror("ret==NULL");
}
else
{
cout << ret->_val << endl;
}
ret = FindKthToTail(head, 2);//预计9
if (ret == nullptr)
{
perror("ret==NULL");
}
else
{
cout << ret->_val << endl;
}
}
int main()
{
test();
return 0;
}
为了提高代码的鲁棒性,我们要考虑所有会让程序崩溃的情况,首先k的值我们需要检查,k<0或者k的值比链表的结点数还大,会让程序直接崩溃,还有当链表为空的时候,程序也会崩溃,所以我们要检查完整!