• 数据结构学习笔记5:说一说几种常见的链表算法


    之前整理的链表的基础知识部分,相信很多刷算法或者出去面试的同学都会发现。链表作为一个经常使用的数据结构,和他相关的考题真的很常见。今天我们来研究一下几种常见的链表相关的算法,我先把要分享的算法写在前面,可能比较入门,如果你一看题目就知道怎么做了,那恭喜你,你的算法已经入门了。

    本篇文章涉及的链表算法:

    1. 反转单向链表
    2. 求链表的中间节点
    3. 判断链表是不是回文结构
    4. 判断链表中是否包含环
    5. 合并两个有序链表

    反转单向链表

    审题

    做任何算法题之前,第一步肯定是审题,如果对题目的理解有误,那绝对做不出正确的答案,还会大量的浪费时间。我们这里简单说一下什么是反转单向链表。

    假设我们有一个链表,它的顺序是这样的:

    A -> B -> C -> D

    经过反转之后,我们希望结果是:

    D -> C -> B ->A

    解题思路

    因为这个反转单向链表真的是非常非常常见的算法题目,所以对这个题目有很多种解。我们今天说一种笔者认为最简单、最好理解的借助哨兵节点反转单向链表算法。还记得如何表示一个空链表吗?head=null 表示链表中没有结点了。其中 head 表示头结点指针,指向链表中的第一个结点。如果我们引入哨兵结点,在任何时候,不管链表是不是空,head 指针都会一直指向这个哨兵结点。我们也把这种有哨兵结点的链表叫带头链表。相反,没有哨兵结点的链表就叫作不带头链表。

    因为哨兵结点一直存在,所以插入第一个结点和插入其他结点,删除最后一个结点和删除其他结点,都可以统一为相同的代码实现逻辑了。我们可以通过这种方法来简化编程难度的技巧,在很多代码实现中都有用到。

    代码实现步骤:

    1. 创建一个head节点作为哨兵使用,使这个节点的后继指针指向希望反转的链表
    2. 创建一个节点Prev指向node,希望交换的第一个节点
    3. 创建一个节点指向node.next,也就是希望交换的第二个节点
    4. 迭代整个链表
    5. 循环交换位置,交换的节点放到head节点的后继节点上

    代码实现

        public Node reverseNode(Node node) {
            // 创建哨兵节点
            Node head = new Node();
            head.next = node;
    
            Node pred = node;
            Node next = node.next;
            while (next != null) {
                pred.next = next.next;
                next.next = head.next;
                head.next = next;
                next = pred.next;
            }
            return head.next;
        }
    

    求链表的中间节点

    审题

    显然题目的意思是让我们从链表中获取中间的那个节点。例如节点为:

    A -> B -> C

    那我们就获取B节点返回即可

    但是刚才说这种是指节点的长度是奇数的情况。如果是偶数呢?那中间的节点应该有两个啊,那怎么办呢?这种时候就要和面试官或者出题者沟通他们到底想要哪一个,然后针对情况进行返回。给大家举个例子吧,原始链表结构如下:

    A->B->C->D

    显而易见的是,这个链表的中间节点有俩分别是B节点和C节点,这种情况就需要和出题者沟通一下,他想要的是哪一个。

    解题思路

    使用快慢指针的方式,慢指针一次遍历一个节点,快指针一次遍历两个节点。这样设计的思路是:因为快指针的遍历速度是慢指针的两倍,所以当快指针遍历完成的时候,慢指针的位置应该是整个链表的1/2处也就是中间位置。

    那知道了解题思路之后我们来看一下代码吧

    代码实现

    public Node findMiddleNode(Node node){
        Node fast = node;
        Node slow = node;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
    

    判断链表是不是回文结构

    审题

    想要判断一个链表是否是回文结构,首先要知道什么是回文结构。百度百科中对回文结构是这么解释的:

    双链DNA中含有的两个结构相同、方向相反的序列称为回文结构,每条单链以任一方向阅读时都与另一条链以相同方向阅读时的序列是一致的,例如5'GGTACC3' 3'CCATGG5'.

    回文结构序列是一种旋转对称结构,在轴的两侧序列相同而反向。当然这两个反向重复序列不一定是连续的。

    解题思路

    以B->H->A->H->B为例,我们可以思考一下,咱们平时是如何判断它是不是回文的。我们会先找到这个链表的中间节点,然后把后半部分进行反转,然后查看和前半部分是不是一样。但是我们的实际做法有点反人类了,我们是把前半部分进行反转再和后半部分进行匹配。

    那为什么这么设计呢,原因很简单。为了获取中间节点,我们要通过快慢指针的方式对链表进行遍历。所以当我们遍历完成之后,我们已经遍历完前半部分。为了减少遍历的数量,我们完全可以倒叙的存储已经遍历的节点,然后将其和中间节点之后的节点依次比对。如果完全一样则说明是回文,否则就不是。

    代码实现

    public Boolean isPalindrome(Node node){
        Node prev = null;
        Node fast = node;
        Node slow = node;
        while(fast != null && fast.next != null){
            Node temp = slow.next;
            slow.next = prev;
            prev = slow;
            slow = temp;
            fast = fast.next.next;
        }
        if (fast != null){
            slow = slow.next;
        }
        while(slow != null){
            if (!Object.equals(slow,prev)){
                return false;
            }
            slow = slow.next;
            prev = prev.next;
        }
       	return true;
    }
    

    判断链表中是否包含环

    待施工……

    合并两个有序链表

    待施工……

  • 相关阅读:
    Redux
    版本控制(.git + .svn + SourceTree)
    前端埋点
    前端IDE:VSCode + WebStorm
    浏览器
    Mutation Observer
    函数节流与函数去抖
    React 初识
    Ajax
    JS
  • 原文地址:https://www.cnblogs.com/joimages/p/13228207.html
Copyright © 2020-2023  润新知