【数据结构-6】算法美学之单链表(1)

文章目录

分享几道LeetCode的关于单链表的OJ题,是笔试中的高频考题哦。

一、移除链表元素

移除链表元素

先看题目描述:

思路一:遍历链表找值为val的节点,执行删除指定位置的节点的操作。由于用到了嵌套的while循环,所以时间复杂度为O(n²)。

c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    ListNode* pcur = head;
    while(pcur)
    {
        if(pcur->val == val)
        {   
            //要删除的是头节点
            if(pcur == head)
            {
                head = head->next;
                free(pcur);
                pcur = head;
            }
            else
            {
                //要删除的不是头节点,从头找前驱节点
                ListNode* prev = head;
                while(prev->next != pcur)
                {
                    prev = prev->next;
                }
                prev->next = pcur->next;
                free(pcur);
                pcur = prev->next;
                
            }
        
        }
        else
        {
            pcur = pcur->next;
        }
        
    }
  
    return head;
}

思路二:创建新链表,遍历原链表,将值不为val的节点拿下来尾插。时间复杂度为O(n)。

c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    ListNode* newHead,*newTail;
    newHead = newTail = NULL;

    ListNode* pcur = head;
    while(pcur)
    {
        if(pcur->val != val)
        {
            if(newHead == NULL)
            {
                newHead = newTail = pcur;
            }
            else
            {
                newTail->next = pcur;
                newTail = newTail->next;
            }
        }
        pcur = pcur->next;
    }

    if(newTail)
    {
        newTail->next = NULL;
    }
   
   return newHead;
}

二、反转链表

反转链表

题目描述:

思路一:创建新链表,遍历原链表,将原链表中的每个节点头插到新链表中。

c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    ListNode* newHead,*newTail;
    newHead = newTail = NULL;

    ListNode* pcur = head;
    while(pcur)
    {
        ListNode* next = pcur->next;
        if(newHead == NULL)
        {
            newHead = newTail = pcur;
            newTail->next = NULL;
        }
        else
        {
            pcur->next = newHead;
            newHead = pcur;
        }
        pcur = next;
    }
    return newHead;
}

以上思路的代码优化,(头插法天然就支持空的 newHead 场景):

c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    ListNode* newHead = NULL;
    ListNode* pcur = head;
    while(pcur)
    {
        ListNode* next = pcur->next;
        pcur->next = newHead;
        newHead = pcur;
        pcur = next;
    }
    return newHead;
}

思路二:创建三个指针。

c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
   if(head == NULL)
   {
    return head;
   }
   ListNode* n1, *n2, *n3;
   n1 = NULL, n2 = head, n3 = n2->next;
   while(n2)
   {
    n2->next = n1;
    n1 = n2;
    n2 = n3;
    if(n3)
       n3 = n3->next;
   }
   return n1;
}

三、链表的中间结点

链表的中间节点

题目描述:

思路一:求链表的总长度,总长度除2求中间节点的位置,返回中间位置的节点。

c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
    ListNode* pcur = head;
    int count = 0,step = 0;
    while(pcur)
    {
        count++;
        pcur = pcur->next;

    }
    pcur = head;
    step = count/2;
    while(step--)
    {
        pcur = pcur->next;

    }
    return pcur;
}

思路二:快慢指针。进入循环后,慢指针每次走一步,快指针每次走两步。

在奇数个节点中,fast->next为空时,slow走到了中间节点的位置;在偶数个节点中,fast为空时,slow走到了中间节点的位置。

让我们把思路转换为代码,注意这里循环结束的条件是fast 和fast->next同时不为NULL,因为只要有一个为NULL,就要结束循环,二者是 || 的关系,而要进入循环就得是(fast || fast->next)的取反操作,结果为(fast && fast->next):

另外,这里fast和fast->next不能调换位置,也就是在while条件中,fast->next不能写在了fast前面。因为如果fast走两步后走到了NULL,再上来先判断fast->next,属于对NULL指针解引用。

c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
    ListNode* slow = head;
    ListNode* fast = head;

    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }

    return slow;
}

四、合并两个有序链表

合并两个有序链表

题目描述:

思路一:将节点放到指定位置之前。将一条链表作为被插入链,遍历另一条链表的每一个节点,比较这条链表当前节点的值和被插入链中节点值的大小,找到被插入链中第一个值比当前节点值大的节点,将这个节点插入到被插入链中这个更大的值的节点的前面。(dummy:占位符,在这里是哨兵位节点)

c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    ListNode* dummy = (ListNode*)malloc(sizeof(ListNode));
    dummy->next = list1;
    ListNode* prev = dummy;
    ListNode* curl2 = list2;

    while(curl2)
    {
        ListNode* nextl2 = curl2->next;

        ListNode* pos = prev->next;
        while(pos && pos->val < curl2->val)
        {
            prev = pos;
            pos = pos->next;
        }

        curl2->next = pos;
        prev->next = curl2;

        curl2 = nextl2;
    }

    ListNode* ans = dummy->next;
    free(dummy);
    dummy = NULL;
    return ans;

}

思路二:创建新的空链表,从头开始比较两个链表中当前位置的节点的大小,将节点值更小的节点尾插进新的链表中,往后遍历链表,直到其中一条链表走到NULL,将另一条链表中剩下的所有节点直接一次性尾插进新链表中。时间复杂度为O(n)

c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
 {
    if(list1 == NULL)
    {
        return list2;
    }
    if(list2 == NULL)
    {
        return list1;
    }

    ListNode* newHead,*newTail;
    newHead = newTail = NULL;
    ListNode* l1 = list1;
    ListNode* l2 = list2;

    while(l1 && l2)
    {
        if(l1->val < l2->val)
        {
            if(newHead == NULL)
            {
                newHead = newTail = l1;
            }
            else
            {
                newTail->next = l1;
                newTail = l1;
            }
            l1 = l1->next;
        }
        else
        {
            if(newHead == NULL)
            {
                newHead = newTail = l2;
            }
            else
            {
                newTail->next = l2;
                newTail = l2;
            }
            l2 = l2->next;
        }
    }

    if(l1)
    {
        newTail ->next = l1;
    }
    if(l2)
    {
        newTail->next = l2;
    }
    return newHead;
  
}

以上代码在while循环里的if-else各自的主体部分,有大量重复代码,让我们来做下优化。创建一个头结点让头指针指向就可以省去if(newHead == NULL)这部分判空了,也就可以省去好几句的重复代码。

但记住,最后需要释放malloc出来的这个头节点。

c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
 {
    if(list1 == NULL)
    {
        return list2;
    }
    if(list2 == NULL)
    {
        return list1;
    }

    ListNode* newHead,*newTail;
    newHead = newTail = (ListNode*)malloc(sizeof(ListNode));
    ListNode* l1 = list1;
    ListNode* l2 = list2;

    while(l1 && l2)
    {
        if(l1->val < l2->val)
        {
              
            newTail->next = l1;
            newTail = l1;
            l1 = l1->next;
        }
        else
        {
            
            newTail->next = l2;
            newTail = l2;
            l2 = l2->next;
        }
    }

    if(l1)
    {
        newTail ->next = l1;
    }
    if(l2)
    {
        newTail->next = l2;
    }
    
    ListNode* retNode = newHead->next;
    free(newHead);
    newHead = NULL;
    return retNode;
}
相关推荐
希望我万事顺遂1 小时前
力扣hot100-滑动窗口最大值
数据结构·算法·leetcode
꧁细听勿语情꧂2 小时前
二叉树余题,排序、直接插入排序、希尔排序
c语言·开发语言·数据结构·算法
是main不是漫2 小时前
【双指针|LeetCode1089 复写零】巧用逆向思路,破解原地覆盖陷阱
数据结构·算法·leetcode
专注仿真12 小时前
知识问答模型用到的算法集详解及代码实例
python·算法·代码·大模型训练·控制算法
罗西的思考13 小时前
【轻量化复刻大模型 / KIMI K3】AttnRes 模块交互式演示
人工智能·算法·机器学习
好悬给我拽开线16 小时前
ZILOT 论文解析
算法
饮水桶_T17 小时前
JAVA IO流笔记
开发语言·数据结构·算法
星夜离尘5618 小时前
MATLAB 离散数据分箱:半开区间、末端点与计数、加权和、平均
算法·matlab
Pniubi18 小时前
力扣76.最小覆盖子串
算法·leetcode·职场和发展