数据结构第四讲:单链表OJ题

这一讲全部都是关于单链表的数据结构题目,这里有几个注意点:
1.博客中不会出现关于题目的讲解,讲解通过注释或画图来表现出来
2.所有题目都会上传两种代码:一种是代码纯享版,一种是添加了注释之后的代码,所有的画图都会在代码最后进行展示
3.理解是自己在写代码时自己的一些理解,可能不太成熟,请见谅

1.移除链表元素

链接: OJ题目链接

typedef struct ListNode ListNode;

struct ListNode* removeElements(struct ListNode* head, int val) {
	//创建一个头节点和尾节点
    ListNode *phead, *ptail;
    phead = ptail = NULL;

    ListNode* pcur = head;
    while (pcur) {
        if (pcur->val != val) {
            if (phead == NULL) {
                phead = ptail = pcur;
            } else {
                ptail->next = pcur;
                ptail = ptail->next;
            }
        }
        pcur = pcur->next;
    }
    if (ptail) {
        ptail->next = NULL;
    }
    return phead;
}
typedef struct ListNode ListNode;

struct ListNode* removeElements(struct ListNode* head, int val) {
    //首先创建了一个头节点和尾节点
    //理解1:对于单链表OJ题,知道了一个头节点和尾节点就可以了,头节点方便找到链表的头部
    //尾节点用来找到在哪个地方插入新的节点
    ListNode *phead, *ptail;
    //对链表指针进行初始化
    phead = ptail = NULL;

    //使用一个指针来保存头节点,防止头节点移动
    ListNode* pcur = head;
    while (pcur) {
        //如果节点中的数据值不等于要删除的数据,将该节点进行链接
        if (pcur->val != val) {
            if (phead == NULL) {
                phead = ptail = pcur;
            } else {
                //这里一定要写成pcur,不能写成head
                ptail->next = pcur;
                ptail = ptail->next;
            }
        }
        pcur = pcur->next;
    }
    if (ptail) {
        ptail->next = NULL;
    }
    return phead;
}

2.反转链表

链接: OJ题目链接

typedef struct ListNode ListNode;

struct ListNode* reverseList(struct ListNode* head) {
    if (head==NULL || head->next==NULL) {
        return head;
    }
    ListNode *left, *phead, *right;
    left = NULL;
    phead = head;
    right = head->next;
    while (phead) {
        phead->next = left;
        left = phead;
        phead = right;
        if(right)
            right = right->next;
    }
return left;
}
typedef struct ListNode ListNode;
//使用三个指针即可
struct ListNode* reverseList(struct ListNode* head) {
    if (head==NULL || head->next==NULL) {
        return head;
    }
    ListNode *left, *phead, *right;
    left = NULL;
    phead = head;
    right = head->next;
    while (phead) {
        phead->next = left;
        left = phead;
        phead = right;
        if(right)
            right = right->next;
    }
return left;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.链表的中间节点

链接: OJ题目链接

typedef struct ListNode ListNode;

struct ListNode* middleNode(struct ListNode* head) {
    ListNode *fast, *slow;
    fast = slow = head;

    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}
typedef struct ListNode ListNode;

struct ListNode* middleNode(struct ListNode* head) {
    //这道题定义了两个指针,使用的是快慢指针的思想
    //想像两个人A和B在走路,B每次走的距离都比A走的长二倍,那么当B走到终点时,A恰好走到中点
    //这样我们就找到了中间节点的位置了
    ListNode *fast, *slow;
    fast = slow = head;

    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

在这里插入图片描述
在这里插入图片描述

4.合并两个有序链表

链接: OJ题目链接

typedef struct ListNode ListNode;

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if (list1 == NULL) {
        return list2;
    }
    if (list2 == NULL) {
        return list1;
    }
    ListNode *phead, *ptail;
    phead = ptail = NULL;
    ListNode* p1 = list1;
    ListNode* p2 = list2;

