单链表面试题汇总

之前我们实现过单链表的一些简单接口,而单链表还有一些面试题非常重要,今天来看一下一些常见面试题的实现
(注:本文调用的简单接口实现:http://blog.csdn.net/qq_34021920/article/details/76594389

1.从尾到头打印单链表

void RPrintList(pList plist);//从尾到头打印链表(不改变链表)

从尾到头打印单链表,而不能改变链表本身,可以用递归很好的实现

void RPrintList(pList plist)
{
    if (plist == NULL)
    {
        return;
    }
    else if (plist->next == NULL)
    {
        printf("%d-->", plist->data);
    }
    else
    {
        RPrintList(plist->next);
        printf("%d-->", plist->data);
    }
}

具体测试:

void test1()
{
    pList plist = NULL;
    InitLinkList(&plist);
    PushFront(&plist, 1);
    PushFront(&plist, 2);
    PushFront(&plist, 3);
    PushFront(&plist, 4);
    PushFront(&plist, 5);
    PrintList(plist);
    RPrintList(plist);
}
int main()
{
    test1();
    system("pause");
    return 0;
}

这里写图片描述

2. 删除一个无头单链表的非尾节点(不能遍历链表)

void EraseNotTail(pNode pos);//删除一个无头单链表的非尾节点

首先我们可以先把要删除节点的下一个节点保存下来,然后将该节点和它下一个节点的值进行交换,最后删除掉那个保存的下个节点

void EraseNotTail(pNode pos)
{
    pNode del = NULL;
    assert(pos->next);
    del = pos->next;
    pos->data = del->data;
    pos->next = del->next;
    free(del);
    del = NULL;
}

具体测试:

void test2()
{
    pList plist = NULL;
    pNode pos = NULL;
    InitLinkList(&plist);
    PushFront(&plist, 1);
    PushFront(&plist, 2);
    PushFront(&plist, 3);
    PushFront(&plist, 4);
    PushFront(&plist, 5);
    PrintList(plist);
    pos = Find(plist, 2);
    EraseNotTail(pos);
    PrintList(plist);
}
int main()
{
    test2();
    system("pause");
    return 0;
}

这里写图片描述

3. 在无头单链表的一个非头节点前插入一个节点

void InsertFrontNode(pNode pos, DataType d);//在无头单链表的一个非头结点前插入一个节点

同上一题类似,我们可以在指定位置的后面插入一个新节点,然后交换新节点和指定节点的数据即可

void InsertFrontNode(pNode pos, DataType d)
{
    pNode newNode = BuyNode(d);
    DataType tmp = 0;
    newNode->next = pos->next;
    pos->next = newNode;
    //交换数据
    tmp = pos->data;
    pos->data = newNode->data;
    newNode->data = tmp;
}

具体测试:

void test3()
{
    pList plist = NULL;
    pNode pos = NULL;
    InitLinkList(&plist);
    PushFront(&plist, 1);
    PushFront(&plist, 2);
    PushFront(&plist, 3);
    PushFront(&plist, 4);
    PushFront(&plist, 5);
    PrintList(plist);
    pos = Find(plist, 2);
    InsertFrontNode(pos, 6);
    PrintList(plist);
}
int main()
{
    test3();
    system("pause");
    return 0;
}

这里写图片描述

4. 单链表实现约瑟夫环

void JosephCycle(pList plist, int k);//单链表实现约瑟夫环

约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。

void JosephCycle(pList plist, int k)
{
    pNode cur = plist;
    pNode del = NULL;
    int num = 0;
    while (1)
    {
        num = k;
        //当链表只剩下一个元素时停止循环
        if (cur == cur->next)
        {
            break;
        }
        while (--num)
        {
            cur = cur->next;
        }
        printf("%d ", cur->data);//打印删除的元素
        //进行交换删除(将要删除节点的data和它下一个节点的data进行交换,删除下一个节点)
        del = cur->next;
        cur->data = del->data;
        cur->next = del->next;
        free(del);
    }
    printf("\n剩下人的编号:%d\n", cur->data);
}

具体测试:(在这里我们就用约瑟夫的小故事,给上41个人,每次报数报到3的人出列)

void test4()
{
    pList plist = NULL;
    int i = 0;
    InitLinkList(&plist);
    for (i = 41; i >= 1; i--)
    {
        PushFront(&plist, i);
    }
    Find(plist, 41)->next = plist;
    JosephCycle(plist, 3);
}
int main()
{
    test4();
    system("pause");
    return 0;
}

这里写图片描述

5. 逆置/反转单链表

void ReverseList(pList *pplist);//逆序链表

与第一题从尾到头打印单链表不同的是,逆序链表我们要改变的是链表本身。可以先将链表的第一个节点取下来,保存在一个newHead中,随后依次把剩下的节点取下来连在第一个节点的前面。最后将newHead赋给管理之前链表得到指针pplist

void ReverseList(pList *pplist)
{
    pNode cur = *pplist;
    pNode tmp = NULL;
    pNode newHead = NULL;
    assert(pplist);
    //链表为空或者链表只有一个节点
    if ((*pplist == NULL) || (cur->next == NULL))
    {
        return;
    }
    while (cur)
    {
        tmp = cur;
        cur = cur->next;
        tmp->next = newHead;
        newHead = tmp;
    }
    *pplist = newHead;
}

逆序链表的测试我们在下一个题中一起展示

6. 单链表排序

void BubbleSort(pList *pplist);//排序链表

因为现在暂时还没有接触快速排序,所以在这里只实现冒泡法排序链表

void BubbleSort(pList *pplist)
{
    pNode cur = *pplist;
    pNode tail = NULL;
    assert(pplist);
    //当链表为空或者只有一个节点时
    if ((cur == NULL) || (cur->next == NULL))
    {
        return;
    }
    //总趟数
    while (cur->next != tail)
    {
        //一趟排序
        while (cur->next != tail)
        {
            if ((cur->data) > (cur->next->data))
            {
                DataType tmp = cur->data;
                cur->data = cur->next->data;
                cur->next->data = tmp;
            }
            cur = cur->next;
        }
        tail = cur;
        cur = *pplist;
    }
}

具体测试

void test6()
{
    pList plist = NULL;
    pNode pos = NULL;
    InitLinkList(&plist);
    PushFront(&plist, 7);
    PushFront(&plist, 2);
    PushFront(&plist, 4);
    PushFront(&plist, 3);
    PushFront(&plist, 5);
    PushFront(&plist, 1);
    PushFront(&plist, 6);
    printf("正常:");
    PrintList(plist);
    ReverseList(&plist);
    printf("逆序后:");
    PrintList(plist);
    BubbleSort(&plist);
    printf("排序后:");
    PrintList(plist);
}
int main()
{
    test6();
    system("pause");
    return 0;
}

这里写图片描述

7.合并两个有序链表,合并后依然有序

pList Merge(pList *pplist1, pList *pplist2);//合并两个有序单链表,合并后依然有序
pList DMerge(pList *pplist1, pList *pplist2);//递归实现两条链表合并

在这里我们分别用递归和非递归进行实现,直接上代码:

pList Merge(pList *pplist1, pList *pplist2)
{
    pNode cur1 = *pplist1;
    pNode cur2 = *pplist2;
    pList newHead = NULL;
    pNode tail = NULL;
    assert(pplist1);
    assert(pplist2);
    if ((*pplist1 == NULL) && (*pplist2 == NULL))
    {
        return NULL;
    }
    if (*pplist1 == *pplist2)
    {
        return *pplist1;
    }
    if (*pplist1 == NULL)
    {
        return *pplist2;
    }
    if (*pplist2 == NULL)
    {
        return *pplist1;
    }
    if ((cur1->data) > (cur2->data))
    {
        newHead = cur2;
        cur2 = cur2->next;
    }
    else
    {
        newHead = cur1;
        cur1 = cur1->next;
    }
    newHead->next = NULL;
    tail = newHead;
    while (cur1&&cur2)
    {
        if ((cur1->data) > (cur2->data))
        {
            tail->next = cur2;
            cur2 = cur2->next;
        }
        else
        {
            tail->next = cur1;
            cur1 = cur1->next;
        }
        tail = tail->next;
        tail->next = NULL;
    }
    if (cur1 == NULL)
    {
        tail->next = cur2;
    }
    else if (cur2 == NULL)
    {
        tail->next = cur1;
    }
    return newHead;
}
pList DMerge(pList *pplist1, pList *pplist2)//递归实现两条链表合并
{
    pNode cur1 = *pplist1;
    pNode cur2 = *pplist2;
    pList newHead = NULL;
    pNode tail = NULL;
    assert(pplist1);
    assert(pplist2);
    if ((cur1 == NULL) && (cur2 == NULL))
    {
        return NULL;
    }
    if (cur1 == cur2)
    {
        return cur1;
    }
    if (cur1 == NULL)
    {
        return cur2;
    }
    if (cur2 == NULL)
    {
        return cur1;
    }
    if ((cur1->data) > (cur2->data))
    {
        newHead = cur2;
        cur2 = cur2->next;
    }
    else
    {
        newHead = cur1;
        cur1 = cur1->next;
    }
    newHead->next = NULL;
    if (cur1 == NULL || cur2 == NULL);
    {
        tail = newHead;
        tail->next = DMerge(&cur1, &cur2);
    }
    return newHead;
}

具体测试:

void test7()
{
    pList plist1 = NULL;
    InitLinkList(&plist1);
    PushFront(&plist1, 9);
    PushFront(&plist1, 7);
    PushFront(&plist1, 5);
    PushFront(&plist1, 3);
    PushFront(&plist1, 1);

    pList plist2 = NULL;
    InitLinkList(&plist2);
    PushFront(&plist2, 10);
    PushFront(&plist2, 8);
    PushFront(&plist2, 6);
    PushFront(&plist2, 4);
    PushFront(&plist2, 2);
    PushFront(&plist2, 0);

    pList newList1 = NULL;
    newList1 = Merge(&plist1, &plist2);
    PrintList(newList1);
    pList newList2 = NULL;
    newList2 = DMerge(&plist1, &plist2);
    PrintList(newList2);
}

这里写图片描述

8. 查找单链表的中间节点,要求只能遍历一次链表

pNode FindMidNode(pList plist);//查找链表的中间节点(只能遍历一次链表)

查找链表中间节点,我们可以定义两个指针,快指针每次次走两步,慢指针每次走一步,当快指针到达链表尾部时,慢指针所在的位置就是中间节点的位置。

pNode FindMidNode(pList plist)
{
    pNode fast = plist;
    pNode slow = plist;
    if (plist == NULL)
    {
        return NULL;
    }
    while (fast&&fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}

具体测试:

void test8()
{
    pNode pos = NULL;
    pList plist1 = NULL;
    InitLinkList(&plist1);
    PushFront(&plist1, 9);
    PushFront(&plist1, 7);
    PushFront(&plist1, 5);
    PushFront(&plist1, 3);
    PushFront(&plist1, 1);

    pList plist2 = NULL;
    InitLinkList(&plist2);
    PushFront(&plist2, 10);
    PushFront(&plist2, 8);
    PushFront(&plist2, 6);
    PushFront(&plist2, 4);
    PushFront(&plist2, 2);
    PushFront(&plist2, 0);

    pList newList1 = NULL;
    newList1 = Merge(&plist1, &plist2);
    PrintList(newList1);
    pos = FindMidNode(newList1);
    printf("中间节点:%d\n", pos->data);
}
int main()
{
    test8();
    system("pause");
    return 0;
}

这里写图片描述

9. 删除链表的倒数第K个结点

void DelKNode(pList plist, int k);//删除链表的倒数第K个节点

同样的,定义两个指针。一个指针first先走上K 步,然后第二个指针second再和first指针同时走,当first指针到达链表尾部时,second指针所在的位置就为倒数第K个节点的位置,随后我们用之前讲过交换数据的方法删除即可
(注意,因为是用交换删除的方法,如果K=1,则它没有下一个节点。我们可以对这种情况进行单独处理,这时候函数就和尾删一样了,我在这里直接调用尾删的接口)

void DelKNode(pList plist, int k)
{
    pNode first = plist;
    pNode second = plist;
    if (k == 1)
    {
        PopBack(&plist);
        return;
    }
    while (first&&first->next)
    {
        //找到倒数第K个节点
        first = first->next;
        if (--k <= 0)
        {
            second = second->next;
        }
    }
    //交换删除倒数第K个
    if (k <= 0)
    {
        pNode del = second->next;
        second->data = del->data;
        second->next = del->next;
        free(del);
        del = NULL;
    }
}

具体测试:

void test9()
{
    pList plist = NULL;
    InitLinkList(&plist);
    PushFront(&plist, 1);
    PushFront(&plist, 2);
    PushFront(&plist, 3);
    PushFront(&plist, 4);
    PushFront(&plist, 5);
    PushFront(&plist, 6);
    PushFront(&plist, 7);
    PrintList(plist);
    DelKNode(plist, 1);
    PrintList(plist);
    DelKNode(plist, 3);
    PrintList(plist);
}
int main()
{
    test9();
    system("pause");
    return 0;
}

这里写图片描述

10. 判断单链表是否带环?若带环,求环的长度?求环的入口点?

pNode CheckCycle(pList plist);//判断链表是否带环
int GetCircleLength(pNode meet);//求环的长度
pNode GetCircleEntryNode(pNode meet, pList plist);//求环的入口点

(1)判断链表是否带环
还是定义上两个指针,快指针每次走两步,慢指针每次走一步,如果链表带环的话,两个指针必定会相遇,而且相遇点一定在环上。

pNode CheckCycle(pList plist)//判断链表是否带环
{
    pNode fast = plist;
    pNode slow = plist;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)//快指针和慢指针一定会在环上相遇
        {
            return slow;//相遇点
        }
    }
    return NULL;
}

