有关链表的一些面试题

下面来写一写链表的笔试题

整个工程基于这篇博客下的代码


下面上代码,题目和解题思路都在注释里交待……

//1.从尾到头打印链表
void LinkListPrintReverse(LinkNode* head)//思路:递归
{
    if (head == NULL)
    {
        return;
    }
    LinkListPrintReverse(head->next);
    printf("[ %c ]", head->data);

}

//2.删除一个无头单链表的非尾节点(不能遍历链表)
void LinkListErase2(LinkNode** phead, LinkNode* pos)
{
    if (phead == NULL)
    {//非法输入
        return;
    }
    if (*phead == NULL)
    {//空链表
        return;
    }
    /*
    if (pos->next == NULL)
    {//此时是尾部删除,此处不做判断,题中说明为非尾节点
        //尾删
    }
    */
    //下面思路:a b c ,删b时,b的位置是pos,把c的值赋给pos位置,覆盖掉b,再删除c节点即可。
    //为什么删除pos后一个比较好操作?答:若直接删除pos,pos还保存着pos->next这个信息,删除pos后,此信息丢失
    //若删除pos后一个,也就是直接free掉pos->next;
    if (pos == NULL||pos->next==NULL)
    {//以下对pos解引用了,此处必须先对pos指针进行非空判断
        return;
    }
    pos->data = pos->next->data;
    //记录要删除的位置
    LinkNode* to_erase = pos->next;
    pos->next = pos->next->next;//这一步一定要,要把pos的next置为之前pos的next的next,这样才把要删除的节点完全的孤立出来。
    DestroyNode(to_erase);
}

//3.在节点前插入值,不能遍历
void LinkListMyBefore(LinkNode* pos, LinkNodeType value)
{
    if (pos == NULL)
    {
        return;
    }
    // 链表 a b c d ,在c之前插入e
    // (1) 在pos后创建新节点: a -> b-> c-> d
   //       new_node : c -> NULL
    LinkNode* new_node = CreateNode(pos->data);
// (2) new_node->d
    new_node->next = pos->next;
    //(3) pos->new_node
    pos->next = new_node;
    //pos的值改为value
    pos->data = value;
}

//4.约瑟夫环问题,规定一次走M步,淘汰每一次指向的元素,看幸存者是谁。带环
LinkNode* JospehCircle(LinkNode* head, int M)
{
    if (head == NULL)
    {//空链表
        return;
    }
    if (head->next == head)
    {//只有一个元素
        return head;
    }
    LinkNode* cur = head;
    while (cur->next != head)
    {
        int i = 1;
        for (; i < M; ++i)
        {
            cur = cur->next;
        }
        //此时cur指向的元素即为要淘汰的
        printf("[ %c ]", cur->data);
        cur->data = cur->next->data;
        LinkNode* to_erase = cur->next;
        cur->next = to_erase->next;
        DestroyNode(to_erase);
    }
    //此时剩下的cur即为幸存者,lucky boy
    return cur;
}

//5.单链表逆置
void  LinkListReverse1(LinkNode** phead)
{
    //1.判断phead是否合法
    if (phead == NULL)
    {
        return;
    }
    //2.判断是否为空链表
    if (*phead == NULL)
    {
        return;
    }
    //3.判断链表是否只有一个元素
    if ((*phead)->next == NULL)
    {
        return;
    }
    //4.思路:遍历单链表,删除当前元素后,进行头插。时间复杂度:O(n)
    // (1)定义一个cur,指向头指针,用来遍历单链表
    LinkNode* cur = *phead;
    //(2)进行遍历,直到最后一个元素
    while (cur->next != NULL)  //这里的条件必须是cur的下一个不为空,若为cur不为空,程序崩溃。多画图
    {
        //(3)定义一个节点来保存要删除的元素的节点信息
        LinkNode* to_delete = cur->next;
        cur->next = to_delete->next;
        //(4)把要删除的to_delete插入到头指针之前
        to_delete->next = *phead; 
        //(5)重置头指针
        *phead = to_delete;
        //cur = cur->next;//不需要修改cur的位置,上面已经将cur的next改变
    }
    //此时已将整个单链表遍历头插完成,cur指向最后一个元素
    return;
}

