链表环问题解题技巧与实现

引言

链表环问题是算法领域中的经典问题,它不仅涉及到链表操作,还可能涉及到双指针、哈希表等算法的应用。本文将对力扣上的一些经典链表环问题进行分析,并提供详细的C++代码实现。

环形链表检测

141. 环形链表

问题描述:给定一个链表,判断链表中是否有环。

问题分析:使用快慢指针法,慢指针每次移动一步,快指针每次移动两步,若存在环,则快指针会追上慢指针。

代码实现

using namespace std;

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *slow = head, *fast = head;
        while (fast && fast->next) {
            slow = slow->next;           // 慢指针移动一步
            fast = fast->next->next;     // 快指针移动两步
            if (slow == fast) return true; // 相遇说明存在环
        }
        return false; // 快指针到达链表尾部,说明无环
    }
};

142. 环形链表 II

问题描述:给定一个链表,如果链表中有环,请返回环的入口。

问题分析:在快慢指针相遇后,一个指针重置为头节点,两个指针以相同速度移动,再次相遇时即为环的入口。

代码实现

using namespace std;

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *slow = head, *fast = head;
        // 使用快慢指针法找到环的相遇点
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast) break;
        }
        if (!fast || !fast->next) return nullptr; // 无环

        slow = head; // 重置慢指针到头节点
        while (slow != fast) {
            slow = slow->next;
            fast = fast->next;
        }
        return slow; // 再次相遇的节点即为环的入口
    }
};

160. 相交链表

问题描述:找出两个链表相交的起始节点。

问题分析:首先找到两个链表的长度,然后让较长链表的指针先移动相应步数,之后两个指针以相同速度移动,相遇时即为相交起始点。

代码实现

using namespace std;

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *pA = headA, *pB = headB;
        // 找到链表尾节点
        while (pA) pA = pA->next;
        while (pB) pB = pB->next;
        
        // 如果pA或pB为nullptr,说明至少有一个链表为空,直接返回nullptr
        if (!pA || !pB) return nullptr;
        
        // 让pA和pB指向较长链表的头节点
        if (pA == pB) pA = headA, pB = headB; // 两个链表长度相同
        else if (pA->next) pA = headA; // pA较长
        else pB = headB; // pB较长
        
        // 以相同速度移动两个指针,相遇点即为相交起始节点
        while (pA != pB) {
            pA = pA->next;
            pB = pB->next;
        }
        return pA;
    }
};

725. 分隔链表

问题描述:给定一个链表和一个值 x ,请将链表中小于 x 的节点排在大于等于 x 的节点之前。

问题分析:使用快慢指针找到链表中第一个大于等于 x 的节点,然后重新连接链表。

代码实现

 
using namespace std;

class Solution {
public:
    ListNode* splitList(ListNode* head, int x) {
        ListNode *leftTail = nullptr, *rightHead = nullptr, *rightTail = nullptr;
        ListNode *current = head, *prev = nullptr;

        // 遍历链表,分离小于x和大于等于x的节点
        while (current) {
            ListNode *next = current->next;
            current->next = nullptr; // 断开连接
            if (current->val < x) {
                if (!leftTail) leftTail = current; // 第一个小于x的节点
                if (prev) prev->next = current; // 连接到前一个小于x的节点
                prev = current;
            } else {
                if (!rightHead) rightHead = current; // 第一个大于等于x的节点
                if (rightTail) rightTail->next = current; // 连接到前一个大于等于x的节点
                rightTail = current;
            }
            current = next;
        }

        // 连接左右两部分链表
        if (prev) prev->next = rightHead;
        if (rightTail) rightTail->next = nullptr; // 最后一个节点指向nullptr
        return leftTail ? leftTail : rightHead;
    }
};

21. 合并两个有序链表

问题描述:将两个有序链表合并为一个新的有序链表。

问题分析:递归或迭代地比较两个链表的节点,将较小的节点添加到新链表中。

代码实现

using namespace std;

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (!l1) return l2;
        if (!l2) return l1;
        
        // 使用哑节点简化边界条件处理
        ListNode dummy(0);
        ListNode *tail = &dummy;
        
        while (l1 && l2) {
            if (l1->val < l2->val) {
                tail->next = l1;
                l1 = l1->next;
            } else {
                tail->next = l2;
                l2 = l2->next;
            }
            tail = tail->next;
        }
        
        // 连接剩余的节点
        tail->next = l1 ? l1 : l2;
        
        return dummy.next; // 返回新链表的头节点
    }
};

876. 链表相似性

问题描述:如果两个链表在某个点相遇,返回相交节点的引用。

问题分析:使用双指针法,同时遍历两个链表,如果它们相遇或到达空节点,则找到了相交点。

代码实现

using namespace std;

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if (!head || !head->next) return true;
        
        // 快慢指针找到链表中点
        ListNode *slow = head, *fast = head, *prev = nullptr;
        while (fast && fast->next) {
            fast = fast->next->next;
            ListNode *temp = slow;
            slow = slow->next;
            slow->next = prev; // 反转前半部分链表
            prev = temp;
        }
        
        // 如果fast不为空,说明链表长度为奇数,slow向前多走一步
        if (fast) slow = slow->next;
        
        // 比较前半部分和反转后的后半部分是否相同
        while (prev) {
            if (prev->val != slow->val) return false;
            prev = prev->next;
            slow = slow->next;
        }
        return true;
    }
};

结语

通过上述问题的分析与实现,我们可以看到链表环问题的多样性和解决策略。无论是使用快慢指针法检测环的存在,还是通过双指针法找到相交链表的起始节点,理解算法背后的逻辑是解决问题的关键。

以上就是本次关于链表中的常见的环问题的求解,通过可以的练习,是能够很快掌握一门技术的。

给博主点个赞吧,后续更多知识的分享

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值