【代码随想录刷题】Day03 链表

在这里插入图片描述

1. 链表的理论基础

1.1 定义

  • 链表是一种通过指针串联在一起的线性结构,每一个结点有两个部分组成,一个是数据域,一个是指针域(用来存放指向下一个结点的指针),最后一个结点的指针域指向null(空指针的意思)
  • 链表的入口结点称为链表的头结点,也就是head

1.2 分类

1.2.1 单链表

单链表中的指针域只能指向结点的下一个结点
在这里插入图片描述

1.2.2 双链表
  • 每一个结点有两个指针域,一个指向下一个节点,一个指向上一个结点
  • 双链表既可以向前查询也可以向后查询
    在这里插入图片描述
1.2.3 循环链表
  • 链表首尾相连
  • 可以用来解决约瑟夫环问题
    在这里插入图片描述

1.3 链表的存储方式

链表是通过指针域的指针来连接在内存中的各个节点,所以链表中的结点在内存中不是连续分布的,而是散乱分布在内存中的某块地址上,分配机制取决于操作系统的内存管理
如图所示:
在这里插入图片描述

1.4 链表的定义

java定义链表节点的方式:

public class ListNode {
    int val;//结点值
    ListNode next;//下一个结点

    //结点的构造函数(无参)
    public ListNode() {
    }
    //结点的构造函数(有一个参数)
    public ListNode(int val) {
        this.val = val;
    }

    //结点的构造函数(两个参数)
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

1.5 链表的操作

1.5.1 删除
1.5.2 插入

1.6 链表的性能分析

插入/删除(时间复杂度)查询(时间复杂度)适用场景
数组O(n)O(1)数据量固定,频繁查询,较少i增删
链表O(1)O(n)数据量不固定,频繁增删,较少查询

2. 【203】移除链表元素

2.1 方法理论

以链表1 4 2 4为例,移除元素4
在这里插入图片描述
这种情况下,就是让结点的next指针直接指向下下一个结点就可以了
由于单链表的特殊性,只能指向下一个节点,刚刚删除的是链表中的第2个和第4个结点,如果删除的是头结点,那么就会涉及到以下两种链表的操作方式:

  • 直接使用原链表来进行删除操作
  • 设置一个虚拟头结点再进行删除操作

2.1.1 直接使用原链表来进行删除操作

移除头结点:只需将头指针向后移动移位即可

2.1.2 设置一个虚拟头结点再进行删除操作

在这里插入图片描述
这样的话就和移除链表中其他节点的方式统一了。

2.2 解题

	/**
     * 在原链表中删除元素
     * 不添加虚拟头结点
     * 时间复杂度O(n)
     * 空间复杂度O(1)
     * @param head
     * @param val
     * @return
     */
public static ListNode removeElements(ListNode head,int val){
        //首先需要考虑头结点为不为空
        if (head==null){
            return head;
        }
        //头结点不为空,且头结点的值等于目标值,就把头指针向后移
        while (head != null && head.val==val){
            head=head.next;
        }
        //头结点的值不等于目标值
        ListNode cur=head; //定义一个临时指针来遍历
        while (cur!=null){
            //cur的后继节点不为空,且后继节点的值等于目标值
            while (cur.next!=null && cur.next.val==val){
                cur.next=cur.next.next;
            }
            //cur的后继节点的值不等于目标值,cur指针就向后移动
            cur=cur.next;
        }
        return head;

    }
/**
     * 构建虚拟头结点
     * 时间复杂度O(n)
     * 空间复杂度O(1)
     * @param head
     * @param val
     * @return
     */
    public static ListNode removeElements1(ListNode head,int val){
        if (head==null){
            return head;
        }
        //因为删除可能涉及到头结点,所以设置dummyHead结点,统一操作
        ListNode dummyHead=new ListNode(-1,head);//把虚拟结点和原链表连接上
        //删除元素必须要有前驱才行
        ListNode pre=dummyHead;
        ListNode cur=head;
        while (cur!=null){
            if (cur.val==val){
                pre.next=cur.next;
            }else {
                /**
                 * 不等于目标值的话,cur需要向后移动,
                 * 那么在向后移动的过程中,找到了目标值,需要做删除操作,
                 * 那么此时需要找到其前驱元素
                 * 其前驱元素就暂存在pre中
                 */

                pre=cur;
            }
            cur=cur.next;//如果不等于目标值,则cur指针向后移动遍历
        }
        //使用虚拟头结点方法,最后返回的是dummyHead.next,而不是head
        //因为head有可能被删除了
        return dummyHead.next;

    }

3.【707】设计链表

单链表
  • 首先定义链表
public class ListNode {
    int val;//结点值
    ListNode next;//下一个结点

