链表翻转的变形 | 算法必看系列八

简介: 本节课会通过三道变形题讲解链表的翻转,相信学完本节课会对链表有一个更加深刻地印象。

查看上文:链表翻转的方法

链表翻转的变形实战

链表翻转的变形实战

接下来我们来看看链表翻转的变形:
变形题 1:给定一个链表的头结点 head,以及两个整数 from 和 to ,在链表上把第 from 个节点和第 to 个节点这一部分进行翻转。例如:给定如下链表,from = 2, to = 4 head–>5–>4–>3–>2–>1 将其翻转后,链表变成 head–>5—>2–>3–>4–>1

有了之前翻转整个链表的解题思路,现在要翻转部分链表就相对简单多了,主要步骤如下:

1、根据 from 和 to 找到 from-1, from, to, to+1 四个结点(注意临界条件,如果 from 从头结点开始,则 from-1 结点为空, 翻转后需要把 to 设置为头结点的后继结点, from 和 to 结点也可能超过尾结点,这两种情况不符合条件不翻转)。
2、对 from 到 to 的结点进行翻转
3、将 from-1 节点指向 to 结点,将 from 结点指向 to + 1 结点
image.png
知道了以上的思路,代码就简单了,按上面的步骤1,2,3 实现,注释也写得很详细,看以下代码(对 from 到 to 结点的翻转我们使用迭代翻转,当然使用递归也是可以的,限于篇幅关系不展开,大家可以尝试一下)。

/**
 * 迭代翻转 from 到 to 的结点
 */
