0.基本概念
- 单链表结点组成
struct ListNode {
int data;
struct ListNode* next;
ListNode() {}
ListNode(int data, ListNode* next = NULL) : data(data), next(next) {}
};
- 有无头结点只对尾插法的操作有影响,对头插法没有影响
- 尾插法一定要提供尾指针
- 在单链表的基础上可以派生出单循环链表、双链表、双循环链表
1.添加/删除链表节点
添加值为 x 的结点
带头结点的递增有序的单链表 L 添加一个值为 x 的结点,使其仍保持递增有序
void insert(ListNode* head, int x) {
ListNode* pre = L;
ListNode* cur = head->next;
// 先找到要插入的位置
while(cur != NULL) {
if (x <= cur->data) {
break;
} else {
pre = cur;
cur = cur->next;
}
}
ListNode* s = new ListNode(x);
pre->next = s;
s->next = cur;
}
删除所有值为 x 的结点
迭代法:删除带头结点的单链表 L 中所有值为 x 的结点
void deleteAllx(ListNode* head, int x) {
ListNode* pre = L;
ListNode* cur = head->next;
ListNode* u;
while(cur != NULL) {
if (cur->data == x) {
pre->next = cur->next;
u = cur;
cur = cur->next;
delete u;
} else {
pre = cur;
cur = cur->next;
}
}
}
递归法:删除无头结点的单链表 L 中所有值为 x 的结点
void deleteAllx(ListNode* head, int x) {
if (L == NULL)
return;
if (head->data == x) {
ListNode* u = L;
L = head->next;
delete u;
deleteAllx(L, x);
} else {
deleteAllx(head->next, x);
}
}
删除所有重复值的结点
- 删除递增有序的带头结点的单链表 L 中所有重复的元素
void deleteRepeat(ListNode* head) {
ListNode* pre = head->next;
ListNode* cur = pre->next;
ListNode* u;
while(cur != NULL) {
if(pre->data == cur->data) {
pre->next = cur->next;
u = cur;
cur = cur->next;
delete u;
} else {
pre = pre->next;
cur = cur->next;
}
}
}
删除给定指针指向的结点
- 给定带头结点的单链表的某一个结点的指针,在 O ( 1 ) O(1) O(1) 内删除这个结点
void deleteSelectedNode(ListNode* node) {
// 如果此结点是尾结点,我们还是要从头遍历到尾结点的前继结点,再将尾结点删除
if (node->next == NULL) {
ListNode* p = head;
while (p->next != node) {
p = p->next;
}
// 找到尾结点的前继结点,把尾结点删除
delete p->next;
p->next = NULL;
} else {
// 不是尾结点,就将后继结点的值赋给该节点,并删除后继结点
node->data = node->next->data;
ListNode* u = node->next;
node->next = u->next;
delete u;
}
}
2.链表集合问题
求交集 C = A ∩ B
A 和 B 为2个递增有序的带头结点的单链表,求A和B的交集,结果存放在链表C中
在新链表上操作,在C中添加在A、B中都存在的元素
void getIntersection(ListNode* A, ListNode* B, ListNode* C) {
ListNode* pa = A->next;
ListNode* pb = B->next;
ListNode* pc = C->next;
while(pa != NULL && pb != NULL) {
if (pa->data < pb->data) {
pa = pa->next;
} else if (pa->data > pb->data) {
pb = pb->next;
} else {
ListNode* s = new ListNode(pa->data);
pc->next = s;
pc = s; // 将 pc 指向最后一个结点
pa = pa->next;
pb = pb->next;
}
}
pc->next = NULL; // 最后一定要置空,因为c++默认是野指针
}
求并集 A = A ∪ B
A 和 B 为2个递增有序的带头结点的单链表,求 A 和 B 的并集,结果存放在链表A中
在原链表上操作,在A中添加在B中存在,A中不存在的元素
void getUnion(ListNode* A, ListNode* B) {
ListNode* pa = A->next;
ListNode* pb = B->next;
ListNode* pre = A;
ListNode* u;
while(pa != NULL && pb != NULL) {
if (pa->data < pb->data) {
pre = pa;
pa = pa->next;
} else if(pa->data > pb->data) {
u = pb->next; // 暂存后继结点
pb->next = pa;
pre->next = pb;
pb = u;
} else {
pre = pa;
pa = pa->next;
pb = pb->next;
}
}
// 将B中剩余结点直接拼接到A表尾部
if (pb != NULL) {
pre->next = pb;
}
}
求交集 A = A ∩ B
A 和 B 为2个递增有序的带头结点的单链表,求 A 和 B 的交集,结果存放在链表A中
在原链表上操作,在A中删除A中存在,B中不存在的元素
void getIntersection(ListNode* A, ListNode* B) {
ListNode* pa = A->next;
ListNode* pb = B->next;
ListNode* pre = A;
ListNode* u;
while(pa != NULL && pb != NULL) {
if (pa->data < pb->data) {
pre->next = pa->next; // A当前的元素在B中不存在,删除
u = pa;
pa = pa->next;
delete u;
} else if(pa->data > pb->data) {
pb = pb->next; // 不确定A当前的元素在B中是否存在,B要继续向后遍历
} else {
pre = pa; // 相等则保留,同时要把pre指针往后移
pa = pa->next;
pb = pb->next;
}
}
}
求差集 A = A - B
A和B为2个递增有序的带头结点的单链表,求 A 和 B 的差集,结果存放在链表A中
在原链表上操作,在A中删除A、B中都存在的元素:A - B = A - A ∩ B
void getSubtraction(ListNode* A, ListNode* B) {
ListNode* pa = A->next;
ListNode* pb = B->next;
ListNode* pre = A;
ListNode* u;
while(pa != NULL && pb != NULL) {
if (pa->data < pb->data) {
pre = pa; // A当前的元素在B中不存在则保留,同时把pre指针往后移
pa = pa->next;
} else if(pa->data > pb->data) {
pb = pb->next; // 不确定A当前的元素在B中是否存在,B要继续向后遍历
} else {
pre->next = pa->next; // A当前的元素在B中存在,删除
u = pa;
pa = pa->next;
pb = pb->next;
delete u;
}
}
}
判断 A ⊆ B
A和B为2个递增有序的带头结点的单链表,判断A是否为B的子集
bool isSubset(ListNode* A, ListNode* B) {
ListNode* pa = A->next;
ListNode* pb = B->next;
while(pa != NULL && pb != NULL) {
if (pa->data < pb->data) {
return false;
} else if(pa->data > pb->data) {
pb = pb->next;
} else {
pa = pa->next;
pb = pb->next;
}
}
return true;
}
3.链表合并
递增合并为递增
A 和 B 为2个递增有序的带头结点的单链表,将两者合并为递增有序的单链表
ListNode* mergeAsc(ListNode* A, ListNode* B) {
ListNode* head = new ListNode(0);
ListNode* pre = head;
ListNode* pa = A->next;
ListNode* pb = B->next;
while (pa != NULL && pb != NULL) {
if (pa->data <= pb->data) {
pre->next = pa;
pa = pa->next;
} else {
pre->next = pb;
pb = pb->next;
}
pre = pre->next;
}
pre->next = pa != NULL ? pa : pb;
return head;
}
递增合并为递减
A和B为2个递增有序的带头结点的单链表,将两者合并为递减有序的单链表
ListNode* mergeDesc(ListNode* A, ListNode* B) {
ListNode* head = NULL;
ListNode* pa;
ListNode* pb;
while (A->next != NULL && B->next != NULL) {
pa = A->next;
pb = B->next;
if (pa->data <= pb->data) {
A->next = pa->next;
pa->next = head;
head = pa;
}
else {
B->next = pb->next;
pb->next = head;
head = pb;
}
}
while (A->next != NULL) {
pa = A->next;
A->next = pa->next;
pa->next = head;
head = pa;
}
while (B->next != NULL) {
pb = B->next;
B->next = pb->next;
pb->next = head;
head = pb;
}
return head;
}
4.链表翻转
给定链表 head→1→2→3→4,将其翻转为 head→4→3→2→1
头插法
void reverseList(ListNode* head) {
ListNode* temp = null; // 建立一个临时的无头结点的链表
ListNode* cur; // 用于遍历原链表
while(head->next != null) {
cur = head->next;
head->next = cur->next; // 当前结点从原链表中删除
cur->next = temp; // 用头插法将结点插入temp链表
temp = cur;
}
head->next = temp;
}
迭代法
void reverseList(ListNode* head) {
ListNode* pre = head->next;
ListNode* cur = pre->next;
ListNode* temp;
while(cur != null) {
// 在cur指向pre之前一定要先保留cur的后继结点,不然cur指向pre后就找不到后继结点了
temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
head->next->next = null; // 1.删除环并置空
head->next = pre; // 2.修改头结点后继
}
就地翻转法
void reverseList(ListNode* head) {
ListNode* pre = head->next;
ListNode* cur = pre->next;
while(cur != null) {
pre->next = cur->next;
cur-next = head->next;
head->next = cur;
cur = pre->next;
}
}
5.双循环链表
判断双循环链表是否对称
void isSymmetric(ListNode* head) {
ListNode* p = head->next;
ListNode* q = head->prior;
while(p != q && q->next != p) {
if (p->data == q->data) {
p = p->next;
q = q->prior;
} else {
return false;
}
}
return true;
}
6.双指针
- 给定一个带头结点的单链表,返回链表倒数第 k 个结点
- 给定一个带头结点的单链表,返回链表最中间的结点
- 判断一个链表是否有环