链表面试题集锦

链表是数据结构中最常见的一种结构了,由链表而产生的面试题也是十分重要的,所以我总结了常见的链表面试题并对其进行实现:
1.从尾到头打印单链表

    //从尾到头打印单链表,利用辅助栈的结构
    void ReverseShowList(ListNode *head)
    {
        stack<ListNode *> s;
        ListNode *cur=head;
        while(cur)
        {
            s.push(cur);
            cur=cur->next;
        }
        while(!s.empty())
        {
            ListNode *top=s.top();
            cout<<top->data<<" ";
            s.pop();
        }
        cout<<endl;
    }

2.逆置/反转单链表

    //反转单链表
    ListNode *ReverseList(ListNode *head)
    {
        assert(head || head->next);  //头指针为空或者只有一个结点
        ListNode *cur=head;
        ListNode *newHead=NULL;
        ListNode *newCur=NULL;
        while(cur)
        {
            newCur=cur;
            cur=cur->next;
            newCur->next=newHead;
            newHead=newCur;   //更新头
        }
        return newHead;
    }

3.删除无头单链表的非尾结点

    //删除无头单链表的非尾结点,替换法
    void DelNotTail(ListNode *pos)
    {
        assert(pos && pos->next);
        ListNode *del=pos->next;
        pos->data=del->data;
        pos->next=del->next;
        delete del;
        del=NULL;
    }

4.在无头单链表的一个非头结点前插入一个结点

    //在当前结点前插一个结点(无头单链表),实现方法类似删除无头单链表的非尾结点
    void InsertFrontNode(ListNode *pos,int d)
    {
        assert(pos);
        ListNode *newNode=new ListNode(d);
        newNode->next=pos->next;
        pos->next=newNode;
        int tmp=newNode->data;
        newNode->data=pos->data;
        pos->data=tmp;
    }

5.查找链表的中间节点,只允许遍历一遍链表

    //查找链表的中间节点,只允许遍历一遍链表
    ListNode *FindMidNode(ListNode *head)
    {
        ListNode *fast=head;   //快慢指针
        ListNode *slow=head;
        while(fast && fast->next)
        {
            fast=fast->next->next;
            slow=slow->next;
        }
        return slow;
    }

6.删除倒数第k个结点,只允许遍历一次链表

    //删除倒数第k个结点,只允许遍历一遍链表
    void DelKNode(ListNode *head,int k)
    {
        assert(head);
        ListNode *prev=head;
        ListNode *slow=head;
        while(prev)
        {
            k--;
            //slow指向倒数第k个结点
            if(k < 0)
            {
                slow=slow->next;
            }
            prev=prev->next;   //prev先指向正数第k个结点
        }
        if(k <= 0)   //删除slow所指向的结点
        {
            DelNotTail(slow);
            //ListNode *del=slow->next;
            //slow->data=del->data;
            //slow->next=del->next;
            //delete del;
            //del=NULL;
        }
    }

7.查找链表的中间结点,只允许遍历一遍链表

    //查找链表的中间节点,只允许遍历一遍链表
    ListNode *FindMidNode(ListNode *head)
    {
        ListNode *fast=head;   //快慢指针
        ListNode *slow=head;
        while(fast && fast->next)
        {
            fast=fast->next->next;
            slow=slow->next;
        }
        return slow;
    }

8.合并两个有序的单链表,使其合并后依然有序,不允许开辟新的辅助空间。非递归和递归版本的实现

    //非递归实现
    //合并两个有序的单链表,不开辟新的空间
    ListNode *MergeNoR(ListNode *plist1,ListNode *plist2)
    {
        ListNode *p1=plist1;
        ListNode *p2=plist2;
        ListNode *newHead=NULL;
        ListNode *tail=NULL;
        if(p1 == p2)      //三种特殊情况
            return p1;
        if(p1 == NULL)
            return p2;
        if(p2 == NULL)
            return p1;
        if(p1->data < p2->data)  //为新的链表选取新的头指针
        {
            newHead=p1;
            tail=p1;
            p1=p1->next;
        }
        else
        {
            newHead=p2;
            tail=p2;
            p2=p2->next;
        }
        while(p1 != NULL && p2 != NULL)
        {
            if(p1->data < p2->data)    //选取适合的结点插入
            {
                tail->next=p1;
                tail=tail->next;
                p1=p1->next;
            }
            else
            {
                tail->next=p2;
                tail=tail->next;
                p2=p2->next;
            }
        }
        if(p1 != NULL)    //有可能有的链表的部分数据尚未完全插入
            tail->next=p1;
        if(p2 != NULL)
            tail->next=p2;
        return newHead;
    }
    //递归实现
    ListNode *MergeR(ListNode *plist1,ListNode *plist2)
    {
        ListNode *p1=plist1;
        ListNode *p2=plist2;
        ListNode *plist3=NULL;
        if(p1 == p2)
            return p1;
        if(p1 == NULL)
            return p2;
        if(p2 == NULL)
            return p1;
        if(p1->data < p2->data)
        {
            plist3=p1;
            plist3->next=MergeR(p1->next,p2);
        }
        else
        {
            plist3=p2;
            plist3->next=MergeR(p1,p2->next);
        }
        return plist3;
    }

