每日一题——力扣141. 环形链表(举一反三+思想解读+逐步优化)

news2025/1/21 4:53:58


一个认为一切根源都是“自己不够强”的INTJ

个人主页:用哲学编程-CSDN博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数

Python-3.12.0文档解读

目录

我的写法

专业点评

时间复杂度分析

空间复杂度分析

总结

我要更强

方法2:使用哈希表

方法3:修改节点结构(不推荐)

方法4:反转链表(不推荐)

结论

哲学和编程思想

快慢指针 (Floyd's Cycle-Finding Algorithm)

哈希表法

修改节点结构法

反转链表法

举一反三

技巧1:双指针技术

技巧2:哈希表技术

技巧3:状态标记法

技巧4:反转操作

技巧5:递归与迭代

技巧6:分治法

技巧7:贪心算法

技巧8:动态规划

技巧9:回溯法

技巧10:模拟法


题目链接


我的写法

/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */
 
bool hasCycle(struct ListNode *head) {
    // 定义两个指针,slow 和 quick,初始都指向链表的头节点
    struct ListNode *slow = head;
    struct ListNode *quick = head;
    
    // 如果链表为空或只有一个节点,则不可能存在环,返回 false
    if (!head || !head->next) {
        return false;
    }
    
    // 使用 for 循环来遍历链表,循环条件是 slow 和 quick 及其下一个节点都不为 NULL
    for (; slow && slow->next && quick && quick->next && quick->next->next;) {
        // slow 指针每次移动一步
        slow = slow->next;
        // quick 指针每次移动两步
        quick = quick->next->next;
        
        // 如果 slow 和 quick 指针相遇,则说明链表中存在环
        if (slow == quick) {
            return true;
        }
    }
    
    // 如果循环结束后没有相遇,说明链表中不存在环,返回 false
    return false;
}

 

这段代码实现了一个检测单链表是否存在环的功能,采用的是经典的快慢指针(Tortoise and Hare)算法。下面是对这段代码的专业点评及其时间复杂度和空间复杂度分析。

专业点评

  1. 代码结构清晰:
    • 代码结构合理,使用变量命名清晰,便于理解。
    • 初始检查部分有效地处理了特殊情况(空链表或只有一个节点的链表)。
  2. 循环条件:
    • 循环条件健全,确保在链表遍历过程中不会发生空指针引用错误。
    • for 循环中使用多个条件确保 slow 和 quick 及其下一个节点都有效。
  3. 快慢指针逻辑:
    • 快指针每次移动两步,慢指针每次移动一步,有效地检测链表中的环。
    • 如果存在环,快慢指针必定会相遇。
  4. 代码简洁性:
  • 代码简洁,逻辑明确,没有冗余的操作。

时间复杂度分析

  • 最坏情况:在没有环的情况下,快指针和慢指针遍历链表的每个节点。
    • 在这种情况下,快指针遍历节点数大约为 n/2,慢指针遍历节点数为 n,其中 n 是链表的节点数。
    • 因此,时间复杂度为 O(n)。
  • 最优情况:在存在环的情况下,快慢指针会在环内相遇。
  • 最多需要 O(n) 的时间,因为快指针和慢指针最终会在环内相遇。

综合来看,时间复杂度为 O(n)。

空间复杂度分析

  • 该算法只使用了两个额外的指针变量 slow 和 quick,不依赖于链表的大小。
  • 因此,空间复杂度为 O(1)。

总结

  • 优点:
    • 时间复杂度为 O(n),在所有链表节点遍历完毕前即可得出结果。
    • 空间复杂度为 O(1),只用到了常数额外空间。
    • 代码简洁清晰,易于理解。
  • 缺点:
  • 无明显缺点,该算法是检测链表环的经典方法,几乎是最优解。

改进建议

  • 从代码功能和效率来看,这段代码已经相当高效且简洁,没有明显需要改进的地方。如果需要进一步优化,可以考虑在注释方面增加更详细的解释,以便于其他开发者更好地理解代码逻辑。

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(struct ListNode *head) {
    // 定义两个指针,slow 和 quick,初始都指向链表的头节点
    struct ListNode *slow = head;
    struct ListNode *quick = head;
    
    // 如果链表为空或只有一个节点,则不可能存在环,返回 false
    if (!head || !head->next) {
        return false;
    }
    
    // 使用 for 循环来遍历链表,循环条件是 slow 和 quick 及其下一个节点都不为 NULL
    for (; slow && slow->next && quick && quick->next && quick->next->next;) {
        // slow 指针每次移动一步
        slow = slow->next;
        // quick 指针每次移动两步
        quick = quick->next->next;
        
        // 如果 slow 和 quick 指针相遇,则说明链表中存在环
        if (slow == quick) {
            return true;
        }
    }
    
    // 如果循环结束后没有相遇,说明链表中不存在环,返回 false
    return false;
}

这段代码实现了高效的环检测功能,是生产环境中常用的方法之一。


我要更强

在链表检测环的问题中,代码已经使用了快慢指针(Floyd’s Cycle-Finding Algorithm),这已经是时间复杂度 (O(n)) 和空间复杂度 (O(1)) 的最优解。因此,从理论上讲,无法进一步优化这两个指标。

不过,为了完整性和拓展视野,提供几个不同的方法,即使它们在时间和空间复杂度上未必能优于快慢指针法。下面将详细介绍这些方法并提供相应的代码和注释。

方法2:使用哈希表

利用哈希表记录每个访问过的节点,如果再次遇到已经访问过的节点,则说明存在环。时间复杂度为 (O(n)),空间复杂度为 (O(n))。

#include <stdbool.h>
#include <stdlib.h>

// Definition for singly-linked list.
struct ListNode {
    int val;
    struct ListNode *next;
};

// Definition of the hash table node.
struct HashNode {
    struct ListNode *node;
    struct HashNode *next;
};

// Simple hash function for the hash table.
unsigned int hash(struct ListNode *node) {
    return ((unsigned long)node) % 1024;
}

bool hasCycle(struct ListNode *head) {
    if (!head) return false;
    
    struct HashNode *hashTable[1024] = { NULL };
    
    while (head) {
        unsigned int hashIndex = hash(head);
        struct HashNode *entry = hashTable[hashIndex];
        
        while (entry) {
            if (entry->node == head) {
                return true; // Cycle detected
            }
            entry = entry->next;
        }
        
        struct HashNode *newEntry = (struct HashNode *)malloc(sizeof(struct HashNode));
        newEntry->node = head;
        newEntry->next = hashTable[hashIndex];
        hashTable[hashIndex] = newEntry;
        
        head = head->next;
    }
    
    return false; // No cycle detected
}

方法3:修改节点结构(不推荐)

这种方法临时修改节点结构,标记访问过的节点。虽然时间复杂度和空间复杂度均为 (O(n)),但破坏了链表的原始结构。

// Definition for singly-linked list.
struct ListNode {
    int val;
    struct ListNode *next;
};

bool hasCycle(struct ListNode *head) {
    while (head) {
        // 如果当前节点的值已经被标记,则说明存在环
        if (head->val == -1) {
            return true;
        }
        // 标记当前节点
        head->val = -1;
        head = head->next;
    }
    return false; // 如果遍历完成后没有发现环
}

方法4:反转链表(不推荐)

这种方法通过反转链表来检测环,但会破坏链表的结构,且复杂度较高。

// Definition for singly-linked list.
struct ListNode {
    int val;
    struct ListNode *next;
};

struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* prev = NULL;
    struct ListNode* curr = head;
    
    while (curr) {
        struct ListNode* nextTemp = curr->next;
        curr->next = prev;
        prev = curr;
        curr = nextTemp;
    }
    
    return prev;
}

