
最直接的想法,就是把链表装成数组,然后再判断是否回文。
使用数组判断回文的时候,使用双指针法,分别从前往后、从后往前遍历,判断数字是否相等即可。
分为如下几步:
1 2 3 4 5 ,跳两次,最后快指针到达节点5,慢指针到达节点3。然后划分链表前半部分有2个节点1 2,后半部分有3个节点3 4 5。nullptr指针,慢指针到达中间节点。比如有4个节点1 2 3 4,跳两次,最后快指针到达4的下一个位置nullptr,慢指针到达节点3。然后划分链表前半部分有两个节点1 2,后半部分有2个节点3 4。如图所示:
最后给出代码如下:
bool isPalindrome(ListNode *head)
{
if(head == nullptr || head->next == nullptr)
return true;
// 1.寻找分割链表的位置
ListNode* fast = head;
ListNode* slow = head;
ListNode* pre = head;
while(fast && fast->next)
{
pre = slow; // 先备份pre节点
slow = slow->next; // slow走一步
fast = fast->next->next; // fast走两步
}
// 2.此时slow为后半部分链表的head, pre为前半部分链表的结尾
pre->next = nullptr; // 把前半部分链表切分出来,方便后面while循环遍历
ListNode* list1 = head; // 前半部分链表顺序保持不变
ListNode* list2 = reverseList(slow); // 翻转后半部分链表
// 3.以前半部分链表长度为基准,判断前后链表是否相等
while(list1 != nullptr)
{
if(list1->val != list2->val)
return false;
list1 = list1->next;
list2 = list2->next;
}
// 运行到这里说明前后链表相等,因此原链表就是回文链表
return true;
}
// 翻转链表
ListNode* reverseList(ListNode* head)
{
ListNode* pre = nullptr;
ListNode* cur = head;
while(cur != nullptr)
{
ListNode* tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}

链表存在的问题就是只能单向的访问,所以如果想访问最后一个节点就需要把整个链表遍历完。但是这里又需要不断切换访问前后的节点,所以直接使用链表来访问是不行的,必须借助中间的数据结构。
这里就是借助数组,先把链表的所有节点存储到数组中,然后遍历链表的节点的时候遍历数组就可以了,因为数组是可以利用索引随便访问前后的位置的。
直接给出代码如下,前后访问数组的时候使用双指针就可以实现。
// 1.解法一:使用数组存储ListNode,然后使用首尾双指针来修改list的指向
void reorderList(ListNode *head)
{
if(head == nullptr)
return;
// 1.存储节点
vector<ListNode*> vec;
ListNode* cur = head;
// 注意这里不需要存头结点,因为头结点位置是不动的
while(cur->next != nullptr)
{
vec.push_back(cur->next);
cur = cur->next;
}
// 2.前后双指针重构链表
cur = head; // 头节点,不动
int left = 0; // 指向vec中节点的左指针
int right = vec.size() - 1; // 指向vec中节点的右指针
int count = 0; // 表示当前该存储左指针还是右指针
while(left <= right)
{
if(count % 2 == 0) // 存储右指针
cur->next = vec[right--];
else // 存储左指针
cur->next = vec[left++];
cur = cur->next; // 移动到下一个位置,修改下个节点的指向
count++;
}
// 3.最后一个节点指向nullptr
cur->next = nullptr;
}
这种方法和数组是同一个思想,只不过是使用双端队列,这样不用使用数组的双指针前后访问,其实本质上是没有什么不同的。
// 2.解法二:使用双向队列存储,这样就可以首尾弹出,不用使用双指针了
void reorderList(ListNode *head)
{
if(head == nullptr)
return;
// 1.存储节点
deque<ListNode*> que;
ListNode* cur = head;
// 注意这里不需要存头结点,因为头结点位置是不动的
while(cur->next != nullptr)
{
que.push_back(cur->next);
cur = cur->next;
}
// 2.使用deque前后访问节点,每访问一个节点之后就把它弹出
cur = head; // 头节点,不动
int count = 0; // 表示当前该存储左指针还是右指针
while(!que.empty())
{
if(count % 2 == 0) // 存储右指针
{
cur->next = que.back();
que.pop_back();
}
else // 存储左指针
{
cur->next = que.front();
que.pop_front();
}
count++;
cur = cur->next;
}
// 3.最后一个节点指向nullptr
cur->next = nullptr;
}
上面两种方法都使用了额外的空间来存储链表的节点,所以空间复杂度是O(n)。如果链表非常长,上面的方法就比较浪费空间了,虽然存储的是指针。
所以一个进阶的解法就是要求原地修改链表,而不能有额外的空间消耗。
方法就是将链表分割成两个链表,然后把第二个链表反转,之后在通过两个链表拼接成新的链表。
如下图所示:

切割链表的操作和上一道题目是一样的,但是代码随想录中切割的解法和上一题又稍微有一点不同:
上一题是对于链表切割的时候,如果是偶数节点自然两个链表都是相等的,如果是奇数节点则前链表长度比后链表长度 小1。
但是这道题代码随想录上给出的解答是如果奇数节点则前链表长度比后链表长度 大1。
这个实际是有道理的,可以方便编程。但是连续两道分割链表的题目又使用两种分割方法,感觉太繁琐不容易记忆。所以这里自己还是使用上一道题目的分割方法,即如果是奇数节点,则前链表长度比后链表长度 小1。
1 2 3 4 5:则前链表是1 2,后链表是3 4 5,翻转后是5 4 3。由于前链表的第一个节点一定是固定的,所以新的链表遍历的时候不需要访问前链表的第一个节点。那么访问的时候后链表5 4 3都需要访问,前链表只需要访问一个2。所以奇数情况下,需要访问的节点个数后链表比前链表多2个节点。1 2 3 4:则前链表是1 2,后链表是3 4,翻转后是4 3。同理,后链表需要访问4 3,前链表只需要访问2。所以偶数情况下,需要访问的节点个数后链表比前链表多1个节点。因此在遍历链表的时候,分两个步骤:
最后给出代码如下, 并不难,注意细节即可。
// 3.解法三:拆分链表,把链表分成前后两部分,然后把后半部分翻转,然后依次
void reorderList(ListNode *head)
{
if(head == nullptr || head->next == nullptr)
return;
// 1.快慢指针来拆分链表
ListNode* fast = head;
ListNode* slow = head;
ListNode* pre = head;
while(fast && fast->next)
{
pre = slow;
slow = slow->next;
fast = fast->next->next;
}
// 2.至此,pre是前一个链表的结尾,slow是后一个链表的开头
// 注意cur2可能和cur1长度相等,也可能比cur1长度大1
pre->next = nullptr; // 把前一个链表的尾巴截断,方便后面遍历
ListNode* cur1 = head->next; // 注意为什么cur1从第二个开始:因为第一个一定是head,不会动的
ListNode* cur2 = reverseList(slow); // 后一个链表翻转
// 3.开始遍历,重构链表
int count = 0;
ListNode* cur = head;
// 注意此时要遍历的链表:如果原链表是奇数(假设是5),则分割后前2后3,要遍历的前1后3
// 如果原链表是偶数(假设是4),则分割后前2后2,要遍历的前1后2。因此不管怎样都是前链表先遍历完
while(cur1)
{
if(count % 2 == 0) // 偶数
{
cur->next = cur2;
cur2 = cur2->next;
}
else
{
cur->next = cur1;
cur1 = cur1->next;
}
cur = cur->next;
count++;
}
// 4.处理cur2剩余的链表
while(cur2)
{
cur->next = cur2;
cur2 = cur2->next;
cur = cur->next;
}
// 最后将链表收尾
cur->next = nullptr;
}
// 翻转链表
ListNode* reverseList(ListNode* head)
{
if(head == nullptr || head->next == nullptr)
return head;
ListNode* cur = head;
ListNode* pre = nullptr;
while(cur)
{
ListNode* tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}

这道题目在之前做过的 142环形链表II 中已经做过了,那道题目还要更难一些,因为判断了是否有环之后,还需要判断入环的节点,而这道题只需要判断是否有环,所以是142环形链表II的基础。
判断是否有环的思想就是使用快慢指针:如果在遍历过程中这两个指针相遇,则有环;否则无环。
直接给出代码如下,注意访问指针的时候判断是否有效。
bool hasCycle(ListNode *head)
{
ListNode* fast = head;
ListNode* slow = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(fast == slow)
return true;
}
return false;
}