本文大概解决三个问题,实话说,链表这些问题真是刷新了以前我对链表的“偏见”。

输出单链表倒数第 K 个节点

题目:输入一个单链表,输出此链表中的倒数第 K 个节点。(去除头结点,节点计数从 1 开始)。
两次遍历法

/*计算链表长度*/
int listLength(ListNode* pHead){
int count = 0;
ListNode* pCur = pHead->next;
if(pCur == NULL){
printf("error");
}
while(pCur){
count++;
pCur = pCur->pNext;
}
return count;
}
/*查找第k个节点的值*/
ListNode* searchNodeK(ListNode* pHead, int k){
int i = 0;
ListNode* pCur = pHead;
//计算链表长度
int len = listLength(pHead);
if(k > len){
printf("error");
}
//循环len-k+1次
for(i=0; i < len-k+1; i++){
pCur = pCur->next;
}
return pCur;//返回倒数第K个节点
}

采用这种遍历方式需要两次遍历链表,时间复杂度为O(n^2)。
递归法
解题思想:
(1)定义num = k
(2)使用递归方式遍历至链表末尾。
(3)由末尾开始返回,每返回一次 num 减 1
(4)当 num 为 0 时,即可找到目标节点

int num;//定义num值
ListNode* findKthTail(ListNode* pHead, int k) {
num = k;
if(pHead == NULL)
return NULL;
//递归调用
ListNode* pCur = findKthTail(pHead->next, k);
if(pCur != NULL)
return pCur;
else{
num--;// 递归返回一次,num值减1
if(num == 0)
return pHead;//返回倒数第K个节点
return NULL;
}
}

使用递归的方式实现仍然需要两次遍历链表,时间复杂度为O(n^2)。

所以,重点来了,所以我更倾向于:双指针法

解题思想:

(1)定义两个指针 p1 和 p2 分别指向链表头节点。

(2)p1 前进 K 个节点,则 p1 与 p2 相距 K 个节点。

(3)p1,p2 同时前进,每次前进 1 个节点。

(4)当 p1 指向到达链表末尾,由于 p1 与 p2 相距 K 个节点,则 p2 指向目标节点。

链表与指针:专治“疑难杂症”_时间复杂度


链表与指针:专治“疑难杂症”_指针_02


代码实现:

ListNode* findKthTail(ListNode *pHead, int K){
if (NULL == pHead || K == 0)
return NULL;
//p1,p2均指向头节点
ListNode *p1 = pHead;
ListNode *p2 = pHead;
//p1先出发,前进K个节点
for (int i = 0; i < K; i++) {
if (p1)//防止k大于链表节点的个数
p1 = p1->_next;
else
return NULL;
}

while (p1)//如果p1没有到达链表结尾,则p1,p2继续遍历
{
p1 = p1->_next;
p2 = p2->_next;
}
return p2;//当p1到达末尾时,p2正好指向倒数第K个节点
}

使用双指针法只需遍历链表一次,这种方法更为高效,时间复杂度为O(n)。

上面问题中,让其中一个指针先不动,待条件满足后再让其“出发”,那若是两个指针都在运动,即没有限制条件了,又该怎样考虑呢?

判断链表是否有环的问题

单链表中的环是指链表末尾的节点的 next 指针不为 NULL ,而是指向了链表中的某个节点,导致链表中出现了环形结构。
这种题当然可以用“穷举法”:遍历链表,记录已访问的节点;将当前节点与之前以及访问过的节点比较,若有相同节点则有环。
否则,不存在环。
but,效率过于低下,尤其是当链表节点数目较多,在进行比较时花费大量时间,时间复杂度大致在 O(n^2)。忘了它吧。。。
这样的题当然也可以用“哈希缓存法”,其大致思想是创建一个以节点 ID 为键的 HashSe t集合,用来存储曾经遍历过的节点,然后以后再遍历新节点时,与集合数值相比较,若等,则有环,若不等,则放入,接着比较。
but,不好意思,这种方法我不会。。。
我们依然用“双指针法”,只是,这次变了一个名字,称之为“快慢指针法”:
(1)定义两个指针分别为 slow,fast,并且将指针均指向链表头节点。
(2)规定,slow 指针每次前进 1 个节点,fast 指针每次前进两个节点。
(3)当 slow 与 fast 相等,且二者均不为空,则链表存在环。
若链表中存在环,则快慢指针必然能在环中相遇。这就好比在环形跑道中进行龟兔赛跑。由于兔子速度大于乌龟速度,则必然会出现兔子与乌龟再次相遇情况。因此,当出现快慢指针相等,且二者不为NULL时(即二者不在尾节点处相遇),则表明链表存在环。

bool isExistLoop(ListNode* pHead)  {  
ListNode* fast;//慢指针,每次前进一个节点
ListNode* slow;//快指针,每次前进2个节点
slow = fast = pHead ; //两个指针均指向链表头节点
//当没有到达链表结尾,则继续前进
//快指针一次两个节点,慢指针一次一个节点,要求不能在尾节点处相遇,故而有下面while语句中的条件
while (slow != NULL && fast -> next != NULL) {
slow = slow -> next ; //慢指针前进一个节点
fast = fast -> next -> next ; //快指针前进两个节点
if (slow == fast) //若两个指针相遇,且均不为NULL则存在环
return true ;
}
//到达末尾仍然没有相遇,则不存在环
return false ;
}