【单向链表高频笔试题】

1、单向链表的原地反转

image.png

逻辑:

  • 使用两个指针prev 和 pCur,pCur是需要反转的结点,prev为需要反转的结点的前驱结点

  • 让prev的指针域指向需要反转结点的下一个结点:prev->next = pCur->next

  • 将pCur反转到prev前:pCur->next = head->next

  • 将头指针的next指向反转后的结点:head->next = pCur

  • pCur指向下次需要反转的结点:pCur = prev->next

image.png

image.png

算法实现:单向链表的反转.c

#include <stdio.h>
#include <stdlib.h>
​
#define TRUE   0
#define FALSE -1
​
typedef int ElemType;
​
typedef struct LNode{
    ElemType data;  //数据域
    struct LNode *next; //指针域,指向当前结点的直接后继(下一个结点)
}LNode, *LinkList; //LinkList的类型为 LNode *
​
LinkList list_init()
{
    LNode *t;
    t = (LNode *)malloc(sizeof(LNode)); //创建一个头结点
    t->next = NULL; //头结点的指针域为空
​
    LinkList head;  //定义一个头指针
    head = t; //头指针指向头结点
    return head;
}
​
/*
 * @brief 头插法插入一个结点
 * @param 需要插入的链表的头指针
 * @param 需要插入的数据
 * @return 成功返回TRUE, 失败返回FALSE
 * */
int head_insert(LinkList head, ElemType data)
{
    if (NULL == head)
        return FALSE;
​
    //创建一个新的结点p
    LNode *p = (LNode *)malloc(sizeof(LNode));
    p->data = data;
    //将新的结点p的next指向头节点的下一个结点(head->next)
    p->next = head->next;
    //头节点的next指向新的结点p
    head->next = p;
​
    return TRUE;
}
​
/*
 * @brief 输出链表中的所有结点
 * @param head 链表的头指针
 * @return 成功返回TRUE,失败返回FALSE
 * */
int print_list(LinkList head)
{
    if (NULL == head)
        return FALSE;
​
    //使用一个临时指针对链表进行遍历
    LNode *t;
    t = head->next;
    while (t != NULL)
    {
        printf("%d ", t->data);
        t = t->next;
    }
    printf("\n");
    return TRUE;
}
​
LinkList reverse(LinkList head)
{
    if (NULL == head || NULL == head->next)
        return head;
​
    LNode *prev, *pCur;
    //prev 指向第一个数据结点
    prev = head->next;
    //pCur指向第二个数据结点
    pCur = prev->next;
    if (NULL == pCur) //如果只有一个数据结点,不需要反转
        return head;
​
    while (pCur != NULL)
    {
        prev->next = pCur->next;
        pCur->next = head->next;
        head->next = pCur;
        pCur = prev->next;
    }
​
    return head;
}
​
int main()
{
    LinkList head;
    head = list_init();
​
    int i;
    for (i = 0; i < 5; i++)
        head_insert(head, 100+i);
​
    print_list(head);
​
    head = reverse(head);
    print_list(head);
​
    return 0;
}

2、查找链表的中间结点

要求:只能遍历一次链表

思路:快慢指针

  • 定义两个指针slow、fast

  • slow每次遍历一个结点

  • fast每次遍历两个结点

image.png

image.png

image.png

image.png

算法实现:get_mid.c

#include <stdio.h>
#include <stdlib.h>
​
#define TRUE   0
#define FALSE -1
​
typedef int ElemType;
​
typedef struct LNode{
    ElemType data;  //数据域
    struct LNode *next; //指针域,指向当前结点的直接后继(下一个结点)
}LNode, *LinkList; //LinkList的类型为 LNode *
​
LinkList list_init()
{
    LNode *t;
    t = (LNode *)malloc(sizeof(LNode)); //创建一个头结点
    t->next = NULL; //头结点的指针域为空
​
    LinkList head;  //定义一个头指针
    head = t; //头指针指向头结点
    return head;
}
​
/*
 * @brief 头插法插入一个结点
 * @param 需要插入的链表的头指针
 * @param 需要插入的数据
 * @return 成功返回TRUE, 失败返回FALSE
 * */
int head_insert(LinkList head, ElemType data)
{
    if (NULL == head)
        return FALSE;
​
    //创建一个新的结点p
    LNode *p = (LNode *)malloc(sizeof(LNode));
    p->data = data;
    //将新的结点p的next指向头节点的下一个结点(head->next)
    p->next = head->next;
    //头节点的next指向新的结点p
    head->next = p;
​
    return TRUE;
}
​
/*
 * @brief 输出链表中的所有结点
 * @param head 链表的头指针
 * @return 成功返回TRUE,失败返回FALSE
 * */
