[算法] LC160, 206, 234 – 相交, 反转和回文链表


三道链表简单题(但是也有不好想的地方)

LC160 – 相交链表

一、题目描述

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交

输入 / 输出样例:

Plaintext
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'

输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:No intersection

二、思路

(没有题解来源,因为高赞题解基本没区别)

我原本的想法是写一个 getlen(*L) 函数,先获取 A、B 两个链表的长度,计算其长度之差,然后根据长度差挪动指针,使两个指针对齐。

没想到题解仅用五行就解决了问题:

  • 假设 A、B 链表的长度分别为 a+ca + c 和 b+cb + c(其中 c 是公共部分)。
  • 如果我们尝试组合两个链表,有两种方式,AB 和 BA(即 B 接在 A 后,和 A 接在 B 后),其长度分别为 a+c+b+ca + c + b + c 和 b+c+a+cb + c + a + c;
  • 我们注意到:a+c+b=b+c+aa + c + b = b + c + a;
  • 也就是说,如果我们用两个指针,从 A 和 B 的头部开始,分别遍历链表 AB 和链表 BA,在一定能同时到达末尾的同时(因为 AB 和 BA 长度相等),也一定能同时进入最后的公共段 c(因为进入最后的公共段之前,两个指针走过的长度相等)。

所以:

  • 我们只需要创建两个指针 aPtr 和 bPtr,分别从 A、B 的头节点开始遍历;
  • 当 aPtr 遍历到 A 末尾时,跳转到 B 的头部开始遍历;
  • 当 bPtr 遍历到 B 末尾时,跳转到 A 的头部开始遍历;
  • 两个指针一定能找到相交之点(或最后都为 null)。

三、代码 & 复杂度分析

复杂度分析:

  • 时间:最坏的情况下两个链表都要完全遍历,那么时间就是 O(M+N)O(M + N);
  • 空间:空间复杂度为 O(1)O(1),只需常数级的额外变量。


LC206 – 反转链表

一、题目描述

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

二、思路 & 代码

最简单的问题,无需多言

1. 迭代法

使用三个指针 prev、curr 和 next。

每次循环时,先暂存当前结点的下一个结点,令当前结点指向 prev,然后更新 prev 和 curr(都向后挪一位)。

2. 递归法

考虑清楚一个递归问题就是想明白退出条件、子问题和当前层这三件事:

  • 退出条件(也就是不再继续深入、开始回溯的条件):指针 p 到达原本的末尾结点,此时的 p 就是新 head;
  • 子问题:这个问题的子问题就是“把 p 之后的那部分链表反转好”。在子问题完成之后,p->next 这个链表就已经是反向的了(反转之后 p->next 是尾节点);
  • 当前层要做的事:在当前层,只需要调好 p 和 p->next 的连接就行了,即让 p->next 指向自己,然后让自己指向 null。

三、复杂度分析

  • 迭代法的时间复杂度为 O(n),空间复杂度为 O(1);
  • 递归法的时间、空间复杂度均为 O(n)(因为栈区将有 n 个函数入栈)。


LC234 – 回文链表

一、题目描述

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

true

二、思路 & 代码

用快慢指针找到链表的中点,反转链表的后半部分,然后迭代比较链表的前半和反转后的后半。

快慢指针:当快指针 fast 到达末尾时,慢指针 slow 恰好会到达中点处。

这题也可以使用递归,但我不想再研究递归了,感觉不是很有必要

三、复杂度分析

时间:

  • 找中点:遍历 N/2 个节点,耗时 O(N)O(N);
  • 反转后半部分链表:遍历 N/2 个节点,耗时 O(N)O(N);
  • 比较两个链表:最坏情况同样是遍历 N/2 个节点,耗时 O(N)O(N);
  • 恢复原链表:O(N)O(N);
  • 总的时间复杂度是 O(2N)=O(N)O(2N) = O(N)。

空间:只使用常数级的额外空间,复杂度为 O(1)O(1)。