代码随想录Day3 | 203. 移除链表元素,707. 设计链表,206. 反转链表

Day3

前言

依旧是当天晚上开始,但是发现不懂链表,看了代码随想录的链表基础看懂了,但是依旧不知道怎么用代码去操纵链表,第二天上午某人视频讲解了如何用代码操作链表,醍醐灌顶,开始实操

文章:代码随想录

视频:算法公开课-跟着Carl学算法

LeetCode 203 移除链表元素

自己思路

自己有意识到移除链表的头节点和后续节点的操作是不一样的,但是发现真的把这个思路用代码去实践,卡了半天写不来一点,遂看视频吧

看完讲解

卡哥的第一种方法是原链表删除元素,和我自己的思路不谋而和,听完之后思路更清晰了,既然头结点和后续节点的操作不一样,那么分开分成两次while循环来操作(头结点也需要是while,万一前面连续几个的var都是target呢)

过程中遇到了两个问题:首先是空指针异常,这个在卡哥视频里有讲,但是我当时并不懂为什么会报这样的错,自己写代码的时候,也没有加上非空报错,然后果然报错了。后来我是这样理解的:首先题目给的head可能就是个空节点,这样子cur也会是空节点,那么两个循环都会出现空指针异常情况;另外,如果head中全是targrt,那么给到cur的时候就是空节点,也会报错(mark一下gpt:去获取null的属性,就会空指针异常)

第二个问题是if后面需不需要加else,我第一次写的没有else,因为我认为不管有没有删除操作,cur都要后移。后来发现如果需要删除的话,其实已经变向完成了cur后移动的操作(准确来说应该把cur.next当作参照物和判断对象,不用删的时候后移就改变了cur.next,要删的时候删除操作也改变了cur.next),所以是需要加else的

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        while ((head != null) && (head.val == val)) {
            head = head.next;
        }
        ListNode cur = head;
        while ((cur != null) && (cur.next != null)) {
            if (cur.next.val == val) {
                cur.next = cur.next.next;
            } else {
                cur = cur.next;
            }   
        }
        return head;
    }
}

第二个也是最有效的解题思路,虚拟头节点,这个方法可以将上述需要两次while循环来分开处理头节点和后续节点的问题一次性处理,具体方法就是在head的前面添加一个虚拟节点dummy,使dummy.next指向head,那么后续的代码处理就和上面的第二个while循环一样了

唯一的区别就是while循环中不需要再加上cur!=null了,因为这里的cur是dummy是我们自己new出来的,不会存在空指针异常的问题;然后也同样是用cur来操作,避免直接操作dummy使其发生变化,最后的return就可以直接return dummy.next

class Solution {
    public ListNode removeElements(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;
    }
}

LeetCode 707 设计链表

自己思路

感觉可能会写,但是发现不知道怎么去写代码,为什么那些函数里面都没有传入链表,哦哦哦引用数据类型不传入也行,但是我怎么定义单个节点,不太懂

看完讲解

感觉整体的思路是懂的,为了统一所有的操作,全部都采用虚拟头节点的方法。过程中需要注意两个细节:一个是cur具体指向谁,要指向通过cur可以获取所有需要的节点的位置;另一个是进行插入操作时,为了避免链表被损坏,一定都是先连接后一条边,再连接前一条边。但是我还是不知道怎么定义单个节点,有点没看懂

好的浅浅看了一点代码,原来是需要自己创建一个类去定义链表节点,没有意识到真的..感觉自己才读懂题意,不存在什么传入链表,而是你直接自己定义一个空链表开撸,并且整个类中定义好虚拟头节点全局使用,同时也定义好size用于全局使用和遍历,所以构造函数里面,那就是初始化size和虚拟头节点

写代码的过程中,发现对于ListNode类的定义还是不熟,还有就是对于add和delete操作,也是需要对index进行判断的,不合理的可以直接return,否则会报错空指针异常,同时每次操作也需要对应改变size大小。在delete中,小于0以及大于size-1的就要return。在add中,小于0以及大于size的就要return(可以等于size,相当于尾巴添加),然后在头或者尾添加就可以直接调用这个即可

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

class MyLinkedList {
    int size;
    ListNode dummy;

    public MyLinkedList() {
        size = 0;
        dummy = new ListNode();
    }
    
