【leetcode】 142 环形链表II

解法一:双指针法

思路

    题目要求返回链表开始入环的第一个节点,无环则返回NULL。

    首先判断链表有没有环,采用快慢指针法。fast和slow指针都从head出发(注意:在单纯判断链表有没有环的问题中,slow从head出发,fast从head->next出发,是为了使用while (fast == slow)而不必用do while循环来判断相遇条件。而本题一定要都从head出发才能进行后续相遇条件的推导),fast每次走2步,slow每次走1步,若有环,fast最终会和slow相遇;若无环,fast最终会走到链表尾部,走到链表尾部可以用(fast->next == nullptr || fast->next->next == nullptr)来判断,也可以用(fast == nullptr || fast->next == nullptr)来判断,考虑到给定的链表可能出现空链表的情况,故选择用第二种条件进行判断。无环返回nullptr即可。

    若fast和slow相遇,说明链表有环,接下来找出链表尾入环的第一个节点即可。首先分析快慢指针相遇时两个指针分别走过的步数。

    如上图,假设有环链表,环外的节点数为a,环内节点数为b(含入环处节点)。假设相遇时fast指针走过的节点数为f,slow指针走过的节点数为s,那么:

    f = 2s;       —— fast指针每次比slow指针快1倍,总路程是2倍的关系

    f = s + nb; —— 相遇时一定是在环内,且fast指针比slow指针多走n圈才能追上

    由上式可得 s = nb,f = 2nb。

    对于slow指针而言,每次走到环入口处节点时,走过的路程为 a + nb。而fast和slow相遇时,slow走过了nb,说明从相遇时起,slow再走a步就能走到环入口处。一个从head出发的新指针,每次走1步,走a次便会和slow相遇。因此在slow和fast相遇时,再引入新的指针tmp从head出发,tmp和slow相遇时,tmp和slow指向的节点即为环入口处节点。

步骤

1. 初始化slow和fast: ListNode* slow = head; ListNode* fast = head;

2. do {} while (slow != fast); 循环判断链表是否有环,若有则找到slow和fast相遇的点;若无,返回NULL

(1)如果fast走到链表尾部说明无环:if (fast == nullptr || fast->next == nullptr) return nullptr;

(2)slow每次走1步,fast每次走2步: slow = slow->next; fast = fast->next->next;

3. 循环跳出,找到相遇的点后,引入指向head的新指针tmp:ListNode* tmp = head;

4. while循环找到tmp和slow相遇的点:

while (tmp != slow) {
    tmp = tmp->next;
    slow = slow->next;
}

5. tmp、slow相遇,返回tmp指向的节点。 return tmp;

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow = head;
        ListNode* fast = head;

        do {
            if (fast == nullptr || fast->next == nullptr) return nullptr;

            slow = slow->next;
            fast = fast->next->next;
        } while (slow != fast);

        ListNode* tmp = head;

        while (tmp != slow) {
            tmp = tmp->next;
            slow = slow->next;
        }

        return tmp;
    }
};

分析

时间复杂度:O(N)

在第一个do while循环中,相遇时slow指针走过的步数是小于a+b的,第二个while循环中,slow走的步数是a,也是小于a+b的,因此是线性的时间复杂度。

空间复杂度:O(1)

双指针使用常数个额外空间。

 

解法二:哈希表

思路

遍历给定链表,若遇到之前遍历过的节点,说明链表中有环,否则无环。

步骤

1. 创建哈希表,用来存储遍历过的节点:unordered_set<ListNode*> visited

2. 初始化指针ptr指向head: ListNode* ptr = head;

2. while循环遍历链表,循环终止条件为ptr指向链表尾:while (ptr != nullptr) {}

(1) 若在哈希表中找到当前遍历的节点,则返回该节点: if (visited.count(ptr)) return ptr;

(2) 否则将当前节点加入到哈希表中: visited.insert(ptr);

(3) 然后继续向后遍历: ptr = ptr->next;

3. 循环跳出说明遍历完链表都没找到曾经遍历过的节点,直接return nullptr。

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode*> visited;
        ListNode* ptr = head;
        
        while (ptr != nullptr) {
            if (visited.count(ptr)) return ptr;

            visited.insert(ptr);
            ptr = ptr->next;
        }

        return nullptr;
    }
};

分析

时间复杂度:O(N)

空间复杂度:O(N)

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值