链表经典问题

一,环形链表约瑟夫问题

1,题目描述

编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。

下一个人继续从 1 开始报数。

n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?

例如:一共有5个人,报到2的人离开,1开始报数,1->1,2->2,2离开;接着继续从下一个人开始重新报数,也就是从3开始,3->1,4->2,4离开......依次类推,直到剩下最后一个人。

2,思路(链表)

33第一个方法是用环形链表。当数到2时,head所指向的节点需要被释放,但这样就找不到下一个节点了,所以我们需要一个tail指针来记录上一个节点。比如在删除节点2时,如果直接free掉,就找不到节点3了,应先让tail->next指向head->next,再让head释放掉,最后让head走到下一个节点也就是tail->next;

3,代码实现
typedef struct ListNode ListNode;
 ListNode* buynode(int x)
 {
    //创建节点
    ListNode* node=(ListNode*)malloc(sizeof(ListNode));
    if(node==NULL)
    {
        exit(1);
    }
    node->val=x;
    node->next=NULL;
    return node;
 }
 ListNode* circle(int n)
 {
    //创建带环链表
    ListNode* phead=buynode(1);
    ListNode* ptail=phead;//ptail为尾节点
    for(int i=2;i<=n;i++)
    {
       ptail->next=buynode(i);
       ptail=ptail->next;
    }
    ptail->next=phead;//首尾相连
    return ptail;//返回尾不返回头,是因为我们需要两个指针,一个指向当前位置,一个指向下一个位置
 }
int ysf(int n, int m ) {
    // write code here
    ListNode* prev=circle(n);//起始,prev为尾节点
    ListNode* pcur=prev->next;//起始,pcur为头节点
    int count=1;  //用于计数
    //pcur->next指向本身,链表还剩一个人,循环结束
    while(pcur->next!=pcur)
    {
        if(count==m)
        {
            //此时需要销毁数据
            prev->next=pcur->next;
            free(pcur);
            pcur=prev->next;//销毁后,pcur走向下一个位置
            count=1;//重新开始计数
        }
        else 
        {
            //不需要销毁数据,继续向后走
            prev=pcur;
            pcur=pcur->next;
            count++;//走一步计数加加
        }
    }
    return pcur->val;
    
}

 

二,合并两个有序链表 

1,题目描述

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例 :

2,思路

这两个链表都是有序的,但两个链表的节点个数可能不相等。我们可以创建一个新链表,让两个链表的值向比较后,将较小一个尾插在新链表上。

3,代码实现
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
    if(list1==NULL)
    return list2;
    if(list2==NULL)
    return list1;
    ListNode* l1=list1;
    ListNode* l2=list2;

    //创建新链表
    ListNode* head=NULL;
    ListNode* tail=NULL;
     //遍历l1  l2进行比较
    while(l1&&l2)
    {
        if(l1->val<l2->val)
        {
            //将l1尾插到新链表中
            if(head==NULL)
            {
                head=tail=l1;
            }
            else
            {
                tail->next=l1;
                tail=l1;
            }
          l1=l1->next;  //l1指向下一个节点
        }
        else
        {
          //将l2尾插到新链表中
          if(head==NULL)
          {
            head=tail=l2;
          }
          else
          {
            tail->next=l2;
            tail=l2;
          }
          l2=l2->next;
        }
    }

    //出循环后,l1可能没有遍历完,或者l2可能没有遍历完
    //l1没有遍历完,将剩下的插入到tail后
    if(l1)
    {
        tail->next=l1;
    }
    //l2没有遍历完,将剩下的插入到tail后
    if(l2)
    {
        tail->next=l2;
    }
    return head;
}

三,分割链表

1,题目描述

有一个链表,给一个头节点和一个特定值x,要求对链表进行分割,使所有小于x的节点都出现在大于或等于x节点之前。

 

2,思路

这个和上面的思路相似。我们可以创建两个链表,将原链表中小于x的节点尾插到小链表,

大于或等于x的节点尾插到大链表中。 

3,代码实现
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x){
    if(head==NULL)
    return NULL;
      //创建两个带头链表
      ListNode* lesshead,*lesstail;
      ListNode* greathead,*greattail;
      lesshead=lesstail=(ListNode*)malloc(sizeof(ListNode));
      greathead=greattail=(ListNode*)malloc(sizeof(ListNode));

      ListNode* pcur=head;
      while(pcur)
      {
        //尾插到小链表中
        if(pcur->val<x)
       {
          lesstail->next=pcur;
          lesstail=pcur;
       }
       else
       {
        //尾插到大链表中
        greattail->next=pcur;
        greattail=pcur;
       }
       pcur=pcur->next;
      }
     

      //修改大链表尾节点指向,避免死循环
      greattail->next=NULL;

      //需要先置空,再连接,因为greathead->next可能未知
       //小链表尾节点和大链表有效头节点 首尾相连
      lesstail->next=greathead->next;

      return lesshead->next;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值