Day3
前言
依旧是当天晚上开始,但是发现不懂链表,看了代码随想录的链表基础看懂了,但是依旧不知道怎么用代码去操纵链表,第二天上午某人视频讲解了如何用代码操作链表,醍醐灌顶,开始实操
文章:代码随想录
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();