(2)求环的长度
定义一个指针,从相遇点(见链表判环问题)开始走,在定义一个计数器,每走一步计数器家家,当下次到达相遇点时计数器的大小就是环的长度了。

int GetCircleLength(pNode meet)//求环的长度
{
    int count = 0;
    pNode cur = meet;
    do
    {
        cur = cur->next;
        count++;
    } while (cur != meet);
    return count;
}

(3)求环的入口点
在这里我们介绍两种方法
方法一:从开始到环入口点的距离x = 环长度len的整数倍c*len - 从环入口点到相遇点的距离y
定义一个指针从开始出发,另一个指针从相遇点出发,两个指针再次相遇的点就为入口点
详见下图:
这里写图片描述

方法二:定义两个指针,一个先走环的长度步,另外一个在开始走,两个指针相遇的地方就为环的入口点

pNode GetCircleEntryNode(pNode meet, pList plist)//求环的入口点
{
    //方法一:从开始到环入口点的距离x = 环长度len的整数倍c*len - 从环入口点到相遇点的距离y
    //定义一个指针从开始出发,另一个指针从相遇点出发,两个指针再次相遇的点就为入口点
    //pNode cur = plist;
    //while (cur != meet)
    //{
    //  cur = cur->next;
    //  meet = meet->next;
    //}
    //return cur;
    //方法二:定义两个指针,一个先走环的长度步,另外一个在开始走,两个指针相遇的地方就为环的入口点
    pNode first = plist;
    pNode second = plist;
    int num = GetCircleLength(meet);
    while (num--)
    {
        first = first->next;
    }
    while (first != second)
    {
        first = first->next;
        second = second->next;
    }
    return first;

}