9.单链表实现约瑟夫环

    //单链表实现约瑟夫环
    ListNode *JosephCycle(ListNode *head,int k)
    {
        assert(head);
        ListNode *cur=head;
        while(cur->next != cur)   //有超过一个人存在
        {
            int m=k;     //m保存k的值,可以重复使用
            while(--m)   //cur指向将要退出的结点
            {
                cur=cur->next;
            }
            cout<<"del:"<<cur->data<<endl;
            ListNode *del=cur->next;  //利用交换的方式,最后要删除的结点是cur的下一个结点
            cur->data=del->data;
            cur->next=del->next;
            delete del;
            del=NULL;
        }
        return cur;
    }

10.判断单链表是否带环?若带环则求出链表长度?以及环的入口点?
对这道面试题更加详细的介绍可参考我的这篇博客:

链表面试题之带环问题

    //判断链表是否带环,带环则返回相遇点
    ListNode *CheckCycle(ListNode *head)
    {
        assert(head);
        ListNode *fast=head;    //快慢指针
        ListNode *slow=head;
        while(fast && fast->next)
        {
            fast=fast->next->next;
            slow=slow->next;
            if(fast == slow)
                return slow;
        }
        return NULL;
    }
    //如果带环则求出链表长度
    int GetCircleLength(ListNode *head)
    {
        //从相遇点开始遍历,当再次遇到相遇点时即遍历完整个环
        assert(head);
        ListNode *meet=CheckCycle(head);
        assert(meet);
        ListNode *cur=meet;
        int count=1;
        while(cur->next != meet)
        {
            count++;
            cur=cur->next;
        }
        return count;
    }
    //求出带环链表的入口节点
    ListNode *GetCircleEntryNode(ListNode *head,ListNode *meet)
    {
        assert(head);
        assert(meet);
        //prev从相遇点开始,slow从链表头开始
        //prev和slow的再次相遇点就是入口节点
        ListNode *prev=meet;
        ListNode *slow=head;
        while(prev != slow)
        {
            prev=prev->next;
            slow=slow->next;
        }
        return slow; 
    }

对于求带环链表的入口结点的证明过程可参考下图:

求带环链表的入口结点

11.判断两个链表是否相交,如果相交则求出交点(假设链表不带环)
对这道面试题更加详细的介绍可参考我的这篇博客:

链表面试题之链表相交问题

    //判断链表是否相交,并求出交点(链表不带环)
    bool CheckCross(ListNode *plist1,ListNode *plist2)
    {
        assert(plist1);
        assert(plist2);
        ListNode *p1=plist1;
        ListNode *p2=plist2;
        //p1,p2分别指向两条链表的尾结点
        while(p1->next)
        {
            p1=p1->next;
        }
        while(p2->next)
        {
            p2=p2->next;
        }
        if(p1 == p2)
            return true;    //相交
        else
            return false;   //不相交
    }
    ListNode *GetCrossEntryNode1(ListNode *plist1,ListNode *plist2)
    {
        assert(plist1);
        assert(plist2);
        ListNode *cur1=plist1;
        ListNode *cur2=plist2;
        int count1=0;
        int count2=0;
        //统计链表1和链表2的长度
        while(cur1)
        {
            count1++;
            cur1=cur1->next;
        }
        while(cur2)
        {
            count2++;
            cur2=cur2->next;
        }
        cur1=plist1;
        cur2=plist2;
        //长链表先走count1-count2步,然后两个指针再一起前进
        if(count1-count2 >= 0)
        {
            while(count1-count2)
            {
                cur1=cur1->next;
                count1--;
            }
            while(cur1 != cur2)
            {
                cur1=cur1->next;
                cur2=cur2->next;
            }
        }
        else    //count1 < count2
        {
            while(count2-count1)
            {
                cur2=cur2->next;
                count2--;
            }
            while(cur1 != cur2)
            {
                cur1=cur1->next;
                cur2=cur2->next;
            }
        }
        return cur1;
    }
    ListNode *GetCrossEntryNode2(ListNode *plist1,ListNode *plist2)
    {
        assert(plist1);
        assert(plist2);
        ListNode *cur=plist1;
        //构造环
        while(cur->next)
        {
            cur=cur->next;
        }
        cur->next=plist2;
        ListNode *meet=CheckCycle(plist1);
        return GetCircleEntryNode(plist1,meet);
    }

