代码随想录算法训练营D3|链表理论基础,203.移除链表元素 ,707.设计链表,206.反转链表

目录

链表

链表的操作

删除节点

增加节点

查找节点

链表操作的两种方式

性能分析

203. 移除链表元素

​​​​​​707. 设计链表

206.反转链表


链表

  • (单链表)是通过指针将节点串联一起的线性结构,每个节点由数据域和指针域构成,指针域存放指向下一节点的指针。
  • 除了单链表,还有双链表循环链表两种链表类型
  • 双链表,每个节点有两个指针域,一个指向下一个节点,另一个指向上一个节点。既可以向前查询,也可以向后查询
  • 循环链表,链表首尾相连
  • 链表中的节点在内存中不是连续分布的,散落在内存的不同地址空间上,通过指针串联

链表的操作

删除节点

  • 将要删除节点的前一节点指针指向删除节点的后一节点
  • 至于这一删除节点的内存,系统内存回收机制会去释放该内存
  • O(1)操作

增加节点

  • 将新节点的指针指向添加位置的后一节点,再将要添加位置的前一节点指针指向新节点,
  • O(1)操作

查找节点

  • 虽然以上两个操作都是O(1),但都需要在操作前进行查找,而查找需要从头结点开始逐个查找,因而时间复杂度O(n)

链表操作的两种方式

  1. 直接使用原来的链表来进行操作。
  2. 设置一个虚拟头结点在进行操作。虚拟头节点可以统一对节点的操作,无需区别该节点是否头节点。

性能分析

public class ListNode {

    /**
     * 节点的数据值
     */
    int val;
    /**
     * 下一节点
     */
    ListNode next;

    /**
     * 节点的无参构造函数
     */
    public ListNode() {
    }

    /**
     * 节点的有参构造函数(一个参数)
     *
     * @param val
     */
    public ListNode(int val) {
        this.val = val;
    }

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

203. 移除链表元素

public class RemoveLinkedListElements {
    /**
     * 原链表删除元素方法(需区别是否头节点来进行操作)
     *
     * @param head
     * @param val
     * @return
     */
    public ListNode regularMethod(ListNode head, int val) {
        // 需要定义一个用来遍历链表的移动指针
        ListNode cur;
        // 先判断头节点的值是否等于val,是则头结点后移
        // 因为需要一直往后判断这个头节点是否等于val 直到不等于 所以使用while
        while (head != null && head.val == val) {
            head = head.next;
        }
        // 因为单链表无法向前查找,所以移动指针需要从最新的头节点开始遍历
        cur = head;
        // 判断非头节点的值是否等于val,是则非头节点的上一节点指针指向非头节点的下一节点
        while (cur != null && cur.next != null) {
            if (cur.next.val == val) {
                cur.next = cur.next.next;
            } else {
                cur = cur.next;
            }
        }
        return head;
    }

    /**
     * 虚拟头节点方法(统一头节点和非头节点的操作)
     *
     * @param head
     * @param val
     * @return
     */
    public ListNode dummyHeadMethod(ListNode head, int val) {
        // 定义一个虚拟头节点,其指针指向真实头节点
        ListNode dummy = new ListNode();
        dummy.next = head;

        // 定义一个用于遍历的指针
        ListNode cur = dummy;
        while (cur.next != null) {
            if (cur.next.val == val) {
                // 找到相同的值,则删除该节点
                cur.next = cur.next.next;
            } else {
                // 否则移动遍历的指针
                cur = cur.next;
            }
        }
        return dummy.next;
    }
}

​​​​​​707. 设计链表
 

public class MyLinkedList {
    /**
     * 链表长度
     */
    int size;
    /**
     * 虚拟头节点
     */
    ListNode dummy;

    /**
     * 初始化链表
     */
    public MyLinkedList() {
        size = 0;
        dummy = new ListNode(0);
    }

    /**
     * 获取对应下标节点的值
     *
     * @param index
     * @return
     */
    public int get(int index) {
        // 判断下标值的合法性
        if (index < 0 || index >= size) {
            return -1;
        }
        ListNode cur = dummy;
        // 因为还有虚拟头节点,得多走一步才到指定下标的位置
        for (int i = 0; i <= index; i++) {
            cur = cur.next;
        }
        // 循环结束,cur已走到对应下标位置
        return cur.val;
    }

    /**
     * 插入新头节点
     *
     * @param val
     */
    public void addAtHead(int val) {
        // 新节点
        ListNode newNode = new ListNode(val);
        // 新节点的指针指向真正的头节点(即虚拟头节点的next),没有头节点时也适用
        newNode.next = dummy.next;
        dummy.next = newNode;
        // 长度+1
        size++;

    }

    /**
     * 插入新尾节点
     *
     * @param val
     */
    public void addAtTail(int val) {
        // 新节点
        ListNode newNode = new ListNode(val);
        ListNode cur = dummy;

        for (int i = 0; i < size; i++) {
            cur = cur.next;
        }
        // 此时cur指针已到尾节点的位置,在其之后加新节点,不要忘记了长度+1
        cur.next = newNode;
        size++;
    }

    /**
     * 在指定下标前加入新节点
     *
     * @param index
     * @param val
     */
    public void addAtIndex(int index, int val) {
        ListNode newNode = new ListNode(val);
        // 下标合法值判断
        if (index < 0 || index > size) {
            // 等于链表长度时,相当于在链表尾部加新节点,所以不包含这种情况
            // index为0,size为0,相当于加头节点,也不包含情况
            return;
        }

        ListNode cur = dummy;
        // 遍历cur指针到对应index的上一节点
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        // 先将新节点指针指向下一节点,否则先给cur的next赋值的话,会丢失下一节点
        newNode.next = cur.next;
        cur.next = newNode;
        size++;
    }

    /**
     * 删除指定下标的节点
     *
     * @param index
     */
    public void deleteAtIndex(int index) {
        // 校验下标的合法性
        if (index < 0 || index >= size) {
            return;
        }
        ListNode cur = dummy;
        // 要删除节点,就要将前一节点指向后一节点
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        // 此时cur在指定下标的上一节点
        cur.next = cur.next.next;
        size--;
    }
}

206. 反转链表

        由于链表由指针串联,当涉及对链表操作时,是需要至少2个临时指针来作为遍历的指向或者信息缓存。

        本题建议使用双指针法(实际我认为是多指针,都三个了),后续复习到递归时再尝试使用递归解此题。

public class ReverseList {

    /**
     * 双指针解法(逻辑比递归清晰易懂)
     *
     * @param head
     * @return
     */
    public ListNode twoPointersMethod(ListNode head) {
        // 定义移动的cur指针
        ListNode cur = head;
        // 定义用于存前后节点的临时指针
        ListNode pre = null;
        ListNode tmp;

        // 当cur为null时说明链表已结束
        while (cur != null) {
            // 暂存下一节点
            tmp = cur.next;
            // 当前节点指向上一节点
            cur.next = pre;
            // 更新cur和pre指针
            pre = cur;
            cur = tmp;
        }
        // 当cur指针指向null说明已到尾部,返回新头节点
        return pre;
    }
}

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值