数据结构刷题-链表

203

 自解

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

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


struct ListNode* removeElements(struct ListNode* head, int val){
    while(head != NULL && head->val == val)
    {
        //判断表头元素是否为val,是着表头已到下一个
        head = head->next;
    }
    struct ListNode *beginnig = head;
    while(beginnig != NULL)
    { 
        //判断下一个结构体的元素是不是val,是则把下一个删除
        while(beginnig->next != NULL && val == beginnig->next->val)
        {
            
            beginnig->next= beginnig->next->next;
        }
        //指向下一个结构体
        beginnig = beginnig->next;
        
    }
    return head;
}

         我的想法是,首先判断标头元素是否要删除,如果删除则表头下移一个。然后以第一个不删除的结构体为表头,对后面的元素进行遍历。如果相等则对后一个元素删除,不等,着将遍历结构体指向下一个结构体。

官方解

循环法:

struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* dummyHead = malloc(sizeof(struct ListNode));
    dummyHead->next = head;
    struct ListNode* temp = dummyHead;
    while (temp->next != NULL) {
        if (temp->next->val == val) {
            temp->next = temp->next->next;
        } else {
            temp = temp->next;
        }
    }
    return dummyHead->next;
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/remove-linked-list-elements/solution/yi-chu-lian-biao-yuan-su-by-leetcode-sol-654m/
来源:力扣(LeetCode)

        时间复杂度O(n):需要便利整个链表。

        空间复杂度O(1)

 迭代法

struct ListNode* removeElements(struct ListNode* head, int val) {
    if (head == NULL) {
        return head;
    }
    head->next = removeElements(head->next, val);
    return head->val == val ? head->next : head;
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/remove-linked-list-elements/solution/yi-chu-lian-biao-yuan-su-by-leetcode-sol-654m/
来源:力扣(LeetCode)

         递归法比较难理解,以最后一层递归为例,head2 = removeElements(head->next, val)一定等于NULL,在return时,只需要考虑val想不想等,相等则删除掉head2,返回NULL,不相等则返回head2,这时,head1->next == head2, 然后判读head1的val相等吗,相等则删除head1,返回head2,不相等则返回head1。这个递归的思想,就是返回的永远是最新的一个不相等的节点,然后对上一级递归节点进行质检,若相等,则删除,不等则返回自身。

        时间复杂度:O(n)遍历整个链表。

        空间复杂度:O(n),递归需要调用栈。

 总结

        这个问题其实十分简单,只是自己把头结点和非头结点给割裂开了,自己其实知道哑结点,没有元素,只代表链表头部,但是自己不敢去运用这个东西,下次记得改正。

        其次,就是把问题搞复杂了,现在仔细想想,当前节点的下一个节点的val无非有两种情况:

        1. next->val == val 那么下一个节点应该被删除,那么就有temp->next = temp->next->next,这样就把相等的元素删除了,当next为最后一个元素时,就有temp->next = NULL.

        2. 无非相等和不相等,不相等则就是else的情况,两种情况在一个while循环里面。每次只能执行一个,当下一个元素不删除时,那么当前节点就要先后移动一个了,即temp = temp->next。若next是最后一个节点,那么temp->next = NULL。这时候只需要设置循环条件为temp->next != NULL就行,这样最后一个元素就会退出。

        头结点为空时,就要哑结点的next = NULL同样可以退出循环。

        下次别想那么复杂,把两种情况分成太多种了。

237

请编写一个函数,用于 删除单链表中某个特定节点 。在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问 要被删除的节点 。

题目数据保证需要删除的节点 不是末尾节点 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/delete-node-in-a-linked-list

自解 

         说来惭愧,这道题一时间没有想出来怎么写,只能说被题目给唬住了,我们只能访问我们要删除的节点,无法访问头结点,按照固定的思维,肯定就想,我要删除这个节点,那么我就要找到我这个节点之前的节点,将之前的节点的next给改了,但是这题里面我又找不到之前的节点,那我应该怎么办啊?无从下手

官方解

         看了官方的解释之后,有了一点眉目,简单两行代码就能实现,确实傻了。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
void deleteNode(struct ListNode* node) {
    node->val = node->next->val;
    node->next = node->next->next;
    
}

        这只需要两步,第一步,把要删除的节点的数据替换成下一个节点的数据,然后把要删除的节点的next换成下下个节点。

总结

