王道链表大题算法(部分)

发布于:2025-07-03 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、链表的基本结构

在开始介绍算法之前,我们先定义链表的基本结构:

typedef int ElemType;

// 单链表节点结构
typedef struct LNode {
    ElemType data;        // 数据域
    struct LNode *next;   // 指针域
} LNode, *LinkList;

// 双向链表节点结构(用于访问频率调整算法)
typedef struct DNode {
    ElemType data;
    int freq;             // 访问频率
    struct DNode *pre;
    struct DNode *next;
} DNode, *DLinkList;

二、习题

1. 删除值为 x 的节点

该算法实现从链表中删除所有值为 x 的节点,核心思想是通过前驱指针记录当前节点的前驱,以便在删除节点时能够正确地连接链表。

void Delete_x(LinkList &L, ElemType x) {
    LNode *p = L->next, *pre = L, *m;
    while (p != NULL) {
        if (p->data != x) {
            pre = p;  // pre 记录前驱
            p = pre->next;
        } else {
            m = p;
            p = p->next;
            pre->next = p;  // 恢复成链
            free(m);
        }    
    }
}

2. 删除最小值节点

该算法用于删除链表中的最小值节点,核心是记录最小值节点的前驱,以便进行删除操作。

void Delete_min(LinkList &L) {
    // 核心:记录最小值的前驱
    if (L->next == NULL) return; // 空链表处理
    LNode *pre = L, *p = L;
    int mi = INT_MAX;
    while (p->next != NULL) {
        if (p->next->data < mi) {
            pre = p;
            mi = p->next->data;
        }
        p = p->next;
    } 
    LNode *temp = pre->next;
    pre->next = temp->next; // 越过最小值
    free(temp); // 释放内存
}

3. 链表逆置

链表逆置是一个常见操作,通过头插法可以轻松实现链表的逆置。

LinkList Reverse(LinkList L) {
    LNode *p, *r; // p 用来工作 r 用来保存现场
    p = L->next;
    L->next = NULL; 
    while (p != NULL) {
        r = p->next; // 防止断链
        p->next = L->next; // p 指向 null
        L->next = p; // L作为头,后来的元素插在前面,形成逆置,接到 L 后面
        p = r; // 返回继续
    }
    return L;
}

4. 删除指定范围内的值

删除链表中值在 [min, max] 范围内的节点,核心是保留每个节点的前驱指针。

void Delete_LR(LinkList &L, int min, int max) {
    // 核心:保留每一个点的前驱
    LNode *pre = L, *p = L->next;
    while (p != NULL) {
        if (p->data >= min && p->data <= max) { // 注意区间判断
            pre->next = p->next; // 跳过符合的点,使其成为孤点
            free(p); // 删除
            p = pre->next; // p恢复,即原来的p->next的点
        } else {
            pre = p;
            p = p->next;
        }    
    }
}

5. 寻找公共节点

寻找两个链表的第一个公共节点,通过先对齐链表长度,再同时遍历的方式实现。

int find_same(LinkList L1, LinkList L2) {
    LNode *p1 = L1, *p2 = L2;
    int len1 = 0, len2 = 0;
    // 统计L1长度
    while (p1->next != NULL) {
        p1 = p1->next;
        len1++; 
    }
    while (p2->next != NULL) {
        p2 = p2->next;
        len2++;
    }
    p1 = L1;
    p2 = L2;
    if (len1 > len2) {
        while (len1 != len2) {
            len1--;
            p1 = p1->next;
        }
    } else {
        while (len2 != len1) {
            len2--;
            p2 = p2->next;
        }
    }
    while (p1 != p2 && p1 != NULL && p2 != NULL) { // 修正循环条件
        p1 = p1->next;
        p2 = p2->next;
    }
    if (p1 == p2 && p1 != NULL) return p1->data;
    return -1;
}

6. 链表拆分

将一个链表拆分成两个链表,这里实现的是按顺序交替拆分。

void Creat_AB(LinkList C, LinkList &A, LinkList &B) {
    A = (LinkList)malloc(sizeof(LNode));
    B = (LinkList)malloc(sizeof(LNode));
    A->next = NULL;
    B->next = NULL;
    LNode *p = C->next, *a_tail = A;
    int flag = 1; // 1表示插入A,0表示插入B
    while (p != NULL) {
        if (flag) { // 尾插法插入A
            a_tail->next = p;
            a_tail = p;
        } else { // 尾插法插入B
            LNode *b_tail = B;
            while (b_tail->next != NULL) b_tail = b_tail->next;
            b_tail->next = p;
        }
        p = p->next;
        flag = !flag; // 交替插入
    }
    a_tail->next = NULL;
    LNode *b_tail = B;
    while (b_tail->next != NULL) b_tail = b_tail->next;
    b_tail->next = NULL;
}

7. 删除递增链表中的重复元素

对于递增有序链表,删除重复元素值,只保留一个。

void Delete_same(LinkList &L) {
    LNode *p = L->next, *q;
    if (p == NULL) {
        return;
    } 
    q = p->next;
    while (q != NULL) {
        if (p->data == q->data) {
            p->next = q->next; // 提前改变
            free(q); // 删除相等
            q = p->next; // 防止漏删
        } else {
            p = q;
            q = q->next;
        } 
    }
}

8. 寻找两个有序链表的公共元素

创建一个新链表,包含两个有序链表的公共元素。

