链表反转类算法题

反转链表类

NO1. 反转链表

给定一个长度为 n 的链表,反转该链表,输出表头。

链表反转类算法题

方法一:迭代法(推荐使用)

算法流程:

  • step 1 :特殊情况判断,空链表或只有一个结点的链表,直接返回头结点;
  • step 2 :设置两个指针 curprevcur指向当前结点, prev指向上一个结点(初始为 null);
  • step 3 :遍历整个链表,每到一个结点,断开当前结点 cur与下一个结点 cur.next 的指针,并用临时变量 temp 记录下一个结点,然后当前结点的 next指向上一个结点(实现反转);
  • 轮换 cur与上一个结点 prev,进行下一个结点反转;
  • 循环退出条件为 cur == null
  • step 4 :返回 prev(反转后,原尾节点变成头结点);

链表反转类算法题

代码实现:

public class Main {
    public ListNode ReverseList(ListNode head) {
        // 特殊情况判断
        if(head == null || head.next == null)   return head;
        // 双指针
        ListNode prev = null, cur = head;
        while(cur != null) {
            ListNode temp = cur.next;   // 断开链表,要记录后续⼀个
            cur.next = prev;    // 当前的next指针指向前⼀个
            prev = cur;     // 前⼀个更新为当前
            cur = temp;     // 当前更新为刚刚记录的后⼀个
        }
    }
}

复杂度分析:

  • 时间复杂度:(O(N)),遍历链表一次;
  • 空间复杂度:(O(1)),申请常数个变量;

方法二:递归

根据迭代法,每当我们反转链表中某个结点以后,要遍历进入下一个结点进行反转,相当于对后面的子链表进行反转,那这就是一个子问题,因此我们可以使用递归来解决。

  • 终止条件:当到达链表尾部,要么当前指针为空,要么下一个指针为空,直接向上层返回当前结点;
  • 返回值:每一层向上一层返回翻转后子问题的头结点;
  • 本层任务:先进入后一个结点作为子问题,继续反转,然后记录返回的头结点,连接在本层结点的后面;

算法流程:

  • step 1 :递归向下遍历链表,直到遍历到最后一个结点;
  • step 2 :往上一次逆转两个结点;
  • step 3 :逆转后的尾连接本层的结点 head,并断开两节点的原指针(置为 null);