具体测试:

void test10()
{
    pList plist = NULL;
    pNode pos = NULL;
    pNode entry = NULL;
    InitLinkList(&plist);
    PushFront(&plist, 1);
    PushFront(&plist, 2);
    PushFront(&plist, 3);
    PushFront(&plist, 4);
    PushFront(&plist, 5);
    PushFront(&plist, 6);
    PushFront(&plist, 7);
    PrintList(plist);
    DelKNode(plist, 1);
    PrintList(plist);
    DelKNode(plist, 3);
    PrintList(plist);
    Find(plist, 1)->next = Find(plist, 4);
    pos = CheckCycle(plist);
    if (pos == NULL)
    {
        printf("不带环\n");
    }
    else
    {
        printf("带环\n");
        printf("相遇点:%d\n", pos->data);
    }
    printf("环长度:%d\n", GetCircleLength(pos));
    entry = GetCircleEntryNode(pos, plist);
    printf("入口点:%d\n", entry->data);
}
int main()
{
    test10();
    system("pause");
    return 0;
}

这里写图片描述

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

pNode CheckCross(pList p1, pList p2);//判断两条链表是否相交(两条链表都不带环)

在这里我们只讨论两条链表都不带环的情况
对于求交点的问题我们也给出两种实现方法:
方法一:将两条相交的链表连成一个带环的链表,将第一个链表尾部的next指向第二个链表的头部再利用之前实现过的接口,把问题转化成求环的入口点
方法二:第一条链表的长度为len1,第二条链表的长度为len2,定义一个指针从长度长的链表先走|len1-len2|步,第二个指针再从长度短的链表走,当两个指针第一次指向地址相同的那个节点,则该节点为相交节点。