    while (p1 && p2) {
        if (p1->val <= p2->val) {
            if (phead == NULL) {
                phead = ptail = p1;
            } else {
                ptail->next = p1;
                ptail = p1;
            }
            p1 = p1->next;
        } else {
            if (phead == NULL) {
                phead = ptail = p2;
            } else {
                ptail->next = p2;
                ptail = p2;
            }
            p2 = p2->next;
        }
    }
    if (p1 == NULL) {
        ptail->next = p2;
    } else {
        ptail->next = p1;
    }
    return phead;
}
typedef struct ListNode ListNode;

//这道题的思路是:
//先创建一个新的链表
//再创建两个指针变量p1和p2,比较p1->val和p2->val的大小,谁小就尾插新的链表
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if (list1 == NULL) {
        return list2;
    }
    if (list2 == NULL) {
        return list1;
    }
    ListNode *phead, *ptail;
    phead = ptail = NULL;
    ListNode* p1 = list1;
    ListNode* p2 = list2;

    while (p1 && p2) {
        if (p1->val <= p2->val) {
            if (phead == NULL) {
                phead = ptail = p1;
            } else {
                ptail->next = p1;
                ptail = p1;
            }
            p1 = p1->next;
        } else {
            if (phead == NULL) {
                phead = ptail = p2;
            } else {
                ptail->next = p2;
                ptail = p2;
            }
            p2 = p2->next;
        }
    }
    if (p1 == NULL) {
        ptail->next = p2;
    } else {
        ptail->next = p1;
    }
    return phead;
}

在这里插入图片描述

5.链表分割

链接: OJ题目链接

class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        ListNode* lesshead = (ListNode*)malloc(sizeof(ListNode));
        ListNode* lesstail = lesshead;
        ListNode* bighead = (ListNode*)malloc(sizeof(ListNode));
        ListNode* bigtail = bighead;

        ListNode* pcur = pHead;
        while(pcur)
        {
            if(pcur->val < x)
            {
                lesstail->next = pcur;
                lesstail = lesstail->next;
            }
            else 
            {
                bigtail->next = pcur;
                bigtail = bigtail->next;    
            }
            pcur = pcur->next;
        }
        lesstail->next = bighead->next;
        bigtail->next = NULL;
        return lesshead->next;
    }
};
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        //直接创建两个节点lesshead和bighead
        //val值小于x的尾插到lesshead节点
        //val值大于x的尾插到bighead节点
        ListNode* lesshead = (ListNode*)malloc(sizeof(ListNode));
        ListNode* lesstail = lesshead;
        ListNode* bighead = (ListNode*)malloc(sizeof(ListNode));
        ListNode* bigtail = bighead;

        ListNode* pcur = pHead;
        while(pcur)
        {
            if(pcur->val < x)
            {
                lesstail->next = pcur;
                lesstail = lesstail->next;
            }
            else 
            {
                bigtail->next = pcur;
                bigtail = bigtail->next;    
            }
            pcur = pcur->next;
        }
        lesstail->next = bighead->next;
        //这里必须要将最后一个节点的指针指向NULL
        bigtail->next = NULL;
        return lesshead->next;
    }
};

6.链表的回文结构

链接: OJ题目链接

6.1方法1:创建数组

这个方法并不使用于所有情况,题目要求为空间复杂度为O(1),由于题目要求链表长度不大于900,所以我们创建了一个常量的数组,如果链表长度不确定,该题的空间复杂度就变成了O(n)

class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        //创建一个数组,将节点中的值存储在数组中,通过数组判断是否是回文数
        ListNode* pcur = A;

        int arr[900];
        int sz = 0;
        while(pcur)
        {
            arr[sz++] = pcur->val;
            pcur = pcur->next;
        }
        int left = 0;
        int right = sz-1;
        while(left < right)
        {
            if(arr[left++] != arr[right--])
            {
                return false;
            }
        }
        return true;
    }
};
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        //创建一个数组,将节点中的值存储在数组中,通过数组判断是否是回文数
        ListNode* pcur = A;

        int arr[900];
        int sz = 0;
        while(pcur)
        {
            arr[sz++] = pcur->val;
            pcur = pcur->next;
        }
        //使用数组进行检查的思路为:
        //定义两个头尾指针,判断头尾指针指向的值是否相同
        int left = 0;
        int right = sz-1;
        //left不能够大于right,这可以作为循环条件
        while(left < right)
        {
            if(arr[left++] != arr[right--])
            {
                //当存在一个数不同时,就不是回文数
                return false;
            }
        }
        return true;
    }
};

6.2方法2:反转链表

class PalindromeList {
public:
    ListNode* FindBet(ListNode* phead)
    {
        //快慢指针进行查找
        ListNode* slow, *fast;
        slow = fast = phead;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }

    ListNode* Reserve(ListNode* A, ListNode* bet)
    {
        ListNode* left = NULL;
        ListNode* phead = A;
        ListNode* right = A->next;

        while(phead != bet)
        {
            phead->next = left;
            left = phead;
            phead = right;
            if(right != bet)
                right = right->next;
        }
        return left;
    }

    bool chkPalindrome(ListNode* A) {
        //先查找链表中有几个节点
        int count = 0;
        ListNode* pcur = A;
        while(pcur)
        {
            count++;
            pcur = pcur->next;
        }

        //先找到中间节点
        ListNode* bet = FindBet(A);

        //然后进行逆置
        ListNode* phead = Reserve(A, bet);

        if(count%2 != 0)
            bet = bet->next;
        while(bet)
        {
            if(phead->val != bet->val)
            {
                return false;
            }
            phead = phead->next;
            bet = bet->next;
        }
        return true;
    }
};

7.相交链表

链接: OJ题目链接

typedef struct ListNode ListNode;

struct ListNode* getIntersectionNode(struct ListNode* headA,
                                     struct ListNode* headB) {
    // 首先先判断是否存在相交节点
    // 存在相交节点的充要条件为:两个链表中的最后一个节点是同一个节点
    ListNode *ptail1, *ptail2;
    ptail1 = headA;
    ptail2 = headB;

    int count1 = 0;
    int count2 = 0;
    while (ptail1->next) {
        count1++;
        ptail1 = ptail1->next;
    }
    while (ptail2->next) {
        count2++;
        ptail2 = ptail2->next;
    }
    if (ptail1 != ptail2) {
        return NULL;
    } else {
        // 将长链表截取一部分
        int cut = abs(count1 - count2);
        ListNode* lesslong = headA;
        ListNode* morelong = headB;
        if (count1 > count2) {
            lesslong = headB;
            morelong = headA;
        }
        while (cut--) {
            morelong = morelong->next;
        }
        while (lesslong != morelong) {
            lesslong = lesslong->next;
            morelong = morelong->next;
        }
        return lesslong;
    }
}
typedef struct ListNode ListNode;

//思路为:
//先分别求出两个链表的长度,将长的那个链表截取几个节点,使得两个链表一样长
//分别从两个链表的头节点进行遍历,当两个节点相同时,这个相同的节点就是相交的节点
struct ListNode* getIntersectionNode(struct ListNode* headA,
                                     struct ListNode* headB) {
    // 首先先判断是否存在相交节点
    // 存在相交节点的充要条件为:两个链表中的最后一个节点是同一个节点
    ListNode *ptail1, *ptail2;
    ptail1 = headA;
    ptail2 = headB;

    //后续我们要求长链表向后移动多少,所以我们要知道每一个链表的长度
    int count1 = 0;
    int count2 = 0;
    while (ptail1->next) {
        count1++;
        ptail1 = ptail1->next;
    }
    while (ptail2->next) {
        count2++;
        ptail2 = ptail2->next;
    }
    if (ptail1 != ptail2) {
        return NULL;
    } else {
        // 将长链表截取一部分
        int cut = abs(count1 - count2);
        ListNode* lesslong = headA;
        ListNode* morelong = headB;
        if (count1 > count2) {
            lesslong = headB;
            morelong = headA;
        }
        while (cut--) {
            morelong = morelong->next;
        }
        while (lesslong != morelong) {
            lesslong = lesslong->next;
            morelong = morelong->next;
        }
        return lesslong;
    }
}

8.环形链表一

链接: OJ题目链接

typedef struct ListNode ListNode;

bool hasCycle(struct ListNode *head) {
    ListNode* slow, *fast;
    slow = fast = head;

    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
            return true;
    }
    return false;
}

在这串代码中,我们使用了快慢指针的方法,让慢指针一次走一步,快指针一次走两步,如果两个指针相同了,就证明存在环形链表,那这是为什么呢?

8.1原理解释

在这里插入图片描述

8.2拓展思维

那么如果我让fast指针一次走三个单位,那么两个指针还会不会相遇呢?
在这里插入图片描述
在这里插入图片描述
所以我们知道了:追不上只有一种情况:N为奇数,C为偶数
我们再来看:
在这里插入图片描述
在这里插入图片描述
对于2L = (x+1)C-N这个公式来说,公式左边一定为偶数,当N为奇数时,为让等式成立,那么(x+1)C也必须为奇数,所以说根本就不存在N为奇数,C为偶数的情况

所以说,不管fast比slow快多少,如果它们两个能够相遇,就证明存在环形链表,但是一般我们还是让fast比slow快两倍来求

9.环形链表二

链接: OJ题目链接

typedef struct ListNode ListNode;

struct ListNode *detectCycle(struct ListNode *head) {
    //先判断是否存在环形链表
    ListNode* slow, *fast;
    slow = fast = head;

    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
            break;
    }
    if(fast==NULL || fast->next==NULL)
        return NULL;
    //此时说明肯定存在环形链表,这时需要找到环形节点存在的位置
    ListNode* pcur = head;
    while(1)
    {
        if(fast == pcur)
            return pcur;
        fast = fast->next;
        pcur = pcur->next;
    }
}

这次的代码还是很简单,只需要先通过快慢指针找到两指针的相遇点,再让一个指针指向头指针,将头指针和相遇点指针向后移,两指针相遇的位置就是环形链表成环的位置

9.1原理解释

在这里插入图片描述
在这里插入图片描述

10.随机链表的复制

链接: OJ题目链接

10.1题目解析

题目就是指:
一个节点中多出了一个random指针,该指针指向的位置不确定,可以指向NULL,可以指向自己,可以指向链表中其他节点的地址。
我们要做的是对这样的一个链表进行深拷贝,也就是需要单独创建空间并进行赋值

10.解题代码

typedef struct Node Node;

//申请空间的函数
Node* BuyNode(int x)
{
    Node* newnode = (Node*)malloc(sizeof(Node));
    newnode->val = x;
    newnode->next = newnode->random = NULL;

    return newnode;
}

struct Node* copyRandomList(struct Node* head) {
    if(head == NULL)
        return NULL;
    Node* pcur = head;

    //将链表间链接起来
    while(pcur)
    {
        Node* Next = pcur->next;
        Node* newnode = BuyNode(pcur->val);
        pcur->next = newnode;
        newnode->next = Next;

        pcur = Next;
    }
    //对加入的新的节点进行赋值
    pcur = head;
    while(pcur)
    {
        Node* copy = pcur->next;
        if(pcur->random != NULL)
        {
            copy->random = pcur->random->next;
        }
        pcur = copy->next;
    }
    //将两个链表分开
    pcur = head;
    Node* newHead, *newTail;
    newHead = newTail = pcur->next;
    while(pcur->next->next)
    {
        pcur = pcur->next->next;
        newTail->next = pcur->next;
        newTail = newTail->next;
    }
    return newHead;
}

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值