leetcode 常考链表面试题总结

一. 移除链表元素

原题链接:

移除链表元素

第一种方法: 直接删除法

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

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

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

删除头结点的情况如下:

给出代码 :

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就满足条件了。正是由于有个无意义节点作为头结点会统一操作(把头结点当做普通节点)所以实际链表设计过程中都是有个无意义头结点的,遇到第一个结点不好解决的问题,大家可以设一个节点试试。
给出代码 :

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;
}

方法三 : 递归

递归思路:

给出代码 :

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) 链表结点个数为奇数个时 :

(2) 链表结点个数为偶数个时 :
给出代码 :

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 是否超出链表的长度

给出代码 :

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;
}

四. 合并两个有序链表

原题链接 :

合并两个有序链表

第一种方法 : 尾插迭代法


给出代码 :

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;
    }
};

六. 反转链表

原题链接 :

反转链表

第一种方法 : 双指针

给出代码 :

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;
}

十.环形链表二

原题链接 :

环形链表二

其中【*】为快慢指针首次相遇点,入环前距离为【D】,慢指针入环后走过的距离为【S1】,环剩下距离为【S2】

首次相遇

1.慢指针 S = D + S1

2.快指针 F = D + n(S1 + S2) + S1 其中n>=1,快指针起码走了一圈以上才可能相遇

3.又因为 F = 2S 慢指针走一步,快指针走两步

4.代入1,2 可得 2(D + S1) = D + n(S1 + S2) + S1

各种移项可得 D = (n-1)S1 + nS2 = (n-1)(S1 + S2) + S2

5.其中 n为快指针绕的圈数

n=1 D = S2

n=2 D = 一圈 + S2

n=3 D = 两圈 + S2

所以其实我们并不关心绕了多少圈,就知道 n圈+S2就是入环点了

6.人为构造碰撞机会,让快指针重新出发(但这次一次走一步),只要碰撞了,就是入环位置了

给出代码 :

typedef struct ListNode ListNode;
ListNode* FindMeet(ListNode* head)
{
    ListNode* slow = head,* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(fast == slow)
        {
            return fast;
        }
    }
    return NULL;
}
struct ListNode *detectCycle(struct ListNode *head) 
{
    if(head == NULL)
    {
        return NULL;
    }
    ListNode* meet = FindMeet(head);
    if(meet == NULL)
    {
        return NULL;
    }
    while(head != meet)
    {
        head = head->next;
        meet = meet->next;
    }
    return meet;
}
  • 19
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值