pNode CheckCross(pList p1, pList p2)//判断两条链表是否相交(两条链表都不带环)
{
    pNode cur1 = p1;
    pNode cur2 = p2;
    int len1 = 0;
    int len2 = 0;
    int num = 0;
    //两条链表有任意一条为空则不相交
    if ((cur1 == NULL) || (cur2 == NULL))
    {
        return NULL;
    }
    while (cur1->next)
    {
        cur1 = cur1->next;
        len1++;
    }
    while (cur2->next)
    {
        cur2 = cur2->next;
        len2++;
    }
    //相交返回交点
    if (cur1 == cur2)
    {
        //方法一:将两条相交的链表连成一个带环的链表,将第一个链表尾部的next指向第二个链表的头部
        //再利用之前实现过的接口,把问题转化成求环的入口点
        //cur1->next = p2;
        //pNode meet = CheckCycle(p1);
        //pNode cross = GetCircleEntryNode(meet, p1);
        //return cross;
        //方法二:第一条链表的长度为len1,第二条链表的长度为len2,定义一个指针从长度长的链表先走|len1-len2|步,
        //第二个指针再从长度短的链表走,当两个指针第一次指向地址相同的那个节点,则该节点为相交节点。
        num = abs(len1 - len2);//求绝对值
        if (len1 > len2)
        {
            cur1 = p1;
            cur2 = p2;
        }
        else
        {
            cur2 = p1;
            cur1 = p2;
        }
        for (int i = 0; i < num; i++)
        {
            cur1 = cur1->next;
        }
        while (cur1 != cur2)
        {
            cur1 = cur1->next;
            cur2 = cur2->next;
        }
        return cur1;
    }
    else
    {
        return NULL;
    }
}

