面试 8:快慢指针法玩转链表算法面试(二)

简介: 昨天在最后给大家留了拓展题,不知道大家有没有思考完成,其实南尘说有巨坑是吓大家的啦,实际上也没什么。我们来继续看看昨天这个拓展题。面试题:给定单链表的头结点,删除单链表的倒数第 k 个结点。

昨天在最后给大家留了拓展题,不知道大家有没有思考完成,其实南尘说有巨坑是吓大家的啦,实际上也没什么。我们来继续看看昨天这个拓展题。

面试题:给定单链表的头结点,删除单链表的倒数第 k 个结点。

前面的文章见链接:面试 7:面试常见的链表算法捷径(一)

这个题和前面的文章中增加了一个操作,除了找出来这个结点,我们还要删除它。删除一个结点,想必大家必定也知道:要想操作(添加、删除)单链表的某个结点,那我们还得知道这个节点的前一个节点。所以我们要删除倒数第 k 个结点,就必须要找到倒数第 k+1 个结点。然后把倒数第 k+1 个元素的 next 变量 p.next 指向 p.next.next。

我们找到倒数第 k 个结点的时候,先让 fast 走了 k-1 步,然后再让 slow 变量和 fast 同步走,它们之间就会一直保持 k-1 的距离,所以当 fast 到链表尾结点的时候,slow 刚刚指向的是倒数第 k 个结点。

本题由于我们要知道倒数第 k+1 个结点,所以得让 fast 先走 k 步,待 fast 指向链表尾结点的时候,slow 正好指向倒数第 k+1 个结点。

我们简单思考一下临界值:

  1. 当 k = 1 的时候,删除的值是尾结点。我们让 fast 先走 1 步,当 fast.next 为尾结点的时候,倒数第 k+1 个结点正好是我们的倒数第二个结点。我们删除 slow.next,并让slow.next 指向 slow.next.next = null,满足条件。
  2. 当 k > len 的时候,我们要找的倒数第 k 个元素不存在,直接出错;
  3. 当 1 < k < len 的时候,k 最大为 len-1 的时候,fast 移动 len-1 步,直接到达尾结点,此时,snow 指向头结点。删除倒数第 k 个元素,即删除正数第 2 个结点即可;
  4. 当 k = len 的时候比较特殊,当 fast 移动 len 步的时候,已经指向了 fast.next = null,此时我们其实要删除的是头结点,直接返回 head.next 即可。

所以我们自然能得到这样的代码。

public class Test07 {
    public static class LinkNode {
        int data;
        LinkNode next;

        public LinkNode(int data) {
            this.data = data;
        }
    }

    private static LinkNode delTheSpecifiedReverse(LinkNode head, int k) {
        LinkNode slow = head;
        LinkNode fast = head;
        if (fast == null) {
            throw new RuntimeException("your linkNode is null");
        }
        // 先让 fast 先走 k 步
        for (int i = 0; i < k; i++) {
            if (fast == null) {
                // 说明输入的 k 已经超过了链表长度,直接报错
                throw new RuntimeException("the value k is too large.");
            }
            fast = fast.next;
        }
        // fast == null ,说明已经到了尾结点后面的空区域,说明要删除的就是头结点。
        if (fast == null) {
            return head.next;
        }
        while (fast.next != null) {
            slow = slow.next;
            fast = fast.next;
        }
        slow.next = slow.next.next;
        return head;
    }

    public static void main(String[] args) {
        LinkNode head = new LinkNode(1);
        head.next = new LinkNode(2);
        head.next.next = new LinkNode(3);
        head.next.next.next = new LinkNode(4);
        head.next.next.next.next = new LinkNode(5);
        LinkNode node = delTheSpecifiedReverse(head, 3);
        while (node != null) {
            System.out.print(node.data + "->");
            node = node.next;
        }
    }
}

好了,我们解决了昨天文章中留下的拓展题,今天我们来看看我们链表都还有些怎样的考法。

面试题:定义一个单链表,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。为了方便,我们链表的 data 采用整型。

这是一道反转链表的经典题,我们来屡一下思路:一个结点包含下一结点的引用,反转的意思就是要把原来指向下一结点的引用指向上一个结点。我们可以分为下面的步骤:

  1. 找到当前要反转的结点的下一个结点,并用变量保存,因为下一次要反转的是它,如果我们不保存的话一定会因为前面已经反转,导致无法通过遍历得到这个结点;
  2. 然后让当前结点的 next 引用指向上一个结点,上一个结点初始 null 因为头结点的反转后变成尾结点;
  3. 当前要反转的结点变成下一个要比较元素的上一个结点,用变量保存;
  4. 当前要比较的结点赋值为之前保存的未反转前的下一个结点;
  5. 当前反转的结点为 null 的时候,保存的上一个结点即反转后的链表头结点。

