题目描述
输入一个链表,输出该链表中倒数第 k 个结点。
解题思路
思路一:
这个思路比较简单粗暴,因为要找倒数第 k 个结点,可以利用栈“先进后出”的特性,首先,遍历链表所有的结点,存储到栈内;然后将栈内前 k-1 个结点推出来,那么剩下的栈顶结点就是倒数第 k 个结点。这个方法的空间复杂度是 O(n),时间复杂度 O(n)。
实现代码:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if(!pListHead||k<=0) return NULL;
int len = 0;
stack<ListNode*> Lstack;
while(pListHead){
Lstack.push(pListHead);
pListHead = pListHead->next;
len++;
}
if(k>len) return NULL;
while(k>1){
Lstack.pop();
k--;
}
return Lstack.top();
}
};
思路二:快慢指针
快慢指针在解决单链表是否存在环的时候使用过,可以回顾博客《【剑指offer】链表中环的入口结点》
下面我们再次用快慢指针解决链表问题。想一想,我们在判断倒数第 k 个结点是怎么做的。我们会首先找到结尾结点,然后从后往前找 k 个,那么怎么判断尾结点呢?如果这个结点的下一个结点为空,我们就认为这个是尾结点。
设置快慢指针,我们先让快指针从头结点开始走 k-1 步,然后慢指针再从头结点开始走。快慢指针每次只走一步。这样快指针就可以看作是“探针”,如果快指针指向尾结点,说明慢指针再走 k-1 步就结束了,慢指针指向的结点就是倒数第 k 个结点。
实现代码:
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if(!pListHead||k<=0) return NULL;
// 设置快慢指针
ListNode* fast = pListHead;
ListNode* slow = pListHead;
int len = k;
// 快指针先走 k-1 步。
while(k>1){
// 如果 k 大于链表长度,fast指针会走到 NULL
if(!fast->next) return NULL;
fast = fast->next;
k--;
}
// 确定k<链表长度,快慢指针一起走,直到快指针走到尾结点。
while(fast->next){
fast = fast->next;
slow = slow->next;
}
return slow;
}
};
思路三:
举个例子,看下图。
假设链表有 8 个结点,我们要找倒数第 3 个结点。我们给每个结点加上索引 index,分别是 0-7。那么倒数第 3 个结点的索引是 5,这等于链表长度 - k,k是倒数第几个,也等于最后索引值 - k + 1。基于这个想法,如果我们设置一个结点从头到尾遍历,就可以得到链表长度 len,此时倒数第 k 个结点则在遍历过程中等于比遍历少走了 k-1 步。这样,我们可以设置两个指针,一个是遍历指针,另一个是目标指针。遍历指针从头走到尾,直至指向NULL;目标指针则在遍历了 k-1 步后才开始从头移动,即 i>= k 时,才移动,这样当遍历指针指向NULL时,目标指针就会指向倒数第 k 个结点。
实现代码:
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if(!pListHead||k<=0) return NULL;
// 设置两个指针
ListNode* p = pListHead;
ListNode* q = pListHead;
for(int i=0;p!=NULL;i++){
if(i>=k){
q = q->next;
}
p = p->next;
}
return i<k? NULL:q;
}
};
参考链接:
1.https://www.nowcoder.com/questionTerminal/529d3ae5a407492994ad2a246518148a?f=discussion