原来双指针还能这么用吗……
其实要这么说,很容易就能想起小学的追及问题,在环形跑道上跑步的两个人,跑得快的人到最后反而会跑到跑得慢的人的后面。
我感觉我们直觉的思路偏向于线性和二维的矩形。这么说有点玄学的成分,但是如果我现在说平面,有多少人的第一反应是圆形?大部分人想到的应该还是类似于白纸一样的矩形平面吧。这就是很有意思的地方了,并没有人规定二维平面一定要是矩形的啊?
这种线性思维的一个直接结果,就是我们喜欢(也习惯)用额外的空间去存储遍历过程中的状态。如果再玄学一点,闭环闭环,状态在内部是漏不出去的,而线性的会漏出去,所以需要保存。
环形链表这个题,我觉得大部分人的思路应该是像我一样,用Hash表(暴力遍历是不是有点……):
bool hasCycle(ListNode *head)
{
unordered_map<ListNode *, int> map;
while (head && head->next)
{
if (!map[head])
{
++map[head];
}
else
{
return true;
}
head = head->next;
}
return false;
}
这个思路肯定没什么问题,就是会花费额外的空间。能不能利用这个环形空间的特性呢?当然是能的。不过我没想到就是了……
bool hasCycle(ListNode *head)
{
if (head == nullptr || head->next == nullptr)
{
return false;
}
ListNode *slow = head;
ListNode *fast = head->next;
while (slow != fast)
{
if (!fast || !fast->next)
{
return false;
}
else
{
fast = fast->next->next;
}
}
return true;
}
这就回到一开始提到的追及问题了。
然后就是相交链表。这个一开始的思路又是Hash表,并且用得很顺利:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
ListNode *node = nullptr;
unordered_map<void *, ListNode *> map;
while (headA)
{
map[headA] = headA;
headA = headA->next;
}
while (headB)
{
auto map_node = map.find(headB);
if (map_node != map.end())
{
node = map_node->second;
break;
}
headB = headB->next;
}
return node;
}
前一个我都没想到,这一个就更想不到了。相交链表居然能变成一个环形,莫非所有相交的空间结构都有成为环形的潜质?
官方题解给出了一个解释:
创建两个指针 pApA 和 pBpB,分别初始化为链表 A 和 B 的头结点。然后让它们向后逐结点遍历。
当 pApA 到达链表的尾部时,将它重定位到链表 B 的头结点 (你没看错,就是链表 B); 类似的,当 pBpB 到达链表的尾部时,将它重定位到链表 A 的头结点。
若在某一时刻 pApA 和 pBpB 相遇,则 pApA/pBpB 为相交结点。
想弄清楚为什么这样可行, 可以考虑以下两个链表: A={1,3,5,7,9,11} 和 B={2,4,9,11},相交于结点 9。 由于 B.length (=4) < A.length (=6),pBpB 比 pApA 少经过 22 个结点,会先到达尾部。将 pBpB 重定向到 A 的头结点,pApA 重定向到 B 的头结点后,pBpB 要比 pApA 多走 2 个结点。因此,它们会同时到达交点。
如果两个链表存在相交,它们末尾的结点必然相同。因此当 pApA/pBpB 到达链表结尾时,记录下链表 A/B 对应的元素。若最后元素不相同,则两个链表不相交。
但是并没有给出实现。所以我自己实现了一个:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
ListNode *A_end = nullptr;
ListNode *B_end = nullptr;
ListNode *A_head = headA;
ListNode *B_head = headB;
ListNode *node = nullptr;
// 还没有遍历完一次
// 或者两个链表的结尾是相同的,这说明一定会有交点
while (!A_end || !B_end ||
A_end == B_end)
{
// 判断为空
if (!headA || !headB)
{
break;
}
// 找到交点
if (headA == headB)
{
node = headA;
break;
}
if (headA->next)
{
headA = headA->next;
}
// 遍历了一遍
else
{
A_end = headA;
headA = B_head;
}
if (headB->next)
{
headB = headB->next;
}
// 遍历了一遍
else
{
B_end = headB;
headB = A_head;
}
return node;
}
}