链表入门练习:寻找环的入口节点

NC3 链表中环的入口结点

给出一个链表,如果其中有环,则需找出环的入口节点:即从头结点开始遍历,第一个被访问到的环中的节点。

如下图示,入口节点的值为 2。

方法一:双指针;分析距离

当一个链表有环时,快慢指针必然会进入到环中。想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的(也就是套了一圈)。

当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会追上慢指针。
快慢指针在环上追及
设环的入口节点为 E E E,两指针相遇的节点为 C C C。设头结点 H H H E E E 的距离为 L L L E E E C C C 的距离为 P 1 P_1 P1 C C C E E E 的距离为 P 2 P_2 P2


因为快指针的移动速度是慢指针的两倍,所以可得出:
2 ∗ ( L + P 1 ) = L + ( n + 1 ) ∗ P 1 + n ∗ P 2 , n ∈ N + 2*(L+P_1) = L + (n+1)*P_1 + n*P_2,n \in \mathbb{N}^+ 2(L+P1)=L+(n+1)P1+nP2nN+

其中 n n n 是正整数,代表相遇时快指针已经完整绕环 n n n 圈了。将上式移项可得:
L = ( n − 1 ) ∗ P 1 + n ∗ P 2 , n ∈ N + L = (n-1)*P_1 + n*P_2,n \in \mathbb{N}^+ L=(n1)P1+nP2nN+

设有两个指针 h h h c c c 分别从 H H H C C C 同时出发,每次移动一个节点。当 h h h 到达 E E E 时, c c c 也必然到达 E E E

于是有了一种解决方案:先用快慢指针找到节点 C C C。然后再用两个慢指针分别从 H H H C C C 出发,两慢指针相遇的节点即为入口节点。

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        ListNode *fast = pHead, *slow = pHead;
        while (fast != nullptr && fast->next != nullptr) {
            fast = fast->next->next;
            slow = slow->next;
            
            // 相遇了
            if (fast == slow) {
                ListNode *entry = pHead;
                while (entry != slow) {
                    entry = entry->next;
                    slow = slow->next;
                }
                return entry;
            }
        }
        return nullptr;
    }
};

方法二:修改 next 指针

还有一种比较 hack 的做法,仅在 Linux 下用 C++ 验证过,不确定能否在其他操作系统及编程语言下实现


上图描述了 32/64 位系统对内存地址的划分,不难发现,用户空间地址的最高位全部为 0。我们可利用这一点表示某个节点是否被访问过:

  • 节点指针域的最高位为 0,表示该节点未被访问过。
  • 节点指针域的最高位为 1,表示该节点已经被访问过了。

利用上述标记方法,可以用一个指针找出入口节点。下述代码可在 64 位系统上正确运行。

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
    	const uint64_t mask = 0x8000000000000000;
        while (pHead != nullptr && pHead->next != nullptr) {
            uint64_t &adr = *(uint64_t*)(&(pHead->next));
            if (adr & mask) {
                return pHead;
            }
            pHead = pHead->next;
            adr |= mask;
        }
        return nullptr;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值