void LinkListReverse2(LinkNode** phead)
{
    //以下几步是常规判断
    //1.判断phead是否合法
    if (phead == NULL)
    {
        return;
    }
    //2.判断是否为空链表
    if (*phead == NULL)
    {
        return;
    }
    //3.判断链表是否只有一个元素
    if ((*phead)->next == NULL)
    {
        return;
    }
    //4.思路:定义一个pre指针代表每次交换后的头指针位置,cur指向头指针的下一个元素,next保存cur的下一个的节点信息
    //(1) 定义这三个节点
    LinkNode* cur = (*phead)->next;
    LinkNode* pre = *phead;
    //[ 思考 ??]
    pre->next=NULL;//pre的next指向的是随机地址,此处必须将pre的next置为NULL,否则打印单链表时,条件不成立,将出错。
    while (cur != NULL)
    {
        LinkNode* next = cur->next;
        //(2)修改cur的指向
        cur->next = pre;
        //(3)更新pre、cur的指向
        pre = cur;
        cur = next;
    }
    //修改头指针指向
    *phead = pre;
    return;
}

//6.单链表排序(冒泡&快速)
//交换函数
void Swap(LinkNodeType *a, LinkNodeType * b)
{
    LinkNodeType tmp = *a;
    *a = *b;
    *b = tmp;
}
void LinkListBubbleSort(LinkNode* head)
{
    if (head == NULL)
    {//空链表
        return;
    }
    if (head->next==NULL)
    {//只有一个元素
        return;
    }
    LinkNode* count = head;
    //第一重循环,排序趟数
    for (; count != NULL; count = count->next)
    {
        LinkNode* cur = head;
        LinkNode* tail = NULL;//tail是提供个cur的结束标志
        //第二重循环,将最大的与当前值进行交换,一直冒到最后
        for (; cur->next != tail; cur = cur->next)
        {
            if (cur->data > cur->next->data)
            {
                Swap(&cur->data, &cur->next->data);
            }

        }
        //此时cur已经指向最后一个元素,也就是最大的元素
            //更新tail
            tail = cur;  //tail指向最后一个元素,也就是最后的最大的元素
    }
    return;
}

//7.合并两个有序链表,合并后仍然有序
LinkNode* LinkListMerge(LinkNode* head1, LinkNode* head2)
{
    if (head1 == NULL)
    {
        return head2;
    }
    if (head2 == NULL)
    {
        return head1;
    }
    LinkNode* cur1 = head1;
    LinkNode* cur2 = head2;
    LinkNode* new_head = NULL;//指向新链表的头结点
    LinkNode* new_tail = NULL;//新链表尾节点
    while (cur1 != NULL&&cur2 != NULL)
    {
        if (cur1->data < cur2->data)
        {
            if (new_tail == NULL)
            {
                new_head = new_tail = cur1;
            }
            else
            {
                new_tail->next = cur1;
                new_tail = new_tail->next;
            }
            cur1 = cur1->next;
        }
        else//此处包含相等和cur1->data>cur2->data两种情况
        {
            if (new_tail == NULL)
            {
                new_head = new_tail = cur2;
            }
            else
            {
                new_tail->next = cur2;
                new_tail = new_tail->next;
            }
            cur2 = cur2->next;
        }
    }
    //循环跳出,来判断是因为什么跳出
    //有一方已经到达NULL,要把剩下的一方追加到新链表中
    if (cur1 == NULL)
    {
        new_tail->next = cur1;
    }
    else
        new_tail->next = cur2;
    return new_head;
}

