单链表的相关的简单算法题(C语言)

声明:题目来源:1. 力扣(LeetCode)2. 牛客网(newcoder)。

题目目录:
  1. 删除链表中等于给定值 val 的所有节点。
  2. 反转一个单链表。
  3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
  4. 输入一个链表,输出该链表中倒数第k个结点。
  5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
  6. 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
1. 删除链表中等于给定值 val 的所有节点。

题目链接:移除链表元素
示例:

输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5

本题比较简单,主要考察对链表结构的理解。
解法:定义两个节点类型的指针,一个节点指针用于判断当前节点的值是否与 val 相等,另一个节点指针始终在当前节点的前面一个位置。当前节点的值若等于 val ,则删除当前节点,直到遍历完整个链表。以下代码仅供参考:

struct ListNode* removeElements(struct ListNode* head, int val){
    if(head == NULL){
        return head;
    }
    struct ListNode *pre = head;
    struct ListNode *cur = head->next;
    while(cur != NULL){
        if(cur->val == val){
            pre->next = cur->next;
            free(cur);
        }
        else{
            pre = cur;
        }
        cur = pre->next;
    }
    if(head->val == val){
        return head->next;
    }
    return head;
}
2. 反转一个单链表。

题目链接:反转链表
示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

本题介绍三种解法:

解法1: 将第一个结点指向NULL,其余每个节点的 next 值指向上一个节点,返回最后遍历到的节点。
这种解法简单、易于理解,推荐使用;

解法2: 适用于带头结点的链表,如果不带头结点,也可以创建一个头结点。方法是从第二个节点开始,对每个遍历到的节点使用头插法,最后返回头结点,就得到了一个已翻转的链表。
一次操作的结果如图:
在这里插入图片描述
这种方法实际上和解法1是一样的,只是逻辑不一样而已:
解法1需要另外创建一个指针变量保存下一个需要遍历的节点,并且指向当前节点、上一个节点和下一个节点的指针都要不断移动;
而解法2只需要两个指针变量,且一个始终指向头结点,一个始终指向头结点指向的节点,两个指针不发生移动,只需要不断使用头插操作即可。

解法3: 递归法,从链表的第一个节点开始,不断使用递归直到访问到最后一个节点,然后让该节点指向上一层递归到的节点,与解法1的思想类似,不过是倒过来的:
在这里插入图片描述
以下为解法3的参考代码:

struct ListNode* reverse(struct ListNode* head){
    if(head->next == NULL){
        return head;
    }
    struct ListNode *List = reverse(head->next);
    List->next = head;
    head->next = NULL;
    return head;
}
struct ListNode* reverseList(struct ListNode* head){
    if(head == NULL){
        return head;
    }
    struct ListNode *later = head;
    while(later->next != NULL){
        later=later->next;
    }
    reverse(head);
    return later;
}

解法2的参考代码:

struct ListNode* reverseList(struct ListNode* head){
    if(head == NULL){
        return head;
    }
    struct ListNode *plist = (struct ListNode *)malloc(sizeof(struct ListNode));
    plist->next = NULL;
    struct ListNode *cur = head;
    struct ListNode *next = cur;
    while(next != NULL){
        next = next->next;
        cur->next = plist->next;
        plist->next = cur;
        cur = next;
    }
    head = plist->next;
    free(plist);
    return head;
}
3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

题目链接: 链表的中间节点
示例 1:

输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.

示例 2:

输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。

本题思路很简单,设快慢两个指针同时从头结点开始,慢指针每次向后移动一个节点,快指针移动两个节点,直到快指针遍历到了最后一个节点返回慢指针。
参考代码:

struct ListNode* middleNode(struct ListNode* head){
    if(head == NULL){
        return NULL;
    }
    struct ListNode *MidNode = head;
    struct ListNode *cur = head;
    while(cur && cur->next != NULL){
        cur = cur->next->next;
        MidNode = MidNode->next;
    }
    return MidNode;
}
4. 输入一个链表,输出该链表中倒数第k个结点。

题目链接:输出单链表中的倒数第k个节点
输入描述:

输入说明
1 输入链表结点个数
2 输入链表的值
3 输入k的值

输出描述:

输出一个整数

解法1: 解法1的思路同上题,设快慢两指针,快指针先向后移动 k 个节点,慢指针再和快指针以相同的速度移动,直到快指针指向空返回慢指针。

解法2: 还有一种比较投机的方法就是:使用头插法创建链表,再返回该链表的第 k 个节点即可。
以下是解法1的算法代码:

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
 
typedef struct ListNode {
    int val;
    struct ListNode* next;
}ListNode;
 
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
    if(k == 0){
        return pListHead;
    }
    ListNode *end = pListHead->next;
    for (unsigned int i = 1; i < k; ++i) {
        end = end->next;
    }
    ListNode *dest = pListHead->next;
    while (end->next != NULL) {
        dest = dest->next;
        end = end->next;
    }
    return dest;
}
 
void CreatList(ListNode* pListHead, int length) {
    ListNode* pre = pListHead;
    for (int i = 0; i < length; ++i) {
        ListNode* cur = (ListNode *)malloc(sizeof(ListNode));
        scanf("%d ", &cur->val);
        pre->next = cur;
        pre = pre->next;
    }
    pre->next = NULL;
}
 
int main() {
    int length;
    while(scanf("%d", &length) != EOF){
        ListNode* pListHead;
        pListHead = (ListNode *)malloc(sizeof(ListNode));
        pListHead->val = 0;
        CreatList(pListHead, length);
        int k;
        scanf("%d", &k);
        printf("%d\n", FindKthToTail(pListHead, k)->val);
    }
    return 0;
}
5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

题目链接:合并两个有序链表

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

题目已知两个链表是有序的,因此只需要设两个指针分别指向两个链表,不断移动两个指针并比较当前位置的值,将值较小的节点优先插入新链表,直到某个链表访问完。最后再判断是否有链表未访问完,并将这个链表的后续部分插到新链表的尾部。

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    struct ListNode *newl;    
    if(l1 == NULL){
        return l2;
    }else if(l2 == NULL){
        return l1;
    }
    if(l1->val > l2->val){
        newl = l2;
        l2 = l2->next;
    }
    else{
        newl = l1;
        l1 = l1->next;
    }
    struct ListNode *cur = newl;
    while(l1 != NULL && l2 != NULL){
        if(l1->val > l2->val){
            cur->next = l2;
            l2 = l2->next;
            cur = cur->next;
        }
        else{
            cur->next = l1;
            l1 = l1->next;
            cur = cur->next;
        }
    }
    if(l1 == NULL){
        cur->next = l2;
    }
    else{
        cur->next = l1;
    }
    return newl;
}
6. 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

题目链接:删除链表中的重复元素

示例 1:

输入: 1->1->2
输出: 1->2

示例 2:

输入: 1->1->2->3->3
输出: 1->2->3

这道题目依然没啥难度:创建两个指针,一个 cur 指向当前节点,一个 tmp 指向下一个节点,若两节点相等则删除 tmp 指向的节点,直到 cur 指向的下一个节点与该节点值不相等,再向后移动 cur ······直到 tmp 为空则退出。
参考代码如下:

struct ListNode* deleteDuplicates(struct ListNode* head){
    struct ListNode* cur = head;
    struct ListNode* tmp;
    while(cur && cur->next != NULL){
        tmp = cur->next;
        if(tmp->val == cur->val){
            cur->next = tmp->next;
            free(tmp);
            continue;
        }
        cur = cur->next;
    }
    return head;
}
  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值