链表的相关习题合集(持续更新)

无头单向非循环链表实现(无头指的是无单独的节点头)

在这里插入图片描述

链表是由一个个节点组成的,所以需要创建节点类

在这里插入图片描述


对于每个链表,都有一个头结点,所以创建一个成员变量head,用于指向头结点,用size记录链表的长度
在这里插入图片描述

这里需要用private修饰size是因为防止被外界随意修改,所以需要提供对应的公开方法

在这里插入图片描述

头插法

一般来说,一定要考虑链表为空的情况,尤其是不带头的链表
在这里插入图片描述

如果链表为空,需要让头结点直接指向该节点

如果链表不为空,需要让node 的next指向头结点,然后head指向新的头结点node


但其实如果head等于null,走不等于null的语句也可以正确执行,所以可以优化成这样
在这里插入图片描述

最后不要忘记了需要维护的 size

总结

  1. 头插法不用考虑链表为空的情况(不为空的代码包含两种情况)

尾插法

由于尾插法需要找到最后一个节点的位置,然后将最后一个节点的next指向新插入节点,所以需要考虑链表为空的情况

在这里插入图片描述

总结

  1. 尾插法需找到链表最后一个节点
  2. 尾插法需额外考虑链表为空的情况

指定下标插入

当下标为0或者是size时(size为链表长度),分别调用 头插法和尾插法即可

在这里插入图片描述


当index不是0下标和size下标时,思路如下:

  1. 找到index下标节点的前一个节点

在这里插入图片描述

  1. 先将新节点指向 index下标的节点
    在这里插入图片描述
  2. 将cur的next指向新节点

在这里插入图片描述

代码中需要再考虑一些细节

  1. 需判断下标是否合法
  2. 调用头插法和尾插法后 记得直接return结束函数

在这里插入图片描述

总结

  1. 对于index下标,考虑合法性,考虑头插尾插情况
  2. 定义初识值为head的指针走index-1步
  3. 不需要额外考虑链表为空的情况

查看单链表中是否包含值为key的节点

思路:遍历整个链表,判断每个节点的值即可

在这里插入图片描述

总结

  1. 遍历一遍链表判断即可

  2. 不需额外考虑链表为空

删除第一次出现值为key的节点

思路:

  1. 遍历链表,找到值为key的节点的前一个节点(不考虑头结点值为key的情况)
  2. 若找到了,进行删除操作;没找到,则无事发生
  3. 若头结点值为key,则头结点移一下即可

在这里插入图片描述
假设删除33
在这里插入图片描述
语句为 cur.next = cur.next.next;
在这里插入图片描述