         挺有意思的,好一个借刀杀人,要删除这个,我直接把我换成下一个,然后把下一个给删了。既然不能先删除自己,那就把自己整容成儿子,再假装自己就是儿子来养活孙,神理解。把自己伪装成别人,然后把别人干掉,前一个节点,和后面的节点都不知道这件事,细思极恐!

 61​​​​​​​  

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

示例 1:

自解 

         这个题目,我一共有三个思路,每一个思路都是在之前的基础上加以改进的

//当K很大的时候直接完蛋
 struct ListNode* rotateRight(struct ListNode* head, int k){
     if(head == NULL || head->next == NULL)
     {
         return head;
     }
     else
     {
     struct ListNode* dummyNode = (struct ListNode*) malloc(sizeof(struct ListNode));
     for(int i = 0; i<k ; i++)
     {
         dummyNode->next = head;
        while(dummyNode->next->next->next != NULL)
         {
             dummyNode->next = dummyNode->next->next;
         }
         dummyNode->next->next->next = head;
         head = dummyNode->next->next;
         dummyNode->next->next = NULL;
     }
     return head;
     }
}

        这是第一种思路,当头结点为空或者只有一个头结点即head->next为空时,就直接返回头结点即可,通过用||或表达式,当判断第一个表达式为真时,会直接结束判断,不会判断第二个式子。

        然后创建一个哑结点,哑结点的next指向head,然后通过哑结点找到倒数第二个节点,这个节点的下一个节点,就是右移一次的头结点了,这个节点就成了尾节点。这时的head还是上一次的头结点,将他设置成最后一个节点的下一个节点,然后将head设置成最后一个节点,再通过哑结点修改倒数第二个节点为右移后的最后一个节点。就完成了一次移动。循环移动k次即可。

        但是这种解法有个很大的缺陷,当K很大的时候,你得移动很多次,一个链表一共只有3个元素,要你移动20000次,很多无用功。因此,为了改善这一点,可以先求出链表长度,然后用K对长度取余,因为移动一个链表长度就还原了。所以有了一下的改进代码:

// 要改进,用K对链表长度取余
struct ListNode* rotateRight(struct ListNode* head, int k){
    if(head == NULL || head->next == NULL)
    {
        return head;
    }
    else
    {
    struct ListNode* dummyNode = (struct ListNode*) malloc(sizeof(struct ListNode));
        dummyNode->next = head;
        int count = 1;
        while(head->next != NULL)
        {
            head = head->next;
            count++;
        }
        head = dummyNode->next;
        for(int i = 0; i< k%count ; i++)
        {
            dummyNode->next = head;
            while(dummyNode->next->next->next != NULL)
            {
             dummyNode->next = dummyNode->next->next;
            }
            dummyNode->next->next->next = head;
            head = dummyNode->next->next;
            dummyNode->next->next = NULL;
        }
        return head;
    }
    
}

        与思路1差不多,只是多了一个链表遍历求得链表长度,然后取余,再按照同样的步骤依次挪动就行。

        但是到了这里,发现任然可以继续改进,既然我已经知道了链表的长度了,K取余就能知道要移动多少步,那我可以用长度-移动的步数,就能得到第几个元素是移动后的最后一个元素了。不用一次一次的去移动。

        所以有了思路3,先通过移动head指正遍历链表,得到链表长度,并且最后head会指向最后一个元素,设置head->next = dummyNode->next就能将最后一个节点与当前的头结点连接。然后将head设置成当前的头结点,通过循环找到移动后的最后一个节点,先将最后一个节点的下一个节点,即移动后的头结点,设置成dummyNode->next。然后将其next设置成NULL,再返回 dummyNode->next就是移动后的头结点了。

struct ListNode* rotateRight(struct ListNode* head, int k){
    struct ListNode* dummyNode = (struct ListNode*) malloc(sizeof(struct ListNode));
        dummyNode->next = head;
    if(head == NULL || head->next == NULL)
    {
        return head;
    }
    else
    {
    
        int count = 1;
        while(head->next != NULL)
        {
            head = head->next;
            count++;
        }
        head->next = dummyNode->next;
        head = dummyNode->next;
        for(int i = 1; i<count - k%count ; i++)
        {
            head = head->next;
        }
        dummyNode->next = head->next;
        head->next = NULL
        return dummyNode->next;
    }
    

    
}

总结

        实际上就是考虑到一个移动链表长度还原,然后从指定位置断开就好了,只不过自己一开始就没想到,走了不少弯路而已。 

猜你喜欢

转载自blog.csdn.net/MLuhuihui/article/details/121041581