int print_list(LinkList head)
{
    if (NULL == head)
        return FALSE;
​
    //使用一个临时指针对链表进行遍历
    LNode *t;
    t = head->next;
    while (t != NULL)
    {
        printf("%d ", t->data);
        t = t->next;
    }
    printf("\n");
    return TRUE;
}
​
LNode *get_mid(LinkList head)
{
    LNode *slow, *fast;
    slow = head->next;
    fast = head->next;
    while (fast != NULL && fast->next != NULL)
    {
        fast = fast->next->next;
        slow = slow->next;
    }
​
    return slow;
}
​
int main()
{
    LinkList head;
    head = list_init();
​
    int i;
    for (i = 0; i < 6; i++)
        head_insert(head, 100+i);
​
    print_list(head);
​
    LNode *item;
    item = get_mid(head);
    printf("%d\n", item->data);
​
    return 0;
}

3、判断链表是否有环

image.png

思路:

  • 使用快慢指针

  • 如果存在环,最终在某个时间点快慢指针会相遇

image.png

image.png

image.png

image.png

image.png

image.png

算法实现:find_loop.c

#include <stdio.h>
#include <stdlib.h>
​
#define TRUE   0
#define FALSE -1
​
typedef int ElemType;
​
typedef struct LNode{
    ElemType data;  //数据域
    struct LNode *next; //指针域,指向当前结点的直接后继(下一个结点)
}LNode, *LinkList; //LinkList的类型为 LNode *
​
LinkList list_init()
{
    LNode *t;
    t = (LNode *)malloc(sizeof(LNode)); //创建一个头结点
    t->next = NULL; //头结点的指针域为空
​
    LinkList head;  //定义一个头指针
    head = t; //头指针指向头结点
    return head;
}
​
/*
 * @brief 头插法插入一个结点
 * @param 需要插入的链表的头指针
 * @param 需要插入的数据
 * @return 成功返回TRUE, 失败返回FALSE
 * */
int head_insert(LinkList head, ElemType data)
{
    if (NULL == head)
        return FALSE;
​
    //创建一个新的结点p
    LNode *p = (LNode *)malloc(sizeof(LNode));
    p->data = data;
    //将新的结点p的next指向头节点的下一个结点(head->next)
    p->next = head->next;
    //头节点的next指向新的结点p
    head->next = p;
​
    return TRUE;
}
​
/*
 * @brief 输出链表中的所有结点
 * @param head 链表的头指针
 * @return 成功返回TRUE,失败返回FALSE
 * */
int print_list(LinkList head)
{
    if (NULL == head)
        return FALSE;
​
    //使用一个临时指针对链表进行遍历
    LNode *t;
    t = head->next;
    while (t != NULL)
    {
        printf("%d ", t->data);
        t = t->next;
    }
    printf("\n");
    return TRUE;
}
​
LNode *find_loop(LinkList head)
{
    LNode *fast, *slow ; 
    slow = fast = head->next ; 
   
    while (slow != NULL && fast fast -> next != NULL) 
    { 
        slow = slow -> next ; 
        fast = fast -> next -> next ; 
        if (slow == fast) 
            return slow;
    } 
​
    return NULL;
}
​
int main()
{
    LinkList head;
    head = list_init();
​
    int i;
    for (i = 0; i < 6; i++)
        head_insert(head, 100+i);
    print_list(head);
​
    //人为在链表上添加一个环
    //使用一个指针记住链表上的第三个结点
    LNode *flag = head->next->next->next;
​
    //找到最后一个结点
    LNode *tail = head->next;
    while (tail->next != NULL)
        tail = tail->next;
​
    LNode *p = (LNode *)malloc(sizeof(LNode));
    p->data = 200;
​
    tail->next = p;
    p->next = flag;
​
    LNode *loop = find_loop(head);
    if (loop != NULL)
        printf("%d\n", loop->data);
    else
        printf("no loop\n");
​
    return 0;
}

4、查找带环链表中环的入口

image.png

通过判断一个单向链表是否有环我们知道,如果有环的存在,假设环的长度为r,那么当快指针fast和慢指针slow相遇的时候,假如fast指针已经在环上走了n圈,假如slow走了s步,那么fast就走了2s步,又因为fast走过的步数 = s + n*r,则有下面的等式:

2*s = s + n * r ; (1)

=> s = n*r; (2)

如果假设整个链表的长度是L,入口和相遇点的距离是x(如上图所示),起点到入口点的距离是a(如上图所示),则有:

a + x = s = n * r; (3) 由(2)推出

a + x = (n - 1) * r + r = (n - 1) * r + (L - a) (4) 由环的长度 = 链表总长度 - 起点到入口点的距离求出

a = (n - 1) * r + (L -a -x) (5)

集合式子(5)以及上图我们可以看出,从链表起点开始到入口点的距离a,与从slow和fast的相遇点(如图)到入口点的距离相等。

因此我们就可以分别用一个指针(ptr1, prt2),同时从head与slow和fast的相遇点出发,每一次操作走一步,直到ptr1 == ptr2,此时的位置也就是入口点!

算法实现:find_entrance.c

#include <stdio.h>
#include <stdlib.h>
​
#define TRUE   0
#define FALSE -1
​
typedef int ElemType;
​
typedef struct LNode{
    ElemType data;  //数据域
    struct LNode *next; //指针域,指向当前结点的直接后继(下一个结点)
}LNode, *LinkList; //LinkList的类型为 LNode *
​
LinkList list_init()
{
    LNode *t;
    t = (LNode *)malloc(sizeof(LNode)); //创建一个头结点
    t->next = NULL; //头结点的指针域为空
​
    LinkList head;  //定义一个头指针
    head = t; //头指针指向头结点
    return head;
}
​
/*
 * @brief 头插法插入一个结点
 * @param 需要插入的链表的头指针
 * @param 需要插入的数据
 * @return 成功返回TRUE, 失败返回FALSE
 * */
