【代码训练营】day3 | 203.移除链表元素 & 707.设计链表 & 206.反转链表

所用代码 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;
    }
}

有了结点,链表可以进行定义:如后面707设计链表

注意:
单链表最重要的就是双指针操作,一个前向指针,一个目标指针,很多操作都是两个指针有关。所以我们常常定义一个虚拟头结点进行操作,并使他的下一结点指向head结点。

ListNdoe dummy = new ListNode(-1);
// 虚拟头结点的下一结点指向头结点
dummy.next = head;

移除链表元素 LeetCode203

题目链接:移除链表元素 LeetCode203 - 简单

思路

本题有两种解法,一是采用虚拟头结点,另一种是没有使用虚拟头结点。使用虚拟头结点就不用判断是不是头结点该删除,直接按删除步骤进行删除。但是一般都会使用虚拟头结点进行操作,这样更加的方便。

1、使用虚拟头结点

    public ListNode removeElements(ListNode head, int val) {
        // 新建一个虚拟头结点进行操作
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        // 新建两个指针对原链表进行操作
        ListNode p1 = dummy; // p1指向待删除结点的前向节点
        ListNode p2 = dummy.next; // p2指向待删除节点
        while (p2 != null){
            // 找到值,使该值(p2)的前向指针(p1)指向该值(p2)的后项指针,则删除
            if (p2.val == val){
//                System.out.println("打印p1的值:" + p1.val);
//                System.out.println("打印p2的值:" + p2.val);
                p1.next = p2.next;
                p2 = p2.next; // 找到之后该节点删除,后移一位
                continue;
            }
            p1 = p1.next;
            p2 = p2.next;
        }
        return dummy.next;
    }

2、不使用虚拟头结点

    public ListNode removeElements(ListNode head, int val) {
        // 判断待删除的是不是头结点,删除的时候要头结点不为空结点,否则可能空指针异常
        while (head != null && head.val == val ) {
            head = head.next;
        }
        // 判断是不是空结点,是就直接返回
        if (head == null) {
            return head;
        }

        // 以上保证了head不是空结点且head.val != val

        // 保证了head与head.next都不为空
        ListNode p1 = head; // p1前向指针
        ListNode p2 = head.next; // 指向待删除的结点,上面head.next不为null就是为了这里
        
        // while 这里可以这么写,比上面那种写法更简便一点
        while (p2 != null) {
            if (p2.val == val) {
                p1.next = p2.next;
            } else {
                p1 = p1.next;
            }
            p2 = p2.next;
        }
        return head;
    }

总结

1、使用虚拟头结点可以很快速轻松的解答出这道题,因为删除结点题找到结点后是没法删除的,必须找到该结点的前一个结点才可以删除。而且不用考虑各种头结点是否为null的情况,简单代码量又少。while里面的循环逻辑用下面那个更好一点,就一点都不冗余。

2、所犯的错误:未使用虚拟头结点时,一直报空指针异常,还以为是自己的逻辑有问题,结果是未考虑完整头结点是否为空的情况。

设计链表 LeetCode 707

题目连接: 设计链表 LeetCode 707 - 中等

思路

可采用单链表或双链表的方式。这里采用单链表的方式,单链表最重要的是虚拟头结点的使用!

// 这里是我idea的完整可运行代码,调试很方便!!!

public class DesignNode {
    public static void main(String[] args) {
        MyLinkedList linkedList = new MyLinkedList();
        /*
        linkedList.addAtHead(1);
//        linkedList.printf();
        linkedList.addAtTail(3);
//        linkedList.printf();
        linkedList.addAtIndex(1,2);   //链表变为1-> 2-> 3
        linkedList.printf();
        int i = linkedList.get(1);//返回2
        System.out.println("第xx位结点的值为:" + i);
        linkedList.deleteAtIndex(1);  //现在链表是1-> 3
        System.out.println("删除xx位后的链表:");
        linkedList.printf();*/

        linkedList.addAtTail(1);
        int i = linkedList.get(0);
        System.out.println("第i位的值位:"+i);
        linkedList.printf();

    }
}

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

class MyLinkedList {
    // 新建一个虚拟头结点
    ListNode dummy;
    // 链表长度
    int size;

