每日一题——力扣876. 链表的中间结点(举一反三+思想解读+逐步优化)

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

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


目录

我的写法:

代码点评:

代码点评:

时间复杂度分析:

空间复杂度分析:

总结:

我要更强!

哲学和编程思想:

举一反三

实现分步处理和增量更新:

通过变化的观察发现新的模式:

优化循环和递归操作:

动态调整处理速度或资源分配:

在算法设计中寻找平衡点:

将问题抽象化:

创新和改进现有算法:


​​​​​​题目链接

我的写法:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* middleNode(struct ListNode* head){
    struct ListNode* precede=head->next;
    if(precede==NULL)  // 链表仅1个结点
        return head;
    else if(precede->next==NULL)  // 链表仅2个结点
        return precede;
    
    for(;;)
    {
            head=head->next;
            if(precede)
            {
                precede=precede->next;
                if(precede&&precede->next)
                    precede=precede->next;
                else
                    return head;
            }
            else
                return head;
    }
}

代码点评:

这段代码实现了在一个单链表中找到中间节点的功能。下面是对这段代码的专业点评,包括时间复杂度和空间复杂度的分析。

代码点评:

  1. 边界条件处理:代码首先处理了链表只有1个或2个节点的情况,这是很好的做法,因为它确保了代码在特殊情况下的正确性。
  2. 双指针法:代码使用了两个指针,head和precede,来遍历链表。head每次移动一步,而precede每次移动两步。这样,当precede到达链表末尾时,head恰好指向链表的中间节点。这是一种常见且高效的方法,称为快慢指针法。
  3. 循环条件:循环条件for(;;)是一个无限循环,依赖于循环体内的条件来退出循环。这种写法简洁,但可能不如显式写明循环条件直观。
  4. 指针移动逻辑:precede指针的移动逻辑稍微有些复杂,需要仔细检查以确保其正确性。在每次循环中,precede首先移动一步,然后检查它是否还有下一个节点,如果有,则再移动一步。这种逻辑确保了precede每次移动两步。
  5. 返回值:循环结束后,函数返回head指针,即指向链表中间节点的指针。

时间复杂度分析:

  • 时间复杂度:O(N)。在最坏的情况下,我们需要遍历整个链表的一半。假设链表有N个节点,那么我们需要遍历N/2个节点。因此,时间复杂度是O(N)。

空间复杂度分析:

  • 空间复杂度:O(1)。这段代码只使用了常数级别的额外空间(两个指针),没有使用任何数据结构来存储额外的节点信息。因此,空间复杂度是O(1)。

总结:

这段代码是一个高效的解决方案,它正确地实现了在单链表中找到中间节点的功能。时间复杂度为O(N),空间复杂度为O(1),符合解决此类问题的最佳实践。不过,代码的循环条件和指针移动逻辑可以稍作优化,以提高代码的可读性。例如,可以使用显式的循环条件,并将precede指针的移动逻辑封装到一个单独的函数中,以提高代码的模块化和清晰度。


我要更强!

对于找到单链表的中间节点这个问题,使用快慢指针法已经是最优的解决方案,因为它的时间复杂度是O(N),空间复杂度是O(1)。这种方法不需要遍历链表两次,也不需要额外的空间来存储链表的副本或计算链表的长度。

下面是使用快慢指针法的优化代码,它去掉了不必要的计数器,直接使用fast指针的移动来控制slow指针的移动:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* middleNode(struct ListNode* head) {
    // 初始化快慢指针,都指向链表头部
    struct ListNode* slow = head;
    struct ListNode* fast = head;

    // 使用快慢指针法遍历链表
    // 快指针每次移动两步,慢指针每次移动一步
    // 当快指针到达链表末尾时,慢指针恰好指向中间节点
    while (fast != NULL && fast->next != NULL) {
        slow = slow->next; // 慢指针移动一步
        fast = fast->next->next; // 快指针移动两步
    }

    // 返回慢指针,即中间节点
    return slow;
}

这段代码的逻辑更加简洁,去掉了计数器i,直接在循环中使用fast指针的移动来控制slow指针的移动。这样不仅减少了代码量,还提高了代码的可读性。时间复杂度和空间复杂度与之前的代码相同,都是O(N)和O(1)。

哲学和编程思想:

使用快慢指针法来找到单链表的中间节点体现了以下哲学和编程思想:

  1. 分治思想:快慢指针法可以看作是一种分治策略,通过将遍历任务分配给两个指针,一个快速遍历,一个慢速遍历,从而在遍历过程中找到中间节点。这种思想在处理复杂问题时,将问题分解为更小、更易管理的部分,然后分别解决这些部分。
  2. 相对运动:快慢指针法利用了相对运动的概念。在物理学中,相对运动是指两个物体以不同的速度或方向移动。在这个算法中,快指针和慢指针的相对速度差使得它们在遍历链表时能够相遇在中间节点。
  3. 空间换时间:在某些情况下,为了提高时间效率,我们可能会牺牲一些空间资源。然而,快慢指针法在找到中间节点的过程中,没有使用额外的空间来存储链表的副本或计算链表的长度,因此它是一种空间效率高的解决方案。
  4. 迭代与递归:虽然快慢指针法是一个迭代过程,但它体现了迭代和递归的基本思想,即通过重复一个过程来解决问题。在这个算法中,重复的过程是快慢指针的移动,直到找到中间节点。
  5. 平衡与优化:快慢指针法在时间和空间复杂度之间找到了一个平衡点。它通过一次遍历找到中间节点,避免了多次遍历的需要,同时保持了空间复杂度为常数级别。
  6. 抽象与具体:在编程中,我们经常需要将具体问题抽象为更一般的模型。快慢指针法就是将找到链表中间节点的问题抽象为一个指针移动的问题,通过控制指针的移动来解决具体问题。
  7. 算法设计:快慢指针法是一种特定的算法设计技巧,它体现了算法设计中的创新和效率追求。通过巧妙地设计指针的移动规则,我们能够高效地解决特定类型的问题。

总之,快慢指针法不仅是一种高效的算法技巧,也体现了多种哲学和编程思想,包括分治、相对运动、空间换时间、迭代与递归、平衡与优化、抽象与具体以及算法设计等。这些思想在计算机科学和编程实践中具有广泛的应用。


举一反三

当然,理解一个概念的深层原理可以让你在面对新的挑战时更灵活地应用该概忈。这里是一些基于快慢指针法哲学和编程思想的具体技巧,可以帮助你在不同的编程和问题解决场景中举一反三:

实现分步处理和增量更新:

  • 在处理数据流或实时数据时,使用类似于快慢指针的思想,其中一个指针负责收集数据,另一个负责处理数据。
  • 例如,在股票市场分析中,一个指针可以是实时价格更新,而另一个可以是移动平均线。

通过变化的观察发现新的模式:

  • 在时间序列分析或连续数据集中,通过比较不同速度的变化(例如短期与长期趋势),可以发现重要的转变点或异常。
  • 例如,在金融分析中,快速移动平均和慢速移动平均的交叉点可以被用来指示潜在的买入或卖出信号。

优化循环和递归操作:

  • 当你需要在一个集合中搜索两个相关元素时,考虑是否可以用两个移动的指针来代替两层嵌套循环。
  • 例如,在寻找数组中的两数之和时,可以使用一前一后两个指针来避免不必要的重复搜索。

动态调整处理速度或资源分配:

  • 在资源分配或系统设计中,可以根据任务的紧急程度调整资源的分配速度,类似于调整快慢指针的速度。
  • 例如,在网络流量控制中,可以根据数据包的重要性调整其传输的优先级,以确保关键任务的响应时间最短。

在算法设计中寻找平衡点:

  • 研究不同算法的时间复杂度和空间复杂度,尝试找到适合当前问题的最佳平衡。
  • 例如,在排序中,根据数据集的大小和内存限制,选择适当的排序算法(如快速排序、归并排序或堆排序)。

将问题抽象化:

  • 尝试从具体问题抽象出一般性原则,然后应用到其他类似问题上。
  • 例如,在设计一个系统时,将具体的功能需求抽象成模块和服务,这可以帮助你在其他系统设计中复用这些模块。

创新和改进现有算法:

  • 分析现有算法,并思考如何通过引入新的思想或组件来提高其性能。
  • 例如,在图算法中,如果你需要找到最短路径,可以通过启发式方法(如A*搜索)来优化传统的BFS或Dijkstra算法。

以上为本节所有内容,感谢阅读~

相关推荐
半盏茶香18 分钟前
【C语言】分支和循环详解(下)猜数字游戏
c语言·开发语言·c++·算法·游戏
徐子童22 分钟前
双指针算法习题解答
算法
想要打 Acm 的小周同学呀31 分钟前
LRU缓存算法
java·算法·缓存
劲夫学编程2 小时前
leetcode:杨辉三角
算法·leetcode·职场和发展
毕竟秋山澪2 小时前
孤岛的总面积(Dfs C#
算法·深度优先
浮生如梦_4 小时前
Halcon基于laws纹理特征的SVM分类
图像处理·人工智能·算法·支持向量机·计算机视觉·分类·视觉检测
励志成为嵌入式工程师5 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
师太,答应老衲吧6 小时前
SQL实战训练之,力扣:2020. 无流量的帐户数(递归)
数据库·sql·leetcode
捕鲸叉6 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer6 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法