int head_insert(LinkList head, ElemType data)
{
    if (NULL == head)
        return FALSE;
​
    //创建一个新的结点p
    LNode *p = (LNode *)malloc(sizeof(LNode));
    p->data = data;
    //将新的结点p的next指向头节点的下一个结点(head->next)
    p->next = head->next;
    //头节点的next指向新的结点p
    head->next = p;
​
    return TRUE;
}
​
/*
 * @brief 输出链表中的所有结点
 * @param head 链表的头指针
 * @return 成功返回TRUE,失败返回FALSE
 * */
int print_list(LinkList head)
{
    if (NULL == head)
        return FALSE;
​
    //使用一个临时指针对链表进行遍历
    LNode *t;
    t = head->next;
    while (t != NULL)
    {
        printf("%d ", t->data);
        t = t->next;
    }
    printf("\n");
    return TRUE;
}
​
LNode *find_entrance(LinkList head)
{
    LNode *fast, *slow ; 
    slow = fast = head->next ; 
   
    while (slow != NULL && fast -> next != NULL) 
    { 
        slow = slow -> next ; 
        fast = fast -> next -> next ; 
        if (slow == fast) 
            break;
    } 
    if (slow == NULL || fast -> next == NULL) 
        return NULL ; //没有环,返回NULL值 
​
    LNode * ptr1 = head->next ; //链表开始点 
    LNode * ptr2 = slow ; //相遇点 
    while (ptr1 != ptr2)  
    { 
        ptr1 = ptr1 -> next ; 
        ptr2 = ptr2 -> next ; 
    } 
    return ptr1 ; //找到入口点 
}
​
int main()
{
    LinkList head;
    head = list_init();
​
    int i;
    for (i = 0; i < 6; i++)
        head_insert(head, 100+i);
    print_list(head);
​
    //人为在链表上添加一个环
    //使用一个指针记住链表上的第三个结点
    LNode *flag = head->next->next->next;
​
    //找到最后一个结点
    LNode *tail = head->next;
    while (tail->next != NULL)
        tail = tail->next;
​
    LNode *p = (LNode *)malloc(sizeof(LNode));
    p->data = 200;
​
    tail->next = p;
    p->next = flag;
​
    LNode *loop = find_entrance(head);
    if (loop != NULL)
        printf("%d\n", loop->data);
    else
        printf("no loop\n");
​
    return 0;
}

5、约瑟夫环

问题描述:

传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,从而逃过了这场死亡游戏 。

问题转换:

41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。

1.编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;

2.自退出那个人开始的下一个人再次从1开始报数,以此类推;

3.求出最后退出的那个人的编号。

image.png

解题思路:

  • 构建含有41个结点的单向循环链表,分别存储1~41的值,分别代表这41个人;

  • 使用计数器count,记录当前报数的值;

  • 遍历链表,每循环一次,count++;

  • 判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;

算法实现:JosephRing.c

#include <stdio.h>
#include <stdlib.h>
​
#define TRUE   0
#define FALSE -1
​
typedef int ElemType;
​
typedef struct LNode{
    ElemType data;  //数据域
    struct LNode *next; //指针域,指向当前结点的直接后继(下一个结点)
}LNode, *LinkList; //LinkList的类型为 LNode *
​
/*
 * @brief 输出链表中的所有结点
 * @param head 链表的头指针
 * @return 成功返回TRUE,失败返回FALSE
 * */
int print_list(LinkList head)
{
    if (NULL == head)
        return FALSE;
​
    //使用一个临时指针对链表进行遍历
    LNode *t;
    t = head;
    int i = 0;
    for (i = 0; i < 41; i++)
    {
        printf("%d ", t->data);
        t = t->next;
    }
    printf("\n");
    return TRUE;
}
​
void JosephRing(int num)
{
    //使用不带头结点的方式创建一个约瑟夫环
    int i;
    LNode *head; //第一个结点/头指针
    head = (LNode *)malloc(sizeof(LNode));
    LNode *tail = head;
    for (i = 1; i < 41; i++)
    {
        tail->data = i;
        tail->next = (LNode *)malloc(sizeof(LNode));
        tail = tail->next;
    }
    tail->data = i;
    tail->next = head;
    print_list(head);
​
    LNode *t = head;
    LNode *pre = NULL;
​
    int cnt = 0;
    while (t != t->next)
    {
        cnt++;
        if (cnt == num)   
        {
            printf("%d: out\n", t->data);
            pre->next = t->next;
            free(t);
            t = pre->next;
            cnt = 0;
        }
        else 
        {
            pre = t;
            t = t->next;
        }
    }
    printf("%d :left\n", t->data);
}
​
int main()
{
    JosephRing(3);
​
    return 0;
}
  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值