算法训练营day03|链表|203.移除链表元素,707设计链表,206反转链表

本文介绍了链表的基本概念,包括单向、双向和循环链表,强调了链表与数组在存储上的区别以及其在不同场景下的适用性。详细讲解了链表删除、增加元素的方法,包括使用虚拟头节点优化处理,以及链表反转的两种方法——双指针法和递归法。
摘要由CSDN通过智能技术生成

1.知识点

1.1链表知识

链表节点包含:值阈,指针阈

单向链表:节点指向一个方向,节点由值阈,next指针组成

双向链表:节点指向两个方向,节点由值阈,next指针,prev指针组成

循环链表:尾节点指向头节点,形成环

1.2链表性质

1.数组的存储在内存中是连续的,而链表是分散的,通过节点的指针阈将这些内存区域"连接"起来

2.链表的组成决定了,链表写方便,读不方便,所以写操作多,读操作少的场景可以考虑链表,而数组适合写操作少,读操作多的场景

1.3链表题目处理技巧

1.链表删除元素时,如果头节点是要删除的元素,需要单独考虑,为了避免这种情况,可以新增加一个虚拟头节点,并指向链表头节点,通过虚拟头节点遍历,就可以将头节点和其他节点作为一样的节点了.详见题目203代码.

2.链表增加元素时候,通过虚拟头节点得到要增加位置index的前置节点pre,注意节点处理的顺序

要先处理新增节点new.next=cur.next,最后再处理pre.next=new,详见题目707

2.刷题

203.移除链表元素

LeetCode链接 203. 移除链表元素 - 力扣(LeetCode)

题目描述

方法1:新增虚拟头节点

package daimasuixiangshuati.day03_lianbiao;

/**
 * @Author LeiGe
 * @Date 2023/9/23
 * @Description todo
 */
public class YiChuLianBiaoYuanSu203_2 {

    /**
     * 方法1:设置一个虚拟头节点,dummyHead
     * 方便处理头节点,如果头节点的值是要删除的值,
     * 增加虚拟头节点后,头节点和其他节点一样对待了
     *
     * @param head
     * @param val
     * @return
     */
    public ListNode removeElements(ListNode head, int val) {
        // 设置虚拟头节点,并指向head
        ListNode dummyHead = new ListNode(-2, null);
        dummyHead.next = head;

        //记录前一个节点:用于处理节点
        ListNode pre = dummyHead;
        //当前遍历到的节点:用于遍历
        ListNode cur = head;

        while (cur != null) {
            // 如果当前遍历的节点是要删除的元素
            if (cur.val == val) {
                pre.next = cur.next;
            } else {
                pre = cur;
            }
            // 继续往后节点遍历
            cur = cur.next;
        }

        // 注意这里,如果删除了头节点,头节点就变化了,需要重新赋值头节点
        head = dummyHead.next;
        return head;
    }

    /**
     * 节点类
     */
    public static class ListNode {
        private final int val;
        private ListNode next;

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

时间复杂度O(N)

额外空间复杂度O(1)

方法2:直接用原来的头节点

package daimasuixiangshuati.day03_lianbiao;

/**
 * @Author LeiGe
 * @Date 2023/9/23
 * @Description todo
 */
public class YiChuLianBiaoYuanSu203_3 {

    /**
     * 方法2:不设置虚拟头节点
     *
     * @param head
     * @param val
     * @return
     */
    public ListNode removeElements(ListNode head, int val) {
        // 如果头节点是要删除的节点
        while (head != null && head.val == val) {
            head = head.next;
        }

        if (head == null) {
            return null;
        }

        // 此时的head已经不会被删除了

        //记录前一个节点:用于处理节点
        ListNode pre = head;
        //当前遍历到的节点:用于遍历
        ListNode cur = head.next;

        while (cur != null) {
            if (cur.val == val) {
                pre.next = cur.next;
            } else {
                pre = cur;
            }
            cur = cur.next;
        }

        return head;
    }

