一、求链表的中间节点
876. 链表的中间结点 - 力扣(LeetCode)
快慢指针法:
分别定义两个节点的指针(pSlow和pFast)指向链表的第一个节点,然后两个指针一起往后遍历链表,pFast一次移动两个节点,pSlow一次移动一个节点,通过路程差可知,当pFast遍历完链表时,pSlow只遍历了一半的节点数,此时pSlow指向的节点就是我们要找的中间节点。
链表节点数为奇数和偶数时的情况:
具体代码:
class Solution {
public:
ListNode* middleNode(ListNode* head) {
//快慢指针法
ListNode* pFast = head;
ListNode* pSlow = head;
//注意先判断pFast所指的节点是否为空再判断其next指针是否为空,否则会出现解引用空指针的情况
while(pFast != nullptr && pFast->next != nullptr)
{
pFast = pFast->next->next;//移动两步
pSlow = pSlow->next;//移动一步
}
return pSlow;
}
};
二、反转链表
206. 反转链表 - 力扣(LeetCode)
定义三个指针(pPrev,pCur,pNext),分别指向前一个节点,当前节点,和后一个节点,我们的pPrev和pNext一开始指向空,因为一开始pCur是反转后的尾结点,而头结点有可能为空指针,一开始令pNext = pCur->next会有解引用空指针的风险。每次遍历将当前节点的下一个节点指针改为指向前一个节点指针pPrev,然后更新三个指针的指向,往后移动一步。在更新指针时,因为pCur->next的指向已经改变,要往后遍历就需要保存之前pCur->next所指向的节点,所以我们需要在每次修改pCur->next的指向时先用pNext保存pCur->next的节点地址,当修改好指向后,就可以找到原来pCur的下一个节点了。重复操作直到pCur遍历完整个链表就完成了链表的反转。
下面是示例图:
具体代码:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pPrev = nullptr;
ListNode* pCur = head;
ListNode* pNext = nullptr;
while(pCur != nullptr)
{
//保存pCur后面的节点
pNext = pCur->next;
//将pCur连接上一个节点
pCur->next = pPrev;
//更新指针指向
pPrev = pCur;
pCur = pNext;
}
//此时pPrev就是头结点,pCur指向空
return pPrev;
}
};
三、判断回文链表
LCR 027. 回文链表 - 力扣(LeetCode)
由于单链表不能逆向遍历,判断回文结构时有点麻烦,可以使用暴力算法来解决这个题目,但是,面试题对这道题有严格要求,需要设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
这个时候需要使用我们之前写的求链表的中间节点和反转链表的算法来完成时间复杂度为O(n),额外空间复杂度为O(1)的要求了。具体思路就是先找到这个链表的中间节点,将中间节点后面的子链表视为一个新的链表,将其反转,然后将反转后的结果和前一半的子链表进行对比,看每一个节点是否都相同,否则就不是回文链表。在我们依次比较完后,我们还需要将反转的子链再反转回去,恢复链表原来的结构,防止内存泄漏,因为在函数外头只有第一个子链的头结点,而第二个子链的头结点是找不到的,就会导致第二个子链的节点内存泄漏,并且破坏了链表的原有结构。
将链表拆分成两个子链表:
奇数个:
偶数个:
然后就比较两个链表是否相同就可以了。
具体代码:
class Solution {
public:
//快慢指针求中间节点
ListNode* middleNode(ListNode* head)
{
//快慢指针法
ListNode* pFast = head;
ListNode* pSlow = head;
//注意先判断pFast所指的节点是否为空再判断其next指针是否为空,否则会出现解引用空指针的情况
while(pFast != nullptr && pFast->next != nullptr)
{
pFast = pFast->next->next;//移动两步
pSlow = pSlow->next;//移动一步
}
return pSlow;
}
ListNode* reverseList(ListNode* head)
{
ListNode* pPrev = nullptr;
ListNode* pCur = head;
ListNode* pNext = nullptr;
while(pCur != nullptr)
{
//保存pCur后面的节点
pNext = pCur->next;
//将pCur连接上一个节点
pCur->next = pPrev;
//更新指针指向
pPrev = pCur;
pCur = pNext;
}
//此时pPrev就是头结点,pCur指向空
return pPrev;
}
bool isPalindrome(ListNode* head)
{
//找到中间节点
ListNode* pMid = middleNode(head);
//逆转中间节点后的链表
ListNode* reHead = reverseList(pMid);
//遍历判断两个链表是否相等
ListNode* pList1 = head;
ListNode* pList2 = reHead;
while(pList1 != nullptr && pList2 != nullptr)
{
if(pList1->val != pList2 ->val)
return false;
pList1 = pList1->next;
pList2 = pList2->next;
}
//将两个链表恢复原状,防止内存泄露
reverseList(reHead);
return true;
}
};