C语言数据结构(4.单链表部分典型OJ题目)

前言

紧接上期博客,我们已经介绍了单链表的相关知识点以及实现了单链表的增删改查,那么本期博客就让我们走进单链表的典型OJ题目当中,感受一下单链表独特的魅力吧!


单链表算法题

1.移除链表元素


题型总结

1. 反转(转置或者说逆置)单链表

2.寻找单链表的中间节点

3.输出倒数第k个单链表中的值

4.合并两个单链表


答案解析:

下边便是上述单链表OJ题的答案和思路,需要的可以参考一下:

#define _CRT_SECURE_NO_WARNINGS



//作业题一:
//1.删除链表中等于给定值 val 的所有节点

/*
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
//typedef struct ListNode ListNode;
//struct ListNode* removeElements(struct ListNode* head, int val) {
//    //创建新链表
//    ListNode* newNode, * newTail;
//    newNode = newTail = NULL;
//    //遍历新链表
    ListNode* pcur = head;
    while (pcur)
    {
        //当原链表中的值和val不同的时候,将原链表中的值尾插到新链表中去
        if (pcur->val != val)
        {
            //当新链表为空的时候
            if (newNode == NULL)
            {
                newNode = newTail = pcur;
            }
            else
            {
                //当新链表不为空的时候
                newTail->next = pcur;
                newTail = newTail->next;
            }
        }
        pcur = pcur->next;
    }
    //防止出现旧链表为同一个数,然后val也为这个数,导致新链表中没有数可尾插,使得新链表仍然为空,那么就让新链表的头结点置为空即可。
    if (newTail)
        newTail->next = NULL;
    return newNode;
}




//作业题二:
//2.反转一个单链表。
// 思路一:创建一个新的链表,将旧链表遍历,然后从头头插到新链表中去
//思路二:创建三个指针实现一个单链表的反转,也叫逆置这个单链表,这样可以不用创建一个新的链表
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    //处理空链表
    if (head == NULL)
    {
        return head;
    }
    //创建三个指针
    ListNode* n1, * n2, * n3;
    n1 = NULL; n2 = head; n3 = n2->next;
    while (n2)
    {
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        //由于n3会提前走到空,所以要对它进行单独的判断
        if (n3)
            n3 = n3->next;
    }
    //此时n1就是旧链表反转之后的新的头结点
    return n1;
}





//作业三:
/*3.给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点
作业内容
3.给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。*/
//思路一;分为两次循环,第一次循环:求链表的总长度,计算中间节点的位置,
// 第二次循环,根据中间节点的位置走到中间节点
//思路二:这个题目涉及快慢指针,慢指针每次走一步,快指针一次走两步
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
    ListNode* slow = head, * fast = head;
    //慢指针一次走一步
    //快指针一次走两步
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    //此时的slow指的就是中间结点
    return slow;
}




//作业四:
//4.输入一个链表,输出该链表中倒数第k个结点。
//思路一://先逆置原链表,然后再根据k的值查找链表中对应的值
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
int kthToLast(struct ListNode* head, int k) {
    //先逆置原链表,然后再根据k的值查找链表中对应的值
    ListNode* n1, * n2, * n3;
    n1 = NULL, n2 = head, n3 = n2->next;
    while (n2)
    {
        n2->next = n1;
        n1 = n2;
        //n3先到达为空,所以要对n3进行处理。
        if (n3)
            n3 = n3->next;
    }
    ListNode* newHead = n1;
    while (k--)
    {
        newHead = newHead->next;
    }
    return newHead->val;
}





//思路二:双指针法
//首先创建两个双指针,先让一个指针走k步,然后两个双指针再同时一步一步的走。
// 之后,当先走的指针为空的时候,后走的指针刚好到达倒数第k个结点,也就知道倒数第k个结点的值了
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
int kthToLast(struct ListNode* head, int k) {
    // 双指针 src, dst 都指向头节点 head
    ListNode* src = head, * dst = head;
    // dst 先走 k 步
    for (int i = 0; i < k; i++)
        dst = dst->next;
    // 当 dst 走过尾节点时跳出
    while (dst != NULL)
    {
        // src, dst 都向前走 1 步
        src = src->next;
        dst = dst->next;
    }
    return src->val;
}






//作业五:
//5.将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
//方案一:
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    // 先处理链表为空的情况
    if (list1 == NULL) {
        return list2;
    }
    if (list2 == NULL) {
        return list1;
    }
    // 创建一个新链表
    ListNode* newHead = NULL, * newTail = NULL;
    // 创建两个新指针指向链表的头结点
    ListNode* l1 = list1;
    ListNode* l2 = list2;
    while (l1 && l2) // 注意这里是且,不是或
    {
        if (l1->val < l2->val) {
            // l1尾插到新的链表中去,但是,新链表也有可能为空,所以还要在判断一下
            if (newHead == NULL) {
                newHead = newTail = l1;
            }
            else {
                newTail->next = l1;
                newTail = newTail->next;
            }
            l1 = l1->next;
        }
        else {
            if (newHead == NULL) {
                newHead = newTail = l2;
            }
            else {
                newTail->next = l2;
                newTail = newTail->next;
            }
            l2 = l2->next;
        }
    }
    // 跳出循环的条件,无非就是l1为空(l2不为空)或者l2为空(l1不为空)
    if (l1)
        newTail->next = l1;
    if (l2)
        newTail->next = l2;

    return newHead;
}