    public MyLinkedList() {
        dummy = new ListNode(-1);
        size = 0;
    }

    public int get(int index) {
        if (index < 0 || index >= size){
            return -1;
        }
        ListNode p = dummy.next;
        int i = 0;
        while (p != null){
            if (i == index){
                // 找到就直接返回
                return p.val;
            }else{
                i++;
                p = p.next;
            }
        }
        return -1;
    }

    public void addAtHead(int val) {
        ListNode head = new ListNode(val);
        if (size == 0){
            dummy.next = head;
        }else {
            head.next = dummy.next;
            dummy.next = head;
        }
        size++;
    }

    public void addAtTail(int val) {
        ListNode tail = new ListNode(val);
        ListNode p = dummy.next; // p指针操作
        if (p == null){
            // 这里不能用p=tail,这样就改变了p指针的指向,且dummy.next无关系了,
            dummy.next = tail;
        }else {
            while (p.next != null){
                p = p.next;
            }
            // 指向最后了就赋值
            p.next = tail;
        }
        size++;
    }

    public void addAtIndex(int index, int val) {
        // 小于0,头部插入
        if (index <= 0){
            addAtHead(val);
            // 大于size 不插入
        }else if (index > size) {
            return;
            // 等于链表长度,尾部插入
        }else if (index == size){
            addAtTail(val);
        }else {
            ListNode node = new ListNode(val);
            ListNode pre = dummy;
            ListNode cur = dummy.next;
            int i = 0;
            while (cur != null){
                if (i == index){
                    pre.next = node;
                    node.next = cur;
                    // 插入成功,退出循环,否则会一直判断cur的值,然后被卡住!
                    break;
                }else {
                    i++;
                    pre = pre.next;
                    cur = cur.next;
                }
            }
            size++;
        }
    }

    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size){
            return;
        }
        ListNode pre = dummy;
        ListNode cur = dummy.next;
        int i = 0;
        while (cur != null){
            if (i == index){
                pre.next = cur.next;
                cur = cur.next;
                // 由于只删除一位,删除完成后则退出
                break;
            }else {
                i++;
                pre = pre.next;
            }
            cur = cur.next;
        }
        size--;
    }

    // 打印链表的值
    public void printf(){
        int i = 0;
        ListNode p = dummy.next;
        while (p != null){
            System.out.println("链表第"+i+"位结点的值为:" + p.val + "==> size=" + size);
            p = p.next;
            i++;
        }
    }
}

总结

1、本题全是自己想出来的,没有看解答,运行之后觉得时间和空间还可以优化,然后运行了一下代码随想录的代码发现时间和空间基本差不多,后意识到可能是我采用单向链表的缘故,就把双向链表的代码复制进去发现还是差不多。知道了我的代码还可以继续优化,就暂不现在优化,放以后二刷、三刷改进的时候优化。

设计链表.png


2、设计链表这题有非常多的细节和坑,稍微一不注意就会出错,比如是否取等,什么时候退出循环善于思考,多debug,就可以把大问题分解成小问题并一一解决。

反转链表 LeetCode 206

题目链接:反转链表 LeetCode 206 - 简单

思路

感觉很简单,但是想了很久一直都出错,就画了一幅图,跟着思路四步完成。

反转链表.png

public ListNode reverseList(ListNode head) {
    ListNode p1 = null;
    ListNode p2 = head;
    ListNode move = null;
    
    while (p2 != null){
        // 1.先让移动结点指向p2的next结点
        move = p2.next;
        // 2.再把p2的next结点指向前一结点(p1)
        p2.next = p1;
        // 3.p1向前移动到p2的位置,保证自己在头结点位置
        p1 = p2;
        // 4. p2移向自己的next结点处
        p2 = move;
    }
    return p1;
}

总结

本来这题没做出来都睡了,突然灵光一现想到了就起来又把题做好,一次通过。

反思:** 看起来特别简单自己以为自己很快就能做出来,但是突然卡住了就会想很久有点浪费时间了,以后出现这种情况还是先看解析再来写。毕竟现在基础薄弱,对这些题只是看着熟,做起来一点都不熟,特别是细节的地方需多多进步。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值