    /**
     * 节点类
     */
    public static class ListNode {
        private final int val;
        private ListNode next;

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

时间复杂度O(N)

额外空间复杂度O(1)

707.设计链表

LeetCode链接 707. 设计链表 - 力扣(LeetCode)

题目描述

方法1:单链表实现

package daimasuixiangshuati.day03_lianbiao;

/**
 * @Author LeiGe
 * @Date 2023/9/23
 * @Description todo
 */

//单链表 节点类
class ListNode {
    int val;
    ListNodeDouble next;

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

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

public class SheJiLianBiao707_2 {
    // 记录元素个数
    private int N;
    // 虚拟头节点
    ListNodeDouble dummyHead;

    /**
     * 构造函数
     */
    public SheJiLianBiao707_2() {
        dummyHead = new ListNodeDouble(0);
        N = 0;
    }

    /**
     * get(index):获取链表中index 节点的值。如果索引无效,则返回-1。
     *
     * @param index
     * @return
     */
    public int get(int index) {
        if (index >= N || index < 0) {
            return -1;
        }
        ListNodeDouble cur = this.dummyHead;
        for (int i = 0; i <= index; i++) {
            cur = cur.next;
        }
        return cur.val;
    }

    /**
     * addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点
     *
     * @param val
     */
    public void addAtHead(int val) {
        addAtIndex(0, val);
    }

    /**
     * addAtTail(val):将值为 val 的节点追加到链表的最后一个元素
     *
     * @param val
     */
    public void addAtTail(int val) {
        addAtIndex(N, val);
    }

    /**
     * addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。
     * 如果 index 等于链表的长度,则该节点将附加到链表的末尾。
     * 如果 index 大于链表长度,则不会插入节点。
     *
     * @param index
     * @param val
     */
    public void addAtIndex(int index, int val) {
        if (index > N) {
            return;
        }

        if (index < 0) {
            index = 0;
        }
        ListNodeDouble newNode = new ListNodeDouble(val);
        ListNodeDouble pre = this.dummyHead;
        // 找到要插入节点的前驱
        for (int i = 0; i < index; i++) {
            pre = pre.next;
        }
        
        // 注意:最后处理pre.next
        newNode.next = pre.next;
        pre.next = newNode;
        N++;
    }

    /**
     * deleteAtIndex(index):如果索引 index 有效,则删除链表中下标为 index 的节点。
     *
     * @param index
     */
    public void deleteAtIndex(int index) {
        if (index >= N || index < 0) {
            return;
        }
        N--;
        if (index == 0) {
            dummyHead = dummyHead.next;
            return;
        }
        ListNodeDouble pre = this.dummyHead;
        for (int i = 0; i < index; i++) {
            pre = pre.next;
        }
        pre.next = pre.next.next;
    }
}

方法2:双链表实现

package daimasuixiangshuati.day03_lianbiao;

/**
 * @Author LeiGe
 * @Date 2023/9/24
 * @Description todo
 */

//双链表 节点类
class ListNodeDouble {
    int val;
    ListNodeDouble next;
    ListNodeDouble prev;

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

public class SheJiLianBiao707_3 {
    int N;

    // 记录链表虚拟头节点和尾节点
    ListNodeDouble head;
    ListNodeDouble tail;

    /**
     * 构造函数
     */
    public SheJiLianBiao707_3() {
        this.N = 0;
        this.head = new ListNodeDouble(0);
        this.tail = new ListNodeDouble(0);

        //这一步非常关键,否则在加入头节点的操作中会出现null.next错误
        head.next = tail;
        tail.prev = head;
    }

    /**
     * get(index):获取链表中index 节点的值。如果索引无效,则返回-1。
     *
     * @param index
     * @return
     */
    public int get(int index) {
        if (index >= N || index < 0) {
            return -1;
        }
        ListNodeDouble cur = this.head;
        // 判断是哪一边遍历时间更短.
        if (index >= N / 2) {
            cur = tail;
            for (int i = 0; i < N - index; i++) {
                cur = cur.prev;
            }
        } else {
            for (int i = 0; i <= index; i++) {
                cur = cur.next;
            }
        }
        return cur.val;
    }

    /**
     * addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点
     *
     * @param val
     */
    public void addAtHead(int val) {
        addAtIndex(0, val);
    }