用代码实现就是:

public class Test08 {

    private static class LinkNode {
        int data;
        LinkNode next;

        LinkNode(int data) {
            this.data = data;
        }
    }

    private static LinkNode reverseLink(LinkNode head) {
        // 上一个结点
        LinkNode nodePre = null;
        LinkNode next = null;
        LinkNode node = head;
        while (node != null) {
            // 先用 next 保存下一个要反转的结点,不然会导致链表断裂。
            next = node.next;
            // 再把现在结点的 next 引用指向上一个结点
            node.next = nodePre;
            // 把当前结点赋值给 nodePre 变量,以便于下一次赋值
            nodePre = node;
            // 向后遍历
            node = next;
        }
        return nodePre;
    }

    public static void main(String[] args) {
        LinkNode head = new LinkNode(1);
        head.next = new LinkNode(2);
        head.next.next = new LinkNode(3);
        head.next.next.next = new LinkNode(4);
        head.next.next.next.next = new LinkNode(5);
        LinkNode node = reverseLink(head);
        while (node != null) {
            System.out.print(node.data + "->");
            node = node.next;
        }
    }
}

链表可以考的可真多,相信不是小伙伴都和我一样,云里雾里了,那我们今天就讲到这里,后面还要继续考算法,你,打起精神,别睡着了。

目录
相关文章
|
1月前
|
存储 算法
算法入门:专题一:双指针(有效三角形的个数)
给定一个数组,找出能组成三角形的三元组个数。利用“两边之和大于第三边”的性质,先排序,再用双指针优化。固定最大边,左右指针从区间两端向内移动,若两短边之和大于最长边,则中间所有组合均有效,时间复杂度由暴力的O(n³)降至O(n²)。
|
5月前
|
机器学习/深度学习 算法
24. 两两交换链表中的节点, 19.删除链表的倒数第N个节点 ,面试题 02.07. 链表相交
1. **两两交换链表中的节点**:通过引入虚拟头结点,使所有节点都能采用统一的交换逻辑,避免对头结点单独处理。 2. **删除链表的倒数第N个节点**:利用双指针技巧,让快慢指针保持N个节点的距离,当快指针到达末尾时,慢指针正好指向待删除节点的前一个节点。 3. **链表相交**:先计算两链表长度并调整起点,确保从相同距离末尾的位置开始遍历,从而高效找到相交节点或确定无交点。 以上方法均在时间复杂度和空间复杂度上进行了优化,适合用于理解和掌握链表的基本操作及常见算法设计思路。
|
7月前
|
存储 算法 物联网
解析局域网内控制电脑机制:基于 Go 语言链表算法的隐秘通信技术探究
数字化办公与物联网蓬勃发展的时代背景下,局域网内计算机控制已成为提升工作效率、达成设备协同管理的重要途径。无论是企业远程办公时的设备统一调度,还是智能家居系统中多设备间的联动控制,高效的数据传输与管理机制均构成实现局域网内计算机控制功能的核心要素。本文将深入探究 Go 语言中的链表数据结构,剖析其在局域网内计算机控制过程中,如何达成数据的有序存储与高效传输,并通过完整的 Go 语言代码示例展示其应用流程。
137 0
|
8月前
|
存储 监控 算法
员工电脑监控系统中的 C# 链表算法剖析-如何监控员工的电脑
当代企业管理体系中,员工电脑监控已成为一个具有重要研究价值与实践意义的关键议题。随着数字化办公模式的广泛普及,企业亟需确保员工对公司资源的合理利用,维护网络安全环境,并提升整体工作效率。有效的电脑监控手段对于企业实现这些目标具有不可忽视的作用,而这一过程离不开精妙的数据结构与算法作为技术支撑。本文旨在深入探究链表(Linked List)这一经典数据结构在员工电脑监控场景中的具体应用,并通过 C# 编程语言给出详尽的代码实现与解析。
154 5
|
9月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
318 30
|
9月前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
415 25
|
9月前
|
存储 监控 算法
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
在数字化办公时代,公司监控上网软件成为企业管理网络资源和保障信息安全的关键工具。本文深入剖析C++中的链表数据结构及其在该软件中的应用。链表通过节点存储网络访问记录,具备高效插入、删除操作及节省内存的优势,助力企业实时追踪员工上网行为,提升运营效率并降低安全风险。示例代码展示了如何用C++实现链表记录上网行为,并模拟发送至服务器。链表为公司监控上网软件提供了灵活高效的数据管理方式,但实际开发还需考虑安全性、隐私保护等多方面因素。
174 0
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
|
11月前
|
算法 容器
【算法】——双指针算法合集(力扣)
移动零,复写零,快乐数,盛最多水的容器,有效三角形的个数,和为s的两个数(查找总价格为目标值的两个商品 ),三数之和,四数之和
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法

热门文章

最新文章