单链表经典OJ题(三)

简介: 单链表经典OJ题(三)

1、反转链表

206. 反转链表 - 力扣(LeetCode)

翻转链表的实质就是更改当前结点的前驱结点和后继结点

假设原链表为:1->2->3->4->5->NULL,具体解题思路如下:

由题意得,反转后的链表为NULL<-1<-2<-3<-4<-5,那么我们假设三个指针用于实现这一反转过程,其中令cur指针指向原链表的第一个结点,pre指向链表最后一个结点的下一个结点也就是空结点NULL(既然是反转链表那就要全部反转完,即使是空结点也要反转,pre其实就相当于一个引路人的作用它告诉cur你要将你所指向结点的next指针指向我所指的对象),最后令tmp指针指向cur指针指向结点的下一个结点,具体情况如下图所示:

然后我们开始反转操作,首先要将链表第一个结点的后继结点变为NULL,所以要执行的操作就是cur->next = pre,此时原链表中的1->2就变成了NULL<-1 (与1->NULL一个意思只是为了方便理解写成了前者),而pre在完成NULL的引路工作后就要进行下一个引路工作了,它的下一个引路工作就是将1->2变为1<-2,所以他这时就要指向1,也就是cur此时指向的结点,所以此时令pre=cur即可,循环的最后为了防止链表只有一个结点就结束了,所以此时仍需令cur向前走一步然后再跳出循环,即令cur=tmp,这样就可以做到在下次循环开始时可以对下一个要操作的结点是否为空进行一个判断,如果不为空才能进入循环。

关于后续几次循环,具体操作过程不再过多赘述仅以以下图片展示每次循环后的结果:

至此链表反转完成(虽然看着怪怪的但是的确是正确的)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* pre = NULL;
    struct ListNode* cur = head;
    while (cur) {
        struct ListNode* next = cur->next;
        cur->next = prev;
        pre = curr;
        cur = next;
    }
    return pre;
}

2、合并两个有序链表

21. 合并两个有序链表 - 力扣(LeetCode)

具体解题思路如下:

1、合并两个链表首先要保证两个链表都不为空,如果其中一个链表为空那么合并后的结果就是另一个非空链表:

//当传入的两个链表其中有一个为空,那么返回另一个链表即可
    if(list1 == NULL)
    {
        return list2;
    }
    if(list2 == NULL)
    {
        return list1;
    }

2、如果两个链表都不为空,那么分别创建两个指针指向两个链表的头结点开始遍历两个链表,同时还要创建一个用于存放两个旧链表合并结果的新链表,我们这里创建一个带有哨兵位的单向链表这样后续进行尾插等操作就不需要考虑头结点是否为空的情况,减少重复代码

//当两个链表都不为空时,遍历链表
LSNode* cur1 = list1;
LSNode* cur2 = list2;
LSNode* newHead,*newTail;
newHead = newTail = (LSNode*)malloc(sizeof(LSNode));
newHead->val = -1;
newHead->next = NULL;

3、开始正式遍历两个旧链表,在遍历过程中需要注意的是只要有一个链表走到了头即cur1或cur2指向空那么就不能再循环了,在都没有走到头的时候,我们就判断cur1和cur2指向的结点的值的大小,如果cur1->val < cur2->val,就将此时cur1指向的结点尾插进新链表,如果cur1->val >= cur2->val,就将此时cur2指向的结点尾插进新链表(记得每次尾插过后新链表中的newTali也就负责指向新链表最后一个有效结点的指针要向后移动以便于下一次的尾插)

//当两个结点有一个走到空就不能进行比较了
    while(cur1 && cur2)
    {
        //把值小的结点尾插到新的链表
        if(cur1->val < cur2->val)
            {
            newTail->next = cur1;
            newTail = newTail->next;
            cur1 = cur1->next;
            }
        //当cur2->val <= cur1->val时
        else
        {
            newTail->next = cur2;
            newTail = newTail->next;
            cur2 = cur2->next;
        }
    }

