链表|反转链表|移除链表元素|链表的中间节点|返回倒数第k个节点|合并两个有序链表(C)

206. 反转链表


用两个指针

p1指向空,p2指向第一个节点

p2的next指向p1,把方向调过来

因为p2的next指向p1,会丢掉后面的节点,所以需要三个节点

前两个指针是为了反转,后一个指针是为了找到下一个节点

p2给给p1,p3给给p2,p3指向下一个

p2指向NULL,循环结束

最后p1指向的就是链表的头

struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* p1, *p2, *p3;
    p1 = NULL;
    p2 = head;
    if (p2)
        p3 = p2->next;

    while (p2)
    {
        p2->next = p1;

        p1 = p2;
        p2 = p3;

        if (p3)
            p3 = p3->next;
    }
    return p1;
}
  • 开始需要判断p2是否为空,如果p2为空,直接返回p1
  • 循环的时候需要判断p3是否为空,如果p3为空,就不需要再往后走了
思路2

头插法

创建一个newhead指针,一开始指向NULL,从上面的节点下来头插即可

用next保存cur的下一个,将cur的next指向newhead,newhead指向cur

再把next给给cur,next继续往下走

继续头插,把cur的next指向newhead

cur给给newhead

next给给cur,next继续往后遍历,直到cur为空结束

struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* cur = head;
    struct ListNode* newhead = NULL;
    while (cur)
    {
        struct ListNode* next = cur->next;
  
        cur->next = newhead;
        newhead = cur;
  
        cur = next;
    }
    return newhead;
}

203. 移除链表元素


删除所有值等于val的节点

通过cur指针遍历链表中的节点,遍历的过程中,用prev指针记录cur的前一个节点

将prev的next指向cur的next,再删除cur

再把cur往后挪

直到cur指向NULL,遍历结束

如果需要处理头节点,套入循环内会比较麻烦

struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* prev = NULL, *cur = head;
    while (cur)
    {
        if (cur->val == val)
        {
            //删除
            if (cur == head)   //如果头节点需要删除,将cur往后挪,直接删除头节点
            {
                head = cur->next;
                free(cur);
                cur = head;
            }
            else
            {
                prev->next = cur->next;
                free(cur);
                cur = prev->next;
            }
        }
        else
        {
            prev = cur;
            cur = cur->next;
        }
    }
    return head;
}
思路2

遍历原链表,把不是val的节点,尾插到新链表

需要用next指针找下一个节点,否则删除了cur,链表会断开

这时候往新链表尾插,时间复杂度会提高,因为需要找尾

只要创建一个tail指针,这样就不需要找尾

当头节点需要删除时,直接往后遍历,直接删除头节点

struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* cur = head;
    struct ListNode* newhead = NULL, * tail = NULL;

    while (cur)
    {
        if (cur->val == val)
        {
            struct ListNode* del = cur;
            cur = cur->next;
            free(del);
        }
        else
        {
            if (tail == NULL)
            {
                newhead = tail = cur;
            }
            else
            {
                tail->next = cur;
                tail = tail->next;
            }
            cur = cur->next;
        }
    }
    if (tail)
        tail->next = NULL;

    return newhead;
}

最后将一个不为val的节点,尾插到新链表,但是这个节点的next还是指向原链表的下一个节点,如果这个下一个节点不是要删除的,继续往后尾插就ok,如果下一个节点要删除,这个尾插到新链表的节点指向的就不是空

但是不能每次尾插之后,把tail的next置为NULL,这样cur会指向空,不能往后遍历

要在循环结束后,给tail的next置空。

这时候如果head是空,cur就是空,循环就不会进去,newhead和tail就是空。

所以需要判断tail是否为空

876. 链表的中间结点


若链表中的节点数是奇数个,返回中间的那一个

若链表中的节点数是偶数个,返回中间两个的第二个

思路1

先遍历一遍,算长度,再遍历一遍,找到中间的节点