LinkList Get_Common(LinkList A, LinkList B) {
    LNode *p = A->next, *q = B->next, *r, *s;
    LinkList C = (LinkList)malloc(sizeof(LNode)); // 创建链表C的头节点
    r = C; // r指向C的头节点,作为尾指针
    while (p != NULL && q != NULL) {
        if (p->data == q->data) {
            s = (LNode*)malloc(sizeof(LNode));
            s->data = p->data;
            r->next = s;
            r = s; // r成为尾节点
            p = p->next;
            q = q->next; 
        } else if (p->data < q->data) {
            p = p->next;
        } else {
            q = q->next;
        }
    }
    r->next = NULL;
    return C;
}

9. 两链表的交集操作

保留链表 A 中同时也在链表 B 中的元素,结果存储在 A 中。

void Create_CommonList(LinkList &A, LinkList B) {
    LNode *pa = A;
    LNode *pb = B->next;
    while (pa->next && pb) {
        if (pa->next->data == pb->data) {
            pa = pa->next;
            pb = pb->next;
        } else if (pa->next->data < pb->data) {
            LNode *temp = pa->next;
            pa->next = temp->next;
            free(temp);
        } else {
            pb = pb->next; // B 小 B后移
        }
    }
    pa->next = NULL; // 剩下的都清除
}

10. 子序列匹配

判断链表 B 是否是链表 A 的子序列。

bool string_match(LinkList A, LinkList B) {
    LNode *p = A->next;
    LNode *q = B->next;
    LNode *start = A->next;
    while (p != NULL && q != NULL) {
        if (p->data == q->data) {
            p = p->next;
            q = q->next;
        } else {
            start = start->next; // 主串后移一位
            p = start;
            q = B->next; // 子串从头开始
        }
    }
    return q == NULL; // 子串遍历完即匹配成功
}

12. 循环链表链接

将两个循环链表链接成一个循环链表。

LinkList Connect_List(LinkList H1, LinkList H2) {
    // H1 尾接到 H2头  H2尾接到 H1头
    LNode *p1 = H1;
    LNode *p2 = H2;
    // 找H1的尾节点
    while (p1->next != H1) {
        p1 = p1->next;
    } 
    p1->next = H2->next;
    // 找H2的尾节点
    while (p2->next != H2) {
        p2 = p2->next;
    }
    p2->next = H1;
    return H1;
}

13. 基于访问频率的节点调整

在双向链表中,当节点被访问时,根据其访问频率调整在链表中的位置。

DLinkList Locate(DLinkList L, ElemType x) {
    DNode *p = L->next;
    while (p && p->data != x) p = p->next;
    if (p == NULL) return NULL;
    else {
        p->freq++;
        if (p->pre == L || p->pre->freq > p->freq) {
            return p;
        }
        // 将 p 挪出
        if (p->next != NULL) p->next->pre = p->pre;
        p->pre->next = p->next;
        DNode *q = p->pre; 
        // 利用 q 找到合适的位置
        while (q != L && q->freq <= p->freq) {
            q = q->pre;
        }
        // 将 p插入
        p->next = q->next;
        if (q->next != NULL) q->next->pre = p;
        p->pre = q;
        q->next = p;
    }
    return p;
}

14. 链表循环右移

将链表循环右移 k 位,通过先成环再断链的方式实现。

LinkList Converse(LinkList &L, int k) {
    if (!L || !L->next) return L;
    LNode *p = L;
    int n = 1;
    // 统计长度并成环
    while (p->next != NULL) {
        p = p->next;
        n++; 
    }
    p->next = L; // 变链成环
    k = k % n; // 处理k大于n的情况
    for (int i = 1; i <= n - k; i++) {
        p = p->next; // 找新链表尾节点
    } 
    L = p->next; // 头节点
    p->next = NULL; // 断链
    return L;
}

15. 检测链表是否存在环

使用快慢指针法检测链表中是否存在环,快指针每次走两步,慢指针每次走一步。

bool FindLoop(LinkList L) {
    if (!L || !L->next) return false; // 空链表或单节点无环
    LNode *fast = L->next, *slow = L; // 从不同位置开始,避免初始相等
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) {
            return true;
        } 
    }
    return false;
}

16. 最大孪生和

对于链表,计算对称位置节点的和的最大值,适用于寻找链表中的最大孪生和。

int PairSum(LinkList L) {
    if (!L || !L->next) return 0;
    LNode *fast = L->next, *slow = L;
    // 找中点
    while (fast && fast->next) {
        fast = fast->next->next;
        slow = slow->next;
    }
    // 逆置后半部分
    LNode *L2 = NULL, *p = slow->next, *temp;
    while (p != NULL) {
        temp = p->next;
        p->next = L2;
        L2 = p;
        p = temp;
    }
    // 计算最大孪生和
    int max = 0;
    p = L->next;
    LNode *q = L2;
    while (q != NULL) {
        if (p->data + q->data > max) {
            max = p->data + q->data;
        }
        p = p->next;
        q = q->next;
    }
    return max;
}

17. 查找倒数第 k 个节点

使用双指针法查找链表中倒数第 k 个节点,先让后指针走 k 步,然后前后指针一起走。

int last_k(LinkList L, int k) {
    if (!L || !L->next) return 0;
    LNode *front = L->next;
    LNode *rear = L->next;
    // 先让 rear 走 k步
    while (k > 0) {
        rear = rear->next;
        k--;
        if (rear == NULL) return 0; // 链表长度小于k
    } 
    // 然后 rear front 一起走
    while (rear != NULL) {
        rear = rear->next;
        front = front->next;
    }
    return front->data;
}