代码随想录算法训练营第三天|203.移除链表元素 707.设计链表 206.反转链表

写在前边的话

        今天开启了链表的学习,python还算熟悉,java是学习阶段,今天要加油呀!

链表的基础知识

链表:动态数据的管理者

        与数组这种静态数据结构不同,链表提供了更为灵活的数据管理方式,尤其在处理不确定大小或经常变动的数据集时,会更加的便捷。

什么是链表?

        链表是一种线性数据结构,其中元素不是在内存中连续存放的,而是通过节点之间的链接来组织。

        每个节点包含两部分:一部分是存储数据的区域,另一部分是一个指针,指向链表中的下一个节点。最后一个节点的指针通常为null,表示链表的结束。

链表的类型

根据节点之间的连接方式划分的话链表有以下几种:

  • 单向链表(Singly Linked List):每个节点仅有一个指向下一个节点的指针。
  • 双向链表(Doubly Linked List):除了指向下一个节点的指针外,每个节点还有一个指向前一个节点的指针,允许双向遍历。
  • 循环链表(Circular Linked List):在单向或双向链表的基础上,最后一个节点的指针不指向bull,而是指向链表的第一个节点,形成一个闭环。

链表的优点

  • 动态性:链表的大小可以在运行时动态改变,可以轻松的插入和删除元素,而不影响其它元素的位置。
  • 节省空间:链表只使用所需的空间,与数组不同的是数组需要预分配固定大小的空间,即使实际用不到那么大空间。
  • 灵活性:与数组相比,链表可以更高效的处理频繁插入和删除操作,因为链表不需要移动大量元素来腾出或填补空位。

链表的缺点

  • 随机访问:链表不支持像数组那样的随机访问,访问链表中的特定元素需要从头开始逐个节点遍历,效率就会较低。
  • 额外的开销:链表中的每个节点都需要额外的存储空间来保存指针信息。

链表的应用

目前工作中用的比较少还不能完全理解掌握链表的妙用,就留给以后补充啦!

好文推荐

好的文章当然大家一起看啦,卡哥总结的链表基础知识更加的全面而且图文结合,看完以后我是有了更好的理解,也推荐给大家链表理论基础

开始算法练习之前

        因为我这次的算法练习会涉及到python和java,所以在这里把两种语言的链表定义写一下,也是为了加深自己的印象,同时为我后续的算法练习做好准备。

python 

class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next

java

ListNode类定义示例,用于整数类型的链表:

public class ListNode {
    // 结点存储的数据
    int val;

    // 下一个结点
    ListNode next;

    // 节点的构造函数(无参)
    public ListNode() {
    }

    // 节点的构造函数,用于创建新的节点(有一个参数)
    public ListNode(int val) {
        this.val = val;
        this.next = null;
    }

    // 节点的构造函数,用于创建新的节点(有两个参数)
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

注:在这个节点创建例子中,对于有一个参数的构造函数,当一个新的ListNode被创建时,其next字段被显式的设置成null,其实可以省略这行代码,因为在java中,对象的成员变量(即引用类型)默认会被初始化null,所以,即使不写this.next=null;next也会默认为null。

这是java语言特性的一部分,所有对象引用类型默认值都是null,除非它们被显式初始化为其他值。因此,在创建链表节点时,如果不限时初始化next,它将自动被设置成null。

如果想要一个更通用的链表节点,可以使用范型来代替具体的类型,这样就可以用定义的节点来创建任何类型的链表,例如:

public class ListNode<T> {
    // 结点存储的数据
    T val;

    // 下一个结点
    ListNode<T> next;

    // 节点的构造函数(无参)
    public ListNode() {
    }

    // 节点的构造函数,用于创建新的节点(有一个参数)
    public ListNode(T val) {
        this.val = val;
        this.next = null;
    }

    // 节点的构造函数,用于创建新的节点(有两个参数)
    public ListNode(T val, ListNode<T> next) {
        this.val = val;
        this.next = next;
    }
}

在这个范型版本中ListNode类中,T代表任意类型,可以将它换成我们需要的类型,比如Integer, String, Double等。(查资料查到的,后边学习一下怎么用,先列在这里。)  


基础知识总结完啦,下边就开始我的算法练习啦,今天是链表的day1。 

203.移除链表元素

题目链接

力扣203.移除链表元素

题目难度

简单

看到题目的第一想法

        第一次做这道题的时候,对虚拟节点这种方法没接触过,会考虑用不使用虚拟节点的方法来写,写的过程中对于头节点的处理会处理不好的情况。

看完代码随想录之后的总结

1. 文章讲解

    代码随想录203链表移除元素

2. 视频讲解

    代码随想录203链表移除视频讲解

3.学到的

        在做链表操作中,可以使用原链表来直接进行删除操作,也可以设置一个虚拟头节点再进行删除操作,设置虚拟头节点的方法比较方便。

4. 代码编写
  • 时间复杂度 O(n)
  • 空间复杂度 O(1)

python

不使用虚拟节点

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:

        # 删除头节点等于val的情况
        while(head and head.val == val):
            head = head.next