思路2

快慢指针

定义一个慢指针,和快指针,慢指针一次走一步,快指针一次走两步,快指针走到结尾的时候,慢指针走的是快指针的一半,恰好就在链表的中间

奇数个

偶数个

struct ListNode* middleNode(struct ListNode* head) {
    struct ListNode* slow = head, *fast = head;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

面试题 02.02. 返回倒数第 k 个节点


快指针先走k步,然后再同时走,走到fast为NULL结束,slow就是倒数第k个

k=3

fast先走3步,之后同时走

当fast指向NULL时,slow指向倒数第3个

int kthToLast(struct ListNode* head, int k) {
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    
    for (k--)
    {
	    //链表没有k步长,倒数第k个就算是空
	    if (fast == NULL)
	    {
		    return NULL;
	    }
        fast = fast->next;
    }
    while (fast)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return slow->val;
}

k--,走k次

--k,走k-1次

21. 合并两个有序链表


取小的尾插

用两个指针list1和list2分别遍历两个有序链表

为了方便尾插定义两个指针head和tail,一开始都是空

尾插时,将tail给给tail的next,list2还是给给list2的next继续往后遍历

取小的尾插,list2往后走

取小的尾插,list1往后走

tail的next赋给tail

如果有一个list指针指向空,就结束了,剩下一个链表,直接连接到后面

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if (list1 == NULL)
        return list2;
  
    if (list2 == NULL)
        return list1;
  
    struct ListNode* head = NULL, *tail = NULL;
    
    while (list1 && list2)
    {
        if (list1->val < list2->val)
        {
            if (tail == NULL)
            {
                head = tail = list1;
            }
            else
            {
                tail->next = list1;
                tail = tail->next;
            }
            list1 = list1->next;
        }
        else
        {
            if (tail == NULL)
            {
                head = tail = list2;
            }
            else
            {
                tail->next = list2;
                tail = tail->next;
            }
            list2 = list2->next;
        }
    }

    if (list1)
    {
        tail->next = list1;
    }

    if (list2)
    {
        tail->next = list2;
    }

    return head;
}
思路2

加入哨兵位,方便尾插,最后把哨兵位free掉

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if (list1 == NULL)
        return list2;
  
    if (list2 == NULL)
        return list1;
  
    struct ListNode* head = NULL, *tail = NULL;

    head = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
    while (list1 && list2)
    {
        if (list1->val < list2->val)
        {
            tail->next = list1;
            tail = tail->next;
  
            list1 = list1->next;
        }
        else
        {
            tail->next = list2;
            tail = tail->next;
  
            list2 = list2->next;
        }
    }

    if (list1)
    {
        tail->next = list1;
    }
    if (list2)
    {
        tail->next = list2;
    }

    struct ListNode* del = head;
    head = head->next;
    free(del);

    return head;
}

这样尾插的时候可以不用判断tail是否为空

相关推荐
A懿轩A38 分钟前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
半盏茶香43 分钟前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
️南城丶北离2 小时前
[数据结构]图——C++描述
数据结构··最小生成树·最短路径·aov网络·aoe网络
✿ ༺ ོIT技术༻2 小时前
C++11:新特性&右值引用&移动语义
linux·数据结构·c++
字节高级特工2 小时前
【C++】深入剖析默认成员函数3:拷贝构造函数
c语言·c++
计算机学长大白3 小时前
C中设计不允许继承的类的实现方法是什么?
c语言·开发语言
XH华8 小时前
初识C语言之二维数组(下)
c语言·算法
菜鸡中的奋斗鸡→挣扎鸡9 小时前
滑动窗口 + 算法复习
数据结构·算法
axxy200010 小时前
leetcode之hot100---240搜索二维矩阵II(C++)
数据结构·算法
Uu_05kkq11 小时前
【C语言1】C语言常见概念(总结复习篇)——库函数、ASCII码、转义字符
c语言·数据结构·算法