链表part01
- 链表理论基础
203.移除链表元素
707.设计链表
206.反转链表
链表理论基础
链表概念
- 链表(单链表):
一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域,一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)
- 双链表:
每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。既可以向前查询也可以向后查询。
- 循环链表: 链表首尾相连。
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
定义链表的节点
C++
单向链表节点定义
struct ListNode {
int data; //存储的数据
ListNode *next; // 指向下一个节点的指针
// 构造函数
ListNode(int x) : data(x), next(nullptr) {}
};
双向链表节点定义
struct DoublyListNode {
int data; // 存储的数据
DoublyListNode *prev; // 指向前一个节点的指针
DoublyListNode *next; // 指向下一个节点的指针
// 构造函数
DoublyListNode(int x) : data(x), prev(nullptr), next(nullptr) {}
};
nullptr
是一个特殊的指针值,表示该指针不指向任何对象,这是链表节点的典型初始化方式。
python
单向链表节点定义
class ListNode:
def __init__(self, data):
self.data = data # 存储数据
self.next = None # 指向下一个节点的引用,默认为None
双向链表节点定义
class DoublyListNode:
def __init__(self, data):
self.data = data # 存储数据
self.prev = None # 指向前一个节点的引用,默认为None
self.next = None # 指向下一个节点的引用,默认为None
类的 __init__
方法是一个特殊的方法,称为构造函数。它在创建新对象时自动调用。
链表的操作
删除节点
在C++里最好是再手动释放无用的节点,释放这块内存。
其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。
添加节点
链表的增添和删除都是O(1)操作,也不会影响到其他节点。
203.移除链表元素
给你一个链表的头节点
head
和一个整数val
,请你删除链表中所有满足Node.val == val
的节点,并返回 新的头节点 。示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]示例 2:
输入:head = [], val = 1 输出:[]示例 3:
输入:head = [7,7,7,7], val = 7 输出:[]
使用虚拟头节点(dummy_node)
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]:
#创建一个虚拟头节点
dummy_node = ListNode(next=head)
current = dummy_node
while current.next:
if current.next.val == val:
current.next = current.next.next
else:
current = current.next
return dummy_node.next
return 头结点的时候,return dummy_node.next
, 这才是新的头结点
Python有自己的内存回收机制,不需要自己手动释放被删除的节点!
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *dummy_node = new ListNode(0);
dummy_node->next = head;
ListNode *current = dummy_node;
while(current->next!=NULL){
if(current->next->val == val){
ListNode *tmp = current->next;
current->next = current->next->next;
delete tmp;
}
else{
current = current->next;
}
}
head = dummy_node->next;
delete dummy_node;
return head;
}
};
(ListNode *)malloc(sizeof(ListNode))
这行代码执行了两个主要操作:
-
动态内存分配:
malloc(sizeof(ListNode))
是一个调用malloc
函数的表达式,用于动态地分配足够的内存来存储一个ListNode
结构体。 -
类型转换:
(ListNode *)
是一个强制类型转换。malloc
返回一个void *
类型的指针,这是一个通用指针类型,可以指向任何类型的数据。在这个例子中要将void *
类型转换为相应的指针类型ListNode *
。
malloc和free是在C语言中的写法,刚开始写第一遍的时候用了,但是编译通不过,感觉自己还是没忘记C语言哈哈。
后来用new创建新的虚拟节点,将 malloc
替换为 new
,将 free
替换为 delete,修改之后通过了
这是C++中推荐的做法,因为 new
和 delete
不仅负责内存的分配和释放,还会调用对象的构造函数和析构函数。
707.设计链表
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:
val
和next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。如果是双向链表,则还需要属性
prev
以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。实现
MyLinkedList
类:
MyLinkedList()
初始化MyLinkedList
对象。int get(int index)
获取链表中下标为index
的节点的值。如果下标无效,则返回-1
。void addAtHead(int val)
将一个值为val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。void addAtTail(int val)
将一个值为val
的节点追加到链表中作为链表的最后一个元素。void addAtIndex(int index, int val)
将一个值为val
的节点插入到链表中下标为index
的节点之前。如果index
等于链表的长度,那么该节点会被追加到链表的末尾。如果index
比长度更大,该节点将 不会插入 到链表中。void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为index
的节点。
python
class ListNode:
def __init__(self,val=0,next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummy_head = ListNode()
self.size = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.size:
return -1
current = self.dummy_head.next
for i in range(index):
current = current.next
return current.val
def addAtHead(self, val: int) -> None:
self.dummy_head.next = ListNode(val,self.dummy_head.next)
self.size +=1
def addAtTail(self, val: int) -> None:
current = self.dummy_head
while current.next:
current = current.next
current.next = ListNode(val)
self.size +=1
def addAtIndex(self, index: int, val: int) -> None:
if index<0 or index>self.size:
return -1
current = self.dummy_head
for i in range(index):
current = current.next
current.next = ListNode(val,current.next)
self.size +=1
def deleteAtIndex(self, index: int) -> None:
if index<0 or index>=self.size:
return -1
current = self.dummy_head
for i in range(index):
current = current.next
current.next = current.next.next
self.size -=1
C++
class MyLinkedList {
public:
struct ListNode {
int val;
ListNode *next;
ListNode(int val) : val(val), next(nullptr) {}
};
MyLinkedList() {
dummy_head = new ListNode(0);
size = 0;
}
int get(int index) {
if (index < 0 || index >= size) {
return -1;
}
ListNode* current = dummy_head->next;
for (int i = 0; i < index; ++i) {
current = current->next;
}
return current->val;
}
void addAtHead(int val) {
ListNode* newNode = new ListNode(val);
newNode->next = dummy_head->next;
dummy_head->next = newNode;
size++;
}
void addAtTail(int val) {
ListNode* current = dummy_head;
while (current->next) {
current = current->next;
}
current->next = new ListNode(val);
size++;
}
void addAtIndex(int index, int val) {
if (index < 0 || index > size) {
return;
}
ListNode* current = dummy_head;
for (int i = 0; i < index; ++i) {
current = current->next;
}
ListNode* newNode = new ListNode(val);
newNode->next = current->next;
current->next = newNode;
size++;
}
void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
ListNode* current = dummy_head;
for (int i = 0; i < index; ++i) {
current = current->next;
}
ListNode* temp = current->next;
current->next = current->next->next;
delete temp;
size--;
}
private:
ListNode* dummy_head; // 确保在这里声明 dummy_head
int 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);
*/
刚开始的时候出现错误error: use of undeclared identifier 'dummy_head'
C++中要在类的 private
(或 public
)部分被正确声明,如:
private:
ListNode* dummy_head; // 确保在这里声明 dummy_head
int size;
206.反转链表
给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。示例 1:
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]示例 2
输入:head = [1,2] 输出:[2,1]示例 3:
输入:head = [] 输出:[]
双指针法,感觉很妙!
首先定义一个current指针,指向头结点,再定义一个pre指针,初始化为null。
然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。
最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。
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]:
current = head
pre = None
while current:
temp = current.next
current.next = pre
pre = current
current = temp
return pre
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *current = head;
ListNode *pre = NULL;
while(current){
ListNode *temp = current->next;
current->next = pre;
pre = current;
current = temp;
}
return pre;
}
};
在C++中,delete
用于释放之前用 new
分配的动态内存。两者要配合使用。
new用于创建新的节点,动态分布内存。在这题中只涉及到了链表的重新连接,并没有产生新的节点,所以用new是不合适的。
今天用了GPTdebug,真的很给力嘿