最后考虑头结点的好处

  1. 若在开头就考虑,则需循环重复判断头结点(因为中间的代码无法考虑头结点),并需要额外判断head是否为空,有可能链表里只有一个节点并且是目标节点,不判断会报空指针异常(总之就是需要多考虑

总结

  1. 需额外考虑链表为空
  2. 遍历链表找到需要删除的节点的上一个节点
  3. 最后考虑头结点
  4. 若维护的size变量,记得加上size–.

删除所有值为key的节点

思路:

  1. 与删除一个值为key的节点的思路差不多,不会在遍历里
  2. 只是需要格外注意一些细节
    在这里插入图片描述
    在这里插入图片描述
    删除元素需要将cur的next指向删除节点的next,此时cur不能往后一步走,因为有可能删除节点的下一个节点也有可能值为key

所以只有不删除元素时,cur才能往后走一步

总结

  1. 需考虑链表为空的情况
  2. 遍历链表,判断cur.next不是cur,保证 cur是每次删除节点的前一个节点(不会删除头结点)
  3. 注意只有当 没删除节点时才移动
  4. 最后需要考虑头结点的情况

得到单链表的长度

只需要返回维护的成员变量size即可
在这里插入图片描述

总结

  1. 链表长度既可以维护一个size变量,也可以通过遍历链表获取
  2. 维护变量size会降低运行时间,但代码容易犯错,需时刻提醒自己还有个size变量

打印链表

遍历链表即可,可以特判当链表为空时打印null

在这里插入图片描述

总结
根据需求进行打印

清空链表

清空链表虽可以直接令head等于null,但过于暴力
在这里插入图片描述
将每个节点的next都置为null则可以提高内存回收效率

思路如下:

  1. 定义两个引用cur和curN
  2. 临时存储cur的next节点

在这里插入图片描述

  1. 将cur的next置为null
    在这里插入图片描述
    4.令cur = curN
    在这里插入图片描述
  2. 重复第2、3、4步
    在这里插入图片描述

最后不能忘记了维护的size

总结

  1. 直接head = null,太过暴力,虽系统也会自动回收内存
  2. 遍历整个链表,将每个节点的next置为null,可以提高内存回收效率
  3. 若维护了变量size,记得将size置为0

//无头单向非循环链表
public class SingleLinkedList {

    //静态内部类 节点
    public static class ListNode {
        public int val;
        public ListNode next;

        public ListNode() {
        }

        public ListNode(int val) {
            this.val = val;
        }
    }

    //成员变量头结点
    public ListNode head;

    //链表的长度
    private int size;


    //头插法
    public void addFirst(int data) {
        //链表为空情况不用特殊考虑
        ListNode node = new ListNode(data);
        node.next = head;
        head = node;
        this.size++;
    }

    //尾插法
    public void addLast(int data) {
        //核心思路:找到最后一个节点
        ListNode node = new ListNode(data);
        if (head == null) {//链表为空的情况
            head = node;
            this.size++;
            return;
        }
        ListNode cur = head;//找到最后一个节点的位置
        while (cur.next != null) {
            cur = cur.next;
        }
        cur.next = node;//最后一个节点的next等于新节点
        this.size++;
    }

    //任意位置插入,第一个数据节点为0号下标
    public boolean addIndex(int index, int data) {
        if (index < 0 || index > this.size) {
            return false;//下标不合法
        }
        if (index == 0) {//下标为0相当于头插
            addFirst(data);
            return true;
        }
        if (index == this.size) {//下标为size相当于尾插法
            addLast(data);
            return true;
        }

        ListNode node = new ListNode(data);
        //让一个节点指针走 index-1 步
        ListNode cur = head;
        int count = index - 1;
        while (count > 0) {
            cur = cur.next;
            count--;
        }

        node.next = cur.next;
        cur.next = node;
        this.size++;
        return true;
    }

    //查找在单链表当中是否包含值为key的节点
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    //删除第一次出现值为key的节点
    public void remove(int key) {
        if (head == null) {//空链表删除不了
            return;
        }
        ListNode cur = head;//
        while (cur.next != null) {
            if (cur.next.val == key) {
                cur.next = cur.next.next;
                this.size--;
                return;//删除一个直接走人
            }
            cur = cur.next;
        }

        //考虑头结点是key的情况
        if (head.val == key) {
            head = head.next;
            this.size--;
        }
    }

    public void display() {
        if (head == null) {
            System.out.println("null");
            return;
        }
        ListNode cur = head;
        while (cur != null) {
            if (cur != head) {
                System.out.print("-->");
            }
            System.out.print(cur.val);
            cur = cur.next;
        }
        System.out.println();
    }

    //删除所有值为key的节点
    public void removeAllKey(int key) {
        if (head == null) {//空链表删不了
            return;
        }
        ListNode cur = head;
        while (cur.next != null) {
            if (cur.next.val == key) {
                cur.next = cur.next.next;
                this.size--;
            } else {//关键点易错点
                cur = cur.next;
            }
        }
        if (head.val == key) {
            head = head.next;
            this.size--;
        }
    }

    //清空链表
    public void clear() {
        ListNode cur = head;
        ListNode curN = null;
        while (cur != null) {
            curN = cur.next;
            cur.next = null;
            cur = curN;
        }
        head = null;
        this.size = 0;
    }

}

无头双向非循环链表模拟实现

在这里插入图片描述

链表由节点组成,需要写一个节点类
在这里插入图片描述

双向链表每个节点都多存储了前一个节点的引用

这里比刚才的单链表多维护一个last,记录的是链表中的最尾节点的引用

头插法

思路:

  1. 链表为空时,将head和last 都等于新创建的节点

  2. 不为空时,进行如下操作
    在这里插入图片描述

  3. node.next = head;
    在这里插入图片描述

  4. head.prev = node
    在这里插入图片描述

  5. head = node;
    在这里插入图片描述

在这里插入图片描述

总结

  1. 需考虑链表为空的情况,并注意容易忽略的last变量
  2. 注意别忘了prev和size

尾插法

思路:

  1. 考虑链表为空的情况,为空的时候将node直接赋值给head和last
  2. 不为空时,利用last进行尾插
    在这里插入图片描述
  3. last.next = node;
    在这里插入图片描述
  4. node.prev = last;
    在这里插入图片描述
  5. last = node;

在这里插入图片描述

在这里插入图片描述

总结

  1. 需考虑链表为空的情况,跟头插法一样,也可以直接调用头插法
  2. 利用last引用插入节点

任意位置插入,第一个数据节点为0号下标

思路:

  1. 先判断下标的合法性

  2. 当下标为0时,相当于头插法

  3. 当下标为链表的长度size时,相当于尾插法
    在这里插入图片描述

  4. 除了头插和尾插,接下来找到index下标的节点
    在这里插入图片描述
    此时cur 指向的节点的下标就是index

  5. 进行插入操作
    在这里插入图片描述

第一步node.next = cur;
在这里插入图片描述

第二步node.prev = cur.prev;

在这里插入图片描述
第三步cur.prev.next = node;

在这里插入图片描述
第四步cur.prev = node;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总结

  1. 考虑插入下标 index 的合法性
  2. 考虑是头插法和尾插法的情况
  3. 找到index下标的节点,进行插入操作
  4. 不需要额外考虑链表为空的情况

查找值为key的节点是否在链表中

思路:与单链表代码相同,遍历链表即可
在这里插入图片描述

总结

  1. 与单链表代码相同
  2. 无需额外考虑链表为空的情况

删除第一次出现关键字为key的节点

思路:

  1. 与单链表思路相同,但需要额外判断删除的节点是否为last节点
  2. 如果为last节点,则last节点 向前一步走
  3. 如果不是last节点,则正常按照单链表时的删除方式删除

在这里插入图片描述
写到这里时,就需要判断删除的节点是否为整个链表的最后一个节点,也就是last节点

在这里插入图片描述

在最后考虑头节点时,也同样需要考虑删除的节点是否为last节点

在这里插入图片描述

在这里插入图片描述

总结

  1. 需额外考虑链表为空
  2. 定义引用cur遍历链表,判断cur的下一个节点是否为目标删除节点
  3. 需要每次判断删除的节点是否为 last 节点
  4. 最后考虑头结点,同样删除的时候也需考虑头节点
  5. 如果删除的节点是last节点,记得改变完last节点后,将last的节点的next置为null
  6. 当删除的节点是头节点时,记得将新head 的prev置为null

删除所有值为key的节点

思路:

  1. 只需将 只删除一个key的节点 的代码稍作修改即可

在这里插入图片描述

总结

  1. 只有当没有删除节点时,cur才需要往后移动一位
  2. 需额外考虑链表为空
  3. 定义引用cur遍历链表,判断cur的下一个节点是否为目标删除节点
  4. 需要每次判断删除的节点是否为 last 节点
  5. 最后考虑头结点,同样删除的时候也需考虑头节点
  6. 如果删除的节点是last节点,记得改变完last节点后,将last的节点的next置为null
  7. 当删除的节点是头节点时,记得将新head 的prev置为null

打印链表

根据自己想法来即可

在这里插入图片描述

清空链表

思路:

  1. 定义两个引用 cur 和 curN
  2. curN 指向cur的下一个节点
  3. 将cur节点的 next和prev置为null
  4. 将cur指向curN,curN指向cur的下一个节点

在这里插入图片描述

总结

  1. 需要考虑链表为空
  2. 通过两个引用,一个引用用于置为null,一个引用用于记录位置
  3. 注意最后不要忘记将last也置为空

选择题1

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以选A,在进行链表指向的改动时,一定要最后修改已知节点的指向,比如这个p节点,一定是先修改p.prev的next,然后再修改p的prev,如果先修改p的prev,那么就丢失了原先的那个点。

选择题2

在这里插入图片描述
解:当head.next 等于 head或 head.prev 等于 head 时,此时说明链表为空,所以选C

给定 x, 把一个链表整理成前半部分小于 x, 后半部分大于等于 x 的形式

牛客网:给定 x, 把一个链表整理成前半部分小于 x, 后半部分大于等于 x 的形式

思路:

  1. 创建两个链表less和big,一个用于存储小于x的节点,一个用于存储大于等于x的节点
  2. 将链表less的尾和big的头拼接起来则为最终结果链表

创建两个节点存储两个链表的头节点
在这里插入图片描述


由于需要每次尾插,所以需要一个引用用于记录两个链表当中的尾节点

在这里插入图片描述

接着开始遍历链表

在这里插入图片描述


当节点的值小于x 时,需要考虑less的链表是否为空,只有不为空时,才可以用lessLast尾节点进行插入
在这里插入图片描述

当链表为空时,让头结点和尾节点都指向新节点

在这里插入图片描述
当链表不为空时,进行尾插法

在这里插入图片描述
接下来同理考虑 节点值大于等于x的情况

在这里插入图片描述
最后一定不能忘了让cur向前一步走
在这里插入图片描述

在创建好less和big链表后,接下来,需要将两个链表拼接起来

在拼接的时候,我们需要令lessLast.next = bigHead;

但是当less链表为空时,就不能这么调用
在这里插入图片描述
所以我们需要特判一下两个链表为空的情况

考虑特殊情况后即可直接头尾拼接即可

在这里插入图片描述
这里需要注意的是:一定要将big链表的尾节点的next置为空,它的next是链表当中的某一个节点或null

那为什么刚才特判的时候不置为空?是因为less和big当中的某一个链表为空,那么最后一个节点的next一定为null


在这里插入图片描述

public class Partition {
    public ListNode partition(ListNode pHead, int x) {
        if (pHead == null) {//链表为空
            return null;
        }

        //记录两个链表的头节点
        ListNode lessHead = null;
        ListNode bigHead = null;

        //记录两个链表的尾节点
        ListNode lessLast = null;
        ListNode bigLast = null;

        //开始遍历原链表
        ListNode cur = pHead;
        while (cur != null) {
            if (cur.val < x) {//节点值小于x
                if (lessHead == null) {
                    lessHead = lessLast = cur;
                } else {
                    lessLast.next = cur;
                    lessLast = cur;
                }
            } else {
                if (bigHead == null) {
                    bigHead = bigLast = cur;
                } else {
                    bigLast.next = cur;
                    bigLast = cur;
                }
            }
            cur = cur.next;
        }
        
        //开始拼接
        if (lessHead == null) {
            return bigHead;
        }
        if (bigHead == null) {
            return lessHead;
        }
        
        lessLast.next = bigHead;
        bigLast.next = null;//*
        return lessHead;
    }
}

不带头链表做法(更简便)

可以用带头的链表,这样则不需要考虑下列几种情况

  1. 尾插时不需要考虑链表是否为空
  2. 拼接时不需要考虑 less 链表和 big 链表为空的情况

在这里插入图片描述

总结
流程:

  1. 创建 less 链表 和 big 链表,分别存储小于和大于等于x值的节点
  2. 遍历链表根据节点的值 进行尾插
  3. 将两个链表拼接起来

易错点和注意事项:

  1. 无论带不带头,都需在拼接时,将big链表尾节点的next置为null(非常容易错)
  2. 若带头,则需要在尾插时考虑链表为空的情况,在拼接时考虑less和big链表为空的情况

判断链表是否回文

判断链表是否回文

思路:

方法1:

  1. 遍历链表将每个节点的值存到一个新数组中
  2. 利用数组判断是否回文

方法2:

  1. 利用快慢指针找到链表中间的节点(偶数个向尾偏)
  2. 翻转从中间节点向后的链表
  3. 判断是否回文

翻转前:
在这里插入图片描述
翻转后:
在这里插入图片描述

方法1在牛客网不能用,需要额外开辟空间


在这里插入图片描述

按照思路,我们首先先找到中间节点

快慢指针思路:

  1. 定义两个引用初始都指向头节点
  2. 一个每次走两步,一个每次走一步
  3. 当快的指针没法往后走时,两个指针停止移动
  4. 此时slow指向的节点就为目标节点

定义两个指针:
在这里插入图片描述
开始移动
在这里插入图片描述
注意事项:

  1. fast != null 一定要写在 fast.next != null 的前面
  2. fast != null 保证了走一步的时候不会空指针异常,fast.next != null保证了走的第二步不会空指针异常
  3. 为什么不用判断 slow 为空呢? 因为fast一定比slow快,fast一定比slow先为空或同时为空

得到中间节点后,接下来将以中间节点开头的链表翻转

翻转时,我们需要三个指针相互配合


大致步骤如下:

  1. 提前记录cur的下一个节点
  2. 将cur的next指向prev
  3. prev指向cur
  4. cur指向curN
    在这里插入图片描述

需要注意的是,在翻转链表的时候,一定要将原链表的头节点的next置为null

可以将prev的初始值设置为null,这样当cur为头节点时,cur.next = prev 则可以满足要求

也可以特判一下,加一个if语句,不过前者更简单

翻转函数返回的是翻转后新链表的头节点

在这里插入图片描述

这里的curN可以是head也可以null,因为会在遍历时被改变

在这里插入图片描述

值得注意的是,不可以将 curN = cur.next;放在末尾,会导致空指针异常

当翻转结束时,prev会指向新链表的头节点,最后返回 prev 即可

在这里插入图片描述

接下来是判断

在这里插入图片描述
在这里插入图片描述
值得注意的是,循环条件不能是 cur1 != cur2,这样写会导致空指针异常。

当节点数为偶数个时,有一边会率先是null,然后next不了发生空指针异常。

在这里插入图片描述

如果想要在不破坏链表的情况下判断回文,那么就需要再把链表翻转回来

那么在判断回文时,就不能直接return,我们需要一个标记来判断是否为回文链表

在这里插入图片描述

完整代码:

在这里插入图片描述


总结

流程: 找中间节点 --> 翻转链表 --> 判断回文 (–> 给人家再翻转回来)

  1. 找到中间节点(向尾偏),利用快慢指针
  2. 翻转链表,从中间节点开始
  3. 判断回文,两指针面对面走

注意事项与易错点:

(1) 找中间节点时

  1. 注意fast != null 要写在 fast.next != null 的前面

(2) 翻转链表时

  1. 一定要将原头节点的 next置为null,将prev初始化为null为最佳解决方案
  2. curN = cur.next一定要在循环的最开头,否则会空指针异常
  3. 翻转后链表的新头节点为prev节点

(3)判断回文时

  1. while循环条件里不能是 cur1 != cur2,会导致空指针异常
  2. 若不想破坏原链表,则需要一个标记,在翻转回原链表后,返回标记即可

判定链表相交并求出交点

判定链表相交并求出交点

思路:

方法1:(哈希表法)

  1. 选择任意一个链表,将该链表的所有节点存储到哈希表中
  2. 遍历另一个链表,判断每个节点是否已经在哈希表中了
  3. 若在哈希表中存在,则该节点为链表相交节点
  4. 若遍历完链表,没有节点在哈希表中存在,则两链表不想交

方法2:

  1. 获取两个链表的长度
  2. 定义两个指针,长度较长的链表的指针 走长度差步
  3. 接着两指针同步开始移动,如果两节点相等,则该节点为相交节点
  4. 若任意指针为null,则说明没有交点

方法1:

所有链表题,都先特判null

在这里插入图片描述

接着选择一条链表,将该链表的所有节点存储到哈希表中,这里我选择了headA链表

在这里插入图片描述

然后遍历另一个链表,判断是否存在相交节点
在这里插入图片描述

完整代码如下

在这里插入图片描述


方法2:

链表题都需要特判 链表是否为空

判断完后接下来先获取链表的长度
在这里插入图片描述

获取长度后,谁长谁的指针先走长度差步

在这里插入图片描述

接着同步移动,若为同一节点,则返回该节点,若遍历完,则返回null代表无相交节点

在这里插入图片描述

完整代码:
在这里插入图片描述


总结

方法1:哈希表法

流程:

  1. 将一链表的所有节点存储到哈希表
  2. 遍历另一链表,若节点在哈希表中存在,该节点为目标节点
  3. 遍历完链表若未返回节点,则代表两链表为相交

易错点:

  1. 注意需特判链表为空的情况
  2. set中添加元素的方法是 add 不是put

方法2:

流程:

  1. 获取两链表的长度
  2. 派两个指针,长度长的链表的指针先走 长度差步
  3. 随后两指针同步移动
  4. 若同时指向相同节点,则该节点为相交节点
  5. 若遍历完未找到节点,则返回null

易错点:

  1. 需要判断链表为空
  2. 注意同步移动时,循环条件是 curA != null && curB != null

判定链表带环

判定链表是否带环

思路:

方法1:(利用快慢指针)

  1. 定义fast和slow两个引用分别指向链表的头节点
  2. 在不超出链表长度的前提下,fast每次走两步,slow每次走一步
  3. 如果链表有环,那么两个指针一定会相遇,否则则无环

方法2:(利用哈希表)

  1. 遍历链表,将每个节点存储到哈希表中
  2. 若节点在哈希表中已经存在,则证明链表有环

方法1:

在这里插入图片描述

值得注意的是,fast != null 保证了fast走的第一步不会空指针异常 , fast.next != null 同理保证了走的第二步

而且fast != null 必须在 fast.next != null 的前面

if (fast == slow)该判断必须在slow和fast移动后

如果先判断,由于fast和slow的初始值都是head,那么只要进循环就都会返回true


方法2:

在这里插入图片描述


总结

方法1:(快慢指针法)

流程:

  1. 定义两个指针指向链表的头节点
  2. 一个每次走两步,一次每次走一步
  3. 若相遇则有环,若fast遍历完链表,则无环

易错点:

  1. 循环条件中的fast != null 一定在 fast.next != null 的前面
  2. 先移动,后判断,先判断fast == slow 由于初始值都是head,所以所有情况都是true了
  3. 需要判断链表是否为空

方法2:(哈希表法)

流程:

  1. 遍历链表,将每个节点存储到哈希表中
  2. 若节点已经在哈希表中,则有环
  3. 若遍历完链表,则无环

易错点:

  1. 需要判断链表是否为空

求环的入口点

求环的入口点

方法1:(哈希表法)

思路:

  1. 遍历链表,将每个节点存储到哈希表中
  2. 若节点已在哈希表中,则该节点为环的入口点

该方法虽然简单,但是需要额外开一份空间


方法2:

思路:

  1. 利用快慢指针判定是否有环
  2. 若有环,则快慢两个指针的相遇点到入环节点的距离 等于 头节点到入环节点的距离(证明在后面)
  3. 再定义一个 指针指向头节点
  4. 指向相遇点的节点 和 指向头节点的指针以每次一步的速度 同步移动
  5. 当两个节点相同时,则为入环节点

方法1代码:

在这里插入图片描述


方法2:

距离相同的证明如下:

  1. 假设一个链表,快慢指针相遇点如下图所示 (有环就有相遇点)

在这里插入图片描述

记头节点到 入环点 的距离为 a

入环点到相遇点的距离为 b

相遇点到入环点的距离为 c

在这里插入图片描述
由图可得:

  1. 慢指针移动距离为 a + b
  2. 快指针移动距离为 a + n(b + c) + b,其中 n 为可能移动的圈数
  3. 由于快指针移动的距离是 慢指针的二倍
  4. 可得 2*(a + b) = a + n(b + c) + b
  5. 化简后可得 a = (n - 1) * (b + c) + c
  6. 该式子则可说明 假设 x 从头节点开始往入环点走,y从相遇点往入环点走,那么 x 走了a距离,y走了n-1圈 和 c距离后,两人就会在入环点相遇

问:如果慢指针也转圈了,那么公式是不是推错了?

答:如果转圈则距离为 a + b + n(a + b),在后面化简时 n1(a+b) 会与右边快指针的n2(a+b)消掉(两个圈数不一样所以n1n2),所以不用写


还有一种思路也可以证明距离相等:

  1. 在快慢指针到达相遇点时
  2. 此时在定义一个指针指向头结点记为point,该指针与slow的速度一样
  3. 接着slow和point指针同步移动
  4. 那么当point 走到相遇点时,slow指针相当于走了原 fast指针走过的距离
  5. 则point 指针和 slow 指针也会在 之前的相遇点相遇
  6. 在环中移动并且速度一样还能相遇
  7. 只能说明当point指针 走到入环点时与 slow已经相遇

代码:
1.特判链表为空
在这里插入图片描述

2.在进行完特判后,接着需要获取快慢指针的相遇点
在这里插入图片描述
获取相遇点可以封装成一个函数会比较简单考虑的少,这里先写一个不封装成函数的写法

除了slow指针和fast指针外,这里定义了一个标记flag,用于判断链表是否有环

在这里插入图片描述

注意这里的fast != null 和 fast.next != null,一定是fast != null 在前

注意这里的 if (fast == slow)判断一定是fast和slow移动后才判断

只有当移动后的两指针相遇了这一种情况,才说明有环

在这里插入图片描述


3.创建一个新指针,开始与 slow同步同速移动
在这里插入图片描述
由于无环的情况已经被排除了,所以走到这里一定有环,所以while循环条件判断里无需判断cur是否为空

相遇后返回cur或slow即可

//方法1:哈希表法
    public ListNode detectCycle(ListNode head) {
        //特判链表为空
        if (head == null) {
            return null;
        }

        //遍历链表
        Set<ListNode> hash = new HashSet<>();
        ListNode cur = head;
        while (cur != null) {
            //如果已存在,则该节点为入环节点
            if (hash.contains(cur)) {
                return cur;
            } else {
                hash.add(cur);
            }
            cur = cur.next;
        }
        return null;
    }

    //方法2:快慢指针法
    public ListNode detectCycle(ListNode head) {
        //特判链表为空
        if (head == null) {
            return null;
        }

        //获取快慢指针的相遇点
        ListNode slow = head;
        ListNode fast = head;
        boolean flag = false;//标记链表是否有环

        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {//只有当移动后的两指针相遇这一种情况
                flag = true;   //才能说明有环
                break;
            }
        }
    
        if (!flag) {//无环则返回null
            return null;
        }

        //新节点与slow节点同步移动
        ListNode cur = head;
        while (cur != slow) {//一定有环,cur不会为空
            cur = cur.next;
            slow = slow.next;
        }

        return cur;
    }