        #到这里head的值已经不等于val了,继续删除其余等于val的数据
        cur = head
        while(cur and cur.next):
            if cur.next.val == val:
                cur.next = cur.next.next
            else:
                cur = cur.next
        return head

使用虚拟节点

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        dummy = ListNode(next=head)  #创建一个虚拟节点并且它的下一个节点指向head
        cur = dummy
        while(cur and cur.next):
            if cur.next.val == val:
                cur.next = cur.next.next
            else:
                cur = cur.next
        return dummy.next

java

不使用虚拟节点

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
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;
    }
}

使用虚拟节点

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode dummy = new ListNode(-1, head);
        ListNode cur = dummy;
        while(cur != null && cur.next != null){
            if(cur.next.val == val){
                cur.next = cur.next.next;
            }else{
                cur = cur.next;
            }
        }
        return dummy.next;
    }
}

 说明:我这里python和java的解题思路都是一样的,我主要是对java不熟悉,因此现阶段就按照python的解题思路来写啦,后边如果二刷的话会关注下其它写法。


707.设计链表

题目链接

力扣707设计链表

题目难度

中等

看到题目的第一想法

前边熟悉了虚拟节点的使用方法,看到这道题的时候就自然想到用虚拟节点了,感觉会更方便。

看完代码随想录之后的总结

1. 文章讲解

   代码随想录707设计链表

2. 视频讲解

    代码随想录707设计链表视频讲解

3.学到的

    更加熟悉了链表的操作,练习中对于边界值的判定有所疏忽,调试后找到错误并得到了更好的理解。同时使用虚拟头节点确实会更加方便。

4.代码编写

python

单链表(注意index判断的时候边界的判断,第一次没有写对)

class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = None

class MyLinkedList:

    def __init__(self):
        self.dummy = ListNode(0)
        self.size = 0

    def get(self, index: int) -> int:
        if index < 0 or index > self.size - 1:
            return -1
        
        cur = self.dummy
        for i in range(index):
            cur = cur.next
        return cur.next.val

    def addAtHead(self, val: int) -> None:
        add_data = ListNode(val)
        add_data.next = self.dummy.next
        self.dummy.next = add_data
        self.size += 1

    def addAtTail(self, val: int) -> None:
        add_data = ListNode(val)
        cur = self.dummy
        while(cur.next):
            cur = cur.next
        cur.next = add_data
        self.size += 1
        
    def addAtIndex(self, index: int, val: int) -> None:
        if index < 0 or index > self.size:  #注意这里时>self.size
            return

        cur = self.dummy
        add_data = ListNode(val)
        for i in range(index):
            cur = cur.next
        tmp = cur.next
        cur.next = add_data
        add_data.next = tmp
        self.size += 1

    def deleteAtIndex(self, index: int) -> None:
        if index < 0 or index > self.size - 1: #注意这里时>self.size - 1
            return

        cur = self.dummy
        for i in range(index):
            cur = cur.next
        cur.next = cur.next.next
        self.size -= 1
        




# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)

双链表

Java

单链表(依然是index边界的判定,第一次也写错啦!)

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

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

    }
    
    public void addAtHead(int val) {
        ListNode add_data = new ListNode(val);
        add_data.next = dummy.next;
        dummy.next = add_data;
        size++;
    }
    
    public void addAtTail(int val) {
        ListNode add_data = new ListNode(val);
        ListNode cur = dummy;
        while(cur != null && cur.next != null){
            cur = cur.next;
        }
        cur.next = add_data;
        size++;
    }
    
    public void addAtIndex(int index, int val) {
        if(index < 0 | index > size) return;  //注意index边界的判定
        ListNode add_data = new ListNode(val);
        ListNode cur = dummy;
        for(int i=0; i<index; i++){
            cur = cur.next;
        }
        ListNode tmp = cur.next;
        cur.next = add_data;
        add_data.next = tmp;
        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--;
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

双链表

206.反转链表 

题目链接

力扣206反转链表

题目难度 

简单

看到题目的第一想法

        看到这道题的时候,有一定的思路,就是定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null,然后进行循环反转就行。

看完代码随想录之后的总结

1. 文章讲解

代码随想录206.反转链表

2. 视频讲解

代码随想录206.反转链表视频讲解

3.学到的

熟悉了链表反转的方法。

4.代码编写

    比较喜欢双指针法,所以下边代码的编写我用了双指针方法进行的练习。

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

python

双指针法:

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        cur = head
        pre = None
        while(cur):
            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp
        return pre

注意: pre的初始化,刚开始初始化为了0节点,导致结果中多了一个0节点

递归法:

Java(同样是注意pre的初始化,以及tmp的初始化,开始写的时候)

双指针法:

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        ListNode tmp = null;   //注意这里tmp直接在这里初始化比较好
        while(cur != null){
            tmp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
}

注意: 同样注意pre的初始化,以及要注意tmp的初始化,开始的时候写在了循环里,会使得每次循环都初始化一次tmp。

递归法:


今日感悟

        今天是开始链表练习的第一天,更加熟悉了链表相关的知识,更多的是熟悉了java链表相关的知识以及操作链表,不过还是不太熟悉,所以707中的双链表还有206中的递归算法暂时还没有看,明天或者后边再刷题的时候再补上吧。

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值