链表面试题——下

11.判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算时间复杂度和空间复杂度

①判断链表是否带环

思路:还是利用快慢指针法,快指针一次走两步,慢指针一次走一步。如果有环那么两者最后必然相遇,反之便是不带环。

思考:如果快指针一次走三步、四步或者k步(k>2),慢指针一次走一步,在带环的情况下他两最后能相遇吗?
这里写图片描述

当慢指针刚进入环时,快指针已经走到图中位置,现在他们之间的路程是X,我们设快指针速度为Vf,慢指针速度为Vs,从慢指针进入环到两者相遇,要经过时间T。
T = X / (Vf - Vs)
我们知道这里的X 和 T都是整数,当Vf - Vs = 1时这个式子是恒成立的;
如果Vf - Vs = 2时,只有X为2的倍数时才成立
如果Vf - Vs = 3时,只有X为3的倍数时才成立
如果Vf - Vs = k时,只有X为K的倍数时才成立

我们知道X是不确定的,是一个随机正整数,只有Vf - Vs = 1的条件下才能确保快慢指针最后会在环内相遇。

SListNode* HasCircle(SListNode* list)  //判断链表是否带环
{
    /*时间复杂度O(n)*/
    assert(list != NULL);

    SListNode* pFast = list;
    SListNode* pSlow = list;

    while (pFast != NULL && pFast->_next != NULL)
    {
        pFast = pFast->_next->_next;
        pSlow = pSlow->_next;

        if (pFast == pSlow)
        {
            return pFast;
        }
    }
    return NULL;
}

②求环的长度

思路:上一个程序会返回快慢指针第一次在环内相遇的节点位置,这个节点是 pMeetNode ,我们把这个节点记下来。
让一个指针cur从这个节点开始走,绕环一圈,它每走一步计数器count++,当cur再次走到pMeetNode这里时,count的值即为环的长度。

size_t GetCircleLenth(SListNode* pMeetNode) //判断环的长度
{
    /*时间复杂度O(n)*/
    assert(pMeetNode != NULL);  //pMeetNode是 pFast和pSlow 第一次相遇的节点

    size_t count = 1;
    SListNode* cur = pMeetNode->_next;

    while (cur != pMeetNode)
    {
        cur = cur->_next;
        count++;
    }
    return count;
}

③找环的入口
这里写图片描述

思路:这题有一个数学结论
在快慢指针第一次相遇的地点放一个指针pMeetNode,在头结点处放一个指针cur,然后两者同时走(都是每次走一步),两个指针最后会在环的入口处相遇。

如图,我们设链表头结点到环的入口距离为X
环的入口到快慢指针第一次相遇的节点pMeetNode的距离为D
环的周长为Y

我们先求出第一问中,当快慢指针第一次在环内相遇时,两者走过的路程。
快指针路程 F=X+nY+D F = X + n Y + D ; //n是两指针相遇时,快指针绕环走过的圈数
慢指针路程 S=X+D S = X + D
因为前者速度是后者两倍
所以: F=2×S F = 2 × S
即: X+nY+D=2×(X+D) X + n Y + D = 2 × ( X + D )
则: nY=X+D n Y = X + D
那: nYD=X n Y − D = X
(n1)Y+YD=x ( n − 1 ) Y + Y − D = x
最后得出的结论是nY - D = X。我们分析分析这个式子:
nY - D 代表的是 pMeetNode到环入口的距离
X代表的是头结点到环入口的距离。
这个式子最后推出这两者是相等的。
所以上面那个解题结论就是这么推到过来的。

SListNode* GetEntranceNode(SListNode* list, SListNode* pMeetNode) //找出环的入口
{
    /* 指针 pMeetNode, 和表头指针cur
     *两个指针同时往后走,相遇点便是环的入口*/
    assert(list != NULL && pMeetNode != NULL);

    SListNode* cur = list;
    while (cur != pMeetNode)
    {
        cur = cur->_next;
        pMeetNode = pMeetNode->_next;
    }
    return cur;
}

