链表
链表相关的核心点
- NULL 异常处理
- dummy node(哑巴节点)
- 快慢指针
- 插入一个节点到排序链表
- 从一个链表中移除一个节点
- 翻转链表
- 合并两个链表
- 找到链表的中间节点
哑巴节点
哑节点(dummy node)是初始值为NULL的节点,创建在使用到链表的函数中,可以起到避免处理头节点为空的边界问题的作用,减少代码执行异常的可能性。也就是说,哑节点的使用可以对代码起到简化作用(省略当函数的入口参数为空时的判断)。
// 普通结构体
struct ListNode {
int val;
struct ListNode *next;
};
// 定义函数
ListNode *addNode( ListNode *node, int num){ // 函数返回的是尾节点
struct ListNode *new = (struct ListNode*) malloc(sizeof(struct ListNode) * num);
node->next = new;
return new;
}
// 问题是当node节点为空时,便会产生异常,node->next = new会报错,应该修改如下
ListNode *addNode( ListNode *node, int num){ // 函数返回的是尾节点
struct ListNode *new = (struct ListNode*) malloc( sizeof(struct ListNode) * num );
if( null == node ){ // 头节点为空的情况
return new; // 直接返回新建的节点(头节点的地址),相当于新建了一组链表
}
node->next = new;
return new;
}
// 定义哑节点如下
struct ListNode *dummyNode= (struct ListNode*) malloc(sizeof(struct ListNode));
dummyNode->val = NULL;
dummyNode->next = NULL;
// 函数addNode()重新定义如下
ListNode *addNode( ListNode *dummyNode, int num) // 函数返回的是尾节点
struct ListNode *new = (struct ListNode*) malloc( sizeof(struct ListNode) * num );
/* 此处不再需要处理头节点为空的情况,因为dummyNode一定非空 */
dummyNode->next = new;
return new;
}
快慢指针
快慢指针就是定义两根指针,移动的速度一快一慢,以此来制造出自己想要的差值。这个差值可以让我们找到链表上相应的节点。比如,我们把一个链表看成一个跑道,假设a的速度是b的两倍,那么当a跑完全程后,b刚好跑一半,以此来达到找到中间节点的目的。
常见题型
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
// 递归
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == NULL) {
return head;
}
if(head->next){
head->next = deleteDuplicates(head->next);
if(head->val == head->next->val) {
head = head->next; // 删除head
}
}
return head;
}
};
// 迭代
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* current = head;
while(current) {
while(current->next && current->val == current->next->val) {
current->next = current->next->next;
}
current = current->next;
}
return head;
}
};
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现的数字。
思路:链表头结点可能被删除,所以用dummy node辅助删除
// 递归
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == NULL)
return head;
if(head->next && head->val == head->next->val) {
while(head->next && head->val == head->next->val) {
head = head->next;
}
return deleteDuplicates(head->next);
} else {
head->next = deleteDuplicates(head->next);
}
return head;
}
};
// 迭代
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == NULL) {
return head;
}
// 链表头结点可能被删除,所以用dummy node辅助删除
struct ListNode *dummyNode= (struct ListNode*) malloc(sizeof(struct ListNode));
dummyNode->next = head;
head = dummyNode; // 之后不管head怎么移动,dummyNode->next就是head移动完的链表的头结点
int temp;
while(head->next && head->next->next) {
if(head->next->val == head->next->next->val) {
temp = head->next->val;
while(head->next && head->next->val == temp) {
head->next = head->next->next;
}
} else {
head = head->next;
}
}
return dummyNode->next;
}
};
注意点
◉ A->B->C 删除 B,A->next = C
◉ 删除用一个dummy node节点辅助(允许头节点可变)
◉ 访问 X->next 、X->value 一定要保证 X != NULL
反转一个单链表。
思路:用一个prev节点保存向前指针,next保存向后的临时指针
// 递归,参考https://leetcode-cn.com/problems/reverse-linked-list/solution/zhu-bu-tu-jie-di-gui-die-dai-by-sucongcjs/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == NULL || head->next == NULL) {
return head;
}
ListNode* ret = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return ret;
}
};
// 迭代
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *next = NULL;
ListNode *prev = NULL;
while(head) {
/**
* 保存当前head->next节点,防止重新赋值后被覆盖
* 一轮之后状态:NULL <- 1 2 -> 3 -> 4
* prev head
*/
next = head->next;
head->next = prev;
prev = head; // prev 移动
head = next; // head 移动
}
return prev;
}
};
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
思路:先遍历到 m 处,翻转,再拼接后续,注意指针处理
// 递归,参考https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/bu-bu-chai-jie-ru-he-di-gui-di-fan-zhuan-lian-biao/
class Solution {
public:
ListNode* successor = NULL; // 后驱节点
// 反转以 head 为起点的 n 个节点,返回新的头结点
ListNode* reverseN(ListNode* head, int n) {
if (n == 1) {
// 记录第 n + 1 个节点
successor = head->next;
return head;
}
// 以 head.next 为起点,需要反转前 n - 1 个节点
ListNode* last = reverseN(head->next, n - 1);
head->next->next = head;
// 让反转之后的 head 节点和后面的节点连起来
head->next = successor;
return last;
}
ListNode* reverseBetween(ListNode* head, int m, int n) {
// base case
if (m == 1) {
return reverseN(head, n);
}
// 前进到反转的起点触发 base case
head->next = reverseBetween(head->next, m - 1, n - 1);
return head;
}
};
// 迭代
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
// 思路:先遍历到m处,翻转,再拼接后续,注意指针处理
// 输入: 1->2->3->4->5->NULL, m = 2, n = 4
if (head == NULL) {
return head;
}
// 头部变化所以使用dummy node
ListNode* dummyNode = (struct ListNode*)malloc(sizeof(struct ListNode));
dummyNode->next = head;
head = dummyNode;
// 最开始:0->1->2->3->4->5->NULL
ListNode* pre = NULL;
int i = 0;
while (i < m) {
pre = head;
head = head->next;
i++;
}
// 遍历之后: 1(pre)->2(head)->3->4->5->NULL
// i = 1
int j = i;
ListNode* next = NULL;
ListNode* mid = head; // 用于中间节点连接
while (head != NULL && j <= n) {
// 第一次循环: 1(pre) NULL<-2 3(head)->4->5->NULL
ListNode* temp = head->next;
head->next = next;
next = head;
head = temp;
j++;
}
// 循环需要执行四次
// 循环结束:1(pre) NULL<-2<-3<-4 5(head)->NULL
pre->next = next;
mid->next = head;
return dummyNode->next;
}
};
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
思路:通过 dummy node 链表,连接各个元素
// 递归
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == nullptr) {
return l2;
} else if (l2 == nullptr) {
return l1;
} else if (l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
} else {
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
};
// 迭代
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == NULL) {
return l2;
} else if (l2 == NULL) {
return l1;
}
// 头部变化所以使用dummy node
ListNode* dummyNode = (struct ListNode*)malloc(sizeof(struct ListNode));
ListNode* head = dummyNode;
while (l1 != NULL && l2 != NULL) {
if (l1->val < l2->val) {
head->next = l1;
l1 = l1->next;
} else {
head->next = l2;
l2 = l2->next;
}
head = head->next;
}
while (l1 != NULL) {
head->next = l1;
head = head->next;
l1 = l1->next;
}
while (l2 != NULL) {
head->next = l2;
head = head->next;
l2 = l2->next;
}
return dummyNode->next;
}
};
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
思路:将大于 x 的节点,放到另外一个链表,最后连接这两个链表
// 迭代
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
if (head == NULL) {
return head;
}
ListNode* headDummy = (struct ListNode*)malloc(sizeof(struct ListNode));
headDummy->next = head;
head = headDummy;
ListNode* tailDummy = (struct ListNode*)malloc(sizeof(struct ListNode));
ListNode* tail = tailDummy;
while (head->next) {
if (head->next->val < x) {
head = head->next;
} else {
ListNode* temp = head->next;
head->next = head->next->next;
tail->next = temp;
tail = tail->next;
}
}
tail->next = NULL;
head->next = tailDummy->next;
return headDummy->next;
}
};