在求相遇点时,可以封装成一个函数,这样既简单又不会出错

在这里插入图片描述


总结

方法1:(哈希表法)

流程:

  1. 遍历链表,将所有的节点存储到哈希表中
  2. 若存储的节点已经在哈希表中存在,则该节点为环的入口点
  3. 若遍历完链表,则代表无环,则返回null

易错点:

  1. 需要判断链表为空
  2. set添加元素的方法是add,而不是put

方法2:(快慢指针法)

  1. 利用快慢指针判环,求得两指针相遇点
  2. 利用 头节点到入环点的距离 和 相遇点到入环点的距离相等这一现象
  3. 创建新指针与原slow节点同步同速开始移动
  4. 相遇时即为入环点

易错点:

  1. 需特判链表为空
  2. 求相遇点时,只有当slow和fast 移动后 的相遇才相当于是有环(有可能都停在head),所以while循环里需要先移动,后判断(特别易错)
  3. 求相遇点时,while循环的判断条件里的 fast != null 一定在 fast.next != null 的后面
  4. 求相遇点,封装成一个函数最佳(函数的功能为 有环返回相遇点,无环返回null),不用函数时,用一个布尔类型标记最佳

合并两个有序链表

合并两个有序链表

记所需合并链表为 list1 和 list2,记结果链表为res

