【一刷《剑指Offer》】面试题 15:链表中倒数第 k 个结点

简介: 【一刷《剑指Offer》】面试题 15:链表中倒数第 k 个结点

力扣对应题目链接:LCR 140. 训练计划 II - 力扣(LeetCode)

核心考点 :链表,前后指针的使用,边界条件检测。


一、《剑指Offer》内容


二、分析问题

较优解题思路:

  1. 题目中的链表是单链表,也就不能从后往前进行。
  2. 可以定义两个指针(快慢双指针),fast 指针先走 k 步,再让 slow 指针跟在后面,使用 “前后指针” 的方式,当 fast 指针到达结尾,slow 指针也就是倒数第 k 个节点。(其实原理就是:fast 指针和 slow 指针之间一直保持着 k 的距离)

三、代码

//力扣AC代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* trainingPlan(ListNode* head, int cnt) {
        ListNode* fast=head;
        ListNode* slow=head;
        while(cnt>0 && fast!=nullptr)
        {
            fast=fast->next;
            cnt--;
        }
        while(fast!=nullptr)
        {
            fast=fast->next;
            slow=slow->next;
        }
        return slow;
    }
};
 
//严谨写法
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* trainingPlan(ListNode* head, int cnt) {
        if(head==nullptr || cnt<0) return head;
        ListNode* fast=head;
        ListNode* slow=head;
        while(cnt>0 && fast!=nullptr)
        {
            fast=fast->next;
            cnt--;
        }
        while(fast!=nullptr)
        {
            fast=fast->next;
            slow=slow->next;
        }
        if(cnt>0) return nullptr;
        else return slow;
    }
};

四、扩展

力扣对应题目链接:876. 链表的中间结点 - 力扣(LeetCode)


1、分析题目

  • 如果链表中结点总数为奇数,返回中间结点。
  • 如果结点总数是偶数,返回中间两个结点的任意一个。

为了解决这个问题,我们也可以向上面那道题一样定义一个 fast 和 slow 指针,同时从链表的头结点出发,slow 指针一次走一步,fast 指针一次走两步。当 fast 指针走到链表的末尾时,slow 指针正好在链表的中间。


2、代码

//力扣AC代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* slow=head;
        ListNode* fast=head;
        while(fast && fast->next)
        {
            slow=slow->next;
            fast=fast->next->next;
        }
        return slow;
    }
};

力扣对应题目链接:141. 环形链表 - 力扣(LeetCode)


1、分析题目

这道题也一样,定义两个指针 fast 和 slow,同时从链表的头结点出发,slow 指针一次走一步,fast 指针一次走两步。

  • 如果 fast 指针追上了 slow 指针,那么链表就是环形链表。
  • 如果 fast 的指针走到了链表的末尾(m_pNext 指向 NULL)都没有追上 slow 指针,那么链表就不是环形链表。

2、代码

(1)写法一(推荐)

//力扣AC代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* slow=head;
        ListNode* fast=head;
        while(fast && fast->next)
        {
            slow=slow->next;
            fast=fast->next->next;
            if(fast==slow) return true;
        }
        return false;
    }
};

(2)写法二

//力扣AC代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head==NULL || head->next==NULL) return false;
        ListNode* slow=head;
        ListNode* fast=head->next;
        while(fast!=slow)
        {
            if(fast==NULL || fast->next==NULL) return false;
            slow=slow->next;
            fast=fast->next->next;
        }
        return true;
    }
};

五、举一反三

当我们用一个指针遍历链表不能解决问题的时候,可以尝试用两个指针(快慢双指针)来遍历链表。可以让 fast 指针遍历快一些(比如一次在链表上走两步),或者让它先在链表上走若干步。

 


相关文章
|
7月前
|
机器学习/深度学习 算法
24. 两两交换链表中的节点, 19.删除链表的倒数第N个节点 ,面试题 02.07. 链表相交
1. **两两交换链表中的节点**:通过引入虚拟头结点,使所有节点都能采用统一的交换逻辑,避免对头结点单独处理。 2. **删除链表的倒数第N个节点**:利用双指针技巧,让快慢指针保持N个节点的距离,当快指针到达末尾时,慢指针正好指向待删除节点的前一个节点。 3. **链表相交**:先计算两链表长度并调整起点,确保从相同距离末尾的位置开始遍历,从而高效找到相交节点或确定无交点。 以上方法均在时间复杂度和空间复杂度上进行了优化,适合用于理解和掌握链表的基本操作及常见算法设计思路。
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
185 0
|
存储 算法 安全
HashMap常见面试题(超全面):实现原理、扩容机制、链表何时升级为红黑树、死循环
HashMap常见面试题:红黑树、散列表,HashMap实现原理、扩容机制,HashMap的jd1.7与jdk1.8有什么区别,寻址算法、链表何时升级为红黑树、死循环
|
Python
【Leetcode刷题Python】剑指 Offer 22. 链表中倒数第k个节点
Leetcode题目"剑指 Offer 22. 链表中倒数第k个节点"的Python解决方案,使用双指针法找到并返回链表中倒数第k个节点。
214 5
|
Python
【Leetcode刷题Python】剑指 Offer 18. 删除链表的节点
Leetcode题目"剑指 Offer 18. 删除链表的节点"的Python解决方案,通过使用双指针法找到并删除链表中值为特定数值的节点,然后返回更新后的链表头节点。
219 4
|
存储 算法 Python
【面试题】合井K个升序链表
【面试题】合井K个升序链表
117 0
|
存储 Java
【Java集合类面试十】、HashMap中的循环链表是如何产生的?
在多线程环境下,HashMap在扩容时如果发生条件竞争,元素的插入顺序可能形成循环链表,导致死循环。
|
安全 编译器 C++
【剑指offer】2.2编程语言(p22-p25)——面试题1:string赋值运算函数
【剑指offer】2.2编程语言(p22-p25)——面试题1:string赋值运算函数
143 0
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?