算法:反转单链表

本贴最后更新于 1015 天前,其中的信息可能已经事过景迁

对 leetCode 一个算法的分析学习,支持对单链表内指定区间的反转实现。
206. 反转链表
92. 反转链表 II

import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;

/**
 * 对leetCode一个算法的分析学习
 * 题目:单链表的反转
 *
 * @author hudk
 * @date 2020/5/27 20:20
 */
public class Solution {


    /**
     * 单链表结点
     */
    public static class ListNode {
        int val;
        public ListNode next;

        public ListNode(int x) {
            val = x;
        }

        @Override
        public String toString() {
            return String.valueOf(val);
        }
    }


    /**
     * leetCode 题目:反转单链表1
     *
     * 示例:
     * 输入: 1->2->3->4->5->NULL
     * 输出: 5->4->3->2->1->NULL
     * 进阶:
     * 你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
     *
     * 来源:力扣(LeetCode)
     * 链接:https://leetcode-cn.com/problems/reverse-linked-list
     * 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
     * @param head
     * @return
     */

    /**
     * 方法一
     * 这个方法是本人解答方案,相对使用了比较多的空间
     * 空间复杂度:O(n)
     * 时间复杂度:O(n)
     *
     * @param head
     * @return
     */
    public static ListNode myReverseList(ListNode head) {
        //当链表长度为零时,直接返回null
        if (head == null) {
            return null;
        }
        //引用指向头结点
        ListNode h = head;
        //遍历整个链表,统计链表长度 i
        int i = 1;
        while (h.next != null) {
            i++;
            h = h.next;
        }
        //创建一个和链表长度一样的数组,并将链表的元素按照原顺序逐个放入数组中
        ListNode[] ln = new ListNode[i];
        for (int j = 0; j < i; j++) {
            ln[j] = head;
            head = head.next;
        }
        //再从数组的尾部开始遍历,逐个该表链表元素的next指针指向前一个元素。
        for (int x = i - 1; x > 0; x--) {
            ln[x].next = ln[x - 1];
        }
        //将原来的头结点(现在转置后的尾结点next引用置空)
        ln[0].next = null;
        //返回转置后新的头结点
        return ln[i - 1];
    }

    /**
     * 方法二
     * 这个方法是leetCode上的算法大神解答的方案
     * 利用递归的巧妙与优雅实现
     *
     * 作者:labuladong
     * 链接:https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/bu-bu-chai-jie-ru-he-di-gui-di-fan-zhuan-lian-biao/
     * 来源:力扣(LeetCode)
     * 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
     *
     * @param head
     * @return
     */
    public static ListNode revers(ListNode head) {
        if (head.next == null) {
            return head;
        }
        ListNode last = revers(head.next);
        head.next.next = head;
        head.next = null;
        return last;
    }


    /**
     * 方法三
     * 这个方法是官方解答的方案
     * 利用了迭代的思想,同样简洁且高效
     * 空间复杂度:O(1)
     * 时间复杂度:O(n)
     *
     * 来源:力扣(LeetCode)
     * 链接:https://leetcode-cn.com/problems/reverse-linked-list
     * 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
     *
     * @param head
     * @return
     */
    public static ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        ListNode nextTemp = null;
        while (curr != null) {
            nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }

    /**
     * leetCode 题目:反转单链表2
     * 反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
     *
     * 说明:
     * 1 ≤ m ≤ n ≤ 链表长度。
     *
     * 示例:
     * 输入: 1->2->3->4->5->NULL, m = 2, n = 4
     * 输出: 1->4->3->2->5->NULL
     *
     * 来源:力扣(LeetCode)
     * 链接:https://leetcode-cn.com/problems/reverse-linked-list-ii
     * 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
     */


    /**
     * 方法一 begin*******************************************************************
     * <p>
     * 递归实现单链表的指定区间反转
     * 这个算法的实现,淋漓尽致的体现了递归的优雅与简洁。
     * 适用于链表长度比较短的场景,或对性能要求不高的场景
     * <p>
     * 作者:labuladong
     * 链接:https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/bu-bu-chai-jie-ru-he-di-gui-di-fan-zhuan-lian-biao/
     * 来源:力扣(LeetCode)
     * 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
     *
     * @param head
     * @param m
     * @param n
     * @return
     */
    public static ListNode reverseBetween(ListNode head, int m, int n) {
        if(head == null){
            return null;
        }
        // base case
        if (m == 1) {
            //单链表的前n个结点反转
            return reverseN(head, n);
        }
        // 前进到反转的起点触发 base case
        head.next = reverseBetween(head.next, m - 1, n - 1);
        return head;
    }