思路:

  1. 与合并两个有序数组思路大致相同

  2. 需要定义三个指针分别指向list1、list2和res的头节点。(记为cur1, cur2, last)

  3. 每次比较指向list1和list2的指针的值,值小的节点就尾插到res链表上

  4. 当cur1或指向 cur2等于null时,此时不等于null的一方直接接到 res 链表即可

模拟的过程如下:

其中res结果链表为 一个带头的节点,节点的值对结果毫无影响,可以使任意值

在这里插入图片描述

每次比较cur1 和 cur2 节点的值,1 < 2 所以将cur1 节点直接尾插到 res链表上,并且cur1和last要往后一步走

在这里插入图片描述

接下来再次比较两个指针所指向的节点的值,2 < 3,所以尾插cur2节点,然后记得last和cur2往后一步走

在这里插入图片描述

接着重复比较cur1 和 cur2的值,直到有一方的链表遍历完

在该种情况中,则是上面的链表先遍历完

在这里插入图片描述
此时只需要将cur2节点及其后面的节点,直接拼接到last后面即可

在这里插入图片描述


代码:

链表题都需要考虑链表为空的情况

在这里插入图片描述

接着 创建一个带头新链表 和 三个指针

在这里插入图片描述

接着开始擂台比较,只有小的一方可以走,大的需要留在擂台上继续比较

