在纸上把过程画下来再写程序
一、基础知识
1.定义
链表是通过指针将零散的内存块串联起来,链表(Linked List)是一种常见的线性结构。它不需要一块连续的内存空间,通过指针即可将一组零散的内存块串联起来。我们把内存块成为链表的节点,为了将所有的节点串起来,每个链表的节点除了存储数据之外,还需要记录链表的下一个节点的地址,这个记录下个节点地址的指针我们叫做后驱指针。搜索链表需要O(N)的时间复杂度,这点和数组类似,但是链表不能像数组一样,通过索引的方式以O(1)的时间读取第n个数。链表的优势在于能够以较高的效率在任意位置插入或者删除一个节点。
2. 常见操作
在访问、添加和删除链表节点时,通常需要操作指针来调整节点之间的链接关系。以下是链表的基本操作:
-
访问链表节点:
- 要访问链表的特定位置上的节点,需要遍历链表并依次访问每个节点,直到到达目标位置。
- 通过指针的方式,从头节点开始,逐个跟踪指针的
next
指向下一个节点,直到达到目标节点。
-
在链表中添加节点:
- 在链表的头部添加节点:创建一个新节点,将其指针指向当前头节点,然后将头指针指向新节点。
- 在链表的尾部添加节点:遍历链表直到达到最后一个节点,创建一个新节点,将最后一个节点的指针指向新节点。
- 在链表的中间位置添加节点:遍历链表找到目标位置的前一个节点,创建一个新节点,将新节点的指针指向目标位置的节点,然后将前一个节点的指针指向新节点。
-
从链表中删除节点:
- 删除头节点:将头指针指向头节点的下一个节点,并释放原始头节点的内存。
- 删除尾节点:遍历链表直到达到倒数第二个节点,将倒数第二个节点的指针指向
nullptr
,并释放最后一个节点的内存。 - 删除中间位置的节点:遍历链表找到目标位置的前一个节点,将前一个节点的指针指向目标位置的下一个节点,并释放目标位置节点的内存。
3. 一个链表模板(抄leetcode)
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) {}
};
二、技巧、注意问题和leetcode上的题目
1.双指针:
定义一个fast一个slow两个指针,通过不同的步长遍历列表来寻找某个位置。
leetcode 2095 删除链表的中间节点:
代码如下:
class Solution {
public:
ListNode* deleteMiddle(ListNode* head) {
if(head->next == nullptr){ //为什么要用next
return nullptr;
}
ListNode* fast = head;
ListNode* slow = head;
ListNode* pre = nullptr;
while (fast && fast->next){
fast=fast->next->next;
pre = slow;
slow = slow ->next;
}
pre->next = pre->next->next;
return head;
}
};
错误:删除某个节点要定位到上一个节点处,pre->next = pre->next->next;
不能 用pre=pre->next; 意思一样,但是这是赋值语句。也不能用slow->next 内存消耗会不同。
2. 链表的指向性理解
leetcode206 反转链表
链表问题多关注它的指向性
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode * cur =head;
ListNode* pre = nullptr;
while (cur){
ListNode* tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
};
3. 其他题目上犯的错误
Leetcode328 奇偶链表:
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if (head == nullptr){
return nullptr;
}
ListNode* odd = head;
ListNode* even = head->next;
ListNode* evenHead = even;
while (even != 0 && even->next != 0){
odd->next = even->next;
odd = odd->next;
even->next = odd->next;
even = even->next;
}
odd->next = evenHead;
return head;
}
};
错误:要用一个中间值evenhead来存储偶数链表
一些自己理解错的点:
1. ListNode * p 是指向结构节点的指针,里面只有一个地址。
ListNode * p= new ListNode()是一个结构节点,里面有val和指向下一个节点的结构体指针,而且该节点已经被系统分配内存,在函数体里不会被自动释放。
"ListNode * p" 声明了一个指向 ListNode 类型对象的指针变量 p,但它并没有被初始化,即它的值是未定义的,可能指向任意的内存地址。在使用该指针之前,需要确保为其分配内存或将其指向一个有效的 ListNode 对象。
"ListNode * p = new ListNode()" 则声明了一个指向 ListNode 类型对象的指针变量 p,并通过 new 运算符在堆(heap)上分配了内存空间来存储 ListNode 对象的实例。这样,p 指针被初始化为指向该分配的内存空间,可以直接访问和操作该对象的成员。
总结起来,"ListNode * p" 只是声明了一个指针变量,而 "ListNode * p = new ListNode()" 则声明并初始化了一个指针变量,使其指向一个动态分配的 ListNode 对象。