    /**
     * addAtTail(val):将值为 val 的节点追加到链表的最后一个元素
     *
     * @param val
     */
    public void addAtTail(int val) {
        addAtIndex(N, val);
    }

    /**
     * addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。
     * 如果 index 等于链表的长度,则该节点将附加到链表的末尾。
     * 如果 index 大于链表长度,则不会插入节点。
     *
     * @param index
     * @param val
     */
    public void addAtIndex(int index, int val) {
        if (index > N) {
            return;
        }

        if (index < 0) {
            index = 0;
        }

        N++;
        ListNodeDouble newNode = new ListNodeDouble(val);

        // 找到要插入节点的前驱
        ListNodeDouble pre = this.head;
        for (int i = 0; i < index; i++) {
            pre = pre.next;
        }

        // 记录下一个节点
        ListNodeDouble nextNode = pre.next;

        // 注意:最后修改 pre.next的值
        newNode.next = nextNode;
        nextNode.prev = newNode;

        pre.next = newNode;
        newNode.prev = pre;
    }

    /**
     * deleteAtIndex(index):如果索引 index 有效,则删除链表中下标为 index 的节点。
     *
     * @param index
     */
    public void deleteAtIndex(int index) {
        if (index >= N || index < 0) {
            return;
        }
        N--;
        if (index == 0) {
            head = head.next;
            return;
        }
        // 找到index位置的前驱
        ListNodeDouble pre = this.head;
        for (int i = 0; i < index; i++) {
            pre = pre.next;
        }

        // 注意:最后修改 pre.next的值
        pre.next.next.prev = pre;
        pre.next = pre.next.next;
    }
}

206.反转链表

LeetCode链接 206. 反转链表 - 力扣(LeetCode)

题目描述

方法1:双指针法

package daimasuixiangshuati.day03_lianbiao;

/**
 * @Author LeiGe
 * @Date 2023/9/24
 * @Description todo
 */

public class FanZhuanLianBiao206_2 {
    /**
     * 方法1-双指针法
     * 1.保存下一个节点
     * 2.反转(将当前节点的下一个节点变为上一个节点)
     * 3.前一个节点往后走
     * 4.当前节点往后走
     *
     * @param head
     * @return
     */
    public ListNode1 reverseList(ListNode1 head) {
        //上一个节点
        ListNode1 prev = null;
        //当前节点
        ListNode1 cur = head;
        //下一个节点
        ListNode1 temp;

        while (cur != null) {
            //1.保存下一个节点
            temp = cur.next;
            //2.反转(将当前节点的下一个节点变为上一个节点)
            cur.next = prev;
            //3.前一个节点往后走
            prev = cur;
            //4.当前节点往后走
            cur = temp;
        }
        return prev;
    }

    static class ListNode1 {
        int val;
        ListNode1 next;

        ListNode1() {
        }

        ListNode1(int val) {
            this.val = val;
        }

        ListNode1(int val, ListNode1 next) {
            this.val = val;
            this.next = next;
        }
    }
}

方法2:递归法

package daimasuixiangshuati.day03_lianbiao;

/**
 * @Author LeiGe
 * @Date 2023/9/24
 * @Description todo
 */
public class FanZhuanLianBiao206_3 {
    /**
     * 方法2-递归法
     * 逻辑同双指针法:
     * 1.记录顺序链表的前一个节点,当前节点,下一个节点
     * 2.将当前链表的下一个节点设置为上一个节点
     * 3.更新上一个节点,当前节点
     *
     * @param head
     * @return
     */
    public ListNode reverseList(ListNode head) {
        return reverse(null, head);
    }

    private ListNode reverse(ListNode prev, ListNode cur) {
        if (cur == null) {
            return prev;
        }
        ListNode temp;
        //保存下一个节点
        temp = cur.next;
        //反转
        cur.next = prev;
        //递归
        return reverse(cur, temp);
    }

    public class ListNode {
        int val;
        ListNode next;

        ListNode() {
        }

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

        ListNode(int val, ListNode next) {
            this.val = val;
            this.next = next;
        }
    }
}

3.小结

链表基础知识,链表删除元素,链表增加元素,链表反转

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值