    //结点的构造函数(无参)
    public ListNode() {
    }
    //结点的构造函数(有一个参数)
    public ListNode(int val) {
        this.val = val;
    }

    //结点的构造函数(两个参数)
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}
  • 进行链表操作
public class MyLinkedList {
    //存储链表元素的个数
    int size;
    //虚拟头结点
    ListNode head;


    //初始化MyLinkedList对象
    public MyLinkedList() {
        size=0;
        head=new ListNode(0);

    }
    // 获取下标为index的结点的值  若下标无效,返回-1
    public int get(int index) {
        //index从0开始
        //首先判断index是否合法
        if (index<0 || index>=size){
            return -1;
        }

        /**
         * 定义一个临时指针来遍历
         * 原因:若直接使用头结点来遍历的话,在遍历的过程中,头结点的值发生了变化
         * 最后要返回头结点时,已不是原来的值
         */
        ListNode curNode=head;
        //包含一个虚拟头结点,所以查找的是第index+1个结点
        for (int i=0;i<=index;i++){
            curNode=curNode.next;
        }
        return curNode.val;


    }
    //将一个值为 val 的节点插入到链表中第一个元素之前。
    // 在插入完成后,新节点会成为链表的第一个节点。
    //也就是在index=0前插入节点
    public void addAtHead(int val) {
        addAtIndex(0,val);

    }
    //将一个值为 val 的节点追加到链表中作为链表的最后一个元素
    public void addAtTail(int val) {
        addAtIndex(size,val);

    }
    //将一个值为 val 的节点插入到链表中下标为 index 的节点之前。
    // 如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。
    // 如果 index 比长度更大,该节点将 不会插入 到链表中
    public void addAtIndex(int index, int val) {
        if (index>size){
            return;
        }
        if (index<0){
            index=0;
        }
        size++;
        //找到要插入节点的前驱    使用这个指针进行遍历
        ListNode preNode=head;
        for (int i=0;i<index;i++){
            //在找到目标节点之前,一直遍历,最终在index-1处停止
            preNode=preNode.next;
        }
        ListNode toAddNode=new ListNode(val);
        //插入节点
        toAddNode.next=preNode.next;
        preNode.next=toAddNode;

    }
    //如果下标有效,则删除链表中下标为 index 的节点
    public void deleteAtIndex(int index) {
        //首先判断index是否合法
        if (index<0 || index>=size){
            return;
        }
        size--;
        if (index==0){
            head=head.next;
            return;
        }
        //定义临时指针进行遍历
        ListNode preNode=head;
        for (int i=0;i<index;i++){
            preNode=preNode.next;
        }
        //删除节点
        preNode.next=preNode.next.next;

    }
}

4.【206】反转链表

思路

如果再定义一个新的链表,实现反转,其实有点浪费内存空间
只需要改变链表的next指针的指向,就可以直接将链表反转,如图所示:
在这里插入图片描述

  • 双指针法
  • 首先定义一个cur指针用来遍历,指向头结点;再定义一个pre指针,初始化为null(因为头结点反转后会变为尾结点,为节点的指针为null)
  • 接下来开始反转,注意要使用temp来保存一下cur.next结点,(因为接下来要改变cur.next的指向了,以防后续操作找不到此节点)
  • 然后,循环走代码逻辑,继续移动pre和cur指针
  • 最后,cur指针指向null,pre指向原链表的尾结点,也就是新链表的头结点,循环结束。此时可以return pre即可。
public ListNode reverseList(ListNode head){
        ListNode preNode=null;
        ListNode curNode=head;//临时指针来遍历
        ListNode temp=null;
        while (curNode!=null){
            temp=curNode.next;//保存下一个节点
            curNode.next=preNode;//指针反转
            //指针向后移动
            //两个指针移动顺序不能变
            preNode=curNode;
            curNode=temp;
        }
        return preNode;//最后preNode指向头结点  
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值