链表是一种通过指针串联在一起的线性结构,每一个结点由两部分组成:
- 数据域
- 指针域
最后一个结点的指针域指向 null。
每个结点只有一个指向下一个结点的指针。
每一个结点有两个指针域,一个指向下一个结点,一个指向上一个结点。
双链表既可以向前查询也可以向后查询。
即链表首尾相连。
链表在内存中不是连续分布的。
链表通过指针域的指针链接在内存中散乱分布的各个结点,结点位置取决于操作系统的内存管理机制。
// 单链表
struct ListNode {
int val; // 结点上存储的元素
ListNode *next; // 指向下一个结点的指针
ListNode(int x) : val(x), next(NULL) {} // 结点的构造函数,不定义也行
// 不过 C++ 默认生成的不会初始化成员变量
};
// 自定义构造函数的结点初始化:
ListNode* head = new ListNode(5);
// 默认构造函数的结点初始化:
ListNode* head = new ListNode();
head->val = 5;
ListNode* to_del;
prev->next = to_del->next;
free(to_del);
ListNode* head;
ListNode* new_node(val);
new_node->next = head->next;
head->next = new_node;
类型 | 插入/删除 | 查询 | 适用场景 |
---|---|---|---|
数组 | 数据量固定,频繁查询,较少增删 | ||
链表 | 数据量不固定,频繁增删,较少查询 |
- 数组在定义时,长度就是固定的,如果想要改动数组长度,就需要重新定义 (C++)。
- 链表长度不固定,可以动态增删,适合数据量不固定,频繁增删,较少查询的场景。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp; // 保存cur的下一个节点
ListNode* cur = head;
ListNode* pre = NULL;
while(cur) {
temp = cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next
cur->next = pre; // 翻转操作
// 更新 pre 和 cur指针
pre = cur;
cur = temp;
}
return pre;
}
};
class Solution {
public:
ListNode* reverse(ListNode* pre,ListNode* cur){
if(cur == NULL) return pre;
ListNode* temp = cur->next;
cur->next = pre;
// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
// pre = cur;
// cur = temp;
return reverse(cur, temp);
}
ListNode* reverseList(ListNode* head) {
// 和双指针法初始化是一样的逻辑
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(NULL, head);
}
};
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 边缘条件判断
if(head == NULL) return NULL;
if (head->next == NULL) return head;
// 递归调用,翻转第二个节点开始往后的链表
ListNode *last = reverseList(head->next);
// 翻转头节点与第二个节点的指向
head->next->next = head;
// 此时的 head 节点为尾节点,next 需要指向 NULL
head->next = NULL;
return last;
}
};
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
dummyHead->next = head; // 虚拟结点指向 head,方便后面做删除操作
ListNode* cur = dummyHead;
while(cur->next != nullptr && cur->next->next != nullptr) {
ListNode* tmp = cur->next; // 记录临时节点
ListNode* tmp1 = cur->next->next->next; // 记录临时节点
cur->next = cur->next->next;
cur->next->next = tmp;
cur->next->next->next = tmp1;
cur = cur->next->next; // cur 移动两位,准备下一轮交换
}
return dummyHead->next;
}
};
用快慢双指针即可,只要两指针相遇就说明链表有环。
设:
x
: 头结点到环形入口结点的结点数y
: 环形入口结点到双指针相遇结点的结点数z
: 双指针相遇结点到环形入口结点的结点数
相遇时,slow
指针走过的结点数为 x + y
,fast
走过的结点数为 x + y + n (y + z)
,n
为 fast
指针在环内走了 n
圈才遇到 slow
指针。
fast
每走两步 slow
走一步,因此有: (x + y) * 2 = x + y + n * (y + z)
化简为: x + y = n * (y + z)
因此: x = n * (y + z) - y
将 n * (y + z)
= (n - 1) (y + z) + y + z
代入,得:
x = (n - 1) (y + z) + z (n >= 1)
- 当
n = 1
时,x = z
- 当
n > 1
时,fast
指针需要转n
圈才能遇到slow
。
这意味着,从头结点出发一个指针,从相遇结点也出发一个指针,这两个指针每次只走一个结点,当它们相遇时就是环形入口结点。