[数据结构与算法刷题]移除链表元素&相交链表

74 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第24天,点击查看活动详情

前言

        数据结构与算法刷题专栏第二篇,本文是基于C语言实现的。

        本文就来分享一波作者对单链表的一些习题的学习心得与见解。

        笔者水平有限,难免存在纰漏,欢迎指正交流。

Leetcode203——移除链表元素

题目描述

        给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

链接Leetcode203

示例

示例 1:

输入: head = [1,2,6,3,4,5,6], val = 6 输出: [1,2,3,4,5]

示例 2 :

输入: head = [ ], val = 1 输出: [ ]

示例 3:

输入: head = [7,7,7,7], val = 7 输出: [ ]

提示:

  • 列表中的节点数目在范围 [0, 104] 内
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

核心代码模式

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 struct ListNode* removeElements(struct ListNode* head, int val)
 {
 
 }

思路分析与代码实现

1.前后指针原地修改法

        要删掉值为val的结点,那我们就遍历链表,遇到符合条件的结点就删掉。毕竟是要删掉结点,那就设置两个指针prev和cur一前一后。cur不为NULL就一直找下去,找到要删除结点时,要注意一下是不是头结点,要是头结点的话就就是头删了,先把cur下一个结点地址给头指针head,释放掉cur位置的结点,再把head值给cur让它指向下一个结点;若不是头结点,那就先把cur下一个结点地址给prev的next指针,让prev指向cur下一个结点,释放掉cur位置的结点,再把prev下一个结点的地址给cur,让它指向下一个结点。没找到要删除的结点的话就继续找下去。

代码实现

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* prev = head;
    struct ListNode* cur = head;

    while(cur)
    {

        if(cur->val == val)
        {
            if(cur == head)
            {
                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.新链表尾插法

        可不可以新建一个链表,定义新的头指针为newHead,然后就把值不等于val的结点放到新链表去,等于val的结点我们把它释放掉,所以我们要一个指针来遍历链表,定义cur指针。这里把值不等于val的结点放到新链表用的是尾插,那好了,尾插要是还要来个指针一个一个去找前一结点可太麻烦了,不妨定义一个尾指针tail用来指向新链表的尾部,这样尾插就方便多了。要注意有个坑,尾插到最后要记得把新链表最后一个结点的next指针给置为NULL。

\

代码实现

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

    while(cur)
    {
        if(cur->val != val)
        {

            if(tail == NULL)
            {
                newHead = cur;
                tail = cur;
            }
            else
            {

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

    return newHead;
}

试试带头链表

        我们可以试试看用带头链表,头指的就是哨兵结点,不是用来存放有效数值的,而是用作辅助结点的。用哨兵结点在这有什么用呢?你想啊,之前不用它时是不是还要判断一下tail是不是NULL,但是用哨兵结点后tail至少是指向哨兵结点的,不会为NULL,是不是就省了点事?这里的哨兵结点我们命名为guard。

代码实现

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* cur = head;
    struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* tail = guard;

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

Leetcode160——相交链表

题目描述

        给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

        图示两个链表在节点 c1 开始相交:

        题目数据保证整个链式结构中不存在环。

注意:函数返回结果后,链表必须 保持其原始结构

提示:

  • listA 中节点数目为 m
  • listB 中节点数目为 n
  • 1 <= m, n <= 3 * 104
  • 1 <= Node.val <= 105
  • 0 <= skipA <= m
  • 0 <= skipB <= n

如果 listA 和 listB 没有交点,intersectVal 为 0

如果 listA 和 listB 有交点,intersectVal == listA[skipA] == listB[skipB]

 

进阶

        你能否设计一个时间复杂度 O(m + n) 、仅用 O(1) 内存的解决方案?

链接Leetcode160

核心代码模式

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    
}

思路分析与代码实现(C语言)

        这里不考虑暴力求解。如果两链表相交就一定会有公共交点,不过不能通过比较结点值来判断相交,因为结点值相同并不意味着相交,相交是指向同一结点,而结点值相同可以是两个位置完全不同的结点。比结点地址才能判断是不是公共结点,不过怎么比呢?是不是用两个指针分别指向两个链表,然后一个一个对应比较地址呢?在这里是行不通的,为什么?因为两链表的长度不一定相同,这样比会发生错位。

        我们先看看如何判断相交,你想啊,两链表要是相交的话,链表的尾结点是不是一定是同一个?是吧,要是不相交的话就不是同一个尾结点。只要判断尾结点是不是同一个结点就可以判断出两链表是否相交。

        不过题目要我们求出来的是相交的起始结点,这怎么求呢?前面说的比结点地址的方法有可能会有错位是吧,那可不可以消除了错位问题再一个一个比对呢?我们这里使用快慢指针,快指针指向长的链表,让快指针先走差距步,差距步就是两链表长度的差值,然后再让快慢指针同时走,每次走一步,比对两链表的结点地址是否相同,若是相同了就说明找到相交的起始结点了。

        要注意链表为空的可能性,可以在一开始判断一下。链表长度就用指针遍历链表计数器计数来求,顺便还把尾结点找到了,顺便比一下尾结点地址是否相同,要是不同直接返回NULL,对应链表不相交的情况。

代码实现

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
     if(headA == NULL && headB == NULL)
         return NULL;

    struct ListNode* tailA = headA;
    struct ListNode* tailB = headB;

    int cntA = 1;
    int cntB = 1;

    while(tailA->next)
    {
        tailA = tailA->next;
        cntA++;
    }

    while(tailB->next)
    {
        tailB = tailB->next;
        cntB++;
    }

    if(tailA != tailB)
        return NULL;

    struct ListNode* longList = headA;
    struct ListNode* shortList = headB;

    if(cntA < cntB)
    {
        longList = headB;
        shortList = headA;
    }

    int gap = abs(cntA - cntB);

    while(gap--)
    {
        longList = longList->next;
    }

    while(longList != shortList)
    {
        longList = longList->next;
        shortList = shortList->next;
    }

    return longList;

}

以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~

src=http___c-ssl.duitang.com_uploads_item_201708_07_20170807082850_kGsQF.thumb.400_0.gif&refer=http___c-ssl.duitang.gif