C语言的单链表面试题----进阶

题目

  1. 判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算每个算法的时间复杂度&空间复杂度。
  2. 判断两个链表是否相交,若相交,求交点。(假设链表不带环)
  3. 判断两个链表是否相交,若相交,求交点。(假设链表可能带环)【升级版】

求带环链表环长

(无环返回 -1)

思路

  1. 快慢指针法,快指针一次走两步,慢指针一次走一步,如果链表带环,则从慢指针到达入口点开始,由于快慢指针都在环上移动,每次移动快指针都追慢指针一步,慢指针走一圈之内,快指针一定追上慢指针。
  2. 相遇之后,一次追一步,再次相遇刚好追一圈——环的长度。

写代码

  1. 创建快慢指针等于头指针,快指针及其下一节点不为空(忽略慢指针),进入循环,快指针走两步,慢指针走一步,如果快~等于慢~,break 结束循环。
  2. 判断,如果快~不等于慢~或快指针下一节点为NULL(如果链表只有一个节点,快慢指针都没走,快~等于慢~),无环,返回NULL。
  3. 此时已确定链表带环,创建计数器 count = 0,循环,计数,快慢指针再次相遇为止。
  4. 返回 count。

代码

int SizeOfRing(ListNode* pList)
{
    ListNode* fast = pList;
    ListNode* slow = pList;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)
            break;
    }
    if (fast != slow || fast->next == NULL)
        return -1;
    else
    {
        int count = 0;
        do{
            fast = fast->next->next;
            slow = slow->next;
            count++;
        } while (fast != slow);
        return count;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

求环入口点

(无环返回 NULL)

思路一(快慢指针法)

  1. 同求环长,快慢指针相遇停止。
  2. 此时,将路程分为三段,一是 T——尾长(非环长度),二是 S——慢指针入环到被追上,三是 C——快指针追慢指针多跑的圈。那么,慢指针走的路程为 T+S,快指针走的路程为 T+S+C,可得 2(T+S)=T+S+C,T=C-S,此时,一个指针从起点出发走 T步,一个指针从快慢指针相遇点出发走 C-S 步,此时两指针刚好都在入口点。
  3. 两指针一个从头结点出发,一个从快慢指针相遇点出发,一步一步走,相遇的节点即为入口点(走的步数即为尾长 T(C-S))。

写代码

  1. 判断是否带环并处理不带环情况,同求环长。
  2. 两个指针,一个从头结点出发,一个从快慢指针相遇点出发,循环各走一步,相遇为止,返回任意一个指针。

代码

ListNode* IntranceOfRing(ListNode* pList)
{
    ListNode* fast = pList;
    ListNode* slow = pList;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)
            break;
    }
    if (fast != slow || fast->next == NULL)
        return NULL;
    while (pList != slow)
    {
        pList = pList->next;
        slow = slow->next;
    }
    return pList;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

思路二(转化无环相交链表法)

(断开完了记得接上) 
1. 求快慢指针相遇点。 
2. 保存相遇点下一节点 next,相遇点下一节点置空(断环)。 
3. 用原头结点和 next 调用求无环链表相交节点函数(intersect1(),见下题),保存为 ret。 
4. 接环(相遇点下一节点赋 next),返回 ret。

相交链表求交点(无环)intersect1()

(无交点返回 NULL)

写代码

  1. 若无环链表相交,则尾节点相同(长这样 >— ),新建指针遍历两链表,并统计步数(求交点用)。
  2. 尾节点不同,返回 NULL。
  3. 比较两链表长度,快指针从长链表头结点开始,走比短链表长的节点数。
  4. 慢指针从另一链表头结点开始,与快指针一起一步一步走,指针相同时停止。返回任意一个指针。

代码

ListNode* intersect1(ListNode* pList1, ListNode* pList2)
{
    int count1 = 0, count2 = 0;
    ListNode* p1 = pList1, *p2 = pList2;
    while (p1->next)
    {
        count1++;
        p1 = p1->next;
    }
    while (p2->next)
    {
        count2++;
        p2 = p2->next;
    }
    if (p1 != p2)return NULL;
    if (count1 >= count2)
    {
        count1 -= count2;
        while (count1--)
            pList1 = pList1->next;
        while (pList1 != pList2)
        {
            pList1 = pList1->next;
            pList2 = pList2->next;
        }
        return pList1;
    }
    else
    {
        count2 -= count1;
        while (count2--)
            pList2 = pList2->next;
        while (pList1 != pList2)
        {
            pList1 = pList1->next;
            pList2 = pList2->next;
        }
        return pList1;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

相交链表求交点(可能带环)intersect2

(无交点返回 NULL, 环上相交返回第一个链表的环入口点)

思路

  1. 先列举情况 
    这里写图片描述
  2. 求交点之前,需要先判断是否带环,用环入口点函数判断,并保存入口点。
  3. 两个入口点都为空,是 1、2 两种情况,直接使用无环链表求交点函数。
  4. 如果只有一个为空,一定是第三种情况,不相交,返回 NULL。
  5. 剩下便是两入口点都不为空,且入口点相同,则是 3、4 两种情况,不必做区分,统计从两头结点到入口点的长度,然后(同无环链表求交点3、4) 比较两链表到入口点长度,快指针从长链表头结点开始,走比短链表长的节点数。慢指针从另一链表头结点开始,与快指针一起一步一步走,指针相同时停止。返回任意一个指针。
  6. 区分最后两种情况通过一个指针从其中一个入口点出发,走一圈之内,如果与另一入口点相遇,是第 7 种情况,返回第一个链表的入口点(也可以是第二个),否则是第 6 种情况,返回 NULL。

代码

ListNode* intersect2(ListNode* pList1, ListNode* pList2)
{
    ListNode* intr1 = IntranceOfRing(pList1);
    ListNode* intr2 = IntranceOfRing(pList2);
    if (intr1 == NULL && intr2 == NULL)
        return intersect1(pList1, pList2);
    else if (intr1 == NULL || intr2 == NULL)
        return NULL;
    else if (intr1 == intr2)
    {
        int count1 = 0, count2 = 0;
        ListNode* p1 = pList1, *p2 = pList2;
        while (p1->next != intr1)
        {
            count1++;
            p1 = p1->next;
        }
        while (p2->next != intr1)
        {
            count2++;
            p2 = p2->next;
        }
        if (count1 >= count2)
        {
            count1 -= count2;
            while (count1--)
                pList1 = pList1->next;
            while (pList1 != pList2)
            {
                pList1 = pList1->next;
                pList2 = pList2->next;
            }
            return pList1;
        }
        else
        {
            count2 -= count1;
            while (count2--)
                pList2 = pList2->next;
            while (pList1 != pList2)
            {
                pList1 = pList1->next;
                pList2 = pList2->next;
            }
            return pList1;
        }
    }
    else
    {
        ListNode* pList = intr2->next;
        while (pList != intr2 && pList != intr1)
        {
            pList = pList->next;
        }
        return pList == intr1 ? intr1 : NULL;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值