具体测试:

void test11()
{
    pList plist1 = NULL;
    pNode cross = NULL;
    InitLinkList(&plist1);
    PushFront(&plist1, 10);
    PushFront(&plist1, 9);
    PushFront(&plist1, 7);
    PushFront(&plist1, 5);
    PushFront(&plist1, 3);
    PushFront(&plist1, 1);

    pList plist2 = NULL;
    InitLinkList(&plist2);
    PushFront(&plist2, 6);
    PushFront(&plist2, 4);
    PushFront(&plist2, 2);
    PushFront(&plist2, 0);
    Find(plist2, 6)->next = Find(plist1, 5);
    cross = CheckCross(plist1, plist2);
    printf("相交点:%d\n", cross->data);
}
int main()
{
    test11();
    system("pause");
    return 0;
}

这里写图片描述

12. 复杂链表的复制。

pComplexNode BuyComplexNode(DataType d);//创建复杂链表的节点
void PrintComplexList(pComplexNode p);//打印复杂链表
pComplexNode CopyComplexList(pComplexNode head);//复制复杂链表

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

//复杂链表
typedef struct ComplexNode
{
    DataType *data;
    struct ComplexNode *next;
    struct ComplexNode *random;
}ComplexNode, *pComplexNode;

我们可以把复杂链表的复制分为三个部分
(1)、复制复杂链表的每个节点,并插入到该节点的后面
(2)、调整复制节点的random指针指向
(3)、分离两条链表
贴一张我在实现过程中画的草图,希望能够帮助大家更好的理解(草图!草图!草图!)
紫色区域为random指针域,绿色区域为next指针域
我们可以看到,复制节点的random指向刚好就是指向它那个节点random指针的next的指向。
这里写图片描述

