环形链表和相交链表

原来双指针还能这么用吗……

其实要这么说,很容易就能想起小学的追及问题,在环形跑道上跑步的两个人,跑得快的人到最后反而会跑到跑得慢的人的后面。

我感觉我们直觉的思路偏向于线性和二维的矩形。这么说有点玄学的成分,但是如果我现在说平面,有多少人的第一反应是圆形?大部分人想到的应该还是类似于白纸一样的矩形平面吧。这就是很有意思的地方了,并没有人规定二维平面一定要是矩形的啊?

这种线性思维的一个直接结果,就是我们喜欢(也习惯)用额外的空间去存储遍历过程中的状态。如果再玄学一点,闭环闭环,状态在内部是漏不出去的,而线性的会漏出去,所以需要保存。

环形链表这个题,我觉得大部分人的思路应该是像我一样,用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;
    }
}
发布了110 篇原创文章 · 获赞 132 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/HermitSun/article/details/103012896