亿点说明:对于要用带头结点或者不带头结点的单链表问题,题目只要不特别指明,默认都是对带头结点的单链表进行操作
一、两个单链表的操作问题
1. A、B递增,求C = A ∪ B (并集,归并)
无题目链接
(1) C递增
// 利用尾插法将两个递增单链表归并
LNode* merge(LNode* A, LNode* B)
{
auto dummy = new LNode(-1);
auto cur = dummy;
A = A->next, B = B->next;
while (A && B)
{
if (A->data <= B->data)
{
cur->next = A;
A = A->next;
cur = cur->next;
}
else
{
cur->next =B;
B = B->next;
cur = cur->next;
}
}
if (A) cur->next = A;
else cur->next = B;
return dummy;
}
(2) C递减
// 利用头插法将两个递增单链表归并
LNode* merge(LNode* A, LNode* B)
{
auto dummy = new LNode(-1);
dummy->next = NULL;
A = A->next, B = B->next;
while (A && B)
{
if (A->data <= B->data)
{
auto s = A;
A = A->next; // 不可以放下面两句后面
s->next = dummy->next;
dummy->next = s;
}
else
{
auto s = B;
B = B->next; // 不可以放下面两句后面
s->next = dummy->next;
dummy->next = s;
}
}
while (A)
{
auto s = A;
A = A->next;
s->next = dummy->next;
dummy->next = s;
}
while (B)
{
auto s = B;
B = B->next;
s->next = dummy->next;
dummy->next = s;
}
return dummy;
}
2. A、B递增,求A = A - B (差集),A递增
无题目链接
LNode* difference(LNode* A, LNode* B)
{
auto p = A->next, q = B->next;
auto pre = A;
while (p && q)
{
if (p->data < q->data)
{
pre = p;
p = p->next;
}
else if (p->data > q->data)
q = q->next;
else
{
auto r = p;
pre->next = r->next;
p = p->next;
free(r);
}
}
return A;
}
3. A、B递增,求A = A ∩ B (交集),A递增
无题目链接
LNode* converge(LNode* A, LNode* B)
{
auto p = A->next, q = B->next;
auto pre = A;
while (p && q)
{
if (p->data < q->data)
{
auto r = p;
pre->next = r->next;
p = p->next;
free(r);
}
else if (p->data > q->data)
q = q->next;
else
{
pre = p;
p = p->next;
q = q->next;
}
}
while (p)
{
auto r = p;
pre->next = r->next;
p = p->next;
free(r);
}
return A;
}
4. A、B递增有序,公共元素产生单链表C(与上一题求∩相像)
无题目链接
void get_common(LNode* A, LNode* B)
{
auto C = new LNode(-1);
auto cur = C;
auto p = A->next, q = B->next;
while (p && q)
{
if (p->data < q->data) p = p->next;
else if (p->data > q->data) q = q->next;
else
{
auto s = new LNode(p->data);
cur->next = s;
cur = cur->next;
p = p->next;
q = q->next;
}
}
cur->next = NULL;
}
5. A = A + B (分解)
(1) A中含有序号为奇数的元素,B中含有序号为偶数的元素
无题目链接
LNode* B = new LNode(-1);
void split(LNode* &A)
{
if (!A->next) return;
auto cur = B;
auto pre = A, p = A->next;
int cnt = 0;
while (p)
{
cnt ++;
if (cnt % 2)
{
pre = p;
p = p->next;
}
else
{
cur->next = p;
cur = cur->next;
pre->next = p->next;
p = p->next;
// 或者这样写
// auto r = p;
// pre->next = p->next;
// p = p->next;
// cur->next = r;
// cur = cur->next;
}
}
cur->next = NULL;
}
(2) A中含有data域为奇数的元素,B中含有data域为偶数的元素,且保持相对顺序
无题目链接
LNode* B = new LNode(-1);
void split(LNode* &A)
{
if (!A->next) return;
auto cur = B;
auto pre = A, p = A->next;
while (p)
{
if (p->data % 2)
{
pre = p;
p = p->next;
}
else
{
cur->next = p;
cur = cur->next;
pre->next = p->next;
p = p->next;
// 或者这样写
// auto r = p;
// pre->next = p->next;
// p = p->next;
// cur->next = r;
// cur = cur->next;
}
}
cur->next = NULL;
}
6. 找出A和B的公共结点
LNode *findFirstCommonNode(LNode* A, LNode* B) {
auto p = A->next, q = B->next;
while (p != q)
{
if (p) p = p->next;
else p = B->next;
if (q) q = q->next;
else q = A->next;
}
return p;
}
二、逆置(反转)问题
1. 将单链表L就地逆置
void reverse(LNode* &L)
{
auto p = L->next;
L->next = NULL;
while (p)
{
// 头插法建立新的链表
auto q = p->next;
p->next = L->next;
L->next = p;
p = q;
}
}
2. 从尾到头反向输出结点值
无题目链接
// 方法1:反转后遍历输出
// 方法2:递归
void print(LNode* L)
{
if (L)
{
print(L->next);
cout << L->data << ' ';
}
}
三、去重问题
1. 去掉递增有序的单链表L中数值相同的结点
LNode* deleteDuplicates(LNode* L) {
if (!L->next) return L;
auto pre = L, p = L->next;
while (p && p->next)
{
if (p->next->val == p->val)
{
int tmp = p->val;
while (p && p->val == tmp) p = p->next;
pre->next = p;
}
else
{
pre = p;
p = p->next;
}
}
return L;
}
2. 去掉单链表中绝对值相等的结点
bool st[10010] = {};
// bool st[10010];
LNode* filterList(LNode* L)
{
// memset(st, false, sizeof st);
auto pre = L, p = L->next;
while (p)
{
if (st[abs(p->val)])
{
auto r = p;
pre->next = p->next;
p = p->next;
free(r);
}
else
{
st[abs(p->val)] = true;
pre = p;
p = p->next;
}
}
return L;
}
四、删除问题
1. 递归删除不带头结点的单链表L中所有值为x的节点
无题目链接
void delete_x(LNode* &L, ElemType x)
{
if (!L) return;
if (L->data == x)
{
auto p = L;
L = L->next;
free(p);
delete_x(L, x);
}
else delete_x(L, x);
}
2. 删除单链表L中所有值为x的结点并释放空间
无题目链接
void delete_x(LNode* &L, ElemType x)
{
// if (!L->next) return;
auto pre = L, p = L->next;
while (p)
{
if (p->data == x)
{
auto r = p;
pre->next = p->next;
p = p->next;
free(r);
}
else
{
pre = p;
p = p->next;
}
}
}
3. 删除单链表L中最小值结点(假设唯一)
无题目链接
void delete_min(LNode* &L)
{
// if (!L->next) return;
auto pre = L, p = L->next;
auto minpre = pre, minp = p;
while (p)
{
if (p->data < minp->data)
{
minpre = pre;
minp = p;
}
pre = p;
p = p->next;
}
minpre->next = minp->next;
free(minp);
}
4. 删除单链表L中所有值介于x和y之间的元素的结点
无题目链接
void delete_xtoy(LNode* &L, ElemType x, ElemType y)
{
if (!L->next) return;
auto pre = L, p = L->next;
while (p)
{
if (p->data > x && p->data < y)
{
auto r = p;
pre->next = p->next;
p = p->next;
free(r);
}
else
{
pre = p;
p = p->next;
}
}
}
5. 递增输出单链表L中各结点的数据元素并释放空间
无题目链接
// 方法1
void dele_print(LNode* &L)
{
while (L->next)
{
auto pre = L, p = L->next;
auto minpre = pre, minp = p;
while (p)
{
if (p->data < minp->data)
{
minpre = pre;
minp = p;
}
pre = p;
p = p->next;
}
cout << minp->data << ' ';
minpre->next = minp->next;
free(minp);
}
free(L);
}
// 方法2:王道书上给的,个人感觉没方法1好用
void dele_print(LNode* &L)
{
while (L->next)
{
auto minpre = L;
auto p = L->next;
while (p->next)
{
if (p->next->data < minpre->next->data)
minpre = p;
p = p->next;
}
cour << minpre->next->data << ' ';
auto r = minpre->next;
minpre->next = r->next;
free(r);
}
free(L);
}
五、排序问题
1. 插入排序:使单链表中元素递增有序
无题目链接
void insert_sort(LNode* L)
{
if (!L->next) return;
auto p = L->next, r = p->next;
p->next = NULL;
p = r;
while (p)
{
r = p->next;
auto pre = L;
while (pre->next && pre->next->data < p->data)
pre = pre->next;
p->next = pre->next;
pre->next = p;
p = r;
}
}
2. 快速排序:使单链表中元素递增有序
LNode* quickSortList(LNode* head) {
if (!head->next) return head;
quick_sort(head->next, NULL);
return head;
}
void quick_sort(LNode *head, LNode *tail)
{
if (head != tail)
{
// 定义参考元素x,永远取头指针指向的元素
int x = head->data;
// 双指针操作,p之前(不包括p)都是小于等于x的,
// p——q(包括p)都是大于等于x的
// p指针作用: 分段用
// q指针作用: 遍历用,检索当前元素dataue与x的关系,
// 若dataue比x小,则扔p前面
LNode *p = head, *q = head->next;
while (q != tail)
{
if (q->data < x)
{
p = p->next;
swap(p->data, q->data);
}
q = q->next;
}
// 遍历完之后,重置参考元素x的位置,
// 即依旧在头指针指向的位置,然后开始递归两段
if (p != head) swap(head->data, p->data);
quick_sort(head, p);
quick_sort(p->next, tail);
}
}
最后——杂七杂八
1. 判断单链表L是否有环
/*
以下文字来自题目链接中牛客官方给的解析:
step 1:设置快慢两个指针,初始都指向链表头。
step 2:遍历链表,快指针每次走两步,慢指针每次走一步。
step 3:如果快指针到了链表末尾,说明没有环,因为它每次走两步,
所以要验证连续两步是否为NULL。
step 4:如果链表有环,那快慢双指针会在环内循环,因为快指针每次走两步,
因此快指针会在环内追到慢指针,二者相遇就代表有环
*/
bool has_cycle(LNode *head) {
auto fast = head, slow = head;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow) return true;
}
return false;
}
2. 查找单链表L中倒数第k个位置上的结点
LNode* FindKthToTail(LNode* head, int k)
{
if (!head->next) return NULL;
int len = 0;
for (auto i = head->next; i; i = i->next) len ++;
int cnt = len - k + 1;
if (k <= 0 || cnt <= 0) return NULL;
auto p = head;
for (int i = 0; i < cnt; i ++) p = p->next;
return p;
}
3. 重排单链表L’ = (a1,an,a2,an-1,a3,an-2,···)
// 不带头结点的反转链表
void reverse(LNode* &b)
{
auto c = b->next;
b->next = NULL;
while (c)
{
auto tmp = c->next;
c->next = b;
b = c;
c = tmp;
}
}
// 两个链表的归并
void merge(LNode* &a, LNode* b)
{
while (b)
{
auto p = a->next, q = b->next;
b->next = a->next;
a->next = b;
a = p, b = q;
}
}
void rearrangedList(LNode* head) {
if (!head->next) return;
// 1. 对链表进行分段
// 结点1 ~ (n + 1)/2是左半段,结点(n + 1)/2 ~ n是右半段
int len = 0;
for (auto i = head; i; i = i->next) len ++;
int left = len + 1 >> 1;
// int len = 0;
// auto i = head;
// while (i) i = i->next, len ++;
// int left = len + 1 >> 1;
auto a = head;
for (int i = 1; i < left; i ++) a = a->next;
// auto a = head;
// while (-- left) a = a->next;
auto b = a->next;
a->next = NULL;
// 2. 反转右边那段链表
reverse(b);
// 3. 合并链表,即将b归并入主链表中
merge(head, b);
}