//8、查找链表中间节点,只能遍历一遍
LinkNode* LinkListFindMidNode(LinkNode* head)
{//定义一个快指针,一个慢指针
    //慢指针走一步,快指针走两步
    //当快指针走到NULL时,慢指针就是中间节点,返回慢指针即可
    LinkNode* slow = head;
    LinkNode* fast = head;
    while (fast != NULL&&fast->next != NULL) //两个条件的顺序必须这么写,前一个条件不成立时,直接跳出,短路求值
    {                                                                    //如果交换顺序,fast->next不为空,而fast为空时,就完蛋了。
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

//9、查找单链表的倒数第K个节点,要求只能遍历一次链表。
LinkNode* LinkListFindLastKNode(LinkNode* head, int k)
{
    if (head == NULL)
    {//非法输入
        return NULL;
    }
    //思路:快指针、慢指针
    //快指针先走K步后,慢指针和快指针再一起走,直到快指针走到尾部,返回慢指针。
    LinkNode* fast = head;
    LinkNode* slow = head;
    //让fast先走K步
    int i = 0;
    for (; i < k; ++i)
    {
        if (fast == NULL)
            break;
        fast = fast->next;
    }
    //判断fast走没走完K步
    if (i != k)
    {//没走完的话,说明k长度超过链表长度
        return NULL;
    }
    while (fast)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

//10、删除链表的倒数第K个节点
void LinkListEraseLastKNode(LinkNode** phead, int k)
{
    if (phead == NULL)
    {//非法输入
        return;
    }
    if (*phead == NULL)
    {//空链表
        return;
    }
    //思路:找到倒数第K+1个节点,直接删除
    int len = LinkListSize(*phead);
    if (k > len)
    {//K就非法了,。没意义
        return;
    }
    if (k == len)
    {//直接就头删,头删需要修改头指针位置,所以函数传参要**
        LinkListPopFront(phead);
        return;
    }
    //接下来,来找到倒数第K+1个节点
    LinkNode* pre = *phead;
    int i = 0;
    for (; i < len - (k + 1); ++i)
    {
        pre = pre->next;
    }
    //此时pre指向倒数第K+1个节点,我们来执行删除动作
    LinkNode*  to_delete = pre->next;
    pre->next = to_delete->next;
    free(to_delete);
}

//11、判定链表是否带环?若带环求环长度?求环入口?
//法一:遍历链表,走一个节点,放入顺序表中,走下一个节点时,与顺序表中保存的节点作对比
//    相同就跳出,带环;不相同就继续下一个。
//  时间复杂度:O(n^2); 空间复杂度O(n);
//法二:优化:定义两个指针,快指针、慢指针,快指针走两步,慢指针走一步,
//  如果快慢指针相遇,则证明有环。
//时间复杂度:O(n);空间复杂度 :O(1);
//下面来实现一下方法二
int LinkListHasCycle(LinkNode* head)
{
    if (head == NULL)
    {//非法输入
        return 0;
    }
    LinkNode* fast = head;
    LinkNode* slow = head;
    while (fast != NULL && fast->next != NULL)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)
        {
            return 1;
        }
        return 0;
    }
}
LinkNode* LinkListHasCycle2(LinkNode* head)
{
    if (head == NULL)
    {//非法输入
        return NULL;
    }
    LinkNode* fast = head;
    LinkNode* slow = head;
    while (fast != NULL && fast->next != NULL)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)
        {
            return slow;
        }
        return NULL;
    }
}
//求环的长度
size_t LinkListCycleLen(LinkNode* head)
{
    LinkNode* meet_node = LinkListHasCycle2(head);
    if (meet_node == NULL)
    {
        return 0;
    }
    LinkNode* cur = meet_node->next;
    size_t len = 1;
    while (cur != meet_node)
    {
        cur = cur->next;
        ++len;
    }
    return len;
}
//求环的入口
//思路:一个指针记录环的相遇点,一个指针记录链表的头结点
//两个指针同时走,相等时就是换环的入口点
LinkNode* LinkListCycleEnter(LinkNode* head)
{
    if (head == NULL)
    {
        return NULL;
    }
    LinkNode* meet_node = LinkListHasCycle2(head);
    LinkNode* cur1 = head;
    LinkNode* cur2 = meet_node;
    while (cur1 != cur2)
    {
        cur1 = cur1->next;
        cur2 = cur2->next;
    }
    return cur1;
}

//12、判断两个链表是否相交,若相交,求交点。(假设链表不带环)
//思路:(1)求len1,len2
//        (2)求差值;(3)长的先走差值;(4)一样长后,两个指针再每次走一步
//       (5)循环(4),在循环中判断指针指向的值是否相等,如果相等,就证明有交点;不相等就返回NULL
LinkNode* LinkListCrossPos(LinkNode* head1, LinkNode* head2)
{
    if (head1 == NULL || head2 == NULL)
    {//非法输入
        return NULL;
    }
    size_t len1 = LinkListSize(head1);
    size_t len2 = LinkListSize(head2);
    LinkNode* cur1 = head1;
    LinkNode* cur2 = head2;
    if (len1 > len2)
    {//head1比较长
        size_t i = 0;
        for (; i < len1 - len2; ++i)
        {
            cur1 = cur1->next;
        }
    }
    else
    {
        size_t i = 0;
        for (; i < len2 - len1; ++i)
        {
            cur2 = cur2->next;
        }
    }
    while (cur1 != NULL || cur2 != NULL)
    {
        if (cur1 == cur2)
        {
            return cur1;
        }
        cur1 = cur1->next;
        cur2 = cur2->next;
    }
    return NULL;
}

猜你喜欢

转载自blog.csdn.net/weixin_38682277/article/details/79932147