public void iterationInvertLinkedList(int fromIndex, int toIndex) throws Exception {

    Node fromPre = null;            // from-1结点
    Node from = null;               // from 结点
    Node to = null;                 // to 结点
    Node toNext = null;             // to+1 结点

    // 步骤 1:找到  from-1, from, to,  to+1 这四个结点
    Node tmp = head.next;
    int curIndex = 1;      // 头结点的index为1
    while (tmp != null) {
        if (curIndex == fromIndex-1) {
            fromPre = tmp;
        } else if (curIndex == fromIndex) {
            from = tmp;
        } else if (curIndex == toIndex) {
            to = tmp;
        } else if (curIndex == toIndex+1) {
            toNext = tmp;
        }
        tmp = tmp.next;
        curIndex++;
    }

    if (from == null || to == null) {
        // from 或 to 都超过尾结点不翻转
        throw new Exception("不符合条件");
    }

    // 步骤2:以下使用循环迭代法翻转从 from 到 to 的结点
    Node pre = from;
    Node cur = pre.next;
    while (cur != toNext) {
        Node next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    
    // 步骤3:将 from-1 节点指向 to 结点(如果从 head 的后继结点开始翻转,则需要重新设置 head 的后继结点),将 from 结点指向 to + 1 结点
    if (fromPre != null) {
        fromPre.next = to;
    } else {
        head.next = to;
    }
    from.next = toNext;
}

变形题 2:给出一个链表,每 k 个节点一组进行翻转,并返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么将最后剩余节点保持原有顺序。

示例 : 给定这个链表:head–>1->2->3->4->5 当 k = 2 时,应当返回: head–>2->1->4->3->5 当 k = 3 时,应当返回: head–>3->2->1->4->5 说明 :

  • 你的算法只能使用常数的额外空间。
  • 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

只要我们能找到翻一组 k 个结点的方法,问题就解决了(之后只要重复对 k 个结点一组的链表进行翻转即可)。

接下来,我们以以下链表为例
image.png
来看看怎么翻转 3 个一组的链表(此例中 k = 3)

  • 首先,我们要记录 3 个一组这一段链表的前继结点,定义为 startKPre,然后再定义一个 step, 从这一段的头结点 (1)开始遍历 2 次,找出这段链表的起始和终止结点,如下图示

什么是链表.gif

  • 找到 startK 和 endK 之后,根据之前的迭代翻转法对 startK 和 endK 的这段链表进行翻转

image.png

  • 然后将 startKPre 指向 endK,将 startK 指向 endKNext,即完成了对 k 个一组结点的翻转。

image.png
知道了一组 k 个怎么翻转,之后只要重复对 k 个结点一组的链表进行翻转即可,对照图示看如下代码应该还是比较容易理解的

/**
 * 每 k 个一组翻转链表
 * @param k
 */
public void iterationInvertLinkedListEveryK(int k) {
    Node tmp = head.next;
    int step = 0;               // 计数,用来找出首结点和尾结点

    Node startK = null;         // k个一组链表中的头结点
    Node startKPre = head;      // k个一组链表头结点的前置结点
    Node endK;                  // k个一组链表中的尾结点
    while (tmp != null) {
        // tmp 的下一个节点,因为由于翻转,tmp 的后继结点会变,要提前保存
        Node tmpNext = tmp.next;
        if (step == 0) {
            // k 个一组链表区间的头结点
            startK = tmp;
            step++;
        } else if (step == k-1) {
            // 此时找到了 k 个一组链表区间的尾结点(endK),对这段链表用迭代进行翻转
            endK = tmp;
            Node pre = startK;
            Node cur = startK.next;
            if (cur == null) {
                break;
            }
            Node endKNext = endK.next;
            while (cur != endKNext) {
                Node next = cur.next;
                cur.next = pre;
                pre = cur;
                cur = next;
            }
            // 翻转后此时 endK 和 startK 分别是是 k 个一组链表中的首尾结点
            startKPre.next = endK;
            startK.next = endKNext;

            // 当前的 k 个一组翻转完了,开始下一个 k 个一组的翻转
            startKPre = startK;
            step = 0;
        } else {
            step++;
        }
        tmp = tmpNext;
    }
}

时间复杂度是多少呢,对链表从头到尾循环了 n 次,同时每 k 个结点翻转一次,可以认为总共翻转了 n 次,所以时间复杂度是O(2n),去掉常数项,即为 O(n)。注:这题时间复杂度比较误认为是O(k * n),实际上并不是每一次链表的循环都会翻转链表,只是在循环链表元素每 k 个结点的时候才会翻转
变形题 3:变形 2 针对的是顺序的 k 个一组翻转,那如何逆序 k 个一组进行翻转呢

例如:给定如下链表, head–>1–>2–>3–>4–>5 逆序 k 个一组翻转后,链表变成

head–>1—>3–>2–>5–>4 (k = 2 时)

这道题是字节跳动的面试题,确实够变态的,顺序 k 个一组翻转都已经属于 hard 级别了,逆序 k 个一组翻转更是属于 super hard 级别了,不过其实有了之前知识的铺垫,应该不难,只是稍微变形了一下,只要对链表做如下变形即可
image.png
代码的每一步其实都是用了我们之前实现好的函数,所以我们之前做的每一步都是有伏笔的哦!就是为了解决字节跳动这道终极面试题!

/**
 * 逆序每 k 个一组翻转链表
 * @param k
 */
public void reverseIterationInvertLinkedListEveryK(int k) {
    // 先翻转链表
    iterationInvertLinkedList();
    // k 个一组翻转链表
    iterationInvertLinkedListEveryK(k);
    // 再次翻转链表
    iterationInvertLinkedList();
}

由此可见,掌握基本的链表翻转非常重要!难题多是在此基础了做了相应的变形而已

总结

本文详细讲解了链表与数组的本质区别,相信大家对两者的区别应该有了比较深刻的认识,尤其是程序局部性原理,相信大家看了应该会眼前一亮,之后通过对链表的翻转由浅入深地介绍,相信之后的链表翻转对大家应该不是什么难事了,不过建议大家亲自实现一遍文中的代码哦,这样印象会更深刻一些!

有时候看起来思路是这么一回事,但真正操作起来还是会有不少坑,纸上得来终觉浅,绝知此事要躬行!

来源 | 五分钟学算法
作者 | 程序员小吴

相关文章
|
算法
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
296 1
|
算法 索引
❤️算法笔记❤️-(每日一刷-141、环形链表)
❤️算法笔记❤️-(每日一刷-141、环形链表)
200 0
|
算法
【❤️算法笔记❤️】-(每日一刷-876、单链表的中点)
【❤️算法笔记❤️】-(每日一刷-876、单链表的中点)
196 0
|
算法
【❤️算法笔记❤️】-每日一刷-23、合并 K 个升序链表
【❤️算法笔记❤️】-每日一刷-23、合并 K 个升序链表
183 0
|
存储 算法
【❤️算法笔记❤️】-每日一刷-21、合并两个有序链表
【❤️算法笔记❤️】-每日一刷-21、合并两个有序链表
346 0
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
443 30
|
11月前
|
存储 监控 算法
员工电脑监控系统中的 C# 链表算法剖析-如何监控员工的电脑
当代企业管理体系中,员工电脑监控已成为一个具有重要研究价值与实践意义的关键议题。随着数字化办公模式的广泛普及,企业亟需确保员工对公司资源的合理利用,维护网络安全环境,并提升整体工作效率。有效的电脑监控手段对于企业实现这些目标具有不可忽视的作用,而这一过程离不开精妙的数据结构与算法作为技术支撑。本文旨在深入探究链表(Linked List)这一经典数据结构在员工电脑监控场景中的具体应用,并通过 C# 编程语言给出详尽的代码实现与解析。
218 5
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
617 25
|
10月前
|
存储 算法 物联网
解析局域网内控制电脑机制:基于 Go 语言链表算法的隐秘通信技术探究
数字化办公与物联网蓬勃发展的时代背景下,局域网内计算机控制已成为提升工作效率、达成设备协同管理的重要途径。无论是企业远程办公时的设备统一调度,还是智能家居系统中多设备间的联动控制,高效的数据传输与管理机制均构成实现局域网内计算机控制功能的核心要素。本文将深入探究 Go 语言中的链表数据结构,剖析其在局域网内计算机控制过程中,如何达成数据的有序存储与高效传输,并通过完整的 Go 语言代码示例展示其应用流程。
204 0
|
12月前
|
存储 监控 算法
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
在数字化办公时代,公司监控上网软件成为企业管理网络资源和保障信息安全的关键工具。本文深入剖析C++中的链表数据结构及其在该软件中的应用。链表通过节点存储网络访问记录,具备高效插入、删除操作及节省内存的优势,助力企业实时追踪员工上网行为,提升运营效率并降低安全风险。示例代码展示了如何用C++实现链表记录上网行为,并模拟发送至服务器。链表为公司监控上网软件提供了灵活高效的数据管理方式,但实际开发还需考虑安全性、隐私保护等多方面因素。
241 0
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