    public int get(int index) {
        if (index < 0 || index > size - 1) {
            return -1;
        }
        ListNode cur = dummy.next;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        return cur.val;
    }
    
    public void addAtHead(int val) {
        addAtIndex(0, val);
    }
    
    public void addAtTail(int val) {
        addAtIndex(size, val);
    }
    
    public void addAtIndex(int index, int val) {
        if (index < 0 || index > size) {
            return;
        }
        ListNode addNode = new ListNode(val);
        ListNode cur = dummy;
        for (int i = 0; i < index; i++){
            cur = cur.next;
        }
        addNode.next = cur.next;
        cur.next = addNode;
        size++;
    }
    
    public void deleteAtIndex(int index) {
        if (index < 0 || index > size - 1) {
            return;
        }
        ListNode cur = dummy;
        for (int i = 0; i < index; i++){
            cur = cur.next;
        }
        cur.next = cur.next.next;
        size--;
    }
}

LeetCode 206 反转链表

自己思路

遵循建议,直接先看视频

看完讲解

首先是双指针的写法,看完视频加自己实操一次后,结合画图感觉还是挺好理解的。要注意的是两点,首先是cur的next要先用临时节点temp保存下来,避免下一次循环时获取不到。再就是前后两次赋值(一次改变方向,一次移动)都要仔细思考赋值的先后顺序,避免被覆盖而获取不到

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur = head;
        ListNode pre = null;
        while (cur != null) {
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
}

然后是在此基础上的递归的写法,上一个写法弄懂之后,这个也是easy。整体的思路和上面的基本一样,只是相当于用嵌套函数的方式来完成了,需要注意的是reverse中再调用reverse的时候也需要加上return,不然会报错

class Solution {
    public ListNode reverseList(ListNode head) {
        return reverse(null, head);
    }

    public ListNode reverse(ListNode pre, ListNode cur) {
        if (cur == null) {
            return pre;
        }
        ListNode temp = cur.next;
        cur.next = pre;
        return reverse(cur, temp);
    }
}

总结

用时:5.5h,依旧还是后面补的

对于链表的确不是很懂,有一个从刚刚接触到慢慢熟悉的过程

链表的定义

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;
    }
}

// 使用无参构造初始化节点
ListNode dummy = new ListNode();

  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我知道了。以下是对您的问题的回答: a. 动态分配内存的方法有很多,可以使用C语言的malloc函数,也可以使用C++中的new操作符,具体方法如下: C语言的malloc函数: ```c struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode)); ``` C++中的new操作符: ```c++ ListNode* node = new ListNode(); ``` b. 初始化单链表需要对链表节点的成员变量进行赋值,以下是一个示例: ```c++ struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(NULL) {} }; ListNode* head = NULL; ``` c. 头插法创建单链表的示例代码如下: ```c++ ListNode* head = new ListNode(); head->next = NULL; for (int i = 0; i < n; i++) { int x; cin >> x; ListNode* node = new ListNode(x); node->next = head->next; head->next = node; } ``` 尾插法创建单链表的示例代码如下: ```c++ ListNode* head = new ListNode(); head->next = NULL; ListNode* tail = head; for (int i = 0; i < n; i++) { int x; cin >> x; ListNode* node = new ListNode(x); node->next = NULL; tail->next = node; tail = node; } ``` d. 取链表某一位的值可以通过遍历链表来实现,以下是一个示例: ```c++ ListNode* p = head->next; int pos = 3; for (int i = 1; i < pos && p != NULL; i++) { p = p->next; } if (p == NULL) { cout << "链表长度不足" << endl; } else { cout << p->val << endl; } ``` e. 在链表的某一位进行插入操作可以通过遍历链表找到插入位置,然后插入一个新节点来实现,以下是一个示例: ```c++ ListNode* p = head; int pos = 3; for (int i = 1; i < pos && p != NULL; i++) { p = p->next; } if (p == NULL) { cout << "链表长度不足" << endl; } else { int x = 100; ListNode* node = new ListNode(x); node->next = p->next; p->next = node; } ``` f. 遍历链表可以通过循环遍历链表节点来实现,以下是一个示例: ```c++ ListNode* p = head->next; while (p != NULL) { cout << p->val << " "; p = p->next; } cout << endl; ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值