bool hasCycle(struct ListNode* head) {
    if (!head || !head->next) {
        return false;
    }
    
    struct ListNode* reversedHead = reverseList(head);
    
    // 如果反转后的链表头指针等于原链表头指针,说明有环
    return reversedHead == head;
}

结论

在检测链表环的问题上,快慢指针算法已经是最优解,具有 (O(n)) 时间复杂度和 (O(1)) 空间复杂度。其他方法虽然也能实现环检测,但在性能和实用性上不如快慢指针高效。


哲学和编程思想

在解决链表环检测问题的过程中,可以看到一些重要的哲学和编程思想。这些思想不但帮助我们理解和解决问题,还可以提升编写高效、可靠代码的能力。

快慢指针 (Floyd's Cycle-Finding Algorithm)

哲学思想:

  1. 对称性与均衡:通过两个不同步的指针(快指针和慢指针),我们可以同时观察链表的不同部分。这个方法体现了对称性和均衡的哲学思想。
  2. 渐进性:慢指针逐步遍历链表每一步,而快指针以更快的速度前进。这个思想类似于在哲学中逐步接近真理的过程。

编程思想:

  1. 双指针技术:利用两个指针以不同速度遍历数据结构。
  2. 空间效率:仅使用固定数量的额外空间,使算法在空间复杂度上最优。

哈希表法

哲学思想:

  1. 记忆与记录:通过记录已经访问过的节点,我们可以检测到重复。这个方法类似于人类记忆和识别重复事件的过程。
  2. 关联性:哈希表的使用体现了关联性哲学,通过快速定位和匹配记录来检测环的存在。

编程思想:

  1. 哈希表:利用哈希表的快速查找特性来记录和检测节点。
  2. 时间效率:虽然增加了空间复杂度,但提高了时间效率。

修改节点结构法

哲学思想:

  1. 变更与保留:通过临时变更节点的值或结构来标记已访问的节点,体现了变更与保留的哲学思想。
  2. 牺牲与利益:在一定程度上牺牲链表的完整性来换取检测环的简便性。

编程思想:

  1. 状态标记:通过修改数据结构的状态来记录访问情况。
  2. 时间效率:虽然这种方法简单直接,但不推荐因为它破坏了原始数据结构。

反转链表法

哲学思想:

  1. 反转与复原:通过反转链表实现检测环,体现了反转与复原的哲学思想。
  2. 对立统一:链表的反转与检测过程展现了对立统一的哲学观点。

编程思想:

  1. 反转操作:利用链表反转的技巧来检测环。
  2. 结构变化:尽管能实现目标,但会破坏链表结构,因此在实际应用中不推荐。

总结

这些方法展示了不同的哲学和编程思想,包括对称性、记忆与记录、变更与保留、反转与复原等。在解决问题时,理解这些思想不仅帮助选择适当的算法,还可以提高编程能力和对问题的深刻理解。


举一反三

为了帮助你在编程中举一反三,下面提供了一些基于哲学和编程思想的技巧。这些技巧将帮助你在不同情境下应用这些思想:

技巧1:双指针技术

哲学思想:对称性与均衡

应用场景:

  • 排序数组中的问题,如两数之和、三数之和。
  • 快慢指针用于链表问题,如检测链表环、找到中间节点。
  • 滑动窗口技术用于子数组问题,如最大子数组和、最小覆盖子串。

思维方式:

  • 考虑从两端同时向中间推进。
  • 或者从头和尾同时进行遍历,找到符合条件的值。

技巧2:哈希表技术

哲学思想:记忆与记录

应用场景:

  • 查找问题,如查找数组中是否存在重复元素。
  • 计数问题,如字符频率统计、两数组交集。
  • 映射问题,如从一个集合映射到另一个集合。

思维方式:

  • 利用快速查找的特性,记录已经访问过的元素及其信息。
  • 适用于需要频繁查找和更新数据的问题。

技巧3:状态标记法

哲学思想:变更与保留

应用场景:

  • 动态规划问题,如最小路径和、背包问题。
  • 图的遍历问题,如深度优先搜索(DFS)、广度优先搜索(BFS)。
  • 状态转移问题,如记忆化搜索、状态压缩。

思维方式:

  • 通过记录状态来避免重复计算。
  • 使用数组或其他数据结构保存中间结果。

技巧4:反转操作

哲学思想:反转与复原

应用场景:

  • 数据结构的反转,如反转链表、反转字符串。
  • 双端队列问题,如使用双指针反转数组的一部分。
  • 操作顺序颠倒问题,如栈的应用。

思维方式:

  • 考虑如何从尾到头进行操作,或从两端向中间靠拢。
  • 适用于需要反转或改变顺序的问题。

技巧5:递归与迭代

哲学思想:自相似性

应用场景:

  • 分治算法,如快速排序、归并排序。
  • 树的遍历,如前序、中序、后序遍历。
  • 数学问题,如斐波那契数列、组合数计算。

思维方式:

  • 递归方法通常用于自然分治的问题。
  • 确保理解递归的基线条件和递归条件。
  • 考虑如何将递归转换为迭代来提高效率。

技巧6:分治法

哲学思想:整体与部分

应用场景:

  • 排序算法,如快速排序、归并排序。
  • 搜索算法,如二分搜索、最近点对问题。
  • 动态规划问题中的优化,如矩阵链乘法。

思维方式:

  • 将问题分解为更小的子问题,解决每个子问题后合并结果。
  • 适用于可以自然分割的问题。

技巧7:贪心算法

哲学思想:局部最优与全局最优

应用场景:

  • 路径问题,如最短路径、最小生成树。
  • 资源分配问题,如活动选择、背包问题。
  • 排序问题,如区间调度、会议室安排。

思维方式:

  • 在每一步选择局部最优解,希望通过一系列局部最优解达到全局最优。
  • 适用于贪心选择性质和最优子结构的问题。

技巧8:动态规划

哲学思想:阶段性最优

应用场景:

  • 序列问题,如最长上升子序列、最长公共子序列。
  • 数组问题,如最大子数组和、矩阵路径问题。
  • 计数问题,如硬币兑换、字符串匹配。

思维方式:

  • 分阶段求解问题,每个阶段的解依赖于上一个阶段的解。
  • 使用状态转移方程和初始条件来解决问题。

技巧9:回溯法

哲学思想:试探与回溯

应用场景:

  • 组合问题,如全排列、组合数。
  • 棋盘问题,如八皇后、数独。
  • 路径问题,如迷宫求解、图的 Hamiltonian 路径。

思维方式:

  • 通过试探法尝试所有可能的解,当发现当前试探不成功时,进行回溯。
  • 适用于需要穷举所有解或找到一个可行解的问题。

技巧10:模拟法

哲学思想:逐步逼近

应用场景:

  • 操作模拟,如旋转矩阵、文字处理。
  • 数学问题,如大数计算、数列生成。
  • 游戏问题,如棋盘游戏、博弈问题。

思维方式:

  • 逐步模拟问题的步骤,接近最终结果。
  • 考虑如何将实际问题的步骤逐步实现。

通过理解和应用这些技巧,可以在不同类型的编程问题中举一反三,从而提高解决问题的效率和灵活性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1719382.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cdo | 常用命令

整理一下平时经常会使用的cdo命令 如何来更改netcdf数据中的变量名呢&#xff1f; 假设我现在有一个sst月平均数据,希望将里面的变量名称sst修改为sst_new netcdf oisst_monthly { dimensions:lat 180 ;lon 360 ;time UNLIMITED ; // (476 currently)nbnds 2 ; variable…

V90 PN总线伺服通过FB285速度控制实现正弦位置轨迹运动(解析法和数值法对比测试)

V90总线伺服相关内容请参考专栏系列文章,这里不在详述 1、V90伺服PN总线速度随动控制 V90伺服PN总线速度随动控制(手摇轮功能)_手摇轮可以接总线plc吗?-CSDN博客文章浏览阅读632次。V90PN总线控制相关内容,请参考下面文章链接:博途1200/1500PLC V90 PN通信控制 (FB284功能…

【html】用html模拟微信布局

您做的这个模拟微信布局的作品很不错,使用了Flexbox布局来实现元素的灵活排列。以下是关于您代码的一些分析和建议: 效果图: 代码分析: 全局样式重置: 您使用了* { margin: 0; padding: 0; }来重置所有元素的边距。这是一个常见的做法,可以避免不同浏览器默认样式的差…

ARM32开发——LED点灯

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 点灯的两种方式灌入电流法输出电流法扩展板点灯点灯方式点亮LED1-4完整实现 点灯的两种方式 不同颜色LED&#xff0c;达到相同亮度…

LLM Agent提效揭秘4:多智能体协作工作流深度剖析

在此之前&#xff0c;我们已经从论文到代码深度解读了吴恩达&#xff1a;GPT-4Agents Workflow&#xff0c;性能比肩GPT-5的三种大语言模型Agent工作流&#xff1a;反思、工具使用和规划。今天我们将深入最后一种Agent工作流&#xff1a;多智能体协作工作流。 想象一个未来&am…

3.spring源码:refresh()第一个方法prepareRefresh()

重点: 1.了解prepareRefresh()方法的作用:就是为容器刷新做了准备工作 2.整体了解refresh()方法 synchronized加锁的原因:刷新和销毁( "refresh" and "destroy" 都是一个完整的过程,需要加锁 改方法进入可以看到是空的,是为了扩展用的 该方法我们可以自己重…

visual studio code 全局搜索

VScode写代码的时候&#xff0c;会经常性的需要进行查找代码&#xff0c;那么怎么在Visual Studio Code中进行查找呢&#xff0c;下面就来大家vscode全局搜索的方法。 想要在vscode全局搜索进行全局搜索&#xff0c;使用快捷键CTRLSHIFTF即可进行搜索&#xff0c;也可以在左边…

GSM信令流程(附着、去附着、PDP激活、修改流程)

1、联合附着流程 附着包括身份认证、鉴权等 2、去附着流程 用户发起去附着 SGSN发起去附着 HLR发起去附着 GSSN使用S4发起去附着 3、Activation Procedures(PDP激活流程) 4、PDP更新或修改流程 5、Deactivate PDP Context 6、RAU(Routeing Area Update)流程 7、鉴权加…

NATS-研究学习

NATS-研究学习 文章目录 NATS-研究学习[toc]介绍说明提供的服务内容各模式介绍测试使用发布订阅&#xff08;Publish Subscribe&#xff09;请求响应&#xff08;Request Reply&#xff09;队列订阅&分享工作&#xff08;Queue Subscribers & Sharing Work&#xff09;…

vue+vant移动端显示table表格加横向滚动条

vant移动端显示table效果&#xff0c;增加复选框&#xff0c;可以进行多选和全选。加横向滚动条&#xff0c;可以看全部内容。 <template><div class"app-container"><div class"nav_text" style"position: relative;"><…

简单介绍QKeySequenceEdit的使用

QKeySequenceEdit是Qt框架中的一个便捷用户界面组件&#xff0c;用于输入和显示键盘快捷键。它提供了一个简单的界面&#xff0c;允许用户输入一个键盘快捷键&#xff0c;并将其显示为一个字符串。这在需要配置快捷键的应用程序中非常有用。在本文中&#xff0c;我们将详细介绍…

【C++】——string模拟实现

前言 string的模拟实现其实就是增删改查&#xff0c;只不过加入了类的概念。 为了防止与std里面的string冲突&#xff0c;所以这里统一用String。 目录 前言 一 初始化和销毁 1.1 构造函数 1.2 析构函数 二 迭代器实现 三 容量大小及操作 四 运算符重载 4.1 bool…

二叉树的顺序实现-堆

一、什么是堆 在数据结构中&#xff0c;堆&#xff08;Heap&#xff09;是一种特殊的树形数据结构&#xff0c;用数组存储&#xff0c;通常被用来实现优先队列。 堆具有以下特点&#xff1a; 堆是一棵完全二叉树&#xff08;Complete Binary Tree&#xff09;&#xff0c;即…

uni-app的网络请求库封装及使用(同时支持微信小程序)

其实uni-app中内置的uni.request()已经很强大了&#xff0c;简单且好用。为了让其更好用&#xff0c;同时支持拦截器&#xff0c;支持Promise 写法&#xff0c;特对其进行封装。同时支持H5和小程序环境&#xff0c;更好用啦。文中给出使用示例&#xff0c;可以看到使用变得如此…

算法(六)计数排序

文章目录 计数排序技术排序简介算法实现 计数排序 技术排序简介 计数排序是利用数组下标来确定元素的正确位置的。 假定数组有10个整数&#xff0c;取值范围是0~10&#xff0c;可以根据这有限的范围&#xff0c;建立一个长度为11的数组。数组下标从0到10&#xff0c;元素初始…

智慧校园有哪些特征

随着科技的飞速进步&#xff0c;教育领域正经历着一场深刻的变革。智慧校园&#xff0c;作为这场变革的前沿代表&#xff0c;正在逐步重塑我们的教育理念和实践方式。它不仅仅是一个概念&#xff0c;而是一个集成了物联网、大数据、人工智能等先进技术的综合生态系统&#xff0…

Nginx(openresty) 开启目录浏览 以及进行美化配置

1 nginx 安装 可以参考:Nginx(openresty) 通过lua结合Web前端 实现图片&#xff0c;文件&#xff0c;视频等静态资源 访问权限验证&#xff0c;进行鉴权 &#xff0c;提高安全性-CSDN博客 2 开启目录浏览 location /file{alias /data/www/; #指定目录所在路径autoindex on; …

差旅游记|绵阳印象:与其羡慕他人,不如用力活好自己。

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 来绵阳之前同事就问: “雷工&#xff0c;能吃辣嘛&#xff1f;”。 “还行&#xff0c;能吃点辣。” “那你去了四川别说能吃点辣&#xff0c;那边的能吃点比跟你说的能吃点不太一样” 01 你好 今天打车&#xff0c;上…

信息学奥赛初赛天天练-17-阅读理解-浮点数精准输出与海伦公式的巧妙应用

PDF文档公众号回复关键字:20240531 1 2023 CSP-J 阅读程序1 阅读程序&#xff08;程序输入不超过数组成字符串定义的范围&#xff1a;判断题正确填√&#xff0c;错误填&#xff1b;除特殊说明外&#xff0c;判断题1.5分&#xff0c;选择题3分&#xff0c;共计40分&#xff0…

python 如何判断一组数呈上升还是下降趋势

目录 一、引言 二、基本概念 三、判断方法 直接比较法 斜率法 统计检验法 四、方法比较与选择 五、案例分析 六、总结 一、引言 在数据处理和分析的领域中&#xff0c;判断一组数是否呈现上升或下降趋势是一个重要的环节。这不仅能够帮助我们了解数据的基本变化…