    /**
     * 递归实现单链表的前n个结点反转
     */
    static ListNode successor = null; // 后驱节点

    // 反转以 head 为起点的 n 个节点,返回新的头结点
    public static ListNode reverseN(ListNode head, int n) {
        if(head == null){
            return null;
        }
        if (n == 1) {
            // 记录第 n + 1 个节点
            successor = head.next;
            return head;
        }
        // 以 head.next 为起点,需要反转前 n - 1 个节点
        ListNode last = reverseN(head.next, n - 1);

        head.next.next = head;
        // 让反转之后的 head 节点和后面的节点连起来
        head.next = successor;
        return last;
    }

    /**方法一end*******************************************************************/


    /**
     * 方法二
     * 这是本人的解答方案,使用了迭代的思路,并将各种情况逐一考虑,分别处理。
     * 代码量比较多,但自我感觉逻辑看起来更加清晰一些。
     *
     * @param head 链表头结点
     * @param m    翻转区间开始位置
     * @param n    翻转区间结束位置
     * @return
     */
    public static ListNode myReverseBetween(ListNode head, int m, int n) {
        if(m <= 0){
            m = 1;
        }
        int size = size(head);
        if(n > size){
            n = size;
        }
        //如果传入为空,直接返回空
        if (head == null) {
            return null;
        }
        //如果m = n,说明转置后等于没转置,所以不做处理直接返回原链表
        if (m == n) {
            return head;
        }
        //1、当m等于1时,倒序之后,第一个结点一定会和第n+1个结点相连
        //2、并且第n个结点,会成为新链表的头结点
        if (m == 1) {
            ListNode perv = null;
            ListNode crr = head;
            ListNode nodeOne = null;
            int i = 1;
            while (crr != null) {
                ListNode next = crr.next;
                if (i == 1) {
                    //暂时记住第一个结点,后面它将会与第n+1个结点相连
                    nodeOne = crr;
                    nodeOne.next = null;
                    //为迭代做准备,从第2个结点开始迭代地做“翻转”动作,故将第1个结点当做下次循环时的“前置结点”
                    perv = crr;
                }
                //从第二个结点开始,一直到第n个结点,逐一"翻转"他们的next
                if (i > m && i < n) {
                    crr.next = perv;
                    perv = crr;
                }
                //第n个结点
                if (i == n) {
                    //第一个结点的的next引用指向了第n+1个结点
                    nodeOne.next = crr.next;
                    //翻转第n个结点的next
                    crr.next = perv;
                    //第n个结点,会成为新链表的头结点
                    head = crr;
                    //由于后面得结点不需要做处理了,故跳出循环即可
                    break;
                }
                //迭代
                crr = next;
                i++;
            }
        }
        //1、如果m>1,第m-1个结点会与第n个结点相连,第m个结点会与第n+1个结点相连
        //2、然后,第m+1个到第n个结点的next依次“翻转”
        //3、头结点不变
        if (m > 1) {
            ListNode perv = null;
            ListNode crr = head;
            ListNode nodeMp = null;//第m个结点的前一个结点
            ListNode nodeM = null;//第m个结点
            int i = 1;
            while (crr != null) {
                ListNode next = crr.next;
                if (i == m - 1) {
                    //暂时记住第m-1个结点,后面它将会与第n个结点相连
                    nodeMp = crr;
                    nodeMp.next = null;
                }
                if (i == m) {
                    //暂时记住第m个结点,后面它将会与第n+1个结点相连
                    nodeM = crr;
                    nodeM.next = null;
                    //为迭代做准备,从m+1个结点开始迭代地做“翻转”动作,故将第m个结点当做下次循环时的“前置结点”
                    perv = crr;
                }
                //第m+1个到第n个结点的next依次“翻转”
                if (i > m && i < n) {
                    crr.next = perv;
                    perv = crr;
                }
                if (i == n) {
                    //第m个结点的next引用指向了第n+1个结点
                    nodeM.next = crr.next;
                    //翻转第n个结点的next
                    crr.next = perv;
                    //第m-1个结点的next引用指向了第n个结点
                    nodeMp.next = crr;
                    //由于后面得结点不需要做处理了,故跳出循环即可
                    break;
                }
                //迭代
                crr = next;
                i++;
            }
        }
        return head;
    }

    /**方法二end*******************************************************************/