在这里插入图片描述

接着没遍历完的链表往后一拼接就行

在这里插入图片描述

最后返回头节点即可,注意,由于我们是带头的节点,所以返回的是newHead.next;

在这里插入图片描述


在这里插入图片描述

public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //特判链表为空
        //如果一方为空,合并完就是另一个链表
        if (list1 == null) {
            return list2;
        }
        if (list2 == null) {
            return list1;
        }

        //带头链表
        ListNode newHead = new ListNode(0);
        //三个指针
        ListNode cur1 = list1;
        ListNode cur2 = list2;
        ListNode last = newHead;

        //重复比较
        while (cur1 != null && cur2 != null) {
            if (cur1.val <= cur2.val) {
                //尾插cur1节点
                last.next = cur1;
                last = cur1;
                cur1 = cur1.next;
            } else {
                //尾插cur2节点
                last.next = cur2;
                last = cur2;
                cur2 = cur2.next;
            }
        }

        //拼接剩余节点,不用执着于last拼接完就有可能
        //不是最后一个节点了
        if (cur1 == null) {
            last.next = cur2;
        } else {
            last.next = cur1;
        }

        return newHead.next;
    }

总结

合并两个有序链表与合并两个有序数组思路差不多

流程:

  1. 创建一个带头的新链表
  2. 创建三个指针分别指向所给链表和新链表
  3. 擂台比较两链表指针的值,小的一方,尾插到新链表中,大的一方继续比较
  4. 当出现遍历完链表的情况,则谁没遍历完拼接谁

易错点:

  1. 需判断链表为空的情况,有一方为空,则直接返回另一链表的头节点
  2. 如果新链表不带头,需额外单独比较一次,给新链表的头节点初始化
  3. 如果新链表带头,则最后返回的是 头节点的下一个节点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值