pComplexNode BuyComplexNode(DataType d)//创建复杂链表的节点
{
    pComplexNode newNode = (pComplexNode)malloc(sizeof(ComplexNode));
    if (newNode == NULL)
    {
        perror("malloc");
        exit(EXIT_FAILURE);
    }
    newNode->data = d;
    newNode->next = NULL;
    newNode->random = NULL;
    return newNode;
}
void PrintComplexList(pComplexNode p)//打印复杂链表
{
    pComplexNode cur = p;
    while (cur)
    {
        printf("[%d]-->(%d)-->", cur->data, cur->random->data);
        cur = cur->next;
    }
    printf("NULL\n");
}
pComplexNode CopyComplexList(pComplexNode head)//复制复杂链表
{
    pComplexNode pold = head;
    pComplexNode cur = NULL;
    pComplexNode newNode = NULL;
    pComplexNode newHead = NULL;

    //1.复制原来复杂链表的每个节点并插入到该节点后
    while (pold)
    {
        cur = pold->next;
        newNode = BuyComplexNode(pold->data);
        pold->next = newNode;
        newNode->next = cur;
        pold = cur;
    }
    //2.调整random指针的指向
    pold = head;
    while (pold)
    {
        newNode = pold->next;
        if (pold->random)
        {
            cur = pold->random->next;
            newNode->random = cur;
        }
        pold = newNode->next;
    }
    //3.分离两条链表
    pold = head;
    if (pold != NULL)
    {
        newHead = pold->next;
    }
    while (pold->next->next)
    {
        newNode = pold->next;
        pold->next = newNode->next;
        pold = newNode->next;
        newNode->next = pold->next;
    }
    pold->next = NULL;
    return newHead;
}

具体测试:

void test12()
{
    pComplexNode newList = NULL;
    pComplexNode p1 = NULL;
    pComplexNode p2 = NULL;
    pComplexNode p3 = NULL;
    pComplexNode p4 = NULL;
    p1 = BuyComplexNode(1);
    p2 = BuyComplexNode(2);
    p3 = BuyComplexNode(3);
    p4 = BuyComplexNode(4);
    p1->next = p2;
    p2->next = p3;
    p3->next = p4;
    p1->random = p3;
    p2->random = p4;
    p3->random = p2;
    p4->random = p3;
    PrintComplexList(p1);
    newList = CopyComplexList(p1);
    PrintComplexList(newList);
}
int main()
{
    test12();
    system("pause");
    return 0;
}

这里写图片描述

我总结的面试题就这么多,在之后的学习中如果还遇到较好的面试题也会加进来。

猜你喜欢

转载自blog.csdn.net/qq_34021920/article/details/76601226