链表反转类算法题
public class Main {
    public ListNode ReverseList(ListNode head) {
        // 递归停止条件
    if(head == null || head.next == null)   return head;
        ListNode newHead = ReverseList(head.next);  // 反转下一个
        // 本层任务,如
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

复杂度分析:

  • 时间复杂度:(O(N)),遍历链表一次;
  • 空间复杂度:(O(N)),递归栈的深度就是链表的长度(N);

NO2. 链表内指定区间反转

将一个结点为 size 的链表 m 位置到 n 位置之间的区间反转;
链表其他部分不变,返回头结点;

方法一:头插法迭代(推荐)

第一步肯定是要先找到第 m 个位置,然后不断反转,直到到达 n 位置;由于m可能为1,即头结点,那反转之后 head 就不再指向头结点了,所以,先构造一个虚拟头结点;

算法流程:

  • step 1 :可以在链表头添加一个虚拟头结点,后续返回时去掉就好了;
  • step 2 :使用两个指针, cur指向当前结点, prev指向前驱节点;
  • step 3 :依次遍历链表,到达第 m 个位置;
  • step 4 :从 m 到 n 位置的结点,依次断掉指向下一个结点的指针,指向前驱,实现反转;
  • step 5 :返回虚拟头结点的 next

链表反转类算法题

代码实现:

public class Solution {
    public ListNode reverseBetween (ListNode head, int m, int n) {
        // 虚拟头结点
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        // 双指针(反转链表常规操作)
        ListNode prev = dummy, cur = head;
        // 找到第 m 个结点
        for(int i = 1; i < m; i++) {
            prev = cur;
            cur = cur.next;
        }
        // cur 指向 m,开始反转
        for(int i = m; i < n; i++) {
            ListNode tmp = cur.next;
            cur.next = tmp.next;    // 1
            tmp.next = pre.next;    // 2 开始头插到 pre 后面
            pre.next = tmp;         // 3
        }
        return dummy.next;
    }
}

复杂度分析:

  • 时间复杂度:(O(N)),最坏情况下,需要遍历链表一次;
  • 空间复杂度:(O(1)),申请常数个变量;

方法二:递归

链表反转类算法题

算法流程:

  • step 1 :先利用递归找到反转区间的起点;
  • step 2 :再利用递归从第 n 个位置开始反转,往上拼接;

代码实现:

public class Solution {
    public ListNode reverseBetween (ListNode head, int m, int n) {
        // 找到第 m 个结点,执行反转
        if(m == 1)    return reverse(head, n);
        // 找 第 m 个结点
        ListNode node = reverseBetween(head.next, m - 1, n - 1);
        head.next = node;   // 拼接已翻转
        return head;
    }

    ListNode tmp = null;    // tmp用来暂存第n个结点的后继
    public ListNode reverse(ListNode head, int n){
        // 递归停止
        if(n == 1) {
            tmp = head.next;
            return head;
        }
        ListNode node = reverse(head.next, n - 1);    //进⼊⼦问题
        head.next.next = head; //反转
        head.next = tmp; //拼接
        return node;    // 返回上一层
    }
}

复杂度分析:

  • 时间复杂度:(O(N)),最坏情况下,需要遍历链表一次;
  • 空间复杂度:(O(N)),最坏情况下,递归栈的深度就是链表的长度(N);

NO.3 链表结点每K个一组翻转

给定一个链表,从头开始每 K 个作为一组,将每组的链表结点翻转;
组与组之间的位置关系保持不变;
如果最后链表末尾剩余不足 K 个结点,则不反转,直接放在尾部;

方法一:头插法迭代

链表反转类算法题

算法流程:

  • step 1 :反转过程中要改变 head 指针,就得设个虚拟头结点,定义双指针 precur
  • step 2 :统计链表总长度 len ,用于得出翻转组数 len / k
  • step 3 :外循环控制翻转组数,内循环控制组内的k个结点翻转(头插 k – 1次)

代码实现:

public class Main {
    public ListNode reverseKGroup (ListNode head, int k) {
        // 特判
        if(head == null || head.next == null || k < 2)  return head;
        // 要改变头结点的,就得设个虚拟头结点
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        // 双指针,基本操作
        ListNode pre = dummy, cur = head;
        // 统计链表总长度
        int len = 0;
        ListNode p = head;
        while(p != null) {
            len++;
            p = p.next;
        }
        // 翻转组数,仅翻转 len / k
        for(int i = 0; i < len / k; i++) {
            // 外循环控制组数,内循环控制组内的k个结点翻转(头插 k - 1次)
            for(int j = 1; j < k; j++) {
                // 把一组内的结点 temp 头插到 pre 后
                ListNode temp = cur.next;
                cur.next = temp.next;
                temp.next = cur;
                pre.next = temp;
            }
            // 一组翻转完成,去下一组,
            pre = cur;      // pre 指向上一组尾结点 cur
            cur = cur.next;     // cur 指向下一组的第一个结点
        }
        return dummy.next;
    }
}

复杂度分析:

  • 时间复杂度:(O(N)),需要遍历链表两次;
  • 空间复杂度:(O(1)),申请常数个变量;

方法二:递归

终止条件:当进行到最后一个分组,元素个数不足 k 个,就将剩余的最后一个分组直接返回给上层;

返回值:每一层要返回给上层的是翻转后这个分组的头结点,并且后面已经连接好了所有的已翻转好的分组链表;

本层任务:先遍历 k 次,找到该分组尾结点的位置,然后从分组的头结点遍历到尾结点,依次翻转;

算法流程:

  • step 1 :翻转前,找到每次分组的尾节点的下一个结点,即下一分组的头结点
  • step 2 :采用常规的翻转链表操作;
  • step 3 :递归进行拼接;

链表反转类算法题

代码实现:

public class Main{
    public ListNode reverseKGroup (ListNode head, int k) {

        ListNode tail = head;
        // tail指向本组尾节点的下一个节点,即下一分组的头结点
        for(int i = 0; i < k; i++) {
            // 最后一分组不足 k 个结点,直接向上层返回 head
            if(tail == null)    return head;
            tail = tail.next;
        }
        // 常规的翻转链表操作
        ListNode pre = null, cur = head;
        while(cur != tail) {
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        // 当前尾指向下⼀段要翻转的链表
        head.next = reverseKGroup(tail, k);
        //
        return pre;
    }
}

复杂度分析:

  • 时间复杂度:(O(N)),一共遍历链表 n 个结点;
  • 空间复杂度:(O(1)),申请常数个变量;

总结

链表反转类算法题,有两种解决思路:

  1. 遍历链表,利用两个滚动指针,不断改变当前结点的 next 指针的指向(当前结点的 next 指向前驱),每次改变指针时都要暂存下一个节点;
  2. 头插法,每次把结点头插到 prev 指针后,实现反转;

Original: https://www.cnblogs.com/afei688/p/16600527.html
Author: 阿飞的客栈
Title: 链表反转类算法题

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/644235/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • [转]winscp以命令行方式同步服务器数据到PC机磁盘上

    博客地址:http://www.cnblogs.com/wolf-sun/ 博客版权:如果文中有不妥或者错误的地方还望高手的你指出,以免误人子弟。如果觉得本文对你有所帮助不如【推荐…

    技术杂谈 2023年6月1日
    0102
  • 用GO开发企业级分布式云存储系统

    一、基础架构 二、开发工具 posted @2019-04-20 14:05 努力哥 阅读(1724 ) 评论() 编辑 Original: https://www.cnblogs…

    技术杂谈 2023年5月31日
    085
  • 什么是GUI?

    图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面。 图形用户界面是一种人与计算机通信的界面显…

    技术杂谈 2023年5月31日
    086
  • 使用plantuml,业务交接就是这么简单

    使用plantuml,业务交接就是这么简单 你好,我是轩脉刃。 最近交接了一个业务,原本还是有挺复杂的业务逻辑的,但发现交接过来的项目大有文章,在项目代码中有一个docs文件夹,里…

    技术杂谈 2023年6月1日
    095
  • 知识点复习(持续更新版)

    高等数学 线性代数 如何判断向量组的线性相关性? 由线性相关定义去判断 令向量组的线性组合为零,研究系数的取值情况,线性组合为零当且仅当系数皆为零,则该向量组线性无关;若存在不全为…

    技术杂谈 2023年7月23日
    090
  • CentOS7 firewall开启,开放端口操作

    防火墙开机启动 systemctl enable firewalld.service 查看防火墙状态 firewall-cmd –state 开启防火墙 systemctl st…

    技术杂谈 2023年6月1日
    0105
  • 运算符(1)

    算数运算符 在Java中,使用算术运算符+、-、*、/表示加、减、乘、除运算。当参与/运算的两个操作数都是整数时,表示整数除法运算;否则,表示浮点除法。整数的求余操作(取模)用%表…

    技术杂谈 2023年7月11日
    063
  • python 对潜在客户数据集 进行数据分析

    大家好,我是小寒。 今天给大家带来一篇 探索性数据分析(EDA) 案例分享。如果觉得不错,可以多多分享。 什么是探索性数据分析 探索性数据分析 (EDA) 是任何数据科学或数据分析…

    技术杂谈 2023年7月25日
    079
  • 1、Swift协程详解:协程简介

    协程的基本概念 协程(Coroutines)不是一个语言特有的概念,也没有一个特别严格的定义,维基百科对它定义也只是对它最核心的非抢占式多任务调度进行了简单的描述: Corouti…

    技术杂谈 2023年6月1日
    087
  • 英国延长 UKCA 标记截止日期

    政府于 2022 年 11 月 14 日宣布,企业将有 2 年的时间来应用新的 UKCA 产品标记。在 2024 年 12 月 31 日之前,企业可以选择使用 UKCA 或 CE …

    技术杂谈 2023年6月21日
    087
  • urandom和random区别

    linux中提供了 /dev/urandom 和 /dev/random 两个特殊设备来提供随机数。那么这两个文件有什么区别呢?要回答这个问题,先需要了解熵这个概念。 熵linux…

    技术杂谈 2023年7月24日
    074
  • HBase二次开发之搭建HBase调试环境,如何远程debug HBase源代码

    版本HDP:3.0.1.0HBase:2.0.0 一、前言 之前的文章也提到过,最近工作中需要对HBase进行二次开发(参照HBase的AES加密方法,为HBase增加SMS4数据…

    技术杂谈 2023年7月24日
    059
  • Python小游戏——猜数字

    1 print("————–我爱鱼———–") 2 temp = input("不妨猜一下甲鱼现在心里想的是哪个数字:…

    技术杂谈 2023年7月24日
    069
  • WCF后传系列(7):消息如何传递之绑定Part 2

    概述 每个服务终结点都包含一个地址Address、一个绑定Binding 和一个契约Contract。契约指定可用的操作,绑定指定如何与服务进行通信,而地址指定查找服务的位置,在W…

    技术杂谈 2023年5月31日
    070
  • 解决Win10锁屏超1分钟,显示器关闭问题

    Win10台式机锁定屏幕1分钟后,系统就会自动关闭显示器。从节能来说是非常不错的,但这不符合大多数人的电脑使用习惯,一会儿就黑屏了看起来不舒服。怎么修改这个系统默认设置呢?解决方法…

    技术杂谈 2023年6月1日
    0109
  • 宝塔配置vnc+wine实现Q群机器人

    图形界面必备 X Window System yum -y groupinstall "X Window System" 安装epel源 yum -y inst…

    技术杂谈 2023年7月25日
    085
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球