leetcode 常考链表面试题总结(持续更新中)

文章目录

  • 一. 移除链表元素
  • 二. 链表的中间结点
  • 三. 链表中倒数第k个结点
  • 四. 合并两个有序链表
  • 五. 分割链表
  • 六. 反转链表
  • 七. 链表的回文结构
  • 八.相交链表
  • 九.环形链表

一. 移除链表元素

原题链接:

移除链表元素

第一种方法: 直接删除法

这种方法需要特殊考虑头结点的删除情况

首先考虑一般情况 ,删除的不是头结点,定义两个指针 prev , cur 一前一后,逐步删除移动

我们由图中可以看到,如果删除的是头结点,prev->next = cur->next 该步就会出错,因为 prev 此时是空指针

leetcode 常考链表面试题总结(持续更新中)_第1张图片

删除头结点的情况如下:
leetcode 常考链表面试题总结(持续更新中)_第2张图片

给出代码 :

struct ListNode* removeElements(struct ListNode* head, int val)
{
     
    if(head == NULL)
    {
     
        return NULL;
    }
    struct ListNode* prev = NULL,* cur = head;
    while(cur != NULL)
    {
     
        if(cur->val == val)
        {
     
            // 删除头结点的情况
            if(cur == head)
            {
     
                cur = cur->next;
                free(head);
                head = cur;
            }
            // 删除非头结点的情况
            else
            {
     
                prev->next = cur->next;
                free(cur);
                cur = prev->next;
            }
        }
        else
        {
     
            prev = cur;
            cur = cur->next;
        }
    }
    return head;
}

第二种方法 : 创建虚拟头结点

该方法可以将删除头结点和其他结点的情况进行统一

我们创建一个新节点来作为整个链表的头结点,该节点中的值没有意义,只是用该节点来方便我们的操作。如果用DummyNode->next=head; 此时 我们操作DummyNode的话就把第一个结点当做了一个普通节点来操作,此时头结点就可以被删除了。最后返回DummyNode->next就满足条件了。正是由于有个无意义节点作为头结点会统一操作(把头结点当做普通节点)所以实际链表设计过程中都是有个无意义头结点的,遇到第一个结点不好解决的问题,大家可以设一个节点试试。
leetcode 常考链表面试题总结(持续更新中)_第3张图片给出代码 :

typedef struct ListNode ListNode;
// 创建虚拟头结点
ListNode* CreateListNode()
{
     
    ListNode* DummyNode = (ListNode*)malloc(sizeof(ListNode));
    if(DummyNode == NULL)
    {
     
        exit(-1);
    }
    else
    {
     
        DummyNode->next = NULL;
    }
    return DummyNode;
}
struct ListNode* removeElements(struct ListNode* head, int val)
{
     
   ListNode* DummyNode = CreateListNode();
   ListNode* cur = head,* prev = DummyNode;
   DummyNode->next = head;
   head = DummyNode;
   // 执行删除操作
   while(cur != NULL)
   {
     
       if(cur->val == val)
       {
     
           prev->next = cur->next;
           free(cur);
           cur = prev->next;
       }
       else
       {
     
           prev = cur;
           cur = cur->next;
       }
   }
   return head->next;
}

方法三 : 递归

递归思路:

leetcode 常考链表面试题总结(持续更新中)_第4张图片给出代码 :

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

二. 链表的中间结点

原题链接 :

链表的中间结点

第一种方法 :

快慢指针

(1) 链表结点个数为奇数个时 :
leetcode 常考链表面试题总结(持续更新中)_第5张图片
(2) 链表结点个数为偶数个时 :
leetcode 常考链表面试题总结(持续更新中)_第6张图片给出代码 :

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

三. 链表中倒数第k个结点

原题链接 :

链表中倒数第k个结点

方法: 快慢指针

先让快指针走 k 步,接着快指针和慢指针一起走,当快指针走到 NULL 时,慢指针走到倒数第 k 个结点

注意判断一下 k 是否超出链表的长度
leetcode 常考链表面试题总结(持续更新中)_第7张图片
给出代码 :

struct ListNode* getKthFromEnd(struct ListNode* head, int k)
{
     
   if(head == NULL)
   {
     
       return NULL;
   }
   struct ListNode* fast = head,* slow = head;
   // fast指针先走 k 步
   while(k--)
   {
     
   		// 判断 k 是否满足条件
       if(fast != NULL)
       {
     
            fast = fast->next;
       }
       else
       {
     
           return NULL;
       }
   }
   // 快慢指针同时移动
   while(fast != NULL)
   {
     
       fast = fast->next;
       slow = slow->next;
   }
   return slow;
}

四. 合并两个有序链表

原题链接 :

合并两个有序链表

第一种方法 : 尾插迭代法

leetcode 常考链表面试题总结(持续更新中)_第8张图片
给出代码 :

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{
     
   if(l1 == NULL || l2 == NULL)
   {
     
       return (l1 == NULL) ? l2 : l1; 
   }
   ListNode* head = NULL,* tail = NULL;
   head = tail = (ListNode*)malloc(sizeof(ListNode));
   while(l1 != NULL && l2 != NULL)
   {
     
        if(l1->val <= l2->val)
        {
     
            tail->next = l1;
            tail = tail->next;
            l1 = l1->next;
        }
        else
        {
     
            tail->next = l2;
            tail = tail->next;
            l2 = l2->next;
        }
   }
   if(l1 == NULL) 
   {
     
       tail->next = l2;
   }
   else
   {
     
       tail->next = l1;
   }
   return head->next;
}