4、尾插结束后,如果cur2先指向空,那么就让cur1的后续结点尾插进新链表,如果cur1先指向空,那么就让cur2的后续结点尾插进新链表,如果cur1和cur2同时指向空,证明合并完成直接返回哨兵位的下一个结点作为新链表的头结点即可,同时记得释放为哨兵位申请的内存空间

if(cur1)
    newTail->next = cur1;
if(cur2)
    newTail->next = cur2;
return newHead->next;
free(newHead);

最终代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode LSNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    //当传入的两个链表其中有一个为空,那么返回另一个链表即可
    if(list1 == NULL)
    {
        return list2;
    }
    if(list2 == NULL)
    {
        return list1;
    }
    //当两个链表都不为空时,遍历链表
    LSNode* cur1 = list1;
    LSNode* cur2 = list2;
    //创建新的空链表--带头(结点)单向不循环链表(后续进行尾插等情况就不需要考虑头结点是否为空的情况,减少重复代码)
    LSNode* newHead,*newTail;
    newHead = newTail = (LSNode*)malloc(sizeof(LSNode));
    newHead->val = -1;
    newHead->next = NULL;
    //当两个结点有一个走到空就不能进行比较了
    while(cur1 && cur2)
    {
        //把值小的结点尾插到新的链表
        if(cur1->val < cur2->val)
            {
            newTail->next = cur1;
            newTail = newTail->next;
            cur1 = cur1->next;
            }
        //当cur2->val <= cur1->val时
        else
        {
            newTail->next = cur2;
            newTail = newTail->next;
            cur2 = cur2->next;
        }
    }
if(cur1)
    newTail->next = cur1;
if(cur2)
    newTail->next = cur2;
return newHead->next;
free(newHead);
}

3、链表的中间结点

876. 链表的中间结点 - 力扣(LeetCode)

利用快慢指针的特性即可解决该题目:如果链表有效结点个数为单数,则当快指针指向的结点为空或快指针指向的下一个结点为空时,慢指针指向该链表的中间结点。如果链表有效结点个数为双数,则当快指针指向的结点为空或快指针指向的下一个结点为空时,慢指针指向该链表的中间两个结点的后一个结点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode LSNode;
struct ListNode* middleNode(struct ListNode* head)
{   
    if(head == NULL)
        return NULL;
    //快慢指针
    LSNode* slow,*fast;
    slow = fast = head;
    //只要fast和fast->next有一个为空则停止循环
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    //当循环结束时,slow必定指向我们要找的链表中间结点
    return slow;
}

4、删除有序数组中的重复项

26. 删除有序数组中的重复项 - 力扣(LeetCode)

假设有序数组为{0,0,1,1,1,2,2,3,3,4},具体解题思路如下:

1、由于官方规定该题目的数组长度numsize大于等于1,所以不需要判0操作

2、初始化两个变量fast和slow为1,令它们两个充当数组下标(这里的fast和slow貌似虽然不是快慢指针但是作用类似),题目要求返回删除数组中重复项后新数组的长度,从官方给我们的多个实例中可以发现数组中的重复项都是相连的,因此我们可以通过覆盖重复项的操作来达到删除重复项的目的,及利用重复项后的数组元素覆盖掉重复的项(如果重复项后的元素也是重复项那就用它后面的数组元素继续覆盖它),而达成这一目的之前我们要保证的是这两个相邻的元素都不相同,如果相同那么就不能执行覆盖操作,需要继续寻找与之不同的元素将其覆盖掉才行

最终代码如下:

int removeDuplicates(int* nums, int numsSize)
{
    int fast = 1, slow = 1;
    while (fast < numsSize) 
    {
        if (nums[fast] != nums[fast - 1]) 
        {
            nums[slow] = nums[fast];
            ++slow;
        }
        ++fast;
    }
    return slow;
}

5、移除链表元素