12.判断两个链表是否相交,如果相交则求出交点(链表可能带环)
对这道面试题更加详细的介绍可参考我的这篇博客:

链表面试题之链表相交问题

    //判断链表是否相交,并求出交点(链表可能带环)
    bool CheckCrossCircle(ListNode *plist1,ListNode *plist2)
    {
        assert(plist1);
        assert(plist2);
        ListNode *meet1=CheckCycle(plist1);
        ListNode *meet2=CheckCycle(plist2);
        //如果有一个不带环则必然不相交
        assert(meet1);
        assert(meet2);
        //两个链表都带环,如果相遇点在同一个环内则必然相交
        ListNode *m1=meet1;
        ListNode *m2=meet2;
        while(m1 != m2)
        {
            m1=m1->next;
        }
        if(m1 == m2)
            return true;
        else
            return false;
    }
    ListNode *GetCrossCircleNode(ListNode *plist1,ListNode *plist2)
    {
        assert(plist1);
        assert(plist2);
        ListNode *meet1=CheckCycle(plist1);
        ListNode *meet2=CheckCycle(plist2);
        assert(meet1);
        assert(meet2);
        ListNode *entry1=GetCircleEntryNode(plist1,meet1);
        ListNode *entry2=GetCircleEntryNode(plist2,meet2);
        if(entry1 == entry2)      //交点在环外
        {
            //tail指向相遇点的第一个节点
            //重新构造环
            ListNode *tail=entry1;
            while(tail->next != entry1)
            {
                tail=tail->next;
            }
            tail->next=plist1;
            ListNode *meet=CheckCycle(plist2);
            return GetCircleEntryNode(plist2,meet);
        }
        else  //交点在环内
        {
            //交点就是entry1或者是entry2
            return entry2;
        }
    }

13.单链表的排序

    void BubbleSort(ListNode *plist)
    {
        assert(plist);
        ListNode *cur=plist;
        ListNode *tail=NULL;
        bool flag=true;
        while(cur != tail)
        {
            while(cur->next != tail)
            {
                if(cur->data > cur->next->data)    //升序排序
                {
                    flag=false;
                    int tmp=cur->data;
                    cur->data=cur->next->data;
                    cur->next->data=tmp;
                }
                cur=cur->next;
            }
            if(flag == true)
            {
                break;
            }
            tail=cur;    //不断缩小比较的范围
            cur=plist;
        }
    }

14.删除链表中重复的结点
题目描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表:1 2 2 2 3 3 4 5 , 处理后为 1 4 5;

    //删除链表中重复的结点
    ListNode* DeleteDuplication(ListNode* head)
    {
        assert(head);
        ListNode *prev=NULL;
        ListNode *cur=head;
        while(cur)
        {
            ListNode *next=cur->next;
            int flag=1;
            //当前结点的数据域和它的下一个结点的数据域相同则修改flag
            if(next && cur->data == next->data)
            {
                flag=0;
            }
            //未修改,则继续遍历下一个结点
            if(flag == 1)
            {
                prev=cur;
                cur=cur->next;
            }
            //修改flag,则删除所有数据域相同的结点
            else
            {
                int d=cur->data;
                ListNode *del=cur;
                while(del && del->data == d)
                {
                    next=del->next;
                    delete del;
                    del=next;
                }
                if(prev == NULL)
                    head=next;
                else
                    prev->next=next;
                cur=next;      //更新cur的值
            }
        }
        return head;
    }

关于对以上面试题的测试代码已经上传到github中,点击下列文字即可查看:

点击此处查看源码

更详细的介绍可参考我的以下博客:
关于链表面试题的一些理解
链表面试题之带环问题
链表面试题之约瑟夫环问题
链表面试题之链表相交问题

在这里就分享结束了~~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值