第二种方法 : 递归

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {
     
    if(l1==NULL)
        return l2;
    if(l2==NULL)
        return l1;
    if(l1->val <= l2->val)
    {
     
        l1->next = mergeTwoLists(l1->next,l2);
        return l1;
    }else
    {
     
        l2->next = mergeTwoLists(l1,l2->next);
        return l2;
    }
}

五. 分割链表

原题链接 :

分割链表

给出代码 :

class Partition {
     
public:
    ListNode* partition(ListNode* pHead, int x)
    {
     
        ListNode* LessHead = (ListNode*)malloc(sizeof(ListNode));
        ListNode* GreaterHead = (ListNode*)malloc(sizeof(ListNode));
        LessHead->next = GreaterHead->next = NULL;
        ListNode* LessHeadTail = LessHead;
        ListNode* GreaterHeadTail = GreaterHead;
        ListNode* cur = pHead;
        while (cur != NULL)
        {
     
            if (cur->val < x)
            {
     
                LessHeadTail->next = cur;
                LessHeadTail = LessHeadTail->next;
            }
            else
            {
     
                GreaterHeadTail->next = cur;
                GreaterHeadTail = GreaterHeadTail->next;
            }
            cur = cur->next;
        }
        GreaterHeadTail->next = NULL;
        LessHeadTail->next = GreaterHead->next;
        ListNode* List = LessHead->next;
        free(LessHead);
        free(GreaterHead);
        return List;
    }
};

六. 反转链表

原题链接 :

反转链表

第一种方法 : 双指针

leetcode 常考链表面试题总结(持续更新中)_第9张图片

给出代码 :

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head)
{
     
    ListNode* n1 = NULL;
    ListNode* n2 = head;
    while(n2 != NULL)
    {
     
        ListNode* n3 = n2->next;
        n2->next = n1;
        n1 = n2;
        n2 = n3;
    }
    return n1;
}

第二种方法 : 头插法

设置一个新节点 newNode , 采取单链表中讲解到的头插的方法 ,即可达到逆序链表的目的

给出代码 :

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head)
{
     
	// 设置新节点
    ListNode* newNode = NULL,* cur = head;
    // 进行头插操作,因为每次改变了 cur->next , 所以用 next 保存 cur 的下一个结点
    while(cur != NULL)
    {
     
        ListNode* next = cur->next;
        cur->next = newNode;
        newNode = cur;
        cur = next;
    }
    return newNode;
}

七. 链表的回文结构

原题链接 :

链表的回文结构

方法 : 先找到链表的中间结点 , 具体方法可参考第二题 , 接着逆序从中间结点起往后的部分(逆序操作可看第六题),判断逆序后和前半部分结点是否相等

给出代码 :

typedef struct ListNode ListNode;
// 逆序链表操作
ListNode* reverseList(ListNode* head)
{
     
    ListNode* n1 = NULL;
    ListNode* n2 = head;
    while(n2 != NULL)
    {
     
        ListNode* n3 = n2->next;
        n2->next = n1;
        n1 = n2;
        n2 = n3;
    }
    return n1;
}
class PalindromeList {
     
public:
    bool chkPalindrome(ListNode* A)
    {
     
        ListNode* fast = A;
        ListNode* slow = A;
        ListNode* prev = NULL;
        // 找到链表中间结点
        while(fast != NULL && fast->next != NULL)
        {
     
            prev = slow;
            slow = slow->next;
            fast = fast->next->next;
        }
        slow = reverseList(slow);
        // 判断前半部分结点和后半部分结点是否相等
        while(A != NULL && slow != NULL)
        {
     
            if(A->val != slow->val)
            {
     
                return false;
            }
            else
            {
     
                A = A->next;
                slow = slow->next;
            }
        }
        return true;
    }
};

八.相交链表

原题链接 :

相交链表

方法 :

(1). 分别遍历两个链表,求出两个链表的长度 la , lb
(2).让指向较长链表的指针移动 abs(lb - la)步
(3).两个指针同时移动,若存在两个指针相等的情况,则返回该结点,若不存在,则返回NULL

给出代码 :

typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
     
    if(headA == NULL || headB == NULL)
    {
     
        return NULL;
    }
    ListNode* curA = headA,* curB = headB;
   int la = 0,lb = 0;
   // 求链表 A 的长度
   while(curA !=NULL)
   {
     
       la++;
       curA = curA->next;
   }
   // 求链表 B 的长度
   while(curB != NULL)
   {
     
       lb++;
       curB = curB->next;
   }
   curA = headA,curB = headB;
   int count = (lb > la) ? (lb - la) : (la - lb);
   int i = 0;
   // 让指向较长链表的指针移动 abs(lb - la)步
   for(i = 0;i < count;i++)
   {
     
       if(lb > la)
       {
     
           curB = curB->next;
       }
       else
       {
     
           curA = curA->next;
       }
   }
   // 两个指针同时移动
   while(curA && curB)
   {
     
       if(curA != curB)
       {
     
            curA = curA->next;
            curB = curB->next;
       }
       else
       {
     
           return curA;
       }
   }
   return NULL;
}

九.环形链表

原题链接 :

环形链表

当一个链表有环时,快慢指针都会陷入环中进行无限次移动,然后变成了追及问题。想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的。当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会追上慢指针。

给出代码 :

typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) 
{
     
    if(head == NULL)
    {
     
        return false;
    }
    ListNode* fast = head,* slow = head;
    while(fast && fast->next)
    {
     
        slow = slow->next;
        fast = fast->next->next;
        if(fast == slow)
        {
     
            return true;
        }
    }
    return false;
}

你可能感兴趣的:(数据结构,链表,数据结构,leetcode,算法,c语言)