写在前边的话
今天开启了链表的学习,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.移除链表元素
题目链接
题目难度
简单
看到题目的第一想法
第一次做这道题的时候,对虚拟节点这种方法没接触过,会考虑用不使用虚拟节点的方法来写,写的过程中对于头节点的处理会处理不好的情况。
看完代码随想录之后的总结
1. 文章讲解
2. 视频讲解
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.设计链表
题目链接
题目难度
中等
看到题目的第一想法
前边熟悉了虚拟节点的使用方法,看到这道题的时候就自然想到用虚拟节点了,感觉会更方便。
看完代码随想录之后的总结
1. 文章讲解
2. 视频讲解
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.反转链表
题目链接
题目难度
简单
看到题目的第一想法
看到这道题的时候,有一定的思路,就是定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null,然后进行循环反转就行。
看完代码随想录之后的总结
1. 文章讲解
2. 视频讲解
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中的递归算法暂时还没有看,明天或者后边再刷题的时候再补上吧。