12.判断两个链表是否相交,若相交,求交点。(假设链表不带环)

思路(判断相交):这里两个链表都是不带环的,所有我们可以找这两条链表的尾节点,如果两者的尾节点是同一个节点,那这两条链表就是相交的。

int SListIsCrossNode(SListNode* list1, SListNode* list2) //判断两链表是否相交
{
    assert(list1 != NULL);
    assert(list2 != NULL);

    SListNode* move1 = list1;
    SListNode* move2 = list2;

    while (move1->_next != NULL)
    {
        move1 = move1->_next;
    }

    while (move2->_next != NULL)
    {
        move2 = move2->_next;
    }

    if (move1 == move2)//相交返回1
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

思路(找相交节点):假设两个链表长度一样,我们可以让两者从头结点开始同时往后走,每走一步判断两者是否相等,如果相等那这个相等的节点便是相交点;

但现在这两条链表的长度不一定相等,我们要手动转换成相等的情况来做。
求出这两条链表的长度的差值K,让长的链表先走K步,然后两者再同时走,这样走到两者相等时,就找到了中间节点。

int GetListLenth(SListNode* list) //求链表长度
{
    SListNode* cur = list;
    int lenth = 0;
    while (cur != NULL)
    {
        lenth++;
        cur = cur->_next;
    }
    return lenth;
}

SListNode* MoveToCrossNode(SListNode* LongList, SListNode* ShortList, size_t gap) //移动到相交点
{
    while (gap--)
    {
        LongList = LongList->_next;
    }
    while (LongList != NULL)
    {
        LongList = LongList->_next;
        ShortList = ShortList->_next;

        if (ShortList == LongList)
        {
            return ShortList;
        }
    }
    return NULL;
}

SListNode* GetCrossNode(SListNode* list1, SListNode* list2)
{
    assert(list1 != NULL);
    assert(list2 != NULL);

    int k = GetListLenth(list1) - GetListLenth(list2);
    SListNode* CrossNode = NULL;

    if (k > 0) //list1比list2长
    {
        CrossNode = MoveToCrossNode(list1, list2, abs(k));
    }

    if (k < 0) //list2比list1长
    {
        CrossNode = MoveToCrossNode(list2, list1, abs(k));

    }

    else //一样长
    {
        CrossNode = MoveToCrossNode(list1, list2, abs(k));
    }

    return CrossNode;
}

13.判断两个链表是否相交,若相交,求交点。(假设链表可能带环)【升级版】

思路: 这里分三种情况讨论
(1)都不带环,转换成12题。
(2)一个带环,一个不带环。那一定不相交。
(3)都带环。都带环也分为以下三种情况(利用两个相遇点(M1,M2) 判断是否带环——让M1不动,M2走一圈,如果两个能相遇,那就相交,反之不相交)
①不相交
②环外相交(入口点相同)——从入口点处把链表截断,问题就转换成求两个不带环链表相交点。
③环内相交(入口点不同)

int SListRingIsCrossNode(SListNode* list1, SListNode* list2)
{
    assert(list1 != NULL);
    assert(list2 != NULL);

    SListNode* pMeetNode1 = HasCircle(list1);//HasCircle是上一题创建的函数
    SListNode* pMeetNode2 = HasCircle(list2);

    SListNode* entry1 = NULL;
    SListNode* entry2 = NULL;

    if (pMeetNode1 == NULL && pMeetNode1 == NULL) // 都不带环的情况
    {
        return 0;
    }

    else if ((pMeetNode1 == NULL && pMeetNode2 != NULL) || (pMeetNode1 != NULL && pMeetNode2 == NULL)) //一个带环一个不带环情况
    {
        return -1; //不相交
    }

    else //两个都带环情况
    {
        SListNode* cur = pMeetNode1->_next;

        while (cur != pMeetNode1)
        {
            if (cur == pMeetNode2) //两者相交
            {
                entry1 = GetEntranceNode(list1, pMeetNode1); //GetEntranceNode是上一题创建的函数
                entry2 = GetEntranceNode(list2, pMeetNode2);

                if (entry1 == entry2) //环外相交
                {
                    return 1;
                }
                else //环内相交
                {
                    return 2;
                }
            }
            cur = cur->_next;
        }
        return -1;//不相交
    }
}

SListNode* GetRingCrossNode(SListNode* list1, SListNode* list2)  //获取相交点
{
    assert(list1 != NULL);
    assert(list2 != NULL);
    SListNode* CrossNode = NULL;

    int result = SListRingIsCrossNode(list1, list2);

    if (-1 == result) 
    {
        CrossNode = NULL; 
    }
    else if (0 == result)
    {
        CrossNode = GetCrossNode(list1, list2); //两链表都不带环
    }
    else if (1 == result) //环外相交
    {
        SListNode* entry = GetEntranceNode(list1, HasCircle(list1));
        entry->_next = NULL;

        CrossNode = GetCrossNode(list1, list2);//GetCrossNode是上一题的函数.
    }

    return CrossNode;
}

14.复杂链表的复制。一个链表的每个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表,返回复制后的新链表。

思路:这里我们把复制过程分为三步:
第一步:复制原链表的每一个节点,把复制的新节点插在原节点的后面。
第二步:处理新节点的random,新节点的random就是原节点的random的下一个节点
第三步:分离两个链表
这里写图片描述

typedef struct ComplexNode
{
    DataType _data;
    struct ComplexNode* _next;
    struct ComplexNode* _random; //_random指向这个链表中的一个随机节点或者NULL,
}ComplexNode;

ComplexNode* BuyComplexNode(DataType x)
{
    ComplexNode* newComplexNode = (ComplexNode*)malloc(sizeof(ComplexNode));
    if (newComplexNode != NULL)
    {
        newComplexNode->_data = x;
        newComplexNode->_next = NULL;
        newComplexNode->_random = NULL;
    }
    return newComplexNode;
}

ComplexNode* CopyComplexList(ComplexNode* list)
{
    /*拷贝链表*/
    ComplexNode* cur = list;
    while (cur != NULL)
    {
        ComplexNode* newComplexNode = BuyComplexNode(cur->_data);
        newComplexNode->_next = cur->_next; 
        cur->_next = newComplexNode;

        cur = newComplexNode->_next;
    }

    /*置random*/
    cur = list;
    while (cur != NULL)
    {
        if (cur->_random != NULL)
        {
            cur->_next->_random = cur->_random->_next;
        }
        cur = cur->_next->_next;
    }


    /*拆减链*/
    cur = list;
    ComplexNode* NewHead = cur->_next;

    ComplexNode* next = cur->_next;
    ComplexNode* Nextnext = next->_next; 

    while (cur != NULL)
    {
        cur->_next = Nextnext;
        if (next->_next != NULL)
        {
            next->_next = Nextnext->_next;
        }

        cur = Nextnext;
        if (cur != NULL)
        {
            next = cur->_next;
            Nextnext = next->_next;
        }
    }
    return NewHead;
}

void ComplexListPrint(ComplexNode* com)//打印链表
{
    assert(com != NULL);
    ComplexNode* cur = com;

    while (cur != NULL)
    {
        if (cur->_random == NULL)
            printf("%d:NULL -> ", cur->_data);
        else
            printf("%d:%d -> ", cur->_data, cur->_random->_data);

        cur = cur->_next;
    }
    printf("NULL \n");
}

运行结果:
这里写图片描述


15.求两个已排序单链表中相同的数据。

void UnionSet(SListNode* list1, SListNode* list2)
{
    while (list1 != NULL && list2 != NULL)
    {
        if (list1->_data < list2->_data)
        {
            list1 = list1->_next;
        }
        else if (list1->_data > list2->_data)
        {
            list2 = list2->_next;
        }
        else
        {
            printf("%d ", list1->_data);
            list1 = list1->_next;
            list2 = list2->_next;
        }
    }
}

运行结果:
这里写图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值