//方案二:
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    // 先处理链表为空的情况
    if (list1 == NULL) {
        return list2;
    }
    if (list2 == NULL) {
        return list1;
    }
    // 创建一个新的非空链表
    ListNode* newHead, * newTail;
    newHead = newTail = (ListNode*)malloc(sizeof(ListNode));
    // 创建两个新指针指向链表的头结点
    ListNode* l1 = list1;
    ListNode* l2 = list2;
    while (l1 && l2) // 注意这里是且,不是或
    {
        if (l1->val < l2->val) {
            // l1尾插到新的链表中去,此时,新链表一定不为空,所以直接尾插即可
            newTail->next = l1;
            newTail = newTail->next;
            l1 = l1->next;
        }
        else {
            newTail->next = l2;
            newTail = newTail->next;
            l2 = l2->next;
        }
    }
    // 跳出循环的条件,无非就是l1为空(l2不为空)或者l2为空(l1不为空)
    if (l1)
        newTail->next = l1;
    if (l2)
        newTail->next = l2;

    ListNode* ret = newHead->next;
    free(newHead);
    newHead = NULL;
    return ret;
}





//作业题六
/*现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,
且不能改变原来的数据顺序,返回重新排列后的链表的头指针。*/
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
#include <cstdlib>
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
        //创建两个不为空的链表
        //分别为小链表和大链表。小链表存储比x小的数据,大链表存储,比x大的数据
        ListNode* lessHead, * lessTail;
        lessHead = lessTail = (ListNode*)malloc(sizeof(ListNode));

        ListNode* greaterHead, * greaterTail;
        greaterHead = greaterTail = (ListNode*)malloc(sizeof(ListNode));

        //遍历原链表,将比x小的尾插到小链表中去,把比x大的数据尾插大链表中去
        ListNode* pcur = pHead;
        while (pcur) { //相当于是pcur!=NULL
            if (pcur->val < x) {
                //尾插到小链表中去
                lessTail->next = pcur;
                lessTail = lessTail->next;
            }
            else {
                //尾插到大链表中去
                greaterTail->next = pcur;
                greaterTail = greaterTail->next;
            }
            pcur = pcur->next;
        }
        //大链表的最后一个节点的指向为空
        greaterTail->next = NULL;
        //大小链表相连
        lessTail->next = greaterHead->next;
        ListNode* ret = lessHead->next;
        free(lessHead);
        free(greaterHead);
        lessHead = greaterTail = NULL;
        return ret;
    }
};



//作业题七:
/*对于一个链表,请设计一个时间复杂度为O(n), 额外空间复杂度为O(1)的算法,判断其是否为回文结构。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
测试样例:
1->2->2->1
返回:true*/



//方法一,遍历链表,将链表中的数据存入一个数组中,然后在数组中判断是否为回文数/*
//struct ListNode {
//    int val;
//    struct ListNode* next;
//    ListNode(int x) : val(x), next(NULL) {}
//}; */
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        // write code here
        int arr[1000];
        ListNode* pucr = A;
        int i = 0;
        //遍历链表,将链表中每一个结点中的数值拿到数组中去
        while (pucr)
        {
            arr[i++] = pucr->val;
            pucr = pucr->next;
        }
        //i即为结点的个数
        //找中间结点,判断是否为回文数
        int left = 0;
        int right = i - 1;
        while (left < right)
        {
            if (arr[left] != arr[right])
            {
                return false;
            }
            left++;
            right--;
        }
        //是回文结构
        return true;
    }
};





//方法二:
//反转链表
//1.通过快慢指针找到中间结点
//2.找到中间结点之后,对结点之后的链表进行反转
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
    ListNode* FindMidNode(ListNode* phead) {
        ListNode* slow = phead;
        ListNode* fast = phead;
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
    ListNode* reverseList(ListNode* phesd) {
        ListNode* n1, * n2, * n3;
        n1 = NULL, n2 = phesd,
            n3 = n2->next;
        while (n2) {
            n2->next = n1;
            n1 = n2;
            n2 = n3;
            if (n3)
                n3 = n3->next;
        }
        return n1;
    }
    bool chkPalindrome(ListNode* A) {
        // write code here
        //1.找中间节点
        ListNode* mid = FindMidNode(A);
        //2.根据中间节点反转后边的列表
        ListNode* right = reverseList(mid);
        //3.从原链表和反转之后的链表比较节点的值
        ListNode* left = A;
        while (right) {
            if (left->val != right->val) {
                return false;
            }
            left = left->next;
            right = right->next;
        }
        return true;
    }
};

总结

以上便是部分单链表的OJ题目,还有双向链表环形链表没有给出具体的解析和代码,环形链表的题目博主放在下边了(大家可以尝试写一写):

环形链表I
提示:(使用快慢指针)
快慢指针
快慢指针,即慢指针⼀次⾛⼀步,快指针⼀次⾛两步,两个指针从链表起始位置开始运⾏,
如果链表
带环则⼀定会在环中相遇,否则快指针率先⾛到链表的未尾
环形链表II
关于双向链表,那就是另外一个专题了,在以后的时间,博主会抽时间写出这个专题的
本篇博客到此结束,谢谢阅读!我们下次栈和队列再见!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值