    /**
     * 测试用例
     * @param args
     */
    public static void main(String[] args) {
        //生成一个长度为10的单链表
        ListNode head = createRandomSingleLinkList(10);
        //打印初始链表
        printLinkList(head);
        //反转测试
        ListNode head1 = myReverseList(head);
        printLinkList(head1);
        //迭代方式反转测试
        ListNode head2 = reverseList(head1);
        printLinkList(head2);
        //递归方式反转测试
        ListNode head3 = revers(head2);
        printLinkList(head3);
        //迭代方式反转指定区间测试
        ListNode head4 = myReverseBetween(head3,2,8);
        printLinkList(head4);
        //递归方式反转指定区间测试
        ListNode head5 = reverseBetween(head4,4,5);
        printLinkList(head5);
    }


    /**
     * 生成一个指定长度的链表
     * @param size
     * @return
     */
    public static ListNode createRandomSingleLinkList(int size){
        if(size == 0){
            return null;
        }
        ListNode head = new ListNode(1);
        ListNode crr= head;
        for(int i=1; i<size; i++){
            ListNode node = new ListNode(i+1);
            crr.next = node;
            crr = node;
        }
        return head;
    }

    /**
     * 生成 0 - 100 范围内的随机正式
     * @return
     */
    public static int randomInt(){
        return (int)Math.floor(Math.random()*100);
    }

    /**
     * 打印链表
     * @param head
     */
    public static void printLinkList(ListNode head){
        List<ListNode> nodes = new ArrayList<>();
        if(head == null){
            System.out.println(nodes);
            return;
        }
        nodes.add(head);
        while (head.next != null){
            nodes.add(head.next);
            head = head.next;
        }
        System.out.println(nodes);
    }

    /**
     * 计算单链表长度
     * @param head
     * @return
     */
    public static int size(ListNode head){
        if(head == null){
            return 0;
        }
        int i = 1;
        while (head.next != null){
            head = head.next;
            i++;
        }
        return i;
    }

}
1 操作
hudk 在 2021-10-11 10:53:17 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖 • 3 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    76 引用 • 390 回帖
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 646 关注
  • IBM

    IBM(国际商业机器公司)或万国商业机器公司,简称 IBM(International Business Machines Corporation),总公司在纽约州阿蒙克市。1911 年托马斯·沃森创立于美国,是全球最大的信息技术和业务解决方案公司,拥有全球雇员 30 多万人,业务遍及 160 多个国家和地区。

    16 引用 • 53 回帖 • 131 关注
  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    41 引用 • 40 回帖
  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1083 引用 • 3461 回帖 • 257 关注
  • 智能合约

    智能合约(Smart contract)是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。智能合约概念于 1994 年由 Nick Szabo 首次提出。

    1 引用 • 11 回帖 • 7 关注
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    262 引用 • 664 回帖
  • WebClipper

    Web Clipper 是一款浏览器剪藏扩展,它可以帮助你把网页内容剪藏到本地。

    3 引用 • 9 回帖 • 4 关注
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    141 引用 • 441 回帖
  • flomo

    flomo 是新一代 「卡片笔记」 ,专注在碎片化时代,促进你的记录,帮你积累更多知识资产。

    4 引用 • 91 回帖 • 1 关注
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 647 关注
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖 • 1 关注
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 378 关注
  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    58 引用 • 22 回帖 • 1 关注
  • sts
    2 引用 • 2 回帖 • 167 关注
  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    70 引用 • 533 回帖 • 736 关注
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 459 关注
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    495 引用 • 1386 回帖 • 331 关注
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖
  • OnlyOffice
    4 引用 • 12 关注
  • Q&A

    提问之前请先看《提问的智慧》,好的问题比好的答案更有价值。

    7012 引用 • 31700 回帖 • 220 关注
  • uTools

    uTools 是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。

    5 引用 • 13 回帖
  • 链书

    链书(Chainbook)是 B3log 开源社区提供的区块链纸质书交易平台,通过 B3T 实现共享激励与价值链。可将你的闲置书籍上架到链书,我们共同构建这个全新的交易平台,让闲置书籍继续发挥它的价值。

    链书社

    链书目前已经下线,也许以后还有计划重制上线。

    14 引用 • 257 回帖 • 1 关注
  • TensorFlow

    TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。

    20 引用 • 19 回帖 • 2 关注
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖 • 1 关注
  • Hprose

    Hprose 是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。你无需专门学习,只需看上几眼,就能用它轻松构建分布式应用系统。

    9 引用 • 17 回帖 • 600 关注