代码随想录算法训练营第三天| 链表理论基础、203.移除链表元素、707.设计链表、206.反转链表
移除链表元素
题目:203.移除链表元素
题目链接:https://leetcode.cn/problems/remove-linked-list-elements/
文章讲解:https://programmercarl.com/0203.%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.html
视频讲解: https://www.bilibili.com/video/BV18B4y1s7R9/
状态:自己写出来了
自己看到题目的第一想法
指针的题目最好都有2个dummyNode作为最前节点和最后节点,这样做指针运算的时候能减少很多边界条件的判断,因此我在head前加了一个dummyNode,然后做遍历。
---------------二刷分割线--------------------
看了下提纲列表,链表处理逻辑中最好增加一个dummyNode用鱼处理head的特殊场景。整体循环过程中不断递进prev和cur用于处理节点的删除,实现的代码:
public ListNode removeElements(ListNode head, int val) {
// 搞一个dummyNode作为最前的虚拟节点,用于删除head的特殊场景代码兼容
ListNode dummy = new ListNode();
dummy.next = head;
ListNode prev = dummy;
ListNode cur = head;
while(cur != null){
if(cur.val == val){
prev.next = cur.next;
}else{
prev = cur;
}
cur = cur.next;
}
return dummy.next;
}
看完代码随想录之后的想法
所有边界条件的判断应该先放在首位,写代码时,第一步要考虑到边界条件。
代码随想录文章内的代码初始化了一个前节点和当前节点作为循环中的变化量。整体思路差不多
自己实现过程中遇到哪些困难
在边界条件判断上有错误,后面自己改了下代码“误打误撞”AC了,后面做链表题时一定要记得可以通过画图来理解。
设计链表
707.设计链表
题目链接:https://leetcode.cn/problems/design-linked-list/
文章讲解:https://programmercarl.com/0707.%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.html
视频讲解:https://www.bilibili.com/video/BV1FU4y1X7WD/
状态:写出来并且通过了,前几次提交代码的一些边界条件没处理好。
自己看到题目的第一想法
设计链表,之前做过,链表最好的处理方式就是初始化两个dummyNode,作为最前和最后的节点,方便所有节点的操作。然后还可以结合hashMap将链表的查找从O(n)降为O(1)。用双向链表简化链表的变化复杂度。
刚开始用hashMap记录索引,后面放弃了,index太难处理。
----------二刷分割线----------
整体写是差不多写出来了,也初始化了2个虚拟节点作为头节点和尾节点方便处理,但是这里关键的是先写addAtIndex,然后addAtHead和addAtTail可以直接调用addAtIndex的方法来实现。这里还有一个关键就是边界条件的判断,每个方法的调用都要注重边界条件的判断。
测试了多次,并且看了代码随想录代码后的代码:
class MyLinkedList {
// 虚拟节点,head的next怎么处理都不会为null
Node head;
// 虚拟节点,tail的prev怎么处理都不会为null
Node tail;
int count = 0;
class Node{
int val;
Node prev;
Node next;
}
public MyLinkedList() {
head = new Node();
tail = new Node();
head.next = tail;
tail.prev = head;
}
public int get(int index) {
Node node = getNode(index);
if(node == null){
return -1;
}else{
return node.val;
}
}
private Node getNode(int index){
//如果index非法,返回-1
if (index < 0 || index >= count) {
return null;
}
Node currentNode = head;
//包含一个虚拟头节点,所以查找第 index+1 个节点
for (int i = 0; i <= index; i++) {
currentNode = currentNode.next;
}
return currentNode;
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(count, val);
}
public void addAtIndex(int index, int val) {
//index大于链表长度
if(index>count){
return;
}
//index小于0
if(index < 0){
index = 0;
}
//找到前驱
Node pre = this.head;
for(int i=0; i<index; i++){
pre = pre.next;
}
//新建结点
Node newNode = new Node();
newNode.val = val;
newNode.next = pre.next;
pre.next.prev = newNode;
newNode.prev = pre;
pre.next = newNode;
count++;
}
public void deleteAtIndex(int index) {
if(index < count){
Node node = getNode(index);
Node preNode = node.prev;
Node nextNode = node.next;
preNode.next = node.next;
nextNode.prev = preNode;
count--;
}
}
}
看完代码随想录之后的想法
整体的思路和我差不多,就是随想录只使用了单链表,没使用双向链表,单向链表在写一些指针增加、减少的场景下会稍微简单一些,不需要处理前序节点。后续自己写代码的时候也可以关注下,需不需要使用更复杂的链表类型去处理。
自己实现过程中遇到哪些困难
链表的核心就是知道链表的赋值是一个引用。
反转链表
题目:206.反转链表
题目链接:https://leetcode.cn/problems/reverse-linked-list/
文章讲解:https://programmercarl.com/0206.%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.html
视频讲解:https://www.bilibili.com/video/BV1nB4y1i7eL/
状态:开写!
自己看到题目的第一想法
链条题目,画图!
画完图后定义pre节点(初始值为null)和cur节点,每次进入循环时先将cur.next引用保留,然后将cur的指针指向pre执行反转。
处理完一次AC搞定!
时间复杂度 O(n)
空间复杂度 O(1)
看完代码随想录之后的想法
除了双指针法还有递归的做法,递归的做法核心也是先保存下一节点,反转,然后再继续递归。
--------------------二刷记录------------------------
反转链表注意新建一个null的pre节点。
public ListNode reverseList(ListNode head) {
if(head == null){
return head;
}
// 经典反转链表,pre cur 2个节点往前递进
ListNode pre = null;
ListNode cur = head;
while(cur != null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
自己实现过程中遇到哪些困难
无困难,很流畅
今日收获&学习时长
收获:做链表题的核心就是画图+理解链表节点的赋值是引用赋值。
工作上的事情占了很大一部分精力,链表因为之前做过一些题目,所以再次写题的时候就比较流畅,后续还是需要关注下自己的时间安排。
整体时长应该2个小时多。