数据结构每日亿题(一)

  • Post author:
  • Post category:其他




一.移除链表元素

原题传送门:

力扣

题目:

在这里插入图片描述



1.1不带哨兵位的写法

这里我们的大概思路是用到链表中尾插的一种方法,先定义三个指针:

在这里插入图片描述

首先看cur->val是否和val相等,如果不相等,就拿下来:

在这里插入图片描述

像这样,但是这一步是怎么实现的呢?其实这里就相当于让newhead,tail指向链表的第一个元素

tail = newhead = cur;

像这样:

在这里插入图片描述

这就相当于把第一个节点给拿下来了,因为我们先不看第一个节点后面的内容,就是现在我们抽象一点。

好了,现在我们既然把第一个拿下来了,我们接下来要拿第二个:

首先cur指向的位置应该移到下一个元素上面:

cur = cur->next;

在这里插入图片描述

然后判断是否是val,不是就移下来:

在这里插入图片描述

这一步就是将tail->next = cur,然后tail = tail->next,cur = cur->next。两个指针都指向了下一个元素:

在这里插入图片描述

如果此时cur->val == val的话,cur就把这块空间释放掉,然后指向下一个元素。

在这里插入图片描述

就这样一步一步的走,肯定要放在循环里面,但是循环什么时候结束呢?

在这里插入图片描述

我们发现最后一步其实是这样的,也就是cur遍历结束指向了最后的空指针就停止。

循环结束就真的结束了吗?当然不是我们仔细看上面图:发现5那个元素本来指向的是6,但是现在我们把6去掉了,会不会就造成了5这个节点里的指针变成了一个野指针,所以我们在循环结束后手动置成空:

tail->next = NULL;

大概的流程就是这样了,但是我们还要判断一个特殊情况:

如果给你的链表里的元素全是val,这时候就不会有元素移动下来,因为tail,newhead两个指针刚开始指向的是NULL,但是我们注意刚才的步骤我们要最后把tail->next置成空指针,如果tail本身就是一个空指针就会造成一个空指针解引用的错误,所以此时我们再加一个判断,如果tail是空指针的话就直接返回NULL.

最后的程序代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* cur = head;
    struct ListNode* newhead, *tail = NULL;
    while(cur)
    {
        if(cur->val != val)
        {
            if(tail == NULL)
            {
                tail = newhead = cur;
            }
            else
            {
                tail->next = cur;
                tail = tail->next;
            }
            cur = cur->next;
        }
        else
        {
        	//先将需要释放的节点下一个位置保存下来,否则释放完就找不到下一个节点的位置了
            struct ListNode* next = cur->next;
            free(cur);
            cur = next;
        }
    }
    
    //判断tail是不是空指针,是就返回NULL
    if (tail == NULL)
    {
        return NULL;
    }
	
	//将最后一个节点的末尾设置成空
    tail->next = NULL;

    return newhead;
}



1.2带哨兵位的写法

现在我来在讲一个方法。

首先我们要知道什么叫哨兵位,哨兵位就是我们手动给一个链表加一个头:

在这里插入图片描述

但是方法我们还是用尾插的方法。步骤和之前一样:

在这里插入图片描述

这样子我们返回的结构就不能是newhead,我们要返回的应该是newhead->next。

这是带哨兵位的写法:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* removeElements(struct ListNode* head, int val) {
    //这里我们用malloc新开辟一个节点用来当头
    struct ListNode* newhead = (struct ListNode*)malloc(sizeof(struct ListNode));
    
    //新开辟的节点指向之前的第一个节点
    newhead->next = head;
    struct ListNode* tail = newhead;

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

其实看到这里,会发现代码其实简洁了不少。



二.反转一个链表

原题传送门:

力扣

题目:

在这里插入图片描述

如果这种题放在数组里,说不定能马上就写出来了,但是这个是链表,在链表里你用两个指针在这里遍历可行不通的。

第一题用的是链表的尾插,这里我们再试试头插。

现在我们也定义三个指针。

在这里插入图片描述

既然是反转那最终结果肯定就是5->4->3->2->1,然而我们第一个能拿到的只有第一个节点,为了能达到逆序的效果,我们每新取一个节点,都要放在之前节点的前面

第一步:

在这里插入图片描述

我们直接将第一个节点拿下来,但是会发现此时这个节点直接就指向了NULL,这样第二个节点就找不到了,所以在拿下来之前在定义一个指针next把第二个节点的位置保存下来,然后我们在更新一下newhead的位置,因为newhead是我们新链表的头,往下加一个节点,newhead肯定就要指向那个新节点。newhead更新了,cur,next就可以分别指向它们的下一个位置了:

在这里插入图片描述

像这样的步骤,我们放在循环里,循环结束的标志就是cur指向了末尾:

在这里插入图片描述

把5一下来之后,cur指向的就是NULL,此时就结束循环。

不要看我之前说的那么多,其实就几步:

第一步:把节点拿下来

cur->next = newhead;

第二步:更新头位置

newhead = cur;

第三步:cur回去指向下一个节点

cur = next;

所以这个题目的代码是:

struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* newhead = NULL;
    struct ListNode* cur = head;

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



版权声明:本文为weixin_57418095原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。