203. 移除链表元素 - 力扣(LeetCode)

emm这道题很简单直接看注释即可......

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode LSNode;
struct ListNode* removeElements(struct ListNode* head, int val){
    //还是申请哨兵位的老套路
    LSNode* newHead,*newTail;
    newHead = newTail = (LSNode*)malloc(sizeof(LSNode));
    newHead->val = -1;
    newHead->next = NULL;
    //令新指针pcur指向原链表的头结点head,遍历原链表
    LSNode* pcur = head;
    while(pcur)
    {
        //当不满足.val==val时,开始向新建的空链表中插入
        if(pcur->val != val)
            {
                //1、如果新建的链表为空,插入的新节点就是链表的头结点和尾结点
                if(newHead == NULL)
                    {
                        newHead = newTail = pcur;
                    }
                //2、如果新建的链表不为空,直接尾插,让新插进来的结点作为新的尾结点
                else
                    {
                        newTail->next = pcur;
                        newTail = newTail->next;
                    }
            }
        //当满足pcru->val = val,直接跳过进行下一个读取即可,这一点可以看题目中给的示例得到
        pcur = pcur->next;
    }
    //pcur指向NULL时跳出while循环,此时newTail->next指向的位置仍是旧链表存储数据6的结点,所以此时需要再判断newTail是否为空,如果不为空则让它最后指向的方向置为空,最后再返回哨兵位的下一个结点
    if(newTail)
        newTail->next = NULL;
    return newHead->next;
    free(newHead);
}

6、移除元素

27. 移除元素 - 力扣(LeetCode)

具体解题思路如下:

1、声明并初始化两个变量 srcdst,分别表示源索引和目标索引。初始时,两者都为 0。

2、若当前位置上的元素等于目标值即 nums[src] == val,则增加源索引(跳过需要移除的元素)

3、若当前位置上的元素不等于目标值,则将当前位置上的元素复制到目标位置 nums[dst] = nums[src],同时增加源索引和目标索引(二者都向后走)

4、循环结束后,返回目标索引作为新数组长度

主要是通过遍历原始数组,将非目标值复制到新的位置来实现删除指定元素。它使用两个指针(其实也不算是指针)来追踪读取和写入操作,并根据是否遇到要删除的值来控制它们如何前进。最终达到将非目标值移到前面、覆盖掉要删除项并计算出新数组的长度的目标

具体代码如下:

int removeElement(int* nums, int numsSize, int val)
{
    int left = 0, right = numsSize;
    while (left < right) 
    {
        if (nums[left] == val) 
        {
            nums[left] = nums[right - 1];
            right--;
        } 
        else 
        {
            left++;
        }
    }
    return left;
}

~over~

相关文章
【LeetCode】——链式二叉树经典OJ题详解
在之前的文章讲解了二叉树的链式结构的实现以及前、中、后序的遍历。不知道大家看完后有没有理解和有所收获,今天基于那篇文章给大家分享讲解几道经典的题目,更便于大家的理解和深入。
|
5月前
|
Java
力扣经典150题第六十题:反转链表 II
力扣经典150题第六十题:反转链表 II
33 1
|
6月前
|
机器学习/深度学习 算法 C语言
【C/数据结构与算法】:10道链表经典OJ
【C/数据结构与算法】:10道链表经典OJ
22 1
|
6月前
|
算法 C语言
【数据结构与算法 经典例题】链表的回文结构(图文详解)
【数据结构与算法 经典例题】链表的回文结构(图文详解)
|
7月前
详解单链表经典OJ题-1
详解单链表经典OJ题
29 2
|
7月前
|
算法
详解单链表经典OJ题-2
详解单链表经典OJ题
38 2
|
7月前
|
存储
栈与队列相关OJ题
栈与队列相关OJ题
27 0
|
7月前
|
索引
单链表经典OJ题(四)
单链表经典OJ题(四)
|
7月前
单链表经典OJ题(二)
单链表经典OJ题(二)