【5.7】指针算法-快慢指针解决环形链表

一、题目
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引 从 0 开始)。如果 pos 是 -1,则在该链表中没有环。

示例 1
输入 : h e a d = [ 3 , 2 , 0 ,-4 ] , p o s = 1
输出 : tru e
解释 :链表中有一个环,其尾部连接到第二个节点。

示例 2
输入 : h e a d = [ 1 , 2 ] , p o s = 0
输出 : tru e
解释 :链表中有一个环,其尾部连接到第一个节点。


示例 3
输入 : h e a d = [ 1 ] , p o s = -1
输出 : f a l se
解释 :链表中没有环。

二、解题思路

判断链表是否有环可谓是一个常见的话题。其中,最简单的一种方式当属快慢指针法。慢指针每次前进一步,快指针每次前进两步。若二者相遇,则说明链表有环;若其中一个指针为空,则表明链表没有环。

为何快慢指针一定能判断是否有环呢?我们可以如此思考,倘若有环,那么快慢指针最终都会进入环上。假设环的长度为 m,快慢指针最近的间距是 n,如下图所示。

快指针每次前进两步,慢指针每次前进一步,所以每走一次,快慢指针的间距就会缩小一步。在图一中,走 n 次的时候二者会相遇;在图二中,走 m - n 次的时候二者会相遇。

三、代码实现

cpp 复制代码
#include <iostream>
#include <sstream>
#include <vector>

using namespace std;

// 定义链表节点结构
struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

bool hasCycle(ListNode* head) {
    if (head == nullptr)
        return false;

    // 快慢两个指针
    ListNode* slow = head;
    ListNode* fast = head;

    while (fast != nullptr && fast->next != nullptr) {
        // 慢指针每次走一步
        slow = slow->next;
        // 快指针每次走两步
        fast = fast->next->next;
        // 如果相遇,说明有环,直接返回true
        if (slow == fast)
            return true;
    }

    // 否则就是没环
    return false;
}

// 辅助函数:根据输入字符串构建链表
ListNode* buildList(const string& input, int pos) {
    vector<int> values;
    stringstream ss(input);
    string item;

    // 解析输入字符串,提取链表节点的值
    while (getline(ss, item, ',')) {
        values.push_back(stoi(item));
    }

    // 构建链表
    ListNode* head = nullptr;
    ListNode* tail = nullptr;
    vector<ListNode*> nodes;

    for (int val : values) {
        ListNode* node = new ListNode(val);
        nodes.push_back(node);
        if (head == nullptr) {
            head = node;
            tail = node;
        } else {
            tail->next = node;
            tail = node;
        }
    }

    // 设置环
    if (pos != -1 && pos < nodes.size()) {
        tail->next = nodes[pos];
    }

    return head;
}

int main() {
    // 输入字符串
    string input = "3,2,0,-4";
    int pos = 1;

    // 构建链表
    ListNode* head = buildList(input, pos);

    // 检测链表是否有环
    bool result = hasCycle(head);

    // 打印结果
    cout << "Input: head = [" << input << "], pos = " << pos << endl;
    cout << "Output: " << (result ? "true" : "false") << endl;

    return 0;
}

来思索这样一个问题,在此处,快慢指针中快指针每次行进两步,慢指针每次走一步。倘若慢指针仍然每次走一步,快指针每次走三步的话,能否进行判断呢?又或者快指针每次走 m 步,慢指针每次走 n 步,并且 m 不等于 n,在这样的情形下能不能够进行判断呢?

在链表中判断是否有环的问题中,快慢指针是一种常用的方法。通常情况下,快指针每次走2步,慢指针每次走1步。我们来分析一下其他情况:

1. 慢指针每次走1步,快指针每次走3步

在这种情况下,快指针每次走3步,慢指针每次走1步。我们来分析一下是否能判断链表中是否有环。

分析:
  • 相遇条件:假设链表中有环,快指针和慢指针最终会在某个节点相遇。

  • 步数关系:快指针每次走3步,慢指针每次走1步。

  • 环的长度 :假设环的长度为L

  • 相遇点 :假设慢指针在环中走了k步,快指针在环中走了3k步。

结论:
  • 能判断:如果链表中有环,快指针和慢指针最终会在某个节点相遇。因为快指针每次走3步,慢指针每次走1步,快指针会比慢指针多走2步。如果链表中有环,快指针最终会追上慢指针。

2. 快指针每次走m步,慢指针每次走n步,并且m≠n

在这种情况下,快指针每次走m步,慢指针每次走n步,并且m ≠ n。我们来分析一下是否能判断链表中是否有环。

分析:
  • 相遇条件:假设链表中有环,快指针和慢指针最终会在某个节点相遇。

  • 步数关系 :快指针每次走m步,慢指针每次走n步。

  • 环的长度 :假设环的长度为L

  • 相遇点 :假设慢指针在环中走了k步,快指针在环中走了mk步。

结论:
  • 能判断 :如果链表中有环,快指针和慢指针最终会在某个节点相遇。因为快指针每次走m步,慢指针每次走n步,快指针会比慢指针多走m - n步。如果链表中有环,快指针最终会追上慢指针。

总结

  • 慢指针每次走1步,快指针每次走3步:能判断链表中是否有环。

  • 快指针每次走m步,慢指针每次走n步,并且m≠n:能判断链表中是否有环。

无论是快指针每次走3步,慢指针每次走1步,还是快指针每次走m步,慢指针每次走n步,并且m ≠ n,只要链表中有环,快指针最终会追上慢指针。因此,这些情况下都能判断链表中是否有环。

相关推荐
xsyaaaan5 分钟前
代码随想录Day31动态规划:1049最后一块石头的重量II_494目标和_474一和零
算法·动态规划
淀粉肠kk6 分钟前
C++11列表初始化:{}的革命性进化
c++
zhooyu19 分钟前
C++和OpenGL手搓3D游戏编程(20160207进展和效果)
开发语言·c++·游戏·3d·opengl
HAPPY酷22 分钟前
C++ 和 Python 的“容器”对决:从万金油到核武器
开发语言·c++·python
Jay Kay42 分钟前
GVPO:Group Variance Policy Optimization
人工智能·算法·机器学习
Epiphany.5561 小时前
蓝桥杯备赛题目-----爆破
算法·职场和发展·蓝桥杯
YuTaoShao1 小时前
【LeetCode 每日一题】1653. 使字符串平衡的最少删除次数——(解法三)DP 空间优化
算法·leetcode·职场和发展
茉莉玫瑰花茶1 小时前
C++ 17 详细特性解析(5)
开发语言·c++·算法
cpp_25012 小时前
P10570 [JRKSJ R8] 网球
数据结构·c++·算法·题解
cpp_25012 小时前
P8377 [PFOI Round1] 暴龙的火锅
数据结构·c++·算法·题解·洛谷