指针的面试题

判断链表中是否有环

题目

描述

判断给定的链表中是否有环。如果有环则返回true,否则返回false。

数据范围:链表长度 0≤n≤10000,链表中任意节点的值满足

∣val∣<=100000
要求:空间复杂度 O(1),时间复杂度 O(n)

输入分为两部分,第一部分为链表,第二部分代表是否有环,然后将组成的head头结点传入到函数里面。-1代表无环,其它的数字代表有环,这些参数解释仅仅是为了方便读者自测调试。实际在编程时读入的是链表的头节点。

例如输入{3,2,0,-4},1时,对应的链表结构如下图所示:
在这里插入图片描述

可以看出环的入口结点为从头结点开始的第1个结点(注:头结点为第0个结点),所以输出true。
示例1

输入: {3,2,0,-4},1

返回值: true

说明:第一部分{3,2,0,-4}代表一个链表,第二部分的1表示,-4到位置1(注:头结点为位置0),即-4->2存在一个链接,组成传入的head为一个带环的链表,返回true

示例2

输入:{1},-1

返回值:false

说明: 第一部分{1}代表一个链表,-1代表无环,组成传入head为一个无环的单链表,返回false

示例3

输入:{-1,-7,7,-4,19,6,-9,-5,-2,-5},6

返回值:true

代码

import java.util.*;

/**
 * 定义了链表节点的类。
 * 每个节点包含一个整数值和一个指向下一个节点的引用。
 */
class ListNode {
    int val; // 节点存储的值
    ListNode next; // 指向下一个节点的引用

    // 构造函数,用于创建一个新的节点,初始化其值和下一个节点的引用
    ListNode(int x) {
        val = x;
        next = null;
    }
}

public class Solution {
    /**
     * 检测链表中是否存在环。
     * @param head 链表的头节点。
     * @return 如果链表中存在环,返回true;否则返回false。
     */
    public boolean hasCycle(ListNode head) {
        // 如果头节点为空,则链表不存在环
        if (head == null) {
            return false;
        }
        
        // 初始化两个指针fast和slow,它们都指向头节点
        ListNode fast = head;
        ListNode slow = head;
        
        // 使用循环进行检测,直到fast和slow相遇或者fast到达链表末尾
        do {
            // 如果fast的下一个节点或者下下个节点为空,说明链表没有环
            if (fast.next == null || fast.next.next == null) {
                return false;
            }
            
            // 移动fast指针,每次移动两步
            fast = fast.next.next;
            
            // 移动slow指针,每次移动一步
            slow = slow.next;
        } while (fast != slow); // 如果fast和slow相遇,说明存在环
        
        // 如果循环结束,fast没有和slow相遇,说明链表没有环
        return true;
    }
}

检测链表中是否存在环

使用的是快慢指针(也称为龟兔赛跑算法)来检测链表中是否存在环。以下是快慢指针相遇的逻辑详细解释:

  1. 初始化:我们有两个指针fastslow,它们都从链表的头节点开始。

  2. 移动指针

    • fast指针每次移动两步(即fast = fast.next.next)。
    • slow指针每次移动一步(即slow = slow.next)。
  3. 相遇条件

    • 如果链表中存在环,那么fast指针和slow指针最终会相遇。这是因为fast指针移动的速度是slow指针的两倍,所以它们会在环内相遇。
    • 如果链表中没有环,fast指针会先到达链表的末尾(即fastfast.nextnull),此时fastslow不会相遇。
  4. 检测逻辑

    • 在每次循环中,我们首先检查fast指针的下一个节点或下下个节点是否为null。如果为null,则说明链表没有环,直接返回false
    • 如果fast指针的下一个节点或下下个节点不为null,则继续移动fastslow指针。
    • fastslow指针相遇时,说明链表中存在环,返回true
  5. 结束条件

    • 如果fast指针在移动过程中到达了链表的末尾(即fastfast.nextnull),则说明链表中没有环,返回false

通过这种方式,快慢指针算法能够有效地检测出链表中是否存在环,并且时间复杂度为O(n),空间复杂度为O(1)。


链表中存在环

如果链表中存在环,并且fastslow指针相遇,接下来的处理取决于你想要完成的具体任务。以下是一些常见的处理方式:

  1. 检测环的长度

    • 一旦fastslow相遇,你可以继续移动其中一个指针(通常是slow),每次移动一步,直到再次遇到环的起点。这样你可以计算出环的长度。
  2. 找到环的入口

    • 你可以在fastslow相遇后,将其中一个指针(通常是fast)重新指向头节点,然后同时移动fastslow,每次移动一步。当它们再次相遇时,相遇点就是环的入口。
  3. 删除环

    • 如果你的目标是删除环,可以在找到环的入口后,通过调整指针来删除环。例如,你可以将环的最后一个节点的next指针设置为null
  4. 重新初始化指针

    • 如果你只是需要检测环的存在,那么在fastslow相遇后,你可以直接返回true

以下是一些示例代码,展示如何在fastslow相遇后找到环的入口:

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }
        ListNode fast = head;
        ListNode slow = head;

        while (fast != slow) {
            if (fast == null || fast.next == null) {
                return false;
            }
            fast = fast.next.next;
            slow = slow.next;
        }

        // fast 和 slow 相遇,重新初始化 fast 指向头节点
        fast = head;

        // 当 fast 和 slow 再次相遇时,相遇点就是环的入口
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }

        // 返回 true 表示链表中有环
        return true;
    }
}

在这个示例中,一旦fastslow相遇,我们将fast重新指向头节点,然后同时移动fastslow,直到它们再次相遇。这时的相遇点就是环的入口。这种方法可以确保你找到环的入口,而不仅仅是检测环的存在。


想检测链表中是否存在环,而不需要找到环的入口

如果你只想检测链表中是否存在环,而不需要找到环的入口,你可以简化代码,只保留检测环存在的逻辑。以下是修改后的代码示例:

public class Solution {
    /**
     * 检测链表中是否存在环。
     * @param head 链表的头节点。
     * @return 如果链表中存在环,返回true;否则返回false。
     */
    public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }

        // 初始化两个指针fast和slow,它们都指向头节点
        ListNode fast = head;
        ListNode slow = head;

        // 使用循环进行检测,直到fast和slow相遇或者fast到达链表末尾
        while (fast != null && fast.next != null) {
            // 移动fast指针,每次移动两步
            fast = fast.next.next;

            // 移动slow指针,每次移动一步
            slow = slow.next;

            // 如果fast和slow相遇,说明存在环
            if (fast == slow) {
                return true;
            }
        }

        // 如果fast到达链表末尾,说明没有环
        return false;
    }
}

在这个修改后的代码中,我们只保留了循环,直到fast指针到达链表的末尾或fastslow相遇。如果fastslow相遇,函数立即返回true,表示链表中存在环。如果fast到达链表的末尾(即fastfast.nextnull),则循环结束,函数返回false,表示链表中没有环。

这种方法的时间复杂度是O(n),空间复杂度是O(1),非常高效。

好的,下面列举几道 C++ 指针面试题供参考。 1. 指针和引用有什么区别? 指针和引用都可以用来访问内存中的变量,但是它们有以下几点区别: - 指针可以被赋值为 `nullptr`,而引用不行。 - 指针可以在运行时指向不同的变量,而引用在声明时就必须绑定到一个变量上,并且不能更改绑定的变量。 - 指针可以进行算术运算和解引用操作,而引用不能。 - 指针可以被重新赋值为指向不同类型的变量,而引用必须与原始变量类型匹配。 2. 指针和数组有什么关系? 指针和数组有着紧密的关系,因为数组名其实就是数组首元素的地址。例如,下面的代码定义了一个数组并初始化: ```c++ int arr[] = {1, 2, 3, 4, 5}; ``` 可以通过下标访问数组元素,也可以通过指针访问数组元素。例如,下面的代码使用指针访问数组元素: ```c++ int* p = arr; for (int i = 0; i < 5; i++) { cout << *(p + i) << " "; } ``` 这里的 `p` 是一个指向 `int` 类型的指针,它指向数组 `arr` 的首元素。在循环中,我们通过指针访问数组元素,其中 `*(p + i)` 表示指针 `p` 加上 `i` 个偏移量后所指向的元素。 3. 如何避免空指针引用? 空指针引用是一种常见的程序错误,可以通过以下几种方式避免: - 在使用指针之前,先将其初始化为 `nullptr`。 - 在使用指针之前,先进行空指针判断。 - 使用智能指针,可以自动管理指针的生命周期,并且可以避免空指针引用。 例如,下面的代码演示了如何进行空指针判断: ```c++ int* p = nullptr; if (p != nullptr) { *p = 10; } ``` 在这个示例代码中,我们先将指针 `p` 初始化为 `nullptr`,然后在使用指针之前,先进行空指针判断。由于 `p` 是空指针,所以不会执行赋值